Add UserData in RhinoObject cannot be saved in 3dm file

Hi @dale

I am trying to save the UserData with geometry and save it to 3dm. I find an example rhino-developer-samples/SampleCsAddUserData.cs at b763fa122bd6ff0551d31f67f0c6dd4a97258162 · mcneel/rhino-developer-samples · GitHub

In the example, the userdata is saved in Attributes, which can be saved to 3dm file.

And I started trying to save at difference place, for example, save in Geometry level. This works (can be saved in 3dm).

But when I try to save userdata in obj directly, which is a RhinoObject. In this case, userdata cannot be saved to 3dm file. Just curious for the reason. or this is a bug?

Thanks,
Mingbo

This is issue also happens in Brep’s sub-face.
UserData cannot be saved in Brep’s face, it has to save in the underlying Surface of this BrepFace.

Hi @mingo1214,

Your observation is correct - you cannot save user data directly to a RhinoObject. Keep on mind that a RhinoObject is a runtime object that is not saved to 3dm files. As far as a bug (or not), that’s up for debate.

You should be able to save user data to a BrepFace. You can in C++. I’d need a sample that isn’t working to verify.

– Dale

Hi @dale,

Thanks for your reply!

I have gave up on saving userdata under any geometry level. Instead, I am keeping them inside of Attribute of the RhinoObject.

Here is the problem that I encountered, and what I am trying to do:
I need to save a userdata for a solid Brep, and also have userdata for each subObject of this solid Brep (I tried to save with each surfaces, as I mentioned above, cannot save to BrepFace).

Because I need a solid Brep (need to check if the geometry is closed), I cannot separate each subObject as each individual Breps, and group them together. (I saw a post talking about this method)

With above strategy, I encountered an “EXCEPTION: System.Runtime.InteropServices.SEHException” when I try to do split amount solid Breps (all Breps are defined with above needs). the same split process works fine with the same solid Breps (normal solid Brep, without any userdata).
Here are related split code:

All related posts that I can find from this forum have exception messages or call stacks. I have tried the following setting, but still not getting any more message except the term “SEHException”. I wish I could provide more details for you, but cannot.

So here is my alternative:
I am saving all userdata under Attribute of the RhinoObject for the solid Brep’s info, and its subObjects’. I am using each geometry’s location info as its ID to save/get its userData from Attribute of the top RhinoObject.
(a side question, why don’t we have guid for Brep or BrepFace, or any geometry? is there a better way to track the geometry instead of its location info? I don’t trust the surfaceIndex, as the order might change for whatever reason)

I have a couple more questions in terms of enabling “undo” for userData saved in Attribute. I have looked at all posts about undo process, but still not be able to make it work. I will try to create a separate post about this.

Thanks again,
Mingbo

Hi @mingo1214,

The SDK sample contain a sample that demonstrates how to attach custom user data to a Brep’s surfaces.

https://github.com/mcneel/rhino-developer-samples/blob/6/rhinocommon/cs/SampleCsUserData/Commands/SampleCsAddBrepFaceUserData.cs

Does this help?

– Dale

Hi @Dale,

I ran into the same problem as Mingbo, so I tested the sample you provided, but something odd happens there: If I use “AddBrepFaceUserData” on a surface with previously added userData, it perfectly displays the userdata, but if I run “QueryUserData” on the same surface, it can not find anything.

Can you reproduce this?

Code from AddBrepFaceUserData:

      // Query the surface for user data
      var ud = srf.UserData.Find(typeof(SampleCsUserDataObject)) as SampleCsUserDataObject;
      if (null != ud)
      {
        RhinoApp.WriteLine("{0} = {1}", ud.Description, ud.Notes);
        return Result.Success;
      }

Code from QueryUserData:

      var ud = obj.Attributes.UserData.Find(typeof(SampleCsUserDataObject)) as SampleCsUserDataObject;
      if (null != ud)
        RhinoApp.WriteLine("{0} = {1}", ud.Description, ud.Notes);
      else
        RhinoApp.WriteLine("User data not found.");

Hi @rgr,
You are adding your userdata to the geometry (surface) and on your query you are searching on the attributes. Those are the two places you can add user data to but you will have to decide between them.

Your code could look like this:

var ud = obj.Geometry.UserData.Find(typeof(SampleCsUserDataObject)) as SampleCsUserDataObject;

Not tested

1 Like

Ah yes, that makes sense. Might be the intention of the sample. However, changing the “Attributes” to “Geometry” didnt find me any UserData aswell.

My actual code I’m using(and why I refered to the sample in the first place) looks like the code posted below, but it behaves kind of the same.

I have a custom UserData class which currently has a list of strings and an enum. Adding this UserData to a object and debugging the object, everything worked fine.
Reading it with the method posted below gives me back the enum, but doesn"t find the list(property “data” in this case. Saving the file and loading it shows that there is UserData added to the object in the "details"window in Rhino, but no way to retrive it via code.

Writing userdata:

            var userData = new TestUserData(pline);
            c.UserData.Add(userData);
            doc.Objects.AddCurve(c);

Reading userdata:

public static void OnSelectObjects(object sender, Rhino.DocObjects.RhinoObjectSelectionEventArgs e)
{
            foreach (RhinoObject obj in e.RhinoObjects)
            {
                if (obj.Geometry.UserData != null)
                {
                    TestUserData data = obj.Geometry.UserData.Find(typeof(TestUserData)) as TestUserData;

                    if (data is TestUserData testUD)
                    {
                        if (testUD.Data != null)
                            testUD.Data.ForEach(s => RhinoApp.WriteLine(s));

                        RhinoApp.WriteLine(testUD.Type.ToString());
                    }
                }
            }
        }

Does the basic userdata sample work for you? https://developer.rhino3d.com/guides/rhinocommon/plugin-user-data/

Generally it is advised to attach UserData to Attributes rather then geometry as geometry gets destroyed by many rhino commands and algorithms.

You can also have a look at my test plugIn Octopus, to see a working implementation of attaching userdata and retrieving it after opening a saved 3dm file:

4 Likes

I was under the impression attaching it to geometry is more robust as it is not depending on the object. Looks like I misunderstood than. I`ll look into your examples, thank you for that.

Edit: ok one clue is that, contrary to your example project, my

protected override bool Write(Rhino.FileIO.BinaryArchiveWriter archive)

method never gets called when saving nor does the reading method fire when trying to read UserData from an object/after opening a file.

Does your user data class gave a guid annotation? Had the same issue with data classes where I forgot to add it

1 Like

Thats basicly my whole UserData code so far, strictly copied from the example. Compared it to yours and can`t see a fault(even though I hope its just a simple mistype or else), I might just start over next week and see if it makes any difference.


    [System.Runtime.InteropServices.Guid("07B64D35-CBC0-41B8-BEA7-D6D598C28B03")]
    public class TestUserData : Rhino.DocObjects.Custom.UserData
    {
        public List<string> Data { get; private set; }
        public Type Type { get; private set; }

        public TestUserData()
        {
        }

        public TestUserData(Polyline pline)
        {
            Type = Type.Polyline;
            Data = new List<string>();
            List<Line> segments = pline.GetSegments().ToList();

            foreach (Line segment in segments)
            {
                Data.Add(segment.Length.ToString());
            }
        }

        public override string Description
        {
            get { return "TestUserData"; }
        }

        protected override bool Write(Rhino.FileIO.BinaryArchiveWriter archive)
        {
            ArchivableDictionary dict = new ArchivableDictionary(1, "TestPrj");
            dict.Set("data", Data);
            dict.Set("type", Type.ToString());
            archive.WriteDictionary(dict);

            return !archive.WriteErrorOccured;
        }

        protected override bool Read(Rhino.FileIO.BinaryArchiveReader archive)
        {
            ArchivableDictionary dict = archive.ReadDictionary();

            if (dict.ContainsKey("data"))
            {
                Data = dict["data"] as List<string>;
               // Type = ConvertToType(dict["type"] as string);
            }

            return !archive.ReadErrorOccured;
        }

        private Type ConvertToType(string typeString)
        {
            switch (typeString) 
            {
                case "Polyline":
                    return Type.Polyline;
                default:
                    return Type.Polyline;
            }
        }
    }

Nothing immediately jumps out to me here. Maybe you can post your full compilable code on github and I can have a look around (maybe something in your command goes wrong for example). I won’t have access to a computer until next year though…

1 Like

Hi @rgr,

Looks like you have not overridden the virtual ShouldWrite property. Does doing so help?

– Dale

1 Like

Hi @dale,

yep, that’s what was missing. It now works.

@Lando thank you for your generous offer! Fortunately, not necessary.

Hey,
I’m running in almost the same issue.
I try to write some UserData to a Block (at the defnition and/or at any upcoming reference).

Attaching some custom UserData does not look to be a problem.
Its there, proved by debugging the override “OnTransform”.

Saving the data in the File does not work:

  • The write/read methods are NOT called…
  • ShouldWrite is set to true…
  • Looking up / Collecting the objects with specific UserData on “RhinoDoc.EndOpenDocument” does not find any…
  • “OnTransform” is not called after loading the .3dm…

As I can see from ur previous discussion, there are some objects which can save UserData to .3dm, while others can not.

yah, what about Block-Defs/-Refs?
I haven’t found any information on the WWW yet…

Greets
Mark

Hi @mark_ortler,

If you can provide some sample code, that we can run here, that isn’t working for we’d have a better chance of answering your question.

Thanks,

– Dale

Hi @dale

Attached some code.
Its should run.

UserData_Block.cs (7.2 KB)

Hi @mark_ortler,

Rather than this:

ObjectAttributes blockRef_att = new ObjectAttributes()
{
    UserData =
    {
        blockRef_ud
    }
};

Try this:

ObjectAttributes blockRef_att = new ObjectAttributes();
blockRef_att.UserData.Add(blockRef_ud);

You might also take a look at the SampleCsUserData RhinoCommon sample on GitHub.

– Dale

What worked for us would be something like:

Guid blockRef_guid = doc.Objects.AddInstanceObject(blockDef_index, blockRef_trans, blockRef_att);
var objRef = doc.Objects.Find(blockRef_guid);
objRef.Attributes.UserData.Add(blockRef_ud);
objRef.CommitChanges();