Create Tags using GhPython

Hello,

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 ! :grin:

Thank you in advance !

Hi @alexandre.flamant,

Would a textdot suffice for what you are trying to achieve?
They will face the camera, but you may have an issue of size.

1 Like

Hi @christopher.ho,

Thank you for your answer it could be perfect :smiley: ! 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 :grin:

I can’t find how to add these on my custom display do you know how to do ?

Thank you !

Hi @alexandre.flamant,

Here’s simple example on how to use textdots in GHPython:

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()
            

textdots_python_V1.gh (10.5 KB)

5 Likes

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 :smiley: !

1 Like

Hi @diff-arch thanks again for your script !

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 … :sweat_smile:

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 …

Do you have any idea on how to fix this ?

Thank you :smile:

Hi,

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)

textdots_python_V2.gh (21.2 KB)

3 Likes

It works perfectly ! :smiley:

I did try something similar to solve the problem but it didn’t really work out :sweat_smile: 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 :sweat_smile:

Thank you !

1 Like

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.

1 Like

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
1 Like

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!

Great! Im enjoying Python Machine Learning from Packt Press at the moment for both the theory and python implementation.