Python Script for RhinoAPI to Create Legend

Hello everyone!

I’m a newcomer to RhinoAPI and currently working on a Python script within Rhino (to be used in Grasshopper) for generating a legend similar to the image below:
MicrosoftTeams-image

Here’s the code I’ve written so far, but unfortunately, it’s not functioning as expected, and I’m struggling to identify the issue:

import Rhino
import System.Drawing
import rhinoscriptsyntax as rs
import scriptcontext

class CustomConduit(Rhino.Display.DisplayConduit):
    def __init__(self, colors = [(205,0,71),(255,127,70),(255,219,71),(205,254,113),(90,220,186),(0,128,229),(0,0,206)], scale_factor = 1.0, text_title_dim = 10, text_dim = 8, side_dim = 10, texts = ["0","0.2","0.4","0.6","0.8","1",">1"],title = "U Legend", subtitle = "Utilisation factor [-]", subsubtitle = "GSA Case ", case = "(A1)"):
        self.colors = colors
        self.scale_factor = scale_factor
        self.text_title_dim = text_title_dim
        self.text_dim = text_dim
        self.side_dim = side_dim
        self.texts = texts
        self.title = title
        self.subtitle = subtitle
        self.subsubtitle = subsubtitle
        self.case = case
        
    
    def DrawForeground(self,e):
        bounds = e.Viewport.Bounds
        corner = Rhino.Geometry.Point2d(bounds.Top, bounds.Right)
        TR_corner = Rhino.Geometry.Point2d(corner.X - 50, corner.Y -50)
        x_TR = TR_corner.X
        y_TR = TR_corner.Y
        
        for i in range(len(self.colors)):
            rectangle = System.Drawing.Rectangle.FromLTRB(x_TR - self.side_dim, y_TR - self.side_dim*i , x_TR , (y_TR - self.side_dim*i) - self.side_dim * i)
            color = System.Drawing.Color.FromArgb(self.colors[i][0],self.colors[i][1],self.colors[i][2])
            e.Display.Draw2dRectangle(rectangle,color,1,color)
        
        

def showlegend():
    conduit = None
    
    if scriptcontext.sticky.has_key("myconduit"):
        conduit = scriptcontext.sticky["myconduit"]
    else:
        conduit = CustomConduit()
        scriptcontext.sticky["myconduit"] = conduit
    
    conduit.Enabled = not conduit.Enabled
    
    if conduit.Enabled:
        print "Legend shown"
        
    else:
        print "Legend hidden"
    
    scriptcontext.doc.Views.Redraw()

if __name__=="__main__":
    showlegend()

I prefer not to use external plugins and am currently working with Rhino 7. Any help or suggestions would be greatly appreciated!

Have a look at this topic, this post demonstrates the basic methods I use when drawing legends/HUDs:

1 Like

Thanks for the answer @AndersDeleuran :upside_down_face:. I’ll go and check it for sure!

I need to thank you again @AndersDeleuran !
Now the script works flawlessly.
I’ll leave the gh file with the Python component here in case anybody needs it.
20231221_LegendaConPython.gh (16.4 KB)
For those who are interested I’ll also leave here the Python script:

__author__ = "Paolo.Colombo"
__version__ = "2023.12.21"

import System
import Rhino as rc
import Grasshopper as gh
import GhPython.Assemblies.ExecutingComponent as component

# Some useful description for the component inputs
ghenv.Component.Description = "This component create a personalized Legend. Input all the parameters to see the legend on each Rhino View."
ghenv.Component.Params.Input[0].Description = "Title of the legend as string."
ghenv.Component.Params.Input[1].Description = "List of values to display. The number of values should be the same as the number of colors."
ghenv.Component.Params.Input[2].Description = "List of colors. The number of colors should be the same as the number of values."
ghenv.Component.Params.Input[3].Description = "Subtitle as string."
ghenv.Component.Params.Input[4].Description = "Second line for an additional subtitle as string."
ghenv.Component.Params.Input[5].Description = "Font as string."
ghenv.Component.Params.Input[6].Description = "Scale as a decimal number."
ghenv.Component.Params.Input[7].Description = "X position of the legend inside the rhino views. The value 0 correspond to the extreme left of each view."
ghenv.Component.Params.Input[8].Description = "Y position of the legend inside the rhino views. The value 0 correspond to the extreme top of each view."

class MyComponent(component):
        
    def RunScript(self, Title, Values, Color, SubTitle, SubSubTitle, Font, Scale, x_pos, y_pos):
        #rc.RhinoApp.WriteLine("A 2")
        
        # Assign input variables
        self.heigh = 10
        self.heighT = 8
        self.title = Title
        self.col = Color
        self.val = Values
        self.subtitle = SubTitle
        self.subsubtitle = SubSubTitle
        self.font = Font
        self.scale = Scale
        self.x = x_pos
        self.y = y_pos
        self.black = System.Drawing.Color.Black
        self.yellow = System.Drawing.Color.Yellow
    def DrawForeground(self,sender,arg): # Modify the DrawForeground event to draw whatever we like on the canvas
        #rc.RhinoApp.WriteLine("A 3")
        
        # Check component preview/locked, active GH document and Rhino viewport
        if (not ghenv.Component.Hidden and not ghenv.Component.Locked 
            and ghenv.Component.OnPingDocument() == gh.Instances.ActiveCanvas.Document
            and len(self.val)==len(self.col)):
            #and arg.Viewport.Id == arg.RhinoDoc.ActiveDoc.Views.ActiveView.ActiveViewportID): #do not considerate this 
            
            #rc.RhinoApp.WriteLine("A 4")
            
            # Title is drawn in front of mesh
            pt = rc.Geometry.Point2d(self.x,self.y-self.heigh*self.scale)
            arg.Display.Draw2dText(self.title,self.black,pt,False,self.heighT*self.scale,self.font)
            
            # Subtitle
            pt_sub = rc.Geometry.Point2d(self.x,self.y+self.heigh*(len(self.val)+0.5)*self.scale)
            arg.Display.Draw2dText(self.subtitle,self.black,pt_sub,False,self.heighT*self.scale,self.font)
            
            # values
            for i in range(len(self.val)):
                po = rc.Geometry.Point2d(self.x + (self.heigh*self.scale)*3/2,self.y + self.heigh*(i)*self.scale+self.heighT*0.25*self.scale)
                arg.Display.Draw2dText(self.val[i],self.black,po,False,self.heighT*self.scale,self.font)
            
            #SubSubTitle
            pt_subsub = rc.Geometry.Point2d(self.x, self.y+self.heigh*(len(self.val)+1.5)*self.scale)
            arg.Display.Draw2dText(self.subsubtitle,self.black,pt_subsub,False,self.heighT*self.scale,self.font)
            
            
            # Rectangle is drawn in front of mesh
            for i in range(len(self.val)):
                rec2d = System.Drawing.Rectangle.FromLTRB(self.x, self.y + i*self.heigh*self.scale, self.x + self.heigh*self.scale, self.y + i*self.heigh*self.scale + self.heigh*self.scale)
                arg.Display.Draw2dRectangle(rec2d,self.col[i],1,self.col[i])

    
    def __enter__(self):
        #rc.RhinoApp.WriteLine("A 1")
        rc.Display.DisplayPipeline.DrawForeground += self.DrawForeground
        
    def __exit__(self):
        #rc.RhinoApp.WriteLine("A 5")
        rc.Display.DisplayPipeline.DrawForeground -= self.DrawForeground

Here’s a screenshot of the legend working:

2 Likes