I am trying my hand at creating a custom sprite component for use in Grasshopper that will take a Point list (P input) and File Path (F input) and add the image sprite at the P locations.
I feel like I am close but struggling to figure out how to use the BitmapDrawList method?
import Rhino
import scriptcontext as sc
import Grasshopper as gh
bitmap = None
def create_display_bitmap(F):
global bitmap # Declare bitmap as global to modify the global variable
try:
# Load the image from the specified file path
bitmap = Rhino.Display.DisplayBitmap.Load(F)
return bitmap
except Exception as ex:
print("Error loading the image:", ex)
return None
def custom_sprite(P, bitmap):
# Check if the input image is valid
if not isinstance(bitmap, Rhino.Display.DisplayBitmap):
raise ValueError("Invalid input image. Please provide a valid DisplayBitmap.")
# Get the active display pipeline
dp = Rhino.Display.DisplayPipeline
# Draw the sprite at each specified point location
for point in P:
if point.IsValid:
size = 1.0 # You can adjust the size of the sprite if needed
translation = Rhino.Geometry.Vector3d(0, 0, 0) # No translation from the original point
size_in_world_space = False # Size is in screen space
# Draw the sprite
dp.DrawSprites(bitmap, Rhino.Display.DisplayBitmapDrawList, size, translation, size_in_world_space)
return "Sprites drawn successfully."
# Run the component in Grasshopper
if P and F:
create_display_bitmap(F)
custom_sprite(P, bitmap)
I would appreciate some guidance in how to handle the draw list and any other issues you all see.
That’s odd, I’ve never had that happen. Might be a security issue if you’re loading from a server maybe?
Anywho, I started embedding icons within the literal code of the component itself (i.e. as loooong base64 strings). Meaning you don’t need to issue or load the image files, it’s pretty neat (building on this code to draw into the foreground):
@AndersDeleuran encoding the image as a string is really quite awesome and I was looking for something like that but settled for an image import as it quickly went over my head on how to implement.
My use case is I’m making some “visual aid” icons that sit in front of the draw order of the points they are created at. So you can click the icon and it gets the point selection and runs the associated logic for those specific point selections.
I actually messed around with Unicode emojis for a moment (since those can exist in text dots, text entities, and panels) but graphically those are all over the place and I wanted more control over the visual aids then choosing between a brick wall and a door for example haha.
Also fitting that the BIG logo is indeed a very BIG logo (at least byte string wise )
Hehe, indeed. I encoded a horribly compressed Teams channel icon for this example. I suspect using a clean .png will yield a shorter string. To testing
I ended up combining both of your scripts to make a Custom Symbol Display component that can optionally be drawn in the foreground.
I think I’m going to eventually hardcode a library of the symbols as the base64 strings but for now and for testing the file path method works well enough as I’m still graphically iterating quite a bit.
Here’s the code I went with (mostly @anon39580149’s method with the draw order method from you @AndersDeleuran :
from ghpythonlib.componentbase import executingcomponent as component
import Grasshopper, GhPython
import System
import Rhino
import Rhino as rh
import Rhino.Geometry as rg
import rhinoscriptsyntax as rs
class MyComponent(component):
def RunScript(self, P, F, S, D, V):
if S == None: S = 20
self.F = F
self.P = P
self.D = D # Store the value of D as an instance variable
if self.F:
bitmap = System.Drawing.Bitmap.FromFile(F)
self.B = rh.Display.DisplayBitmap(bitmap)
self.S = S
self.size = V
if self.P and len(P) > 0:
bb = rg.BoundingBox(P)
self.bb = bb
else:
self.bb = rh.Geometry.BoundingBox.Empty
def DrawForeground(self, sender, e):
if self.D: # If D is True, draw the sprite in the foreground
if self.P and self.F:
items = rh.Display.DisplayBitmapDrawList()
items.SetPoints(self.P)
e.Display.DrawSprites(self.B, items, self.S, self.size)
def DrawViewportWires(self, arg):
if not self.D: # If D is False, draw the sprite as before using DrawViewportWires
if self.P and self.F:
items = rh.Display.DisplayBitmapDrawList()
items.SetPoints(self.P)
arg.Display.DrawSprites(self.B, items, self.S, self.size)
def get_ClippingBox(self):
return self.bb
def IsPreviewCapable(self):
return True
def __exit__(self):
rh.Display.DisplayPipeline.DrawForeground -= self.DrawForeground
def __enter__(self):
rh.Display.DisplayPipeline.DrawForeground += self.DrawForeground
@AndersDeleuran playing around with your script I have a lot of new, inspired ideas about interesting use cases for drawing the text and symbol to the viewport 2D like that. Thank you very much, I’m excited to experiment more with it.
Veeery interesting aplication, always a pleasure reading good things in rhino forum! Just curious, are you inserting 2 different images to get that result (warning symbol plus text) or is it just one image which contains everything?