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?
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.
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.
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;
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());
}
}
}
}
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:
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
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…