Access Custom UserData from a Rhino Object

Hello,

I’m fairly new to Grasshopper and C# scripting, so I hope I’m not asking something too stupid.

So here’s my problem:

I’m using the Rhino interface of the FE software SoFiSTiK and I want to write a C# component in Grasshopper, that adds the SoFiSTiK structural data to selected Rhino Object.

I’ve gotten as far as this:

Curve input = x;
Rhino.DocObjects.Custom.UserDataList myobject = input.UserData;
A = myobject.Count;

A is zero, when I just select an ordinary Rhino Curve, but it changes to one when I select a curve that I manually declared as a structural item in Rhino/SoFiSTiK before. So it seems the structural data is stored as UserData.

Now I want to know what exactly the user data is, so I can just write a C# component that automatically adds that information to all curves on one layer and so on. But I can’t seem to find a way to actually read the userdata. Or is that not possible?
Can anyone help me out?

Thanks a lot in advance!

One day when I was really bored I tried to draw a class diagram of (parts of) the RhinoCommon lib, and in my archive I digged up the following diagram (Edit: Corrected a second time). Perhaps it can guide you in how to navigate the classes involved.

< snip >

Edit: See post far below for tested code that can add and retreive info from these classes. I removed the old text here (and old pictures) since they contained errors. The picture below should be correct though.

Fig.1. This updated Class Diagram (R1.2) shows how to access a UserData object by calling “Find”.

Sorry if the former diagram caused confusion.

// Rolf

7 Likes

Mmmm, nice work. This could be an amazing learning resource to include in the Rhinocommon documentation…Sometimes learning/teaching these structures is not very intuitive and doesn’t help the linear way in which you can explore the current documentation (*.chm or online doc).

Thank you very much for that! For now it’s throwing a thousand errors, but I’m working on it. :slight_smile:

I wish I had the time to draw more diagrams.

Currently lack of time makes it a bit risky though since it takes some testing to verify that I read the documentation right (like in this case I had overlooked that the UserData links was actually introduced on the CommonObject level and not as late as Brep as I first had drawn).

False info often is worse than no info… :slight_smile:

But I’d happily draw more diagrams of the parts I need to explore & understand myself if I know that others would also find them useful.

// Rolf

Yes, please. :slight_smile:
I believe that that kind of info will sure be very useful to anyone using (or staring to use, or trying to use) RhinoCommon

… I remember I had a hard time when I started to try to use RhinoCommon … :wink:

BTW … Happy birthday, Rolf ! :slight_smile:
… assimung that your in your time zone the day has not yet changed … :wink:

1 Like

Hello, I’ve been trying your suggestions, but I always get that UserDataLists can’t be indexed with .
Anyone know a way around this?

EDIT:

Even though I’m still interested in how it’s actually done, I found a way around actually writing a component myself. The attributes component in Human did the trick:

Now I just need write a component that adds user data in that manner to all my layers, which seems more simple, using UserData.Add(…).
Here’s hoping it works out the way I planned. :smiley:

Human, Elefront and others can easily do what you are asking in grasshopper, but that wasn’t your question. You can reference, sort, add, revise and reapply userdata seamlessly this way, by layer or whatever.

It’s always nice to know how something actually works. :slight_smile:
Also, I can’t reapply the userdata since that way I always need a reference object, which I usually won’t have when I start a calculation. Also the userdata always needs some editing, for example, the first value needs to increment for each object. Not sure if Elefront can do that.

If you can post a example i can illustrate the workflow, which should work from what i imagine you are trying to achieve.

Well, here’s essentially what I’m trying to do:

So, I’m always starting from scratch, so at the beginning there is no SoFiSTiK userdata like the data I posted further above.

I only wanted to access the userdata earlier so I can explore how SoFiStiK stores data. :slight_smile:

You can probably add the user data when defining the geometry in gh, Bake, add more or modify, then export.

You can identify the nodes to pull info from initially; typically by proximity or a matching data structure that’s in rhino.

I’ll be in and out all day, but if you can put together a small file – with several structural components of each type in rhino – i can show the workflow. I do think you will be pleasantly surprised on its ease and potential for productivity :slight_smile:

I tried (for the first time) to actaully code something that accesses these links. The following code results in the output illustrated far below:

  private void RunScript(Brep Brep, ref object A)
  {
    var brep = Brep;

    Print("========================================");
    Print("Access an ArchiveDictionary via UserData");
    Print("========================================");
    
    // Retreive a dictionary
    var dict = brep.UserData.Find(typeof(Rhino.DocObjects.Custom.UserDictionary)) as Rhino.DocObjects.Custom.UserDictionary;    
    if (dict == null)
    {
      dict = new Rhino.DocObjects.Custom.UserDictionary();
      dict.Dictionary.Clear();
      brep.UserData.Add(dict);
    }
    Print("A: " + dict.Dictionary.Count.ToString());
    
    dict.Dictionary.Name = "First";
    Print("B: " + dict.Dictionary.Name);
    
    dict.Dictionary.Set("FirstKey", "FirstValue");
    dict.Dictionary.Set("SecondKey", "SecondValue");
    dict.Dictionary.Set("ThirdKey", "ThirdValue");    
    Print("C: " + dict.Dictionary.Count.ToString());
    
    dict.Dictionary.Clear();    
    Print("D: " + dict.Dictionary.Count.ToString());
    
    Print("===============================");
    Print("Direct access of UserDictionary");
    Print("===============================");
    
    // try the "direct approach"
    var adict = brep.UserDictionary;    
    Print("D: " + adict.Count.ToString());

    adict.Name = "Second";
    Print("E: " + adict.Name);
    
    adict.Set("FirstKey", "FirstValue");
    adict.Set("SecondKey", "SecondValue");
    adict.Set("ThirdKey", "ThirdValue");

    Print("F: " + adict.Count.ToString());

    // Clear so as to make this test repeatable
    Print("G: Removing Firstkey");
    {
      adict.Remove("FirstKey");
      Print("H: " + adict.Count.ToString());
    }
    Print("I: Removing SecondKey");
    {
      adict.Remove("SecondKey");
      Print("J: " + adict.Count.ToString());
    }
    Print("K: Removing ThirdKey");
    {
      adict.Remove("ThirdKey");
      Print("L: " + adict.Count.ToString());
    }     
  }

Print result:

The gh component:
RIL UserData Test.gh (6.6 KB)

// Rolf

Here you have a code example adding exactly that data which was shown in your picture:

The code:

private void RunScript(Brep Brep, ref object A)
  {
    var brep = Brep;

    Print("===================================");
    Print("Adding UserData From Posted Example");
    Print("===================================");

    System.Object strval = null;

    // Direct approach
    var adict = brep.UserDictionary;

    adict.Clear();
    Print("A: " + adict.Count);

    adict.Name = "UserData: Posted Example";
    Print("B: " + adict.Name);

    adict.Set("SOF_ID", 1001);
    adict.Set("SOF_GRP", "SOF_PROP_COMBO_BYLAYER");
    adict.Set("SOF_SNO", "SOF_PROP_COMBO_NONE");
    adict.Set("SOF_SNOE", "SOF_PROP_COMBO_NONE");
    adict.Set("SOF_STYP", "N");
    adict.Set("SOF_STYP2", "E");
    adict.Set("SOF_NP", "SOF_PROP_COMBO_NONE");
    adict.Set("SOF_SDIV", 0.0);
    adict.Set("SOF_KR", "NEGZ");
    adict.Set("SOF_DROT", 0.0);
    adict.Set("SOF_FIX", "PXPYPZMXMYMZ");


    // OUTPUT
    Print("-----------------------------------");

    adict.TryGetValue("SOF_ID", out strval);
    Print("C: SOF_ID   : " + strval);
    adict.TryGetValue("SOF_GRP", out strval);
    Print("D: SOF_GRP  : " + strval);
    adict.TryGetValue("SOF_SNO", out strval);
    Print("E: SOF_SNO  : " + strval);
    adict.TryGetValue("SOF_SNOE", out strval);
    Print("F: SOF_SNOE : " + strval);
    adict.TryGetValue("SOF_STYP", out strval);
    Print("G: SOF_STYP : " + strval);
    adict.TryGetValue("SOF_STYP2", out strval);
    Print("H: SOF_STYP2: " + strval);
    adict.TryGetValue("SOF_NP", out strval);
    Print("I: SOF_NP   : " + strval);
    adict.TryGetValue("SOF_SDIV", out strval);
    Print("J: SOF_SDIV : " + strval);
    adict.TryGetValue("SOF_KR", out strval);
    Print("K: SOF_KR   : " + strval);
    adict.TryGetValue("SOF_DROT", out strval);
    Print("L: SOF_DROT : " + strval);
    adict.TryGetValue("SOF_FIX", out strval);
    Print("M: SOF_FIX  : " + strval);

    Print("-----------------------------------");
    Print("N: Count " + adict.Count);

    brep.UserDictionary.Clear();
  }

… and the component:
RILUserDataExample 00.gh (10.6 KB)

// Rolf

Hello,

thanks a lot, especially for the updated diagram.
I’ll definitely try to implement this! For now I found a very inelegant way to achieve what I want using Human tools. Out of lack of time I couldn’t do it the pretty way with my own C# component (just not enough practice with C# yet…) but in the later stages of this project I will remedy that. :wink:

I’ve come up with this, so the user has to actually type in the user data.

At a later point, I’ll try to use your code and advice to make a component that creates the user data depending on various inputs that are more easily understandable for a user. :smiley:

1 Like

Glad you found a solution.

Sorry if the code example looked overly complicated. Follows a simplified code snippet which adds two rows of info and reads it again from the std UserDictionary (no null checks, just write and read):

    // insert data
    brep.UserDictionary.Set("SOF_ID", 1001);
    brep.UserDictionary.Set("SOF_GRP", "SOF_PROP_COMBO_BYLAYER");

    // read data
    var str = "";
    brep.UserDictionary.TryGetValue("SOF_ID", out str);
    Print(str); // prints to the "out" port
    brep.UserDictionary.TryGetValue("SOF_GRP", out str);
    Print(str); // prints to the "out" port
    // and so on

// Rolf