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





5.00/5 (14投票s)
基于 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 上的合理阈值。
请注意,只要每个新点继续位于直线上,此修剪算法将继续扩展线段。 演示应用程序显示叉积计算和网格中的三角形数量。 运行该应用程序时,请注意在螺旋的线性段期间,三角形的数量几乎没有增加。