I swear I’ve done this before but every function seems to want the object’s ID (to return it’s ID!!!).
A Geometry is contained by an RhinoObject, assuming you get it from the document so you already should have the object you need to get the ID from.
Perhaps a bit more context is needed - how are you getting your Geometry? What are you trying to do?
There isn’t an ID unless the geometry has been added to the document. I will assume you’re using python as that is what I’m familiar with…
Example 1: Use rhinoscriptsyntax to create objects. Record the guid at the creation event.
import rhinoscriptsyntax as rs
# set up the plane and radius
plane = rs.WorldXYPlane()
radius = 5.0
# create the circle with rhinoscriptsyntax
# rhinoscriptsyntax creates the object AND adds it to the document
# and it returns the guid
circle_id = rs.AddCircle(plane, radius)
Example 2: Use RhinoCommon to create objects and scriptcontext to add them to the document. Record the guid when the object is added to the document.
import Rhino.Geometry as rg
import scriptcontext as sc
# set up the plane and radius
plane = rg.Plane.WorldXY
radius = 5.0
# create the object with RhinoCommon
# RhinoCommon's Circle function returns the actual circle object
# and, as the circle object has not yet been added to the document,
# no guid exists for it yet
circle_ob = rg.Circle(plane, radius)
# add the circle to the document with scriptcontext's appropriate
# AddCircle method, and the guid will be returned
circle_id = sc.doc.Objects.AddCircle(circle_ob)
Example 3: Record the guid when selecting an object (rhinoscriptsyntax)
import rhinoscriptsyntax as rs
object_id = rs.GetObject('Select an object', rs.filter.allobjects, preselect = False, select = False)
Just to be clear: this does not create an Object, but a Geometry. Geometries do not have IDs. That is why you need to add the geometry to the document, the rs.AddCircle creates a RhinoObject and attaches the geometry to it.
I am!! Eventually I want to get back into C# and just be able to use both. Using Python in the script editor allows for rapid testing and development and using C# in Visual Studio might come in handy for compiling and not requiring tons of scripts floating around. Samples are split between C# and Python so knowing both languages helps there too.
This is a really good summary thank-you!!
Much to do with the semantics as an object-orientated programmer would think of both things as objects
. I’m now finally getting it though thank you!!
The actual code I’m writing would open up a new can of worms. I’m trying to make an ‘object modified’ reactor but it’s not so simple. I’m doing something similar in AutoCAD at the same time but in that case I can use a hack and use a command reactor - moving an AutoCAD grip always fires the same command (maybe Rhino does too? and just doesn’t output anything at the command line?) - I wasn’t sure what I was getting out of the eventArgs: https://developer.rhino3d.com/api/rhinocommon/rhino.docobjects.rhinoreplaceobjecteventargs
I think next weekend when I have time I can really get into this and rely less on the AI. Things are a lot more clear now! Thank-you!!
You know you can write C# scripts with _ScriptEditor in Rhino 8 and later, right?
I do! But I even had issues getting them to run - basically I don’t know how to lay out my classes and call “main” - even a small “Hello World” example would help. I haven’t coded with C# for a while.
There is no main in script editor. The simplest C# helloworld script looks like this:
// #! csharp
using System;
Console.WriteLine("Hello world");
You write C# code pretty much the same as you would for Python scripts.
If the script contains classes you just create/declare them when the script runs? And the script itself is a class essentially?
I guess I can create a C# script if it’s just lines of code but when it comes to organizing more complex code I’m not sure the best approach. For example, declaring a function - I think visual studio will start yelling at you if you declare a function outside of a class.
Still confused (re-confused lol). Per this link: How access UserDictionary in Python? - #9 by emilio
The code is correct but it calls the Object() Method on the “Get” objects object so it’s getting an object from an object from an object. I guess I’m confusing the ObjRef (Rhino.DocObjects.ObjRef) with Rhino.DocObjects.RhinoObject. The reference contains the RhinoObject? I’m sure there’s a reason for having containers within containers. For what I’m used to this almost feels like having two extra layers. But once I figure it out it’s figured out and then I can enjoy the advantages (whatever they are) of this system.
As you might have guessed I’m working with user dictionaries (for what I’m doing I think I have to attach the data to the objects). That’s proving a bit challenging as the AI is spitting out code that works, but I cannot figure out why sometimes.
The code below will let you select an object, and then write a key/value pair to its UserText, which you can view by selecting the object, and going into the User Text sub-tab of the Object Properties tab.
#! python 2
import rhinoscriptsyntax as rs
# let user select an object to get the object's guid
object_id = rs.GetObject('Select an object', rs.filter.allobjects, preselect = False, select = False)
# write text to the object's usertext
key = 'key'
value = '25' # note: values must be strings
rs.SetUserText(object_id, key, value)
# now you can look at the object's usertext and see the new field you created
Thanks again Webdunce!! I’m actually aiming for the UserDictionary or UserData that would be hidden from plain view. I’m learning backwards in a sense in that I don’t know why this code even works quite yet.
https://developer.rhino3d.com/api/rhinocommon/rhino.docobjects.custom.userdictionary
https://developer.rhino3d.com/api/rhinocommon/rhino.docobjects.custom.userdata
I only want to use the user attributes (these names are all so similar and getting confusing lol!) as a last resort. It’s a good resort especially short term. For the sake of not being a hoarder I’ll post the “AI’s” code (most likely an amalgamation of several people’s code) below:
import Rhino
import scriptcontext as sc
import rhinoscriptsyntax as rs
# Select an object
obj_id = rs.GetObject("Select an object to attach data to")
if obj_id:
# Get the RhinoObject
rhino_obj = sc.doc.Objects.Find(obj_id)
if rhino_obj:
# Attach data to the geometry's UserDictionary (persistent with geometry)
rhino_obj.Geometry.UserDictionary.Set("MyHiddenKey", "Some hidden value")
rhino_obj.Geometry.UserDictionary.Set("AnotherKey", 42) # Example with integer
# Alternatively, attach to attributes if preferred
# rhino_obj.Attributes.UserDictionary.Set("MyHiddenKey", "Some hidden value")
# Commit changes to update the object in the document
rhino_obj.CommitChanges()
# Retrieve the data to verify
retrieved_value = rhino_obj.Geometry.UserDictionary.GetString("MyHiddenKey")
print("Retrieved value:", retrieved_value) # Outputs: Some hidden value
# Remove data if needed
# rhino_obj.Geometry.UserDictionary.Remove("MyHiddenKey")
# rhino_obj.CommitChanges()
print("Data attached successfully. Save the file and reopen Rhino to confirm persistence.")
This is actually what I’m working with:
https://developer.rhino3d.com/api/rhinocommon/rhino.collections.archivabledictionary
So many things with similar names…
Yeah, I should at least have inserted a few comments there … ![]()
The following is my understanding of RhinoCommon in that code:
gob is an instance of the Rhino.Input.Custom.GetObject class.
I wouldn’t call it an object, rather something that allows the user to select an object.
I might call it an input operator.
Its Object method returns a Rhino.DocObjects.ObjRef instance and … yes,
calling that method Reference might have made thing less confusing, maybe … ![]()
Finally, the Rhino.DocObjects.ObjRef.Object method returns a proper Rhino.DocObjects.RhinoObject instance (rob), which is what we need to get the ObjectAttributes of the object.
I don’t know, and I guess I haven’t been thinking about that so much … ![]()
I know that I can get a RhinoObject from an ObjRef and vice versa when I need it, and am happy with it. ![]()
Thanks emilio! When I say your code from that post a light bulb went off. Since then I’ve been right into the API docs and now I’m getting over the hurdle that was trying to call methods from the wrong things. I feel like I get it now but even if I don’t I know how to track down the issues in the API.
One thing I learnt over these last 5 days is that using OOP for an API for a program that also uses “objects” gets a little tricky in how things are named. An instance of a class is technically an object (in OOP terminology). That makes things feel strange when working with Rhino (or AutoCAD for that matter). In this situation I’m also working a lot with “InstanceObjects”. I’m using the AI a lot and I think the AI was also getting confused.
The names of stuff was getting me too (see my post above - Archivable Dictionaries) - but knowing how to navigate the API docs helps greatly with that as well. Some stuff from the docs is also missing in the Script Editors help section.
// #! csharp
using System;
// a class
public class CustomStuff
{
public string Value { get; private set; }
public CustomStuff(string n)
{
Value = n;
}
}
// a method
CustomStuff CreateStuff(string n)
{
return new(n);
}
CustomStuff stuff = CreateStuff("hello world");
Console.WriteLine(stuff.Value);
Wish I had known about the ability to attach user data to the geometry itself. In my project, I’ve stored all my data in the UserText, which can potentially be edited or removed by the user…it’s unlikely but possible. Thanks for sharing.
Hehe … just another annoyance of OOP …
![]()
A lot of overloading there, but it’s just the way C# works. ![]()
There’s a post about it causing a crash when used in huge numbers so watch out for that. I think for what I’m doing it’ll work nicely though. Once I get better at programming I might attach some sort of list to the file and that way the script (probably a bonified plugin at that point) won’t have to search the object list but rather just a basic list. Managing that will be a little harder (I’ll need events for object modified, erased, copied, etc…).
I am soon going to (try to) make the objects my scripts create editable. Part of that process will be storing and editing data. I was just chatting with Gemini, and he said I could store the data on the Attributes, the Geometry or in UserText (which I currently do). He recommended storing it on the UserDictionary of the Attributes and not the one on the Geometry. Apparently storing data on the geometry itself can cause an uneeded load on the undo stack and maybe in other ways as well.
Below was an example of how Gemini recommended storing and retrieving data (example is in python since that’s what I use)
import Rhino
import scriptcontext as sc
def store_gem_data():
# 1. Get an object from the document
rc, obj_ref = Rhino.Input.RhinoGet.GetOneObject("Select gem to tag", False, Rhino.DocObjects.ObjectType.AnyObject)
if rc != Rhino.Commands.Result.Success: return
rhino_obj = obj_ref.Object()
# Always use Attributes for metadata to avoid heavy geometry copies
attr = rhino_obj.Attributes
# 2. STORE data in the UserDictionary
# Unlike Python dicts, use .Set() to ensure proper .NET serialization
attr.UserDictionary.Set("GemType", "Diamond")
attr.UserDictionary.Set("GemSize", 5.5)
attr.UserDictionary.Set("IsEditable", True)
# Commit changes back to the Rhino document
rhino_obj.CommitChanges()
print("Gem data stored successfully.")
def retrieve_gem_data():
rc, obj_ref = Rhino.Input.RhinoGet.GetOneObject("Select gem to read", False, Rhino.DocObjects.ObjectType.AnyObject)
if rc != Rhino.Commands.Result.Success: return
attr = obj_ref.Object().Attributes
udict = attr.UserDictionary
# 3. RETRIEVE data
if udict.Count > 0:
# Check if a specific key exists before grabbing
if udict.Contains("GemType"):
gem_type = udict["GemType"]
gem_size = udict["GemSize"]
print("Gem found: {0} (Size: {1}mm)".format(gem_type, gem_size))
else:
print("No metadata found on this object.")
if __name__ == "__main__":
store_gem_data()
retrieve_gem_data()
Also, I’ve been using rhinoscriptsyntax’s input functions, which are nice and simple, but Gemini used the Rhino.Input from RhinoCommon for letting the user select objects, which, seems more complicated but may offer more options and gives direct access to the Geometry instead of just the GUID. So, I may look into switching to using that for my user selections eventually.
I have found both Grok and Gemini extremely useful for generating examples of how to do certain things with RhinoCommon, ETO forms or rhinoscriptsyntax (I don’t know if c# has anything like rhinoscriptsyntax…that may be only for python)
