xarial / xcad

Framework for developing CAD applications for SOLIDWORKS, including add-ins, stand-alone applications, macro features, property manager pages, etc.
https://xcad.net
MIT License
126 stars 25 forks source link

Macro Feature Definition Null Exception #111

Closed MJyee closed 9 months ago

MJyee commented 9 months ago

Hi, i need some help with my Macro feature definition. my PMP Page has multiple Tabs and in the Tabs i have multiple Groups that are repeating so for the Data i use a data Class to store each Group's data but on the convert page to param it is constantly having a null exception

convert params to page code

public override MainPMPPage ConvertParamsToPage(IXApplication app, IXDocument doc, MainPMPFeatData par)
        {
            var page = new MainPMPPage();
            page.valuesTab.Loop1.Selections = par.LoopData1.selectionFaces;
            page.valuesTab.Loop1.Distance = par.LoopData1.Distance;
            page.valuesTab.Loop1.left = ConvertDirToBools(par.LoopData1.Directions);
            //page.valuesTab.Loop1.right = ConvertDirToBools(par.LoopDatas[0].Directions);
            page.valuesTab.Loop1.options = par.LoopData1.Options;
            page.valuesTab.Loop1.tol_1 = par.LoopData1.TolVal1;
            page.valuesTab.Loop1.tol_2 = par.LoopData1.TolVal2;

            page.valuesTab.Loop2.Selections = par.LoopData2.selectionFaces;
            //return base.ConvertParamsToPage(app, doc, par);
            return page;
        }

PMP Data

 public class MainPMPFeatData
    {
        public BaseLoopData LoopData1 { get; set; } = new BaseLoopData();
        public BaseLoopData LoopData2 { get; set; } = new BaseLoopData();
        //public List<BaseLoopData> LoopDatas {  get; set; } = new List<BaseLoopData>();
        // other tab data to be stored here
    }

Base Loop Data

public class BaseLoopData
    {
        public List<IXFace> selectionFaces { get; set; } = new List<IXFace>();
        public string Distance { get; set; } = "10.00 mm";
        public Directions Directions { get; set; } = Directions.Left;
        public TollernceOptions Options { get; set; } = TollernceOptions.None;
        public double TolVal1 { get; set; } = 0.00;
        public double TolVal2 { get; set; } = 0.00;
    }

on the data side i originally wanted to use a list of the base data class but was still facing the null exception issue to i tried switching to just individual. i looked through the paametric box example and saw it was using IXEntiry for its selection and using the filter to limit it to select planar faces. so is the list unable to support it ?

artem1t commented 9 months ago

I am afraid the macro feature data only supports simple types (e.g. no sub-classes), you need to flatten MainPMPFeatData. There is a plan to support nested types in the future.

Alternatively, if you still want nested types you can add [ParameterIgnore] on the properties with nested type and use JSON/XML serialization to convert it to a string and store in the macro feature

MJyee commented 9 months ago

i see, thanks for the info, i saw in your examples for the responsive group, you use a action button to update a textbox and checkbox. i want to have a read only text box to display a measured distance between the 2 selected faces on my selection box. where the measured distance will run solidworks measure to calculate the distance.

my problems are how do i get the modeldoc2 instance within the PMPage? and how do i create the property change function to run it ?

i have the selection box and text box get set like this

       [SelectionBoxOptions(SelectionColor = StandardSelectionColor_e.Primary, Style = SelectionBoxStyle_e.MultipleItemSelect, CustomFilter = typeof(Max2FacesSelection))]
        [ControlOptions(height: 50)]
        [Label("Selection")]
        public List<IXFace> Selections
        {
            get => m_Faces;
            set {
                m_Faces = value;
                this.HandleUpdateDistance();
            }
        }

        private void HandleUpdateDistance([CallerMemberName] string prpName = "")
        {
            m_DistanceVal = //<function to return the distance>
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Selections)));
        }
        //--------------------------------------------------------------
        [Label("Distance:")]
        //[TextBlock]
        //[TextBlockOptions(fontStyle: FontStyle_e.Bold)]
        [TextBoxOptions(TextBoxStyle_e.ReadOnly)]
        [ControlOptions(left: 30, top: 100)]
        public string Distance
        {
            get => m_DistanceVal;
            set
            {
                m_DistanceVal = value;
                this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Distance)));
            }
        }

my Measurement function is just a simple string return taking in the 2 faces and modeldoc2


        private string CalculateDistance(ModelDoc2 doc, List<IXFace> faces)
        {
            double distance = 0;
            string retVal;
            //SldWorks sldWorks = sldWorks.ActiveDoc();
            ModelDoc2 modelDoc = doc.
            Measure MeasureTool = modelDoc.Extension.CreateMeasure();
            MeasureTool.ArcOption = 0;

            bool stats = MeasureTool.Calculate(faces);
            if (stats)
            {
                DumpMeasureData(MeasureTool); //debug function to print all the measurement values
            }
            retVal = distance.ToString() + " mm";
            return retVal;
        }
'''
artem1t commented 9 months ago

You can call the OwnerDocument of your IXFace selection. This can be cast to ISwDocument which has a pointer to IModelDoc, e.g.

IModelDoc2 model = ((ISwDocument)(Selections.FirstOrDefault()?.OwnerDocument))?.Model;

This will be null if no elements selected.

MJyee commented 9 months ago

i see thanks for that i forgot you could get the modeldoc from the selected faces heres the completed code for the update handler incase anyone found it useful in the future

private string m_DistanceVal;
private List<IXFace> m_Faces;

    public List<IXFace> Selections
        {
            get => m_Faces;
            set
            {
                m_Faces = value;
                this.HandleUpdateDistance();
            }
        }

        private void HandleUpdateDistance([CallerMemberName] string prpName = "")
        {
            IModelDoc2 model = ((ISwDocument)(m_Faces.FirstOrDefault()?.OwnerDocument))?.Model;
            if (model is null)
                m_DistanceVal = "Null tool";
            else if (m_Faces.Count == 2)
            {
                //m_DistanceVal = Guid.NewGuid().ToString();
                Measure MeasurementTool = model.Extension.CreateMeasure();
                SelectionMgr selMgr = (SelectionMgr)model.SelectionManager;
                Entity[] measureFaces = new Entity[2];

                measureFaces[0] = (Entity)selMgr.GetSelectedObject6(1, (int)LoopMarks.loop1);
                measureFaces[1] = (Entity)selMgr.GetSelectedObject6(2, (int)LoopMarks.loop1);

                bool stats = MeasurementTool.Calculate(measureFaces);
                if (stats)
                {
                    if ((MeasurementTool.IsParallel))
                    {
                        Debug.Print("Is parallel: " + MeasurementTool.IsParallel);
                        //measureType = "Normal";
                        if ((!(MeasurementTool.NormalDistance == -1)))
                        {
                            Debug.Print("Normal distance: " + MeasurementTool.NormalDistance);
                            m_DistanceVal = (MeasurementTool.NormalDistance * 1000).ToString() + " mm";
                        }
                    }
                    else
                    {
                        //measureType = "Radius";
                        if ((!(MeasurementTool.Radius == -1)))
                        {
                            Debug.Print("Radius: " + MeasurementTool.Radius);
                            m_DistanceVal = (MeasurementTool.Radius * 1000).ToString() + " mm";
                        }
                        else if ((!(MeasurementTool.Diameter == -1)))
                        {
                            Debug.Print("Diameter: " + MeasurementTool.Diameter);
                            m_DistanceVal = (MeasurementTool.Diameter * 1000 / 2).ToString() + " mm";
                        }
                    }
                }
                else
                    m_DistanceVal = "-1 mm";
            }

            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Distance)));
        }

        [Label("Distance:")]
        //[TextBlock]
        //[TextBlockOptions(fontStyle: FontStyle_e.Bold)]
        [TextBoxOptions(TextBoxStyle_e.ReadOnly)]
        [ControlOptions(left: 30, top: 100, backgroundColor: KnownColor.White)]
        public string Distance
        {
            get => m_DistanceVal;
            set
            {
                m_DistanceVal = value;
                this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Distance)));
            }
        }
artem1t commented 9 months ago

Alternative (IMHO a better solution) is to inject the pointer to the IXDocument to your page class when you are creating the page in ConvertParamsToPage