使用简单齿轮对发条装置的初步印象
我们希望通过简单的齿轮来初步了解发条装置的视觉效果。我们制作了一个临时的原型。

引言
我想初步了解一下用齿轮制作的时钟会是什么样子。添加了几个齿轮后,很明显大多数齿轮的转速太慢,无法给人留下深刻印象。无论如何,我先完成了第一次尝试,并写下了这个技巧/窍门。如果您想看它运行,请下载.exe文件。请注意,该代码是为初步视觉效果而编写的第一个(也是唯一一个)尝试迭代,不要期待顶级的视觉设计或模范性的正确或通用的代码。
背景
如今,许多 JavaScript 框架/库正在开发中。jQuery 是一个众所周知的例子(提示:我们现在甚至有了 WPF 的 XamlQuery!)。D3 是一个不太为人所知的用于可视化的 JavaScript 库。它包含可视化组件,对数据进行建模,并将数据与 DOM 操作关联起来。给我留下深刻印象的是 d3 wiki Gallery,里面有许多示例,例如 气泡图、碰撞检测、周转轮系(允许所有示例中使用 JavaScript)。周转轮系很酷,我想知道时钟会是什么样子。由于我完全不具备D3.js技能,我决定在 WPF 中制作一个原型。
WPF 中关于时钟的文章已经很多了,我参考了 WPF 中的模拟时钟 来设置齿轮的角度。为了初步印象,我决定使用具有简单齿形文件的齿轮,请参见 用 C# 绘制齿轮。时钟使用的齿轮比是从 Gary's clocks 获取的。请注意,建造发条装置是一个奇怪的爱好,但有些人甚至会在自己的 家中 建造一个天文馆,请在此处 查看这个天文馆的当前状态。
简单齿轮
在主窗口中经过反复试验后,我们使用了一个UserControl
,请参见下面的Gear.xaml代码。GearPath.Data
将在代码隐藏中被齿轮形状覆盖。在代码隐藏中,定义了 WPF 依赖属性NrTeethProperty
、RotationAngleProperty
、ShiftAngleProperty
和FillProperty
。这些属性可以在MainWindow.xaml中设置,其中使用了 Gear User Controls。RotationAngle
(稍后将由定时器设置以实现实时旋转)和Fill
用于Gear.xaml中的绑定。
<UserControl x:Class="ClockGear.Gear"
x:Name="thisGearName"
....>
<Grid>
<Path x:Name="GearPath"
Stroke="Black" StrokeThickness="1" Fill="{Binding ElementName=thisGearName, Path=Fill}"
Data="M 0,0 L 0,20 M 0,0 L 0,-20 M 0,0 L 20,0 M 0,0 L -20,0">
<Path.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{Binding ElementName=thisGearName, Path=RotationAngle}" />
</TransformGroup>
</Path.RenderTransform>
</Path>
</Grid>
</UserControl>
依赖属性NrTeeth
和ShiftAngle
(齿轮角度的初始偏移,0..100 = 一个齿的宽度)用于指定齿轮。它们的PropertyChangedCallback
函数调用DrawGear
。请参见下面的Gear.cs代码片段。
为了初步印象,我们使用了非常简单的齿形文件,这可以在后续迭代中改进。我们从 用 C# 绘制齿轮 中复制了齿轮,请参见该页上的图以了解所用角度的定义。这里使用弧度而不是角度,并在DrawGear
中用直线连接所有点,作为初步近似。请参见下面的Gear.cs代码片段。
//Codesnippets from Gear.xaml.cs
public partial class Gear : UserControl
{
// Fixed for all gears. radius computed from nrTooth, profile radius-toothdepth .. radius
public const float toothWidth = 8;
public const float toothDepth = 6;
public const float chamfer = 1;
....
// Define dep. properties NrTheeth, RotationAngle, ShiftAngle, Fill
public static readonly DependencyProperty NrTeethProperty =
DependencyProperty.Register("NrTeeth", typeof(int), typeof(Gear),
new PropertyMetadata(new PropertyChangedCallback(OnGearChanged)));
public int NrTeeth
{
get { return (int)this.GetValue(NrTeethProperty); }
set { this.SetValue(NrTeethProperty, value); }
}
public static void OnGearChanged(object sender, DependencyPropertyChangedEventArgs args)
{ ...
thisOne.GearPath.Data = DrawGear(nt, ia);
}
private static Point PointFromCircle(double radius, double angle)
.....
public static PathGeometry DrawGear(int nTeeth = 90, float initAngle = 0)
{
float pi = (float)Math.PI;
float radius = (toothWidth * nTeeth) / (2 * pi);
// dtheta angle single tooth; nTeeth*2*dtheta full circle
float dtheta = (float)(Math.PI / nTeeth);
float phi = dtheta * (chamfer) / (toothWidth);
float alpha = dtheta * (toothWidth - 2.0F * chamfer) / (toothWidth);
PathGeometry Result = new PathGeometry();
PathFigure figure = new PathFigure();
figure.IsClosed = true;
// Set startAngle for the beginning of the first tooth.
float startAngle = -dtheta / 2 + initAngle;
float degrees = (float)(startAngle - phi);
// Make a path representing the gear.
for (int i = 0; i < nTeeth; i++)
{
// make 4 line segments
// 1) line form smaller radius to larger radius
LineSegment segment1 = new LineSegment();
segment1.Point = PointFromCircle(radius, degrees + phi);
figure.Segments.Add(segment1);
// set startpoint as first Point so it is skipped.(Figure.IsClosed).
if (i == 0) figure.StartPoint = segment1.Point;
....
segment2.Point = PointFromCircle(radius, degrees + phi + alpha);
....
segment3.Point = PointFromCircle(radius - toothDepth, degrees + dtheta);
....
segment4.Point = PointFromCircle(radius - toothDepth, degrees + 2 * dtheta);
....
degrees += 2 * dtheta;
}
Result.Figures.Add(figure);
return Result;
}
}
指针
对于 Hand UserControl
,采用了类似的方法。它有一个名为RectangleProperty
的依赖属性,用于设置 Path。
发条装置
现在我们将使用 Gear 和 Hands 来组成一个发条装置。第一个问题是我们应该使用哪些齿轮?秒针应该每 60 秒旋转一周,分针应该每 60*60 秒旋转一周。因此,分针和秒针之间有 1:60 的比例。如果我们取一个 8 齿的秒轮,我们就需要一个 8*60=480 齿的分轮。这是一个非常大的齿轮,最好通过几个小步骤来实现比例传递。我们从 Gary's clocks 获取了齿轮比,请参见下一个图像,该图像使用不重叠的齿轮(在程序中,请按按钮)

我们看到以下传动比:(左边最灰色的秒轮)*(深绿色和灰色小齿轮)*(深红色分轮) = (1/12)*(64/8)*(90/1) = 60。对于时针和分针,我们需要一个 12:1 的比例:(灰色分轮)*(薄荷绿齿轮)*(金色时轮) = (1/16)*(48/10)*(40)。
在程序的默认显示中,时轮和分轮具有相同的中心,并使用了两个额外的传动齿轮,它们对齿轮比没有影响。请注意,在二维设置中,很难清晰地呈现重叠的分轮和时轮。这只有在三维设置中,带有空心轴等才可能实现。另请注意,所有齿轮都可以自由地按顺序放置,但传动齿轮也有固定的末端位置。我手动进行了一些调整,但我认为可以找到一个解析解。
下一步是使用齿轮和指针来实现发条装置。可以使用具有相同旋转速度的(可选)齿轮和指针来建模组合单元,但我选择从基础齿轮和指针开始。命名齿轮和指针的RotationAngle
直接在代码隐藏中设置,请参见代码片段。下一步是引入一个带有公共ShaftAngles
的ViewModel
,并将它们绑定到齿轮和指针的RotationAngle
。
private void Timer_Tick(object sender, EventArgs e)
{
var DateTime = System.DateTime.Now;
float hour = DateTime.Hour;
float minute = DateTime.Minute;
float second = DateTime.Second;
float hourAngle = 30.0F * hour + minute / 2.0F + second / 120.0F;
float minuteAngle = 6.0F * minute + second / 10.0F;
float secondAngle = 6.0F * second;
G1.RotationAngle = (float)secondAngle;
H1.RotationAngle = (float)secondAngle;
float x1 = -(second +minute*60 + hour*60*60) *6.0F * 12.0F / 64.0F;
G2.RotationAngle = x1;
G3.RotationAngle = x1;
H2.RotationAngle = x1;
......
}
为了构建发条装置,我们进行了一系列命名指针、齿轮和手动微调(=黑客)的活动,请参见MainWindow
的代码片段。我们使用 Canvas,并将公共指针和齿轮放在公共 Grid 中,因此它们在顶部以相同的相对位置堆叠显示。我们手动设置 Grid 的Canvas.Left
和Top
位置,并在需要时使用ShiftAngle
。最后,设置ZIndex
以确保齿轮和指针的正确可见性。可能可以建模和设置元素的“图层/深度”。
// A snippet from MainWindow.xaml
<Canvas>
<Grid x:Name="Seconds" Canvas.Left="530" Canvas.Top="150" Panel.ZIndex="10" ToolTip="12 - Seconds" >
<vw:Gear x:Name="G1" NrTeeth="12" Fill="Silver" />
<vw:Hand x:Name="H1" Rectangle="-1,-30,2,35" Fill="Blue" />
</Grid>
<Grid Canvas.Left="452" Canvas.Top="197" ToolTip="8*64" >
<vw:Gear x:Name="G2" NrTeeth="64" Fill="Green" />
<vw:Gear x:Name="G3" NrTeeth="8" ShiftAngle="20" Fill="Silver"/>
<vw:Hand x:Name="H2" Rectangle="-1,-4,2,8" Fill="Black" />
</Grid>
关注点
- 大多数齿轮的转速太慢,无法产生真正酷的效果。
- 重叠的分轮和时轮在二维设置中无法清晰呈现。
- 一些计划中的改进使这个初步印象变得不那么重要:更高级的齿形文件(请注意,给定一个转动方向,我们在齿的“背面”有很大的自由度)、更逼真的齿轮填充、嘀嗒声和每小时的“咚”声,以及在 W8.1 现代开始菜单磁贴中的显示。
- 仔细检查后,我们发现一些齿轮有重叠的齿。当两个大齿轮相互作用时,没有出现问题。我们应该更仔细地研究齿轮的理论(齿宽、倒角和深度)或检查代码(例如,考虑边框的
StrokeThickness
)。 - 对于秒表或计时器,我们可以使用转速更快的齿轮,使用 storyboard 可能效率更高。