How to properly interface with GUIDs

Hello

I am trying to understand how to properly interface with Rhino GUIDs in python. When I want to access the properties, whether geometric or otherwise, of the object the GUID represents should I create a new geometry from the GUID using CreateX and work with that in Python? This effectively duplicates the original geometry that the GUID points to, which seems odd, but works. If I want to work with the original geometry via the GUID, where can I find a list of methods for this? I found one such function I was looking for in RS: rs.CurveStartPoint and rs.CurveEndPoint.

These two discussions have been helpful:

https://discourse.mcneel.com/t/how-to-get-line-start-end-without-casting-it-into-curve/90452/11
https://discourse.mcneel.com/t/functionality-of-rs-coercex-methods/65813/2
but I am wondering if there is updated documentation I am missing.

Thanks for pointing me in the right direction of resources, and for general comments on the general approach to working with GUIDs.

Another side of this is how to send geometry through to Grasshopper through GhPython. Should I be using the GUIDs? Convert to geometry first? Somehow access the original geometry via GUID?

If you have a GUID and want to examine the characteristics of the object it represents, you can use Rhino.DocObjects.Tables.ObjectTable.Findid
to retrieve the RhinoObject. From there, you can look at attributes or geometry.

The type of the RhinoObject’s .Geometry varies by whether it’s a line, surface, etc.

Look up the RhinoCommon API docs on developer.rhino3d.com for information on what types of information are available from which types of objects.

1 Like

This is really helpful, thank you @Nathan_Bossett

So could I iterate through a list of GUIDs, retrieve each RhinoObject (using Rhino.DocObjects.Tables.ObjectTable.Findid), and append each RhinoObject to a new list, resulting in a list of RhinoObjects instead of GUIDs?
Is that correct workflow?

1 Like

It’s what I do when I have a list of GUID’s to check. Example, in C# (example provided to show the difference between a text GUID if you have it and the GUID as an actual guid type) with manifest being a text file I’m reading, s being a string, and ro being a rhinoobject

                s = manifest.ReadLine();
                Guid g = new Guid(s);
                ro = doc.Objects.FindId(g);

My usual product workflow mostly involves having objects and not needing to do the lookup from GUID’s: I get them from events such as Select.

When I’m using GUID’s it’s usually to persist data outside of Rhino or to reconnect data that I’ve stored outside of Rhino back to the Rhino model.

1 Like

@Nathan_Bossett Do you work with Python as well as C#?

I am working with this method
objs = rs.ObjectsByLayer(layer_name)

It returns an identifier rather than the object. https://developer.rhino3d.com/api/rhinoscript/selection_methods/objectsbylayer.htm

Of course, I can coerce a brep from the GUID but that is indirect, and I think your approach of always working with the RhinoObjects rather than interfacing through GUIDs is cleaner.

When you say you get the objects from events such as select, you imply that the user must manually select the objects. What about getting the objects from a find by layer type method?

Do you, or anyone else, know of an alternative method to rs.ObjectsByLayer in Rhino and Python, whether RhinoCommon or RhinoScript?

All the rhinoscriptsyntax methods are are just wrappers for RhinoCommon functions. I have never understood this obsession with “cleanness”. You have to get an object stored in the document somehow, why is using the GUID and rhinoscriptsyntax to reference it a problem??? If you really absolutely definitely no two ways about it need to stay in RhinoCommon, go into the library and copy out the GetObjects() method and use that.

C:\Users\<username>\AppData\Roaming\McNeel\Rhinoceros\7.0\Plug-ins\IronPython (814d908a-e25c-493d-97e9-ee3861957f49)\settings\lib\rhinoscript

The above is for V7, in V8 they are in 2 different places for Py 2 and Py 3.

Py 2
C:\Users\<username>\.rhinocode\py27-rh8\site-rhinopython\rhinoscript

Py 3
C:\Users\<username>\.rhinocode\py39-rh8\site-rhinopython\rhinoscript

@Helvetosaur I know you know what you’re doing, so I would defer to your take on this. It’s not an “obsession” with “cleannes”. I am just struggling to find documentation online of the intended workflow moving back and forth between GUIDs and RhinoObjects.

I am creating a Python class and storing various features/derivatives of the input geometry in a class instance. For example, I am calculating the centerline of the input geometry and storing that in a parameter of the class instance. I am also storing the layername of the source layer, and the GUID, etc.

  class StructuralMember:
      """Represents a structural member in the Rhino file."""
      structuralmemberinstances = []
  
      def __init__(self, guid, layername, layerfullpath, brep, centerline):
          """
          Initializes a new instance of the StructuralMember class.
          
          :param guid: Unique identifier for the structural member.
          :param rhinoobject: the actual Rhino object (obj) for the structural member
          :param layername: Name of the layer the member belongs to.
          :param layerobject: Object representing the layer.
          :param brep: Brep (Boundary Representation) for the structural member.
          :param centerline: Centerline of the structural member.
          """
          self.guid = guid
          self.layername = layername
          self.layerfullpath = layerfullpath
          self.brep = brep
          self.centerline = centerline
  
      def __str__(self):
          """
          Returns a string representation of this StructuralMember instance,
          including all its properties.
          """
          return ("StructuralMember (guid=" + self.guid + 
                  ", layername=" + self.layername + 
                  ", layerfullpath=" + self.layerfullpath + 
                  ", brep=" + str(self.brep) + 
                  ", centerline=" + str(self.centerline) + ")")

Would you suggest sticking with GUIDs as the reference to the geometry and then using CoerceBrep every time I need to operate on the geometry?

Also, if you don’t mind clarifying the use of the GetObject constructor, and why you are suggesting it, I don’t follow. The documentation is limited: https://developer.rhino3d.com/api/rhinocommon/rhino.input.custom.getobject/getobject
Brian

Trying not to just duplicate Helvetosaur’s comments…

Not much- I’ve used Python as required (such as when I really wanted one of its libraries).

For me it’s not about ‘clean’ in this case. RhinoObjects and GUIDs are pretty much interchangeable. Which is better is situational.

I track references to the objects because they’re not always RhinoObjects: I often maintain semi-durable references to subobjects like faces and edges of a Brep which don’t have id’s.

At some point the user has to give me some kind of input. :slight_smile:

For objects by layer,
ObjectTable.FindByLayer method (rhino3d.com)
Edit: link was to the wrong findbylayer

Thank you for your reply, and for the reference to FindByLayer method.

So, as an example of my confusion, I believe the following script can take both line objects and line GUIDs as inputs, but only outputs curve objects not curve GUIDs. But why this is I don’t understand.

I think it’s clear, but I am a novice, so I appreciate everyone’s help.

def convert_lines_to_curves(lines):
    curves = []
    for line in lines:
        # Check if the input is a GUID and convert it to a geometry object
        if isinstance(line, str):
            line_obj = rs.coercecurve(line, -1, True)
            if line_obj and rs.IsLine(line_obj):
                print("The centerline is a line.")
                crvstart = rs.CurveStartPoint(line_obj)
                crvend = rs.CurveEndPoint(line_obj)
                curve = Rhino.Geometry.LineCurve(crvstart, crvend)
                curves.append(curve)
            else:
                print("The centerline is not a line or could not be found.")
        elif rs.IsLine(line):
            print("The centerline is a line.")
            crvstart = rs.CurveStartPoint(line)
            crvend = rs.CurveEndPoint(line)
            curve = Rhino.Geometry.LineCurve(crvstart, crvend)
            curves.append(curve)
        else:
            print("The input is not recognized as a line.")
    return curves

Pretty confusing script IMHO, by the way … :slight_smile:
( Mixing Guid’s and RhinoObject’s this way looks, well … confusing to me.
But that’s just me, I guess :blush: )

This script extracts the geometry from the object and returns that.

“curve object” here means Rhino.Geometry.Curve objects/instances
Not RhinoDocs.RhinoObject objects representing curves.

Geometry objects have no Guid.
Only RhinoObjects have one.

Yuo have to create new Rhino objects from those curves, i.e. “drawing” new curves on the screen, so to speak, if you want to have new Guids.

This script does not add any new Rhino object to the document (the screen :wink: ), it just exctracts geometry instances from existing Rhino objects.

Well … I know that at first it’s pretty confusing, particularly if you’re learning both rhinoscriptsyntax and RhinoCommon at the same time, which personally I wouldn’t suggest … :slight_smile:

1 Like

Curves and such represent geometry and nothing else.

RhinoObjects bring geometry and other things into context for use in a model.

RhinoObjects are also more ‘durable’ than the geometry (curves, meshes, surfaces, etc.): a GUID will still be valid after minor edits while something like the faces of a brep might have been rebuilt or a curve might have been replaced.

For the model, there’s ‘is a’ (a curve is a GeometryBase) and ‘has a’ (a RhinoObject can have a GeometryBase).

I prepared this for my own use regarding the class inheritance (‘is a’) portion. You may find it helpful in understanding situations such as getting the Geometry of a RhinoObject (what types of object you might get back). Each object type is documented thoroughly on the RhinoCommon API pages regarding what’s publicly visible for the ‘has a’ portion of the object model.
Rhino3d-V8-RhinoCommon-Class-Diagram-2023-12-03-a.pdf (1.1 MB)

2 Likes

Hi @emilio,

A RhinoObject consists:

A RhinoObject is identified by its unique GUID, which persists between modeling sessions.

Geometry objects do not have GUIDs. And, the GUID of a RhinoObject is store on it’s Attributes.

To see a Rhino object’s GUID, use the What command.

The rhinoscriptsyntax library takes its inspiration from our legacy RhinoScript scripting engine. The intent of rhinoscriptsyntax was to provide an easy way for those who know RhinoScript, which is based on VBScript, to transition to Python.

In RhinoScript, you access objects by using their GUID. This made it easy to intermix RhinoScript code along with command macros.

Also, rhinoscriptsyntax is written in Python and uses RhinoCommon to (attempt to) clone the functions provided by legacy RhinoScript. If your curious what this code looks like, the source is available on GitHub.

In the end, you can choose to use (or not use) rhinoscriptsyntax. Everything it does can be done by just accessing RhinoCommon directly. But rhinoscriptsyntax does made doing some tasks easier, allowing you to type less code.

Hope this helps.

– Dale

5 Likes

Hi Dale,
thanks for your clear explanation !

Thanks emilio, this is useful info

1 Like

Thank you @Nathan_Bossett for the very clear explanation. I can understand RhinoObject as a class with properties. ‘is a’ and ‘has a’ is a nice way of explaining this. It is generous of you to share the hierarchy pdf–I will study it. I have been sorting through the RhinoCommon API but what I lack is a high level understanding of the architecture, so I don’t really understand half of what I am reading.

Thanks for the background Dale. Super helpful

GUIDs are also kept by Object Attributes → Rhino.DocObjects.ObjectAttributes.ObjectId

Object Geometry does not have access to the parent object or its parent object ID.
IMO, that is a major flaw in Rhino Common. Rhino.Geometry members should have access to the parent object identifier in the same way as Object Attributes do.

1 Like

A Rhino object’s GUID is stored on it’s attributes. So they are one and the same. The Rhino object itself is only a runtime object and does not serialize.

This is correct. A Rhino object’s geometry does not point back to the owning Rhino object. However, you can only access a Rhino object’s geometry through the Rhino object itself. So this isn’t an issue.

– Dale

2 Likes

The question I am asking is → Why not?. Object attributes do give the user access to the “owning Rhino object”. Its not only inconsistent its obstructive that the object geometry cannot do likewise. It is no wonder people trying to use Rhino Common are consistently confused about this and asking questions like " How to properly interface with GUIDs"

It only an issue if you are writing code.
As I pointed out in an earlier thread on this very same topic, the typical method (as recommended by McNeel) for a script to obtain Rhino Geometry is to get it from an ObjRef. That means often the users gets the geometry without access to “Rhino object itself”.

That means your statement “you can only access a Rhino object’s geometry through the Rhino object itself” is completely false.
Mostly, users are accessing the Rhino Geometry (if they follow the procedures McNeel recommends) from the ObjRef and not Rhino object itself. It also means that they may have a very hard time figuring out what GUID (or Rhino Object) is connected to the Geometry they have been given by the ObjRef. At the point in the code when they want to know what Attributes are connected to the geometry the ObjRef that gave them the geometry may be long gone.
To make things worse there is zero documentation to give them a clue of how to untangle this mess.

Hi @jim,

Sorry I wasn’t more clear. Let me try again.

Technically, a RhinoObject does not have a GUID. Again, its just a runtime object and only track runtime data, such as selection.

The GUID for a RhinoObject is stored on it’s ObjectAttributes member. Thus, when you call RhinoObject.Id, your really just calling RhinoObject.Attributes.ObjectId. You can use either method, depending on how much you like to type.

My statement is 100% accurate.

A Rhino object’s geometry is stored on the Rhino object. There is no other way to obtain the geometry other than by first obtaining the Rhino object and than asking it for it’s geometry.

You mentioned ObjRef, which is a reference to a RhinoObject. Although you can create these in several ways, ObjRef objects are mostly created by picking objects with GetObject.

An ObjRef contains all the details of the object selection operation, including the parent object you selected, the geometry (or sub-object geometry), the pick location, and more.

To illustrate further, lets use selecting a curve as an example.

var go = new GetObject();
go.SetCommandPrompt("Select curve");
go.GeometryFilter = ObjectType.Curve;
go.SubObjectSelect = false;
go.Get(); // get one object

If the selection operation was successful, you can obtain the ObjRef created by GetObject like this:

var objRef = go.Object(0); // Get the one object

If all you care about is the curve, then you can obtain the selected Curve geometry like this:

var curve = objRef.Curve();

Behind the scenes, ObjRef.Curve is doing this on your behalf:

var rhinoObject = objRef.Object();
if (null != rhinoObject && rhinoObject is CurveObject curveObject)
  return curveObject.CurveGeometry();
else
  return null;

Notice how the curve is obtained from the owning RhinoObject.

You can also obtain the selected Curve geometry like this:

var rhinoObject = objRef.Object();
if (null != rhinoObject && null != rhinoObject.Geometry)
  return rhinoObject.Geometry as Curve;
return null;

So, three different ways to obtain the Curve geometry. Which method you use depends on what you are trying to do.

And, if you want the selected Rhino object’s GUID, you can do this:

var objectId = objRef.ObjectId;

Which, under the hood, just does this:

var rhinoObject = objRef.Object();
if (null != rhinoObject)
  return rhinoObject.Attributes.ObjectId;
else
  return Guid.Empty;

Again, several ways to get there.

Well, of course. This act plays out in any type of programming and it not unique to Rhino.

Yes, I agree.

Let me know if any of this helps.

– Dale

1 Like