相关文章推荐
严肃的火锅  ·  docker: Error ...·  3 月前    · 
高兴的黄豆  ·  用latexmk编译XeLaTeX ...·  8 月前    · 
活泼的牛肉面  ·  android studio 错误 ...·  9 月前    · 
纯真的圣诞树  ·  FileUpload 类 ...·  1 年前    · 

【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)