65.9K
CodeProject 正在变化。 阅读更多。
Home

使用简单齿轮对发条装置的初步印象

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.43/5 (4投票s)

2013 年 10 月 11 日

CPOL

5分钟阅读

viewsIcon

14383

downloadIcon

557

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

ClockWork Hour Gear overlapping

引言

我想初步了解一下用齿轮制作的时钟会是什么样子。添加了几个齿轮后,很明显大多数齿轮的转速太慢,无法给人留下深刻印象。无论如何,我先完成了第一次尝试,并写下了这个技巧/窍门。如果您想看它运行,请下载.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 依赖属性NrTeethPropertyRotationAnglePropertyShiftAnglePropertyFillProperty。这些属性可以在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>

依赖属性NrTeethShiftAngle(齿轮角度的初始偏移,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 获取了齿轮比,请参见下一个图像,该图像使用不重叠的齿轮(在程序中,请按按钮)

ClockWork Hour Gear overlapping

我们看到以下传动比:(左边最灰色的秒轮)*(深绿色和灰色小齿轮)*(深红色分轮) = (1/12)*(64/8)*(90/1) = 60。对于时针和分针,我们需要一个 12:1 的比例:(灰色分轮)*(薄荷绿齿轮)*(金色时轮) = (1/16)*(48/10)*(40)。

在程序的默认显示中,时轮和分轮具有相同的中心,并使用了两个额外的传动齿轮,它们对齿轮比没有影响。请注意,在二维设置中,很难清晰地呈现重叠的分轮和时轮。这只有在三维设置中,带有空心轴等才可能实现。另请注意,所有齿轮都可以自由地按顺序放置,但传动齿轮也有固定的末端位置。我手动进行了一些调整,但我认为可以找到一个解析解。

下一步是使用齿轮和指针来实现发条装置。可以使用具有相同旋转速度的(可选)齿轮和指针来建模组合单元,但我选择从基础齿轮和指针开始。命名齿轮和指针的RotationAngle直接在代码隐藏中设置,请参见代码片段。下一步是引入一个带有公共ShaftAnglesViewModel,并将它们绑定到齿轮和指针的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.LeftTop位置,并在需要时使用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 可能效率更高。
© . All rights reserved.