wpf3d3d-modellinghelix

WPF - Helix Toolkit Auto Rotation


I have been researching this over the last week - 2 weeks and I have been unable to find a solution. I am loading a 3D Model from an STL file and attempting to rotate the 3D model automatically around 1 axis. The idea would be something like a slow-moving animation that displays a 360-degree view around the Y-axis of the model.

XAML:

<Grid>
    <StackPanel x:Name="myViewPort">
        <helix:HelixViewport3D x:Name="viewPort3d" ZoomExtentsWhenLoaded="true" RotateAroundMouseDownPoint="true" CameraRotationMode="Turntable" Height="1000" ShowViewCube="false">
            <helix:DefaultLights/>
            <ModelVisual3D x:Name="visualModel"/>
        </helix:HelixViewport3D>
    </StackPanel>
</Grid>

C#:

public void load3dModel()
    {
        StLReader stlReader = new StLReader();
        Model3DGroup MyModel = stlReader.Read(MODEL_PATH);

        /* Auto Rotate Here */

        System.Windows.Media.Media3D.Material mat = MaterialHelper.CreateMaterial(new SolidColorBrush(Color.FromRgb(255, 255, 255)));

        foreach (System.Windows.Media.Media3D.GeometryModel3D geometryModel in MyModel.Children)
        {
            geometryModel.Material = mat;
            geometryModel.BackMaterial = mat;
        }

        visualModel.Content = MyModel;
    }

The tricky part about this is I need to be able to rotate the model using CODE ONLY. The models generated can be one of the hundreds, and it will depend on the application for what the model will be... So the code needs to be able to handle rotating around the same axis, I can guarantee when the 3D model is exported to the STL file it will be flat along the X-axis.

--- UPDATE --- Attempted Rotation via Storyboard:

public void load3dModel()
    {
        StLReader stlReader = new StLReader();
        Model3DGroup MyModel = stlReader.Read(MODEL_PATH);

        System.Windows.Media.Media3D.Material mat = MaterialHelper.CreateMaterial(new SolidColorBrush(Color.FromRgb(255, 255, 255)));

        foreach (System.Windows.Media.Media3D.GeometryModel3D geometryModel in MyModel.Children)
        {
            geometryModel.Material = mat;
            geometryModel.BackMaterial = mat;
        }

        visualModel.Content = MyModel;

        /* Auto Rotate Here */
        GeometryModel3D geoModel = new GeometryModel3D()
        {
            Transform = new RotateTransform3D()
            {
                Rotation = new AxisAngleRotation3D()
                {
                    Axis = new Vector3D(0, 1, 0),
                    Angle = 0
                }
            }
        };

        MyModel.Children.Add(geoModel);

        var Rotation3DAnimation = new Rotation3DAnimation();

        var FromAxis = new AxisAngleRotation3D()
        {
            Axis = new Vector3D(0, 1, 0),
            Angle = 0
        };

        var ToAxis = new AxisAngleRotation3D()
        {
            Axis = new Vector3D(0, 1, 0),
            Angle = 359
        };

        Rotation3DAnimation.From = FromAxis;
        Rotation3DAnimation.To = ToAxis;
        Rotation3DAnimation.Duration = Duration.Forever; //ADDED DURATION, Still did not work!

        var rotateStoryboard = new Storyboard
        {
            Duration = new Timespan(0, 0, 12),
            RepeatBehavior = RepeatBehavior.Forever,
        };

        Storyboard.SetTarget(Rotation3DAnimation, geoModel.Transform);
        Storyboard.SetTargetProperty(Rotation3DAnimation, new PropertyPath("Rotation"));

        rotateStoryboard.Children.Add(Rotation3DAnimation);
        rotateStoryboard.Begin();
    }

This did not work... Nothing changed?

Thanks!


Solution

  • I am not sure if I understood correctly what you are trying to accomplish so let me know if I misunderstood: In the code you showed, you are loading several GeometryModel3D so are you trying to make them all rotate or just one ?

    One way you could make it rotate is via the Transform property of the GeometryModel3D.

    You will have to set up a DispatcherTimer and update the angle of the rotation on every Tick:

    I made an example based on what you provided where I make one 3D model rotate:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.Load3dModel();
            this.timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(10) };
            this.timer.Tick += Timer_Tick;
            this.timer.Start();
        }
    
        /// <summary>
        /// Change the rotation
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Timer_Tick(object sender, EventArgs e)
        {
            if (this.angle >= 360)
            {
                this.angle = 0;
            }
            else
            {
                //Nothing to do
            }
            this.angle = this.angle + 0.25;
            //You can adapt the code if you have many children 
            GeometryModel3D geometryModel3D = (GeometryModel3D)((Model3DGroup)visualModel.Content).Children.First();
            if (geometryModel3D.Transform is RotateTransform3D rotateTransform3 && rotateTransform3.Rotation is AxisAngleRotation3D rotation)
            {
                rotation.Angle = this.angle;
            }
            else
            {
                ///Initialize the Transform (I didn't do it in my example but you could do this initialization in <see Load3dModel/>)
                geometryModel3D.Transform = new RotateTransform3D()
                {
                    Rotation = new AxisAngleRotation3D()
                    {
                        Axis = new Vector3D(0, 1, 0),
                        Angle = this.angle,
                    }
                };
            }
        }
    
        private DispatcherTimer timer;
    
        public void Load3dModel()
        {
            StLReader stlReader = new StLReader();
            /*
            Model3DGroup MyModel = stlReader.Read(OrLoadFromPath));
            */
            Model3DGroup myModel = new Model3DGroup();
            // Create a mesh builder and add a box to it
            var meshBuilder = new MeshBuilder(false, false);
            meshBuilder.AddBox(new Point3D(0, 0, 1), 1, 2, 0.5);
            meshBuilder.AddBox(new Rect3D(0, 0, 1.2, 0.5, 1, 0.4));
    
            // Create a mesh from the builder (and freeze it)
            var mesh = meshBuilder.ToMesh(true);
    
            // Add 3 models to the group (using the same mesh, that's why we had to freeze it)
            myModel.Children.Add(new GeometryModel3D { Geometry = mesh});
            myModel.Children.Add(new GeometryModel3D { Geometry = mesh, Transform = new TranslateTransform3D(-2, 0, 0)});
            myModel.Children.Add(new GeometryModel3D { Geometry = mesh, Transform = new TranslateTransform3D(2, 0, 0)});
    
            System.Windows.Media.Media3D.Material mat = MaterialHelper.CreateMaterial(new SolidColorBrush(Color.FromRgb(255, 255, 255)));
    
            foreach (System.Windows.Media.Media3D.GeometryModel3D geometryModel in myModel.Children)
            {
                geometryModel.Material = mat;
                geometryModel.BackMaterial = mat;
            }
    
            visualModel.Content = myModel;
        }
    
        private double angle = 0;
    }
    

    The rotation was pretty smooth on my end with those parameters but you will have to test/adapt it on your application.