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

使用 Helix Toolkit 绘制实时 3D 工具路径

starIconstarIconstarIconstarIconstarIcon

5.00/5 (14投票s)

2018 年 5 月 29 日

CPOL

3分钟阅读

viewsIcon

37064

downloadIcon

2081

基于 WPF Helix Toolkit 的绘图控件,用于可视化实时 3D 位置流。

我创建了一个基于 WPF Helix Toolkit 的绘图控件,用于可视化 3D 位置的实时流。

此控件用于绘制 CNC 和其他工业运动应用的刀具路径信息。

配套项目包括绘图控件的源代码以及生成简单向上螺旋轨迹的 WPF 演示应用程序。 在构建项目之前,必须将两个 Helix 库“HelixToolkit.dll”和“HelixToolkit.Wpf.dll”放置在“HelixTraceDemoApp”目录中。 或者,您可以直接安装“HelixToolkit.Wpf”NuGet 包。

绘图控件功能

  • 轴、边界框、网格和标记
  • 轨迹是多色的,并且线粗可变
  • 不必要的点被修剪掉
  • 演示应用程序使用工作线程来生成轨迹

演示应用程序

WPF 演示应用程序只是一个主窗口,其中包含一个 HelixPlot 控件和一个缩放按钮。

<local:HelixPlot x:Name="plot" ShowViewCube="False" />

BackgroundWorker 每 50 毫秒“收集数据”,将数据添加到共享内存结构,并在主 UI 线程上调用 PlotData() 方法。 如果工作线程收集数据的速度快于绘制数据的速度,则使用 List 进行数据存储以避免数据丢失。

private void GatherData(object sender, DoWorkEventArgs e)
{
    while (true)
    {
        Thread.Sleep(50);  // 50ms data sampling period

        double x, y, z;
        Color color;
        // (generate test trace)

        var point = new Point3DPlus(new Point3D(x, y, z), color, 1.5);
        bool invoke = false;
        lock (points)
        {
            points.Add(point);
            invoke = (points.Count == 1);
        }

        if (invoke)
            Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)PlotData);
    }
}

PlotData() 方法读取并清除共享内存结构,并调用 AddPoint() 来绘制新数据。

private void PlotData()
{
    if (points.Count == 1)
    {
        Point3DPlus point;
        lock (points)
        {
            point = points[0];
            points.Clear();
        }

        plot.AddPoint(point.point, point.color, point.thickness);
    }
    else
    {
        Point3DPlus[] pointsArray;
        lock (points)
        {
            pointsArray = points.ToArray();
            points.Clear();
        }

        foreach (Point3DPlus point in pointsArray)
            plot.AddPoint(point.point, point.color, point.thickness);
    }
}

HelixPlot 类

HelixPlot 类继承自 HelixViewport3D,添加了专门的属性和方法来绘制实时 3D 轨迹。 轴标签、边界框大小和其他参数通过一组 public 属性进行配置。

/// <summary>Axis labels separated by commas ("X,Y,Z" default).</summary>
public string AxisLabels { get; set; }

/// <summary>XYZ bounding box for the 3D plot.</summary>
public Rect3D BoundingBox { get; set; }

/// <summary>Distance between ticks on the XY grid.</summary>
public double TickSize { get; set; }

/// <summary>A point closer than this distance from the previous point will not be plotted.</summary>
public double MinDistance { get; set; }

/// <summary>Number of decimal places for the marker coordinates.</summary>
public int DecimalPlaces { get; set; }

/// <summary>Brush used for the axes, grid and bounding box.</summary>
public SolidColorBrush AxisBrush { get; set; }

/// <summary>Brush used for the marker cone and coordinates.</summary>
public SolidColorBrush MarkerBrush { get; set; }

CreateElements 方法

在调用 CreateElements() 方法之前,对配置属性的更改不起作用。 但是,可以在不丢失轨迹数据的情况下动态修改绘图配置。 此方法清除绘图控件并构造轴、边界框、网格和标记对象。 如果存在现有轨迹,则会恢复它。

AddPoint 方法

绘图控件使用 Helix LinesVisual3D 类来可视化轨迹。 在最简单的情况下,AddPoint() 方法将一条线段添加到活动的 LinesVisual3D 对象,并将标记前进到新的位置。 但是,如果颜色或线粗发生变化,则必须创建一个新的 LinesVisual3D 对象。 小于与当前点的最小距离的新点将被忽略。

/// <summary>
/// Adds a point to the current trace with a specified color.
/// </summary>
/// <param name="point">The (X,Y,Z) location.</param>
/// <param name="color">The color.</param>
/// <param name="thickness">The line thickness (optional).</param>
public void AddPoint(Point3D point, Color color, double thickness = -1)
{
    if (trace == null)
    {
        NewTrace(point, color, (thickness > 0) ? thickness : 1);
        return;
    }

    // Less than minimum distance from last point.
    if ((point - point0).LengthSquared < minDistanceSquared) return;

    if (path.Color != color || (thickness > 0 && path.Thickness != thickness))
    {
        if (thickness <= 0)
            thickness = path.Thickness;
        path = new LinesVisual3D();
        path.Color = color;
        path.Thickness = thickness;
        trace.Add(path);
        Children.Add(path);
    }

    // If line segments AB and BC are parallel then remove point B.
    bool sameDir = false;
    var delta = new Vector3D(point.X - point0.X, point.Y - point0.Y, point.Z - point0.Z);
    delta.Normalize();  // use unit vectors (magnitude 1) for the cross product calculations
    if (path.Points.Count > 0)
    {
        double xp2 = Vector3D.CrossProduct(delta, delta0).LengthSquared;
        sameDir = (xp2 < 0.0005);
        // Approx 0.001 seems to be a reasonable threshold from logging xp2 values.
        //if (!sameDir) Title = string.Format("xp2={0:F6}", xp2);
    }

    if (sameDir)  // extend the current line segment
    {
        path.Points[path.Points.Count - 1] = point;
        point0 = point;
        delta0 += delta;
    }
    else  // add a new line segment
    {
        path.Points.Add(point0);
        path.Points.Add(point);
        point0 = point;
        delta0 = delta;
    }

    if (marker != null)
    {
        marker.Origin = point;
        coords.Position = 
            new Point3D(point.X - labelOffset, point.Y - labelOffset, point.Z + labelOffset);
        coords.Text = string.Format(coordinateFormat, point.X, point.Y, point.Z);
    }
}

修剪直线上的点

线性运动在运动控制中非常常见,用于进行直线切割或仅将工具移动到新位置。 长的线性运动将导致沿单条直线的许多数据点。 修剪这些中间点将显着减少轨迹中的线段数量。

AddPoint() 方法正在实时输入 3D 位置流。 将它们称为点 A、B 和 C。使用点 A 的初始方法调用将简单地调用构造初始 LinesVisual3D 对象的 NewTrace() 方法。 使用点 B 的第二个方法调用将绘制第一条线段 AB。 使用点 C 的第三个方法调用将绘制第二条线段 BC。

但是,如果点 B 位于(或非常接近)线段 AC 上,就像在线性运动期间的情况一样怎么办? 我们希望简单地将线段 AB 延伸到点 C,而不是添加第二条线段 BC。

线段 AB 和 BC 是 3 空间中的向量,“叉积幅度”计算 |AB X BC| 是由这两个向量形成的梯形的面积 - 如图所示。 可以清楚地看到,对于两个非常接近平行的向量,该面积会降至零。 在记录这些值后,我得出结论,0.001 是确定点 B 位于线段 AC 上的合理阈值。

请注意,只要每个新点继续位于直线上,此修剪算法将继续扩展线段。 演示应用程序显示叉积计算和网格中的三角形数量。 运行该应用程序时,请注意在螺旋的线性段期间,三角形的数量几乎没有增加。

© . All rights reserved.