【1.9 HelixToolkit学习案例】WPF案例代码解析
此案例演示在 WPF 中使用 HelixToolkit 显示 3D 文件和一些设置
学习 HelixToolkit 的一些对象、光源、相机、视角、旋转、保存成 3D 文件;
学习的目的是做一些简单的3D型材重构,主要参考官网代码
源代码链接:
WPF 使用
NuGet 下载安装包
结果图
xaml 部分代码
第一张图
设置了很多属性:帧率显示、缩放点、左键旋转、鼠标位置、方向轴、立方块等等;
光源肯定是必须的;
创建了两个 HelixVisual3D 对象:螺旋线;
创建了网格线;
<helix:HelixViewport3D Grid.Column="1" x:Name="HView3D" ShowFrameRate="True" ZoomExtentsWhenLoaded="True" ZoomAroundMouseDownPoint="True" PanGesture="RightClick"
RotateGesture="LeftClick" RotateAroundMouseDownPoint="True" IsViewCubeEdgeClicksEnabled="True" CalculateCursorPosition="True" ShowCoordinateSystem="True"
CoordinateSystemLabelForeground="Red" ShowCameraInfo="True" CoordinateSystemLabelX="x" CoordinateSystemLabelY="y" CoordinateSystemLabelZ="z"
Title="Double helix" SubTitle="Colored with a 'rainbow' brush" TextBrush="Red" MouseMove="HView3D_MouseMove">
<ModelVisual3D x:Name="MV3D">
<ModelVisual3D.Transform>
<TranslateTransform3D OffsetZ="-15"/>
</ModelVisual3D.Transform>
<!-- 必备光源 -->
<helix:SunLight/>
<!-- 创建两条螺旋链 -->
<helix:HelixVisual3D Radius="2" Diameter="0.5" Turns="3" Length="30" Fill="{StaticResource RainbowBrush}"/>
<helix:HelixVisual3D Radius="2" Diameter="0.5" Turns="3" Length="30" Phase="180" Fill="{StaticResource RainbowBrush}"/>
<!-- 网格线 -->
<helix:GridLinesVisual3D Width="20" Length="20" MajorDistance="1" MinorDistance="1" Thickness="0.01"/>
</ModelVisual3D>
</helix:HelixViewport3D>
第二张图
同样设置了一些属性
使用 球(SphereVisual3D)和管(TubeVisual3D)重构了一个简单的 3D 型材;
<!-- 左键旋转 右键平移 -->
<helix:HelixViewport3D Grid.Column="2" x:Name="HView3D1" ShowCoordinateSystem="True" PanGesture="RightClick" RotateGesture="LeftClick" ZoomExtentsWhenLoaded="True"
ShowCameraInfo="True" ZoomAroundMouseDownPoint="True" RotateAroundMouseDownPoint="True"
IsViewCubeEdgeClicksEnabled="True" CameraChanged="HView3D1_CameraChanged"
CurrentPosition="{Binding CurrentPosition, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">
<helix:HelixViewport3D.DefaultCamera>
<PerspectiveCamera LookDirection="-1 0 0" UpDirection="0 0 1" FieldOfView="45" NearPlaneDistance="1"/>
</helix:HelixViewport3D.DefaultCamera>
<ModelVisual3D>
<helix:SunLight/>
<!-- 节点处用圆过渡 -->
<helix:SphereVisual3D Center="0 1 0" Radius="0.1" Fill="Blue"/>
<helix:SphereVisual3D Center="0 1.5 0.5" Radius="0.1" Fill="Blue"/>
<helix:SphereVisual3D Center="-1 1.5 0.5" Radius="0.1" Fill="Blue"/>
<!-- 管道节点处重复一次以免管道扭曲 -->
<helix:TubeVisual3D Path="0 0 0, 0 1 0, 0 1 0, 0 1.5 0.5, 0 1.5 0.5, -1 1.5 0.5, -1 1.5 0.5, -1 1.5 1.5" Diameter="0.2" ThetaDiv="20" Fill="Blue"/>
<!-- 网格线 -->
<helix:GridLinesVisual3D Width="2" Length="2" MajorDistance="0.2" MinorDistance="0.2" Thickness="0.01"/>
</ModelVisual3D>
</helix:HelixViewport3D>
第三张图
增加了相机同步功能,也就是第二、三张图的视角是一致的,同步移动;
管(TubeVisual3D)的坐标点使用了数据绑定方式后台创建;
<!-- 相机同步 -->
<helix:HelixViewport3D Grid.Column="3" x:Name="HView3D2" ShowCoordinateSystem="True" PanGesture="RightClick" RotateGesture="LeftClick" ZoomExtentsWhenLoaded="True"
ShowCameraInfo="True" ZoomAroundMouseDownPoint="True" RotateAroundMouseDownPoint="True"
IsViewCubeEdgeClicksEnabled="True" CameraChanged="HView3D2_CameraChanged"
CurrentPosition="{Binding CurrentPosition, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">
<ModelVisual3D>
<helix:SunLight/>
<helix:TubeVisual3D Path="{Binding TubeVisual3DPath}" Diameter="0.2" ThetaDiv="20" Fill="Blue"/>
<!-- 网格线 -->
<helix:GridLinesVisual3D Width="2" Length="2" MajorDistance="0.2" MinorDistance="0.2" Thickness="0.01"/>
</ModelVisual3D>
</helix:HelixViewport3D>
后台代码
代码稍微有点多,大部分是参考了官网案例;
一些变量
public Point3DCollection TubeVisual3DPath { get; set; }
private static readonly Random r = new Random();
private static readonly Brush[] BaseBrush1 = { Brushes.Blue, Brushes.Yellow, Brushes.Red, Brushes.Green };
private static readonly Brush[] BaseBrush2 = { Brushes.Yellow, Brushes.Blue, Brushes.Green, Brushes.Red };
private readonly Stopwatch watch = new Stopwatch();
public Point3DCollection Points { get; set; }
private int NumberOfPoints { get; set; } = 1000;
private readonly string SaveFileFilter = "Bitmap Files(*.png;*.jpg;)|*.png;*.jpg;*.bmp|XAML Files(*.xaml)|*.xaml|Wavefront Files(*.obj)|*.obj|" +
"Wavefront Files zipped(*.ojbz)|*.objz|Extensible 3D Graphics Files(*.x3d)|*.x3d|Collada Fies(*.dae)|*.dae|" +
"STereoLithograhy(*.stl)|*.stl";
构造函数
public HelixToolkitDemo()
InitializeComponent();
InitTube();
AddBases(MV3D, 24, 3, 30);
DataContext = this;
// 设置相机
SetCamera(HView3D);
SetCamera(HView3D1);
SetCamera(HView3D2);
一些方法
/// <summary>
/// 初始化弯管路径
/// </summary>
private void InitTube()
// 定义一条路径
int n = 1800;
double r = Math.Sqrt(3) / 3;
TubeVisual3DPath = CreatePath(0, Math.PI * 2, n, u => Math.Cos(u), u => Math.Sin(u) + r, u => Math.Cos(3 * u) / 3);
//TubeVisual3DPath = new Point3DCollection
// new Point3D(0, 0, 0),
// new Point3D(0, 1, 0),
// new Point3D(0, 1, 0),
// new Point3D(0, 1.5, 0.5),
// new Point3D(0, 1.5, 0.5),
// new Point3D(-1, 1.5, 0.5),
// new Point3D(-1, 1.5, 0.5),
// new Point3D(-1, 1.5, 1.5),
/// <summary>
/// 设置相机
/// </summary>
/// <param name="view"></param>
private void SetCamera(HelixViewport3D view)
(view.Camera as PerspectiveCamera).UpDirection = new Vector3D(0, 0, 1);
(view.Camera as PerspectiveCamera).LookDirection = new Vector3D(-1, 0, 0);
(view.Camera as PerspectiveCamera).FieldOfView = 45;
/// <summary>
/// DNA 中间的连接棒
/// </summary>
/// <param name="model"></param>
/// <param name="number"></param>
/// <param name="turns"></param>
/// <param name="length"></param>
private void AddBases(ModelVisual3D model, int number, double turns, double length)
double b = turns * 2 * Math.PI;
double l = length;
double p1 = 0d;
double p2 = 3.14;
for (int i = 0; i < number; i++)
double u = (double)i / (number - 1);
double bu = b * u;
double x1 = Math.Cos(bu + p1) + Math.Cos(bu + p1);
double y1 = Math.Sin(bu + p1) + Math.Sin(bu + p1);
double z = u * l;
double x2 = Math.Cos(bu + p2) + Math.Cos(bu + p2);
double y2 = Math.Sin(bu + p2) + Math.Sin(bu + p2);
Point3D pt1 = new Point3D(x1, y1, z);
Point3D pt2 = new Point3D(x2, y2, z);
Point3D pt3 = new Point3D(0, 0, z);
int j = r.Next(4);
Brush brush1 = BaseBrush1[j];
Brush brush2 = BaseBrush2[j];
PipeVisual3D ts = new PipeVisual3D
Point1 = pt1,
Point2 = pt3,
Diameter = 0.4,
Material = MaterialHelper.CreateMaterial(brush1)
model.Children.Add(ts);
PipeVisual3D ts2 = new PipeVisual3D
Point1 = pt3,
Point2 = pt2,
Diameter = 0.4,
Material = MaterialHelper.CreateMaterial(brush2)
model.Children.Add(ts2);
/// <summary>
/// 生成三维点集
/// </summary>
/// <param name="n"></param>
/// <param name="time"></param>
/// <returns></returns>
public IEnumerable<Point3D> GeneratePoints(int n, double time)
const double R = 2;
const double Q = 0.5;
for (int i = 0; i < n; i++)
double t = Math.PI * 2 * i / (n - 1);
double u = (t * 24) + (time * 5);
Point3D pt = new Point3D(Math.Cos(t) * (R + (Q * Math.Cos(u))), Math.Sin(t) * (R + (Q * Math.Cos(u))), Q * Math.Sin(u));
yield return pt;
if (i > 0 && i < n - 1)
yield return pt;
/// <summary>
/// 创建弯管路径
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <param name="n"></param>
/// <param name="fx"></param>
/// <param name="fy"></param>
/// <param name="fz"></param>
/// <returns></returns>
private Point3DCollection CreatePath(double min, double max, int n, Func<double, double> fx, Func<double, double> fy, Func<double, double> fz)
Point3DCollection list = new Point3DCollection(n);
for (int i = 0; i < n; i++)
double u = min + ((max - min) * i / n);
list.Add(new Point3D(fx(u), fy(u), fz(u)));
return list;
/// <summary>
/// 增加模型
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButtonAdd_Click(object sender, RoutedEventArgs e)
HelixVisual3D helix = new HelixVisual3D
Origin = new Point3D(10, 0, 0),
Radius = 2,
Diameter = 0.5,
Turns = 10,
Length = 30,
Phase = 0,
Fill = (Brush)FindResource("RainbowBrush"),
MV3D.Children.Add(helix);
一些小功能
/// <summary>
/// 模型大小自适应
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButtonZoom_Click(object sender, RoutedEventArgs e)
HView3D.ZoomExtents(100);
HView3D1.ZoomExtents(100);
HView3D2.ZoomExtents(100);
/// <summary>
/// 显示当前位置
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HView3D_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
if (HView3D.CursorPosition != null)
TB_CursorPositionX.Text = HView3D.CursorPosition.Value.X.ToString();
TB_CursorPositionY.Text = HView3D.CursorPosition.Value.Y.ToString();
TB_CursorPositionZ.Text = HView3D.CursorPosition.Value.Z.ToString();
if (HView3D.CursorOnElementPosition != null)
TB_CursorOnElementPositionX.Text = HView3D.CursorOnElementPosition.Value.X.ToString();
TB_CursorOnElementPositionY.Text = HView3D.CursorOnElementPosition.Value.Y.ToString();
TB_CursorOnElementPositionZ.Text = HView3D.CursorOnElementPosition.Value.Z.ToString();
if (HView3D.CursorOnConstructionPlanePosition != null)
TB_CursorOnConstructionPlanePositionX.Text = HView3D.CursorOnConstructionPlanePosition.Value.X.ToString();
TB_CursorOnConstructionPlanePositionY.Text = HView3D.CursorOnConstructionPlanePosition.Value.Y.ToString();
TB_CursorOnConstructionPlanePositionZ.Text = HView3D.CursorOnConstructionPlanePosition.Value.Z.ToString();
/// <summary>
/// 相机关联动作
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HView3D1_CameraChanged(object sender, RoutedEventArgs e)
if (HView3D1 != null && HView3D2 != null)
CameraHelper.Copy(HView3D1.Camera, HView3D2.Camera);
private void HView3D2_CameraChanged(object sender, RoutedEventArgs e)
if (HView3D1 != null && HView3D2 != null)
CameraHelper.Copy(HView3D2.Camera, HView3D1.Camera);
/// <summary>
/// 特定方向旋转
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButtonRotate_Click(object sender, RoutedEventArgs e)
string name = (sender as RepeatButton).Content.ToString();
Vector3D axis = new Vector3D(0, 0, 0);
if (name.Contains("X"))
axis.X = 1;
else if (name.Contains("Y"))
axis.Y = 1;
else if (name.Contains("Z"))
axis.Z = 1;
int angle = 10;
Matrix3D matrix = MV3D.Transform.Value;
matrix.Rotate(new Quaternion(axis, angle));
MV3D.Transform = new MatrixTransform3D(matrix);
/// <summary>
/// 保存模型
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButtonSave_Click(object sender, RoutedEventArgs e)
SaveFileDialog dialog = new SaveFileDialog
Title = "导出模型",
Filter = SaveFileFilter,
InitialDirectory = "data",
RestoreDirectory = true,
if (dialog.ShowDialog() != true)