Your comments

Egon,


Thanks! With the clues you provided, I have been able to get the subclass working properly. Here's a screenshot of my magnetometer calibration tool, with the left-hand 'raw' viewport coordinate axis symbol created using '<local:LabeledCoordSysVis3D />', and the right-hand 'calibrated' viewport symbol created using

            <h:CoordinateSystemVisual3D /> <!--added 07/12/16 for 'geometry-centered' vs 'viewport-centered' coord sys -->
            <h:BillboardTextVisual3D Position="1.2 0 0" Text="X" />
            <h:BillboardTextVisual3D Position="0 1.2 0" Text="Y" />
            <h:BillboardTextVisual3D Position="0 0 1.2" Text="Z" />

The only visible difference between the two is that the 'X', 'Y', & 'Z' labels on the left are colored the same as the axis arrows, while the ones on the right are all black text.



The next screenshot shows the XAML for both windows and the exposed properties for both the LabeledCoordSysVis3D and CoordinateSystemVisual3D classes



And here is the complete - now working thanks to Egon! - subclass code:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HelixToolkit.Wpf;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;

namespace MyWPFMagViewer2
{
    class LabeledCoordSysVis3D:CoordinateSystemVisual3D
    {
        #region Constants and Fields

        /// <summary>
        /// The arrow lengths property.
        /// </summary>
        public static readonly DependencyProperty AxisLabelSizeProperty = DependencyProperty.Register(
            "AxisLabelSize",
            typeof(double),
            typeof(LabeledCoordSysVis3D),
            new UIPropertyMetadata(1.0, GeometryChanged));

        ///// <summary>
        ///// The x axis label property.
        ///// </summary>
        //public static readonly DependencyProperty XAxisLabelProperty = DependencyProperty.Register(
        //    "XAxisLabel",
        //    typeof(BillboardTextItem),
        //    typeof(LabeledCoordSysVis3D),
        //    new UIPropertyMetadata("X"));
        /// <summary>
        /// The x axis label property.
        /// </summary>
        public static readonly DependencyProperty XAxisLabelProperty = DependencyProperty.Register(
            "XAxisLabel",
            typeof(string),
            typeof(LabeledCoordSysVis3D),
            new UIPropertyMetadata("X"));

        /// <summary>
        /// The y axis color property.
        /// </summary>
        public static readonly DependencyProperty YAxisLabelProperty = DependencyProperty.Register(
            "YAxisLabel",
            typeof(string),
            typeof(LabeledCoordSysVis3D),
            new UIPropertyMetadata("Y"));

        /// <summary>
        /// The z axis color property.
        /// </summary>
        public static readonly DependencyProperty ZAxisLabelProperty = DependencyProperty.Register(
            "ZAxisLabel",
            typeof(string),
            typeof(LabeledCoordSysVis3D),
            new UIPropertyMetadata("Z"));
        #endregion

        #region Constructors and Destructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "LabeledCoordSysVis3D" /> class.
        /// </summary>
        public LabeledCoordSysVis3D()
        {
            base.OnGeometryChanged();
            this.OnGeometryChanged();
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///   Gets or sets the label size.
        /// </summary>
        /// <value>The label size.</value>
        public double AxisLabelSize
        {
            get
            {
                return (double)this.GetValue(AxisLabelSizeProperty);
            }

            set
            {
                this.SetValue(AxisLabelSizeProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets the label of the X axis.
        /// </summary>
        /// <value>The label of the X axis.</value>
        public string XAxisLabel
        {
            get
            {
                return (string)GetValue(XAxisLabelProperty);
            }

            set
            {
                this.SetValue(XAxisLabelProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets the label of the Y axis.
        /// </summary>
        /// <value>The label of the Y axis.</value>
        public string YAxisLabel
        {
            get
            {
                return (string)GetValue(YAxisLabelProperty);
            }

            set
            {
                this.SetValue(YAxisLabelProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets the label of the Z axis.
        /// </summary>
        /// <value>The label of the Z axis.</value>
        public string ZAxisLabel
        {
            get
            {
                return (string)GetValue(ZAxisLabelProperty);
            }

            set
            {
                this.SetValue(ZAxisLabelProperty, value);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// The geometry changed.
        /// </summary>
        /// <param name="obj">
        /// The obj.
        /// </param>
        /// <param name="args">
        /// The args.
        /// </param>
        protected new static void GeometryChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            ((LabeledCoordSysVis3D)obj).OnGeometryChanged();
        }

        /// <summary>
        /// Called when the geometry has changed.
        /// </summary>
        protected override void OnGeometryChanged()
        {
            base.OnGeometryChanged();
            //this.Children.Clear();
            double h = this.AxisLabelSize;

            var xlabel = new BillboardTextVisual3D();
            xlabel.Text = XAxisLabel.ToString();
            xlabel.Foreground = new SolidColorBrush(this.XAxisColor);
            xlabel.HeightFactor = AxisLabelSize;
            xlabel.Position = new Point3D(ArrowLengths * 1.2, 0, 0);
            this.Children.Add(xlabel);

            var ylabel = new BillboardTextVisual3D();
            ylabel.Text = YAxisLabel.ToString();
            ylabel.Foreground = new SolidColorBrush(this.YAxisColor);
            ylabel.HeightFactor = AxisLabelSize;
            ylabel.Position = new Point3D(0, ArrowLengths * 1.2, 0);
            this.Children.Add(ylabel);

            var zlabel = new BillboardTextVisual3D();
            zlabel.Text = ZAxisLabel.ToString();
            zlabel.Foreground = new SolidColorBrush(this.ZAxisColor);
            zlabel.HeightFactor = AxisLabelSize;
            zlabel.Position = new Point3D(0, 0, ArrowLengths * 1.2);
            this.Children.Add(zlabel);
        }

        #endregion
    }
}

I'm sure others can do a better job than this, but I learned a LOT getting this far, and I really appreciate the help from Egon and others - THANKS!!


Frank


Thanks for the info. As an exercise, I attempted to write the subclass you suggested, but got stuck on setting the default property for the axis label. A snippet showing the problem is included below:

        public static readonly DependencyProperty XAxisLabelProperty = DependencyProperty.Register(
            "XAxisLabel",
            typeof(BillboardTextItem),
            typeof(LabeledCoordSysVis3D),
            new UIPropertyMetadata("X"));

The above code compiles fine, but the line


            <local:LabeledCoordSysVis3D />

shows the error "Default value type does not match tpe of property 'XAxisLabel' :-(


I confess that I really don't know what I'm doing in this DependencyPropery declaration, but I tried to copy from the CoordinateSystemVisual3D declaration as much as possible. What I'm trying to do here is set the default property of the X axis label to "X" - what's the proper way to do this?


Full code for the 'LabeledCoordSysVis3D' class is provided below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HelixToolkit.Wpf;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;

namespace MyWPFMagViewer2
{
    class LabeledCoordSysVis3D:CoordinateSystemVisual3D
    {
        #region Constants and Fields

        /// <summary>
        /// The arrow lengths property.
        /// </summary>
        public static readonly DependencyProperty AxisLabelSizeProperty = DependencyProperty.Register(
            "AxisLabelSize",
            typeof(double),
            typeof(LabeledCoordSysVis3D),
            new UIPropertyMetadata(1.0, GeometryChanged));

        /// <summary>
        /// The x axis color property.
        /// </summary>
        public static readonly DependencyProperty XAxisLabelProperty = DependencyProperty.Register(
            "XAxisLabel",
            typeof(BillboardTextItem),
            typeof(LabeledCoordSysVis3D),
            new UIPropertyMetadata("X"));

        /// <summary>
        /// The y axis color property.
        /// </summary>
        public static readonly DependencyProperty YAxisLabelProperty = DependencyProperty.Register(
            "YAxisLabel",
            typeof(BillboardTextItem),
            typeof(LabeledCoordSysVis3D),
            new UIPropertyMetadata("Y"));

        /// <summary>
        /// The z axis color property.
        /// </summary>
        public static readonly DependencyProperty ZAxisLabelProperty = DependencyProperty.Register(
            "ZAxisLabel",
            typeof(BillboardTextItem),
            typeof(LabeledCoordSysVis3D),
            new UIPropertyMetadata("Z"));
        #endregion

        #region Constructors and Destructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "LabeledCoordSysVis3D" /> class.
        /// </summary>
        public LabeledCoordSysVis3D()
        {
            this.OnGeometryChanged();
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///   Gets or sets the label size.
        /// </summary>
        /// <value>The label size.</value>
        public double AxisLabelSize
        {
            get
            {
                return (double)this.GetValue(AxisLabelSizeProperty);
            }

            set
            {
                this.SetValue(AxisLabelSizeProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets the label of the X axis.
        /// </summary>
        /// <value>The label of the X axis.</value>
        public Color XAxisLabel
        {
            get
            {
                return (Color)this.GetValue(XAxisLabelProperty);
            }

            set
            {
                this.SetValue(XAxisLabelProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets the label of the Y axis.
        /// </summary>
        /// <value>The label of the Y axis.</value>
        public Color YAxisLabel
        {
            get
            {
                return (Color)this.GetValue(YAxisLabelProperty);
            }

            set
            {
                this.SetValue(YAxisLabelProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets the label of the Z axis.
        /// </summary>
        /// <value>The label of the Z axis.</value>
        public Color ZAxisLabel
        {
            get
            {
                return (Color)this.GetValue(ZAxisLabelProperty);
            }

            set
            {
                this.SetValue(ZAxisLabelProperty, value);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// The geometry changed.
        /// </summary>
        /// <param name="obj">
        /// The obj.
        /// </param>
        /// <param name="args">
        /// The args.
        /// </param>
        protected new static void GeometryChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            ((LabeledCoordSysVis3D)obj).OnGeometryChanged();
        }

        /// <summary>
        /// Called when the geometry has changed.
        /// </summary>
        protected override void OnGeometryChanged()
        {
            this.Children.Clear();
            double h = this.AxisLabelSize;

            var xlabel = new BillboardTextVisual3D();
            xlabel.Text = XAxisLabel.ToString();
            xlabel.Foreground = new SolidColorBrush(this.XAxisColor);
            xlabel.HeightFactor = AxisLabelSize;
            xlabel.Position = new Point3D(ArrowLengths * 1.1, 0, 0);
            this.Children.Add(xlabel);

            var ylabel = new BillboardTextVisual3D();
            ylabel.Text = XAxisLabel.ToString();
            ylabel.Foreground = new SolidColorBrush(this.XAxisColor);
            ylabel.HeightFactor = AxisLabelSize;
            ylabel.Position = new Point3D(ArrowLengths * 1.1, 0, 0);
            this.Children.Add(ylabel);

            var zlabel = new BillboardTextVisual3D();
            zlabel.Text = XAxisLabel.ToString();
            zlabel.Foreground = new SolidColorBrush(this.XAxisColor);
            zlabel.HeightFactor = AxisLabelSize;
            zlabel.Position = new Point3D(ArrowLengths * 1.1, 0, 0);
            this.Children.Add(zlabel);
        }

        #endregion
    }
}


Got it - Thanks! Is there any way at present to add labels to the 'geometry-centered' coordinate system? I saw there was an issue (Issue #18) about this, with the notation that labels were added, but I don't see that anywhere in the available properties for CoordinateSystemVisual3D, nor did I see anything in the class code source. Am I missing something?


Frank


Ergon,


You were correct - it was the lack of a lighting source. I didn't realize the problem, as the PointVisual3D collection renders fine without any lighting! Once I figured that out, and added a light to the scene, at least that part works well now ;-).


Frank

Ergon,


Tried the new class - no change in behavior. Here's the relevant code:


            //try adding an ellipse to view            
            //EllipsoidVisual3D ell1 = new EllipsoidVisual3D();
            MyEllpsoidVisual ell1 = new MyEllpsoidVisual();
            SolidColorBrush perimeterbrush = new SolidColorBrush(Colors.Red);
            System.Windows.Media.Media3D.Material mat = MaterialHelper.CreateMaterial(new SolidColorBrush(Colors.Red));
            ell1.Material = mat;
            ell1.BackMaterial = mat;
            ell1.Fill = perimeterbrush;
            ell1.Center = new Point3D(0, 0, 0);
            ell1.RadiusX = 1;
            ell1.RadiusZ = 1;
            ell1.RadiusY = 1;
            ell1.Model.BackMaterial = mat;
            ell1.Model.Material = mat;
            ell1.Fill = new SolidColorBrush(Colors.White);
            vp_cal.Children.Add(ell1);

And here's a screenshot of the result. As you can see, I still get a completely black sphere.


Note: In the above code snippet, you'll see that I tried setting every visual property I could think of, including 'Material', 'BackMaterial', 'Fill', 'Model.Material', etc. None of these property changes had any effect on the displayed object color.


Any other thoughts?


Frank


Thanks Egon - I'll give that a try and report back. Does that class have to be in any particular namespace? IOW, can I place this class in it's own file as part of my current WPF project, or does it need to reside somewhere in the Helix Toolkit source tree?


TIA,


Frank


Hmm, I'll look into TubeVisual3D. Regarding the light - The rest of the objects in the view have color (red dots, yellow dots). Only the Ellipsoid object shows in black. Do I need to define the light separately for each item in the model?


TIA,


Frank


Suran,


Not sure if this helps exactly, but I recently did this with a WPF project consisting of a window, a grid, and a HelixViewport3D object. Here's the XAML and the MouseDown event code


<code>

private void vp_raw_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)

{
System.Windows.Point mousept = e.GetPosition(vp_raw);
Debug.Print("At top of MouseDown event, Mouse position " + mousept.ToString());

//code to print out current selected point list
if (selPointsVisual != null)
{
int selcount = selPointsVisual.Points.Count;
for (int i = 0; i < selcount; i++)
{
Point3D selpt = selPointsVisual.Points[i];
Debug.Print("selected pt at index " + i + " = ("
+ selpt.X.ToString("F2") + ", "
+ selpt.Y.ToString("F2") + ", "
+ selpt.Z.ToString("F2") + ")");
}
}
else
{
Debug.Print("No selected points exist");
}

//Step1: Get the current transformation matrix
//get a reference to the visual element containing datapoints
int chldcount = vp_raw.Children.Count;
PointsVisual3D p3dvis = null; //temp object
for (int i = 0; i < vp_raw.Children.Count; i++)
{
Visual3D Vis = (Visual3D)vp_raw.Children[i];
string visnamestr = Vis.GetName();
Debug.Print("visual element [" + i + "]'s name is " + visnamestr);
if (visnamestr != null && visnamestr.Contains("magpoints"))
{
p3dvis = (PointsVisual3D)Vis;
break;
}
}

//use the temp object to get the transformation matrix for the parent viewport
Matrix3D m3D = p3dvis.GetViewportTransform();

//Debug.Print(m3D.ToString());
//Point3D xfrmpt = m3D.Transform(new Point3D(0,0,0));
//string ctrptstr = xfrmpt.X.ToString("F2") + ", "
// + xfrmpt.Y.ToString("F2") + ", "
// + xfrmpt.Z.ToString("F2");
//Debug.Print("(0,0,0) transforms to " + ctrptstr);

//Step2: transfer any currently selected points back to main points list unless SHIFT key is down
if ((Keyboard.Modifiers & ModifierKeys.Shift) != ModifierKeys.Shift)
{
if (selPointsVisual != null)
{
int selcount = selPointsVisual.Points.Count;
int magptcount = m_pointsVisual.Points.Count;
Debug.Print("selcount = " + selcount + ", magptcount = " + magptcount);
if (selcount > 0)
{
for (int i = 0; i < selcount; i++)
{
//get selected point
Point3D selpt = selPointsVisual.Points[i];
string selptstr = selpt.X.ToString("F2") + ","
+ selpt.Y.ToString("F2") + ","
+ selpt.Z.ToString("F2");

Debug.Print("Moving selected pt (" + selptstr + ") to pointsVisual. Before add, pt count is "
+ m_pointsVisual.Points.Count);

//add it to pointsVisual
m_pointsVisual.Points.Add(selPointsVisual.Points[i]);
int newcount = m_pointsVisual.Points.Count;

//check that point got added properly
Point3D newpt = m_pointsVisual.Points[newcount - 1];
string newptstr = newpt.X.ToString("F2") + ","
+ newpt.Y.ToString("F2") + ","
+ newpt.Z.ToString("F2");

Debug.Print("point (" + newptstr + ") added to pointsVsiual at index " + (newcount - 1)
+ ". Count now " + newcount);
}
selPointsVisual.Points.Clear();
}
}
}

//Step3: copy pointsVisual.Points index of any selected points to m_selidxlist
m_selidxlist.Clear();
int ptidx = 0;
foreach (Point3D vispt in m_pointsVisual.Points)
{
Point3D xpt = m3D.Transform(vispt);
double distsq = (mousept.X - xpt.X) * (mousept.X - xpt.X) + (mousept.Y - xpt.Y) * (mousept.Y - xpt.Y);
double dist = Math.Sqrt(distsq);
if (dist < 5)
{
m_selidxlist.Add(ptidx); //save the index of the point to be removed

string visptstr = vispt.X.ToString("F2") + ","
+ vispt.Y.ToString("F2") + ","
+ vispt.Z.ToString("F2");

string xfrmptstr = xpt.X.ToString("F2") + ", "
+ xpt.Y.ToString("F2") + ", "
+ xpt.Z.ToString("F2");
Debug.Print("dist = " + dist.ToString("F2") + ": mousepoint " + mousept.ToString()
+ " matches with magpt[" + ptidx + "] ( " + visptstr + "), "
+ "which transforms to " + xfrmptstr);
}

ptidx++;
}

//move selected points from pointsVisual3D to selpointsVisual3D
int selidxcount = m_selidxlist.Count;
Debug.Print("Selected Index List Contains " + selidxcount + " items");

if (selidxcount > 0)
{

int newcount = 1;
//06/06/16 new try at moving selected points from m_pointsVisual to selPointsVisual
for (int selidx = selidxcount - 1; selidx >= 0; selidx--)//have to work from top down to avoid crashes
{
//retrieve selected point from pointsVisual collection
int visptidx = m_selidxlist[selidx]; //this is the index into pointsVisual.Points collection
Point3D selvispt = new Point3D();
selvispt.X = m_pointsVisual.Points[visptidx].X;
selvispt.Y = m_pointsVisual.Points[visptidx].Y;
selvispt.Z = m_pointsVisual.Points[visptidx].Z;
string selvisptstr = selvispt.X.ToString("F2") + ","
+ selvispt.Y.ToString("F2") + ","
+ selvispt.Z.ToString("F2");
Debug.Print("selected pt (" + selvisptstr + ") added to m_selpoints Collection. Count now "
+ newcount);

//add this point to selPointsVisual
selPointsVisual.Points.Add(selvispt);

//testing - print out added point
int addedcount = selPointsVisual.Points.Count;
Point3D addedpt = new Point3D();
addedpt = selPointsVisual.Points[addedcount - 1];
string addedptstr = addedpt.X.ToString("F2") + ","
+ addedpt.Y.ToString("F2") + ","
+ addedpt.Z.ToString("F2");
Debug.Print("added pt (" + addedptstr + ") added to selPointsVisual. Count now "
+ addedcount);

//remove this point from m_pointsVisual collection
Debug.Print("removing point at m_pointsVisual[" + m_selidxlist[selidx] + "]");
m_pointsVisual.Points.RemoveAt(m_selidxlist[selidx]);
}

//DEBUG!!
Debug.Print("\r\n selPointsVisual Points collection contains the following points");
foreach (Point3D pt3 in selPointsVisual.Points)
{
string newptstr3 = pt3.X.ToString("F2") + ","
+ pt3.Y.ToString("F2") + ","
+ pt3.Z.ToString("F2");
Debug.Print("Selected pt (" + newptstr3 + ")");
}
}
vp_raw.UpdateLayout();//refresh the 'raw' view
}

</code>


And the XAML


<code>

<Window

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyWPFMagViewer2"
xmlns:h="http://helix-toolkit.org/wpf" x:Class="MyWPFMagViewer2.MainWindow"
mc:Ignorable="d"
Title="Raw Magnetometer Data" Height="350" Width="525">
<Grid>

<h:HelixViewport3D x:Name="vp_raw" Margin="0" MouseDown="vp_raw_MouseDown" CoordinateSystemHorizontalPosition="Center" CoordinateSystemVerticalPosition="Center" Orthographic="True" ShowCoordinateSystem="True" Title="Title Goes Here" Grid.ColumnSpan="2"/>

</Grid>
</Window>

</code>




Sorry, no preview feature, so cant tell how this is going to look :-(


Frank