In search Of WoodLandCreatures in a Custom User Data List

I am trying to get Custom User Data to work and be saved correctly.

Everything seems to works but is not saved to a new file. I am using Visual Studio to debug and it is bringing up a new document. After running the WoodLandCreatures command there seems to be a lot of traffic calling the PhysicalData class attached to each text object.

  • OnClick of the text
  • OnClick of the view
  • OnClick of any of the tabs

This plus the save problem makes me think I have something setup incorrectly. I have attached the detail listing in the document created by VS and the one that I saved. The relevant part is this I think. Before the save in debug mode this is in the detail for the text. In the saved document it is missing:

UserData ID: 71FD438F-BB37-4a05-A1BE-11D3E1912079
Plug-in: Simple List
description: Physical Properties
saved in file: yes
copy count: 1

Sorry for the long-winded post but I am hoping it will help someone spot the error.

Thanks for any help.

Best;

Steve

I have got the following code limping along so far:

The PhusicalData Class (Thanks Rhino Support):

using System;
using System.Collections.Generic;
using Rhino;
using System.Runtime.InteropServices;

namespace AsaSimpleList.Model
{
    [Guid("71fd438f-bb37-4a05-a1be-11d3e1912079")]
    public class PhysicalData : Rhino.DocObjects.Custom.UserData
    {
        public int Weight { get; set; }
        public double Density { get; set; }
        public List<String> MyList { get; set; }

        public PhysicalData()
        {
            RhinoApp.WriteLine("public PhysicalData()");
        }

        public PhysicalData(int weight, double density, List<String> myList)
        {
            Weight = weight;
            Density = density;
            MyList = myList;
        }

        public override string Description => "Physical Properties";
        public override bool ShouldWrite => true;

        public override string ToString()
        {
            return $"weight={Weight}, density={Density}";
        }

        protected override void OnDuplicate(Rhino.DocObjects.Custom.UserData source)
        {
            PhysicalData src = source as PhysicalData;
            if (src != null)
            {
                Weight = src.Weight;
                Density = src.Density;
                MyList = src.MyList;
            }
        }

        protected override bool Read(Rhino.FileIO.BinaryArchiveReader archive)
        {
            Rhino.Collections.ArchivableDictionary dict = archive.ReadDictionary();
            if (
                dict.ContainsKey("Weight")
                && dict.ContainsKey("Density")
                && dict.ContainsKey("ListKey"))
            {
                Weight = (int) dict["Weight"];
                Density = (double) dict["Density"];
                MyList = (List<string>) dict["ListKey"];
            }
            return true;
        }

        protected override bool Write(Rhino.FileIO.BinaryArchiveWriter archive)
        {
            var dict = new Rhino.Collections.ArchivableDictionary(1, "Physical");
            dict.Set("Weight", Weight);
            dict.Set("Density", Density);
            dict.Set("ListKey", MyList);
            return true;
        }
    }
}

The calling command:

using System;
using System.Collections.Generic;
using AsaSimpleList.Model;
using Rhino;
using Rhino.Commands;
using Rhino.DocObjects;
using Rhino.Geometry;

namespace AsaSimpleList.Commands
{
    public class WoodLandCreatures : Command
    {
        static WoodLandCreatures _instance;

        public WoodLandCreatures()
        {
            _instance = this;
        }

        ///<summary>The only instance of the WoodLandCreatures  command.</summary>
        public static WoodLandCreatures Instance
        {
            get { return _instance; }
        }

        public override string EnglishName
        {
            get { return "WoodLandCreatures"; }
        }

        protected override Result RunCommand(RhinoDoc doc, RunMode mode)
        {
            //Crate text entity
            TextEntity entity = TextEntity.CreateWithRichText(
                "Chipmunk"
                , doc.Views.ActiveView.ActiveViewport.ConstructionPlane()
                , doc.DimStyles.Current
                , false
                , 0
                , 0
            );

            //Create Attribute then add the current layer to the LayerIndex property
            ObjectAttributes entityAttributes = new ObjectAttributes();
            entityAttributes.LayerIndex = doc.Layers.CurrentLayerIndex;

            //Call public PhysicalData(int weight, double density, List<String> myList)
            //Call public List<String> MyList { get; set; }
            PhysicalData physicalData = new PhysicalData(33, 4, new List<String>()
                {
                    "Bear",
                    "Chipmunk",
                    "Squirrel",
                    "Skunk",
                    "Deer",
                    "Woodpecker",
                    "Owl",
                    "Mouse",
                    "Fox",
                    "Moose",
                    "Hedgehog",
                    "Raccoon",
                    "Rabbit",
                    "Badger",
                    "Philippine eagle",
                }
            );

            //Call public override string Description => "Physical Properties";
            entityAttributes.UserData.Add(physicalData);

            //Create and add entity to current view and plane
            //Call public PhysicalData()
            //Call protected override void OnDuplicate(Rhino.DocObjects.Custom.UserData source)
            //Calls  public int Weight { get; set; }, public double Density { get; set; }, public List<String> MyList { get; set; }
            Guid entityGuid = doc.Objects.AddText(entity, entityAttributes);

            //Find the text object using the guid
            RhinoObject ro = doc.Objects.FindId(entityGuid);

            //Get the physical data class that was created and applied to the attributes of the text
            var objectUserData = ro.Attributes.UserData.Find(typeof(PhysicalData)) as PhysicalData;

            //Call public double Density { get; set; }
            RhinoApp.WriteLine(objectUserData.Density.ToString());

            //Call public int Weight { get; set; }
            RhinoApp.WriteLine(objectUserData.Weight.ToString());

            //Call public List<String> MyList { get; set; }
            //All creatures are listed
            foreach (String item in objectUserData.MyList)
            {
                RhinoApp.WriteLine(item);
            }

            //On each click of of Text, Properties, view 
            //Call public PhysicalData() 
            //Call public override bool ShouldWrite => true;
            //Call protected override bool Write(Rhino.FileIO.BinaryArchiveWriter archive)
            //Call  protected override void OnDuplicate(Rhino.DocObjects.Custom.UserData source)

            //On Save As
            //        public override bool ShouldWrite => true;
            //        protected override bool Write(Rhino.FileIO.BinaryArchiveWriter archive)

            return Rhino.Commands.Result.Success;
        }
    }
}

Befor the save:
text

ID: 9d5bd6e9-0bc6-4275-a841-065c930c96fa (327)
Object name: (not named)
Layer name: Default
Render Material:
source = from layer
index = -1
Attribute UserData:
UserData ID: 563238F9-C201-411d-A7B1-13895A0317AD
Plug-in: Rhino
description: AutoPointsOn
saved in file: no
copy count: 1
UserData ID: 71FD438F-BB37-4a05-A1BE-11D3E1912079
Plug-in: Simple List
description: Physical Properties
saved in file: yes
copy count: 1

Geometry:
Text
(ON_Text)
Point: (0,1.27844,0) X-axis: (1,0,0) Y-axis: (0,1,0)
Height: 0.12500 inches
Wrapping width not set.
Justification: left,top
Runs: 1
Run 0: - Font: Arial
Text : Chipmunk
Not wrapped

  Annotation style:
    Name: Inch Decimal
    Style index: 0
    Style id: BFF1D85E-72AC-4226-884A-872A8473132C
    Text height: 0.12500 inches
    Model space scale: 10
    Scaled text height: 1.25000 inches
    Arrow type:   Solid triangle

After the save:
text

ID: 9d5bd6e9-0bc6-4275-a841-065c930c96fa (2)
Object name: (not named)
Layer name: Default
Render Material:
source = from layer
index = -1
Attribute UserData:
UserData ID: 563238F9-C201-411d-A7B1-13895A0317AD
Plug-in: Rhino
description: AutoPointsOn
saved in file: no
copy count: 1

Geometry:
Text
(ON_Text)
Point: (0,1.27844,0) X-axis: (1,0,0) Y-axis: (0,1,0)
Height: 0.12500 inches
Wrapping width not set.
Justification: left,top
Runs: 1
Run 0: - Font: Arial
Text : Chipmunk
Not wrapped

  Annotation style:
    Name: Inch Decimal
    Style index: 0
    Style id: BFF1D85E-72AC-4226-884A-872A8473132C
    Text height: 0.12500 inches
    Model space scale: 10
    Scaled text height: 1.25000 inches
    Arrow type:   Solid triangle

Quickly fixed code markup for you (enclose code in tripple backticks).

Thanks Nathan! A lot easier to read.

Best;

Steve

Thanks to Rhino Support for all the help in getting this to work. The following code seems to be working but not production ready and only a sample.

Adding User Data to each object or objects attributes will be a performance hit on large files so as with most things, moderation may be wise. I have noted in the code blocks the traffic patterns I see in using this class.

Please let me know if you see any issues that may be a problem.

Thanks again

Steve

Root Sample: Info: RhinoCommon object plug-in user data [McNeel Wiki]
post by: lando.schumpich

UserData Class:

[Guid("71fd438f-bb37-4a05-a1be-11d3e1912079")]
    public class SimpleListData : Rhino.DocObjects.Custom.UserData
    {
        private Guid ClassId = Guid.NewGuid();

        //Your User Data class must have a public parameter-less constructor
        //Each time an object with User Data is selected a new SimpleListData class is instantiated and filled using OnDuplicate
        public SimpleListData()
        {
        }

        public ArchivableDictionary WoodlandCreatures { get; set; }
        public string[] Birds { get; set; }

        public override string Description
        {
            get { return "WoodLand Creatures"; }
        }

        public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder();
            foreach (var valuePair in WoodlandCreatures)
            {
                stringBuilder.AppendFormat($"Key: {valuePair.Key}, Value: {valuePair.Value} :");
            }

            foreach (var b in Birds)
            {
                stringBuilder.AppendFormat($"Bird: {b}");
            }

            return stringBuilder.ToString();
        }

        //On each object move the custom user data is moved back into the new instance
        protected override void OnDuplicate(Rhino.DocObjects.Custom.UserData source)
        {
            SimpleListData src = source as SimpleListData;
            if (src != null)
            {
                WoodlandCreatures = src.WoodlandCreatures;
                Birds = src.Birds;
            }
        }

        //Validation for each object during write to file.
        public override bool ShouldWrite
        {
            get
            {
                if (WoodlandCreatures.Count == 15)
                    return true;

                if (Birds.Length == 2)
                    return true;

                return false;
            }
        }

        //Read from file
        protected override bool Read(Rhino.FileIO.BinaryArchiveReader archive)
        {
            //Validation on read for each object assigned this user data object and guid
            Rhino.Collections.ArchivableDictionary creatures = archive.ReadDictionary();
            string[] birds = archive.ReadStringArray();
            if (creatures.Count == 15)
            {
                WoodlandCreatures = creatures;
            }

            if (birds.Length == 2)
            {
                Birds = birds;
            }

            return true;
        }

        //Write to file
        protected override bool Write(Rhino.FileIO.BinaryArchiveWriter archive)
        {
            archive.WriteDictionary(WoodlandCreatures);
            archive.WriteStringArray(Birds);
            return true;
        }
    }

Calling code:

    [Guid("8808a28f-fa84-4c25-a8df-44be72f1f22f")]
    public class WoodLandCreatures : Rhino.Commands.Command
    {
        public override string EnglishName
        {
            get { return "WoodLandCreatures"; }
        }
        protected override Rhino.Commands.Result RunCommand(RhinoDoc doc, Rhino.Commands.RunMode mode)
        {

            SimpleListData simpleListData = null;

            //If User Data has been attached to selected objects write to command history.
            IEnumerable<RhinoObject> test = doc.Objects.GetSelectedObjects(false, false);
            foreach (RhinoObject ro in test)
            {
                simpleListData = ro.Geometry.UserData.Find(typeof(SimpleListData)) as SimpleListData;
                if (simpleListData != null)
                {
                    //Write the WoodLand Creatures
                    RhinoApp.WriteLine(simpleListData.ToString());
                }
            }

            //Write a woodland creature name to the viewport each time through
            Plane activePlane = doc.Views.ActiveView.ActiveViewport.ConstructionPlane();
            Point3d origin = activePlane.Origin;
            Random rnd = new Random();
            origin.Y = origin.Y + rnd.Next(20);
            activePlane.Origin = origin;
            //Crate text entity
            var entity = TextEntity.CreateWithRichText(
                "Chipmunk"
                , activePlane
                , doc.DimStyles.Current
                , false
                , 0
                , 0
            );

            //Add the text and return the guid then use the guid to find the object.
            Guid entityGuid = doc.Objects.AddText(entity);
            doc.Views.Redraw();
            RhinoObject obj = doc.Objects.FindId(entityGuid);

            //Take a look at the object just created to se if there is User data. Should always be false.
            simpleListData = obj.Geometry.UserData.Find(typeof(SimpleListData)) as SimpleListData;
            if (simpleListData == null)
            {
                ArchivableDictionary activeDirectory = new ArchivableDictionary(1, "Physical");
                activeDirectory.Set("WoodLandCreature01", "Bear");
                activeDirectory.Set("WoodLandCreature02", "Chipmunk");
                activeDirectory.Set("WoodLandCreature03", "Squirrel");
                activeDirectory.Set("WoodLandCreature04", "Skunk");
                activeDirectory.Set("WoodLandCreature05", "Deer");
                activeDirectory.Set("WoodLandCreature06", "Woodpecker");
                activeDirectory.Set("WoodLandCreature07", "Owl");
                activeDirectory.Set("WoodLandCreature08", "Mouse");
                activeDirectory.Set("WoodLandCreature09", "Fox");
                activeDirectory.Set("WoodLandCreature10", "Moose");
                activeDirectory.Set("WoodLandCreature11", "Hedgehog");
                activeDirectory.Set("WoodLandCreature12", "Raccoon");
                activeDirectory.Set("WoodLandCreature13", "Rabbit");
                activeDirectory.Set("WoodLandCreature14", "Badger");
                activeDirectory.Set("WoodLandCreature15", "Philippine eagle");
                simpleListData = new SimpleListData();

                //Save the WoodLand Creatures
                simpleListData.WoodlandCreatures = activeDirectory;

                //Save two birds to an array
                simpleListData.Birds =  new string[] {"Black-footed Laysan", "Short-tailed Anhinga"};

                //Add user data to the object
                obj.Geometry.UserData.Add(simpleListData);
            }
            else
            {
                //RhinoApp.WriteLine("{0} = {1}", simpleListData.Description, simpleListData);
            }

            return Rhino.Commands.Result.Success;
        }
    }