Hello everyone,
I’m returning to something that has been plaguing me for over a year.
How can I use an existing Rhino.Geometry class or create a new class that acts like a polyline, edits(vertices/segments), adds/deletes like a polyline, and looks like a polyline but retains metadata/user text attributes per segment.
The reason this is important (from our perspective) is that having the ability to “drive” advanced workflows (such as Grasshopper scripts or scripting functions with the polyline as an input) often requires a more nuanced understanding and retention of the overall data at the “segment” level.
Of course there is Explode and Shatter or only ever utilizing single line segments with user data attached and then post rationalizing “which are connected in a continuous chain” but this makes editing of said polyline/chain a bit of a UX and dictionary keeping nightmare.
The code below, when ran, will allow you to pick points and create a pseudo PolyCurve object that prints out the following:
-uuid for each curve segment
-polycurve_id for the overall polycurve that the segment belongs to
-the user text attributes assigned on per segment instance
Here is a visual example of the desired behavior, where a single polyline controls the overall “form” but each segment has unique metadata driving it’s behavior (in this case a very basic extrusion in Z direction of metadata key=height, value=“unique value per segment here” scenario)
So it may seem pretty obvious that this is “already acheivable” but the issue is that the exploding works on an index system meaning, if a segment is added or deleted, it “shifts” the data and you may now end up with one segment inheriting incorrect data from the segment that was added/removed because it now has a different index value.
Maintaining a UUID for each segment would prevent this (I believe?) but my issue is that there is no where I can find to actually store this information within the rhino object itself.
Here’s as far as I’ve gotten (python):
import Rhino
import rhinoscriptsyntax as rs
import uuid
import scriptcontext as sc
class CustomPolyCurve:
def __init__(self, points):
self.control_points = points
self.polycurve_id = str(uuid.uuid4()) # Unique ID for the entire polycurve
self.segment_metadata = [] # List to store segment data (UUIDs and other info)
self.polycurve = Rhino.Geometry.PolyCurve() # Rhino PolyCurve to hold segments
self._initialize_segments()
def _initialize_segments(self):
"""Initialize segments within the PolyCurve and assign UUIDs and metadata to each."""
for i in range(len(self.control_points) - 1):
segment_uuid = str(uuid.uuid4())
start_point = self.control_points[i]
end_point = self.control_points[i + 1]
# Create a line segment between points and add to the PolyCurve
line = Rhino.Geometry.LineCurve(start_point, end_point)
self.polycurve.Append(line) # Add the line to the PolyCurve
# Store metadata for each segment
self.segment_metadata.append({
"uuid": segment_uuid,
"polycurve_id": self.polycurve_id,
"user_text": {}
})
# Add the PolyCurve to the document as a single entity
self.polycurve_id = sc.doc.Objects.AddCurve(self.polycurve)
sc.doc.Views.Redraw()
def add_point(self, point):
"""Add a point to extend the polycurve with a new segment and UUID."""
if self.control_points:
last_point = self.control_points[-1]
line = Rhino.Geometry.LineCurve(last_point, point)
self.polycurve.Append(line)
# Update control points and metadata
segment_uuid = str(uuid.uuid4())
self.segment_metadata.append({
"uuid": segment_uuid,
"polycurve_id": self.polycurve_id,
"user_text": {}
})
self.control_points.append(point)
# Update the PolyCurve in the document to reflect changes
sc.doc.Objects.Replace(self.polycurve_id, self.polycurve)
sc.doc.Views.Redraw()
def get_segment_metadata(self, index):
"""Retrieve metadata for a specific segment."""
if 0 <= index < len(self.segment_metadata):
return self.segment_metadata[index]
return None
def set_segment_metadata(self, index, key, value):
"""Set custom metadata for a specific segment."""
if 0 <= index < len(self.segment_metadata):
self.segment_metadata[index]["user_text"][key] = value
def redraw(self):
"""Redraw the PolyCurve in the document if control points or segments change."""
sc.doc.Objects.Replace(self.polycurve_id, self.polycurve)
sc.doc.Views.Redraw()
# Example usage
points = rs.GetPoints(True) # Prompt user to draw polycurve in Rhino viewport
if points:
polycurve = CustomPolyCurve(points)
polycurve.set_segment_metadata(0, "developer status", "seeking help")
polycurve.set_segment_metadata(3, "animal", "unicorn")
polycurve.set_segment_metadata(1, "best software company", "McNeel")
polycurve.redraw() # Redraw the polycurve if control points were modified
# Print segment metadata for verification
print(f">PolyCurve Metadata:\n>>>{polycurve.get_segment_metadata(0)}\n>>>{polycurve.get_segment_metadata(1)}\n>>>{polycurve.get_segment_metadata(3)}\n<End PolyCurve Metadata")
I feel like this is either very easy or complex and I’m somewhere lost in the middle haha.
Thank you all for your help!