I would like to annonate some geometry I create with a GhPython component, like for example, display a specific number next to each point of a set of points, but within this very same script. I managed to add 3D text to my custom display. But it’s not really what i’m after … I’m looking for something similar to Grasshopper Tags which always face the camera.
I can’t find any resources on this. If anyone has a clue i’ll take it !
Thank you for your answer it could be perfect ! I would like to be able to change a few parameters like size or , more importantly, the color of the text, but without a doubt it’s way better than what i’ve been able to do for now …
I can’t find how to add these on my custom display do you know how to do ?
You can change the height, background colour, as well as the font.
import Rhino
ACTIVE_DOC = Rhino.RhinoDoc.ActiveDoc
def create_textdot(text_str, point, height=-1, font=-1):
"""Creates a TextDot at a given location.
Args:
text_string (str): A text to display.
point (Rhino.Geometry.Point3d): A location.
height (int): A font height.
font (str): A font face.
Returns:
The Rhino.Geometry.TextDot().
"""
textdot = Rhino.Geometry.TextDot(text_str, point)
if height > 0:
textdot.FontHeight = height
if font > 0:
textdot.FontFace = font
return textdot
def clear_textdots():
"""Deletes all TextDots from the active Rhino document."""
textdots = ACTIVE_DOC.Objects.FindByObjectType(Rhino.DocObjects.ObjectType.TextDot)
if len(textdots) > 0:
for tdot in textdots:
ACTIVE_DOC.Objects.Delete(tdot, True)
if __name__ == "__main__":
clear_textdots()
if Tag:
attr = Rhino.DocObjects.ObjectAttributes()
attr.ColorSource = Rhino.DocObjects.ObjectColorSource.ColorFromObject
attr.ObjectColor = Color;
for i in xrange(len(Locations)):
tdot = create_textdot(Descriptions[i], Locations[i], Size, Font)
ACTIVE_DOC.Objects.AddTextDot(tdot, attr)
else:
clear_textdots()
Thank you very much !
I made a simple implementation of your script,. I still need to completely understand how your code work, but it seems to perfectly suits my needs !
I came across an issue while implementing it in my components. I think I did understand the way it works, but now I can’t figure out how to make it work with multiple components …
For example, let’s say I want to be able to create textdots with one component to indicate nodes ID. With an another component I want to indicate a vector length.
As all the textdots geometry are stored inside Rhino.RhinoDoc.ActiveDoc the two components keep clearing each other when I use them …
Yes, you’re right! They clear each other because the clear_textdots() function looks for all the textdots in the active Rhino document and deletes each and everyone.
Sure, the new version works with multiple Python components on the canvas. Tagging and untagging works individually now, without one component interfering with another.
You can even input a tree of points (locations) and a tree of text (descriptions), instead of lists. Tagging works fine here, but deleting the tags afterwards doesn’t! I couldn’t figure out how to get it fully working with trees!
If you input your data separately as lists, everything works fine.
Here’s script version 2:
import scriptcontext as sc
from uuid import uuid4
import Rhino
ACTIVE_DOC = Rhino.RhinoDoc.ActiveDoc
def get_unique_id(_depth=0):
"""Returns a unique id that is not already a key in sticky."""
if _depth > 50:
raise ValueError(
"Recursion limit exceeded. Could not get a unique id."
)
id = str(uuid4())
if id not in sc.sticky.keys():
return id
_depth += 1
return get_unique_id(_depth)
def create_textdot(text_str, point, height=-1, font=-1):
"""Creates a TextDot at a given location.
Args:
text_string (str): A text to display.
point (Rhino.Geometry.Point3d): A location.
height (int): A font height.
font (str): A font face.
Returns:
The Rhino.Geometry.TextDot().
"""
textdot = Rhino.Geometry.TextDot(text_str, point)
if height > 0:
textdot.FontHeight = height
if font > 0:
textdot.FontFace = font
return textdot
def clear_textdots(object_ids):
"""Deletes a collection of TextDots from the active Rhino document."""
count = 0
for id in object_ids:
if ACTIVE_DOC.Objects.Delete(id, True):
count += 1
return count
def clear_all_textdots():
"""Deletes all TextDots from the active Rhino document."""
textdots = ACTIVE_DOC.Objects.FindByObjectType(
Rhino.DocObjects.ObjectType.TextDot
)
if len(textdots) > 0:
for tdot in textdots:
ACTIVE_DOC.Objects.Delete(tdot, True)
if __name__ == "__main__":
#sc.sticky = dict()
#clear_all_textdots()
if "component_id" not in globals():
component_id = get_unique_id()
try:
clear_textdots(sc.sticky[component_id])
except:
pass
if Tag:
sc.sticky[component_id] = []
print sc.sticky
print "Creating TextDots:"
for i in xrange(len(Locations)):
attr = Rhino.DocObjects.ObjectAttributes()
attr.ColorSource = Rhino.DocObjects.ObjectColorSource.\
ColorFromObject
attr.ObjectColor = Color;
tdot = create_textdot(Descriptions[i], Locations[i], Size, Font)
id = ACTIVE_DOC.Objects.AddTextDot(tdot, attr)
sc.sticky[component_id].append(id)
print " - {} TextDots placed".format(len(sc.sticky[component_id]))
else:
if component_id in sc.sticky.keys():
print "Deleting TextDots:"
num = clear_textdots(sc.sticky[component_id])
print " - {} TextDots purged".format(len(sc.sticky[component_id]))
sc.sticky.pop(component_id)
I did try something similar to solve the problem but it didn’t really work out and your code is far more comprehensible !
Just to know, is the _depth parameter limitation set to 50 arbitrary or is it linked to technical limitations? Even though it’s highly unprobable to even do a single recursion to get an id
The _depth argument is meant to be a private counter for the recursion depth to prevent
an infinite loop. Better safe than sorry, although it is not necessarily needed in this case!
And yes, the maximum depth of 50 is set arbitrarily.
Interesting! Python usually has a built in recursion limit. Use sys.getrecursionlimit() to find it - it is 2,147,483,647 on my Rhino 5 installation, i.e. there is effectively no recursion limit, wheras Cpython typically uses a few thousand!
Also IronPython seems to have no RecursionError, presumably because dotNet doesnt have any built in recursion depth handling
import sys
sys.setrecursionlimit(1000)
try:
something
except RecursionError:
give up
Yes, exactly, but whenever I write a recursive function I tend to implement a lower limit myself, to avoid simple mistakes in my initial logic to crash Rhino/Grasshopper. Also this should prevent a function from wasting too much time on a simple problem. In this case, reaching even a recursive depth of 50 seems improbable and excessive.
Hm, I believe the maximum recursion depth in C# is something like 18K, but as you may know, IronPython kinda sucks - even the name (because there’s nothing metal about it) -, and it is years behind in development. I’ve done some ML stuff in Python 3 lately and must say that it’s fast!