Hello,
I have the following code that uses DrawOverlay in a Display Conduit to draw Silhouette Boundary Curves on selected objects.
This works great on Breps, Surfaces, Etc. I’m struggling to figure out how to get it to work on Blocks aka Instance Definitions or Instance Definition References
I tested by getting a little hack in my ExplodeBlockHelper function to get the block geometry, join said block geometry, and compute the silhouette of said geometry, however, this is not the result I’m looking for and is of course verrrry slow as it’s looping through all the geometry and such…
Visually with this script I am just trying to achieve what the “Hover Silhouette Highlight” is doing as the example in this snip showcases (But instead of a Hover calling this, I want to be able to call it as a function/module from other scripts for “real-time” visual highlighting and also for “one-time” silhouette use to get the curve objects of the boundary curves (for example on a static, visual diagram):
Here is the result from what I have currently… (Note it did the silhouette on EVERY geometry instead of the “whole”):
Here is the Rhino highlight on a brep:
Here is my version of the highlight on a brep (This is what I want and working correctly):
@menno or @dale is this something either of you would be able to assist me on?
Specifically how to use the Silhouette.Compute method on an Instance Definition to get the overall boundary silhouette instead of the nested objects?
Thank you so much!
Here is the full code in Python 3:
import Rhino
import scriptcontext as sc
import rhinoscriptsyntax as rs
from System.Drawing import Color
class PreviewAttributes:
def __init__(self):
self.ObjectColor = Color.Black
self.ColorSource = Rhino.DocObjects.ObjectColorSource.ColorFromObject
self.WireDensity = 1
self.LayerIndex = -1
self.CurveThickness = 5 # Default thickness for non-silhouette
self.PointStyle = Rhino.Display.PointStyle.ControlPoint
self.PointPixels = 3
self.TextColor = Color.White
@staticmethod
def Selected():
attr = PreviewAttributes()
attr.ObjectColor = Color.BlueViolet # Default is Color.Yellow
attr.ColorSource = Rhino.DocObjects.ObjectColorSource.ColorFromObject
attr.TextColor = Color.Black
return attr
@staticmethod
def New():
attr = PreviewAttributes()
attr.ColorSource = Rhino.DocObjects.ObjectColorSource.ColorFromLayer
attr.LayerIndex = sc.doc.Layers.CurrentLayer.LayerIndex
return attr
@staticmethod
def Warning():
attr = PreviewAttributes()
attr.ObjectColor = Color.Red
attr.ColorSource = Rhino.DocObjects.ObjectColorSource.ColorFromObject
attr.TextColor = Color.White
return attr
class PreviewDisplayConduit(Rhino.Display.DisplayConduit):
def __init__(self):
super(PreviewDisplayConduit, self).__init__()
self._objects = {}
self._hidden_objects = []
self._exploded_geometries = []
self.CreateSilhouette = False
self.Silhouettes = []
def AddObject(self, obj, attributes):
if obj:
self._objects[obj] = attributes
def HideObjects(self, objs_to_hide):
self._hidden_objects = objs_to_hide
rs.HideObjects(self._hidden_objects)
def ClearObjects(self):
self._objects.clear()
self._exploded_geometries.clear()
self.ShowHiddenObjects()
def ShowHiddenObjects(self):
for obj_id in self._hidden_objects:
if rs.IsObjectHidden(obj_id):
rs.ShowObject(obj_id)
self._hidden_objects.clear()
def DrawOverlay(self, e):
self._exploded_geometries.clear()
for obj, attr in self._objects.items():
color = attr.ObjectColor
material = Rhino.Display.DisplayMaterial(color)
if attr.ColorSource == Rhino.DocObjects.ObjectColorSource.ColorFromLayer:
layer = sc.doc.Layers[attr.LayerIndex]
color = layer.Color
if self.CreateSilhouette:
if isinstance(obj, Rhino.Geometry.InstanceReferenceGeometry):
# Compute silhouette for the entire block instance
self.ComputeBlockSilhouette(e, obj, attr)
else:
self.Silhouettes = ComputeSilhouette([obj])
for silhouette in self.Silhouettes:
e.Display.DrawCurve(silhouette, color, 2) # Silhouette curve thickness
else:
if isinstance(obj, Rhino.Geometry.Curve):
e.Display.DrawCurve(obj, color, attr.CurveThickness)
elif isinstance(obj, Rhino.Geometry.Brep):
e.Display.DrawBrepShaded(obj, material)
elif isinstance(obj, Rhino.Geometry.Point):
e.Display.DrawPoint(obj.Location, attr.PointStyle, attr.PointPixels, color)
elif isinstance(obj, Rhino.Geometry.Mesh):
e.Display.DrawMeshShaded(obj, material)
elif isinstance(obj, Rhino.Geometry.TextDot):
e.Display.DrawDot(obj.Point, obj.Text, color, attr.TextColor)
elif isinstance(obj, Rhino.Geometry.InstanceReferenceGeometry):
# Handle block instance differently
self.ComputeBlockSilhouette(e, obj, attr)
else:
Rhino.RhinoApp.WriteLine("Obj Type Is: " + str(obj))
# Draw collected silhouettes
if self.CreateSilhouette:
for silhouette in self.Silhouettes:
e.Display.DrawCurve(silhouette, Color.Red, 2) # Draw silhouette with specific color and thickness
def ComputeBlockSilhouette(self, e, iref, attr):
Rhino.RhinoApp.WriteLine("Compute Block Silhouette Call")
idef = sc.doc.InstanceDefinitions.FindId(iref.ParentIdefId)
if idef is None:
return
# Transform and collect all nested geometries
transformed_geometries = self.TransformInstanceGeometries(iref)
# Create a combined geometry (Brep or Mesh) for the entire block
combined_geom = None
if transformed_geometries:
breps_and_meshes = [g for g in transformed_geometries if isinstance(g, (Rhino.Geometry.Brep, Rhino.Geometry.Mesh))]
if breps_and_meshes:
if all(isinstance(g, Rhino.Geometry.Brep) for g in breps_and_meshes):
combined_geom = Rhino.Geometry.Brep.JoinBreps(breps_and_meshes, sc.doc.ModelAbsoluteTolerance)
elif all(isinstance(g, Rhino.Geometry.Mesh) for g in breps_and_meshes):
combined_geom = Rhino.Geometry.Mesh()
for mesh in breps_and_meshes:
combined_geom.Append(mesh)
if combined_geom:
self.Silhouettes.extend(ComputeSilhouette([combined_geom]))
Rhino.RhinoApp.WriteLine(f"Silhouettes: {self.Silhouettes}")
def TransformInstanceGeometries(self, iref):
idef = sc.doc.InstanceDefinitions.FindId(iref.ParentIdefId)
if idef is None:
return []
xform = iref.Xform
objects = idef.GetObjects()
transformed_geometries = []
for obj in objects:
if obj is None:
continue
geom = obj.Geometry.Duplicate()
if isinstance(geom, Rhino.Geometry.InstanceReferenceGeometry):
nested_geometries = self.TransformInstanceGeometries(geom)
for nested_geom in nested_geometries:
if not nested_geom.Transform(xform):
continue
transformed_geometries.append(nested_geom)
else:
if xform.IsValid and not xform.Equals(Rhino.Geometry.Transform.Identity):
if not geom.Transform(xform):
continue
transformed_geometries.append(geom)
return transformed_geometries
def CalculateBoundingBox(self, e):
bbox = Rhino.Geometry.BoundingBox()
for obj in self._objects.keys():
bbox.Union(obj.GetBoundingBox(True))
e.IncludeBoundingBox(bbox)
class PreviewManager:
def __init__(self):
self.conduit = PreviewDisplayConduit()
def preview_objects(self, objects, attr, activate=True, silhouette=False):
if not objects:
return
self.conduit.CreateSilhouette = silhouette
if not silhouette:
self.conduit.HideObjects(objects)
for obj_id in objects:
rhino_obj = rs.coercegeometry(obj_id)
self.conduit.AddObject(rhino_obj, attr)
self.conduit.Enabled = activate
sc.doc.Views.Redraw()
def clear_preview(self):
self.conduit.Enabled = False
self.conduit.ClearObjects()
self.conduit.ShowHiddenObjects()
sc.doc.Views.Redraw()
def ComputeSilhouette(objs):
Rhino.RhinoApp.WriteLine("Compute Silhouette Call")
# Get the active view
vp = Rhino.RhinoDoc.ActiveDoc.Views.ActiveView
# Get the camera location from the active view
cam_loc = vp.ActiveViewport.CameraLocation
# Define other parameters
st = Rhino.Geometry.SilhouetteType.Boundary
tol = Rhino.RhinoDoc.ActiveDoc.ModelAbsoluteTolerance
a_tol = Rhino.RhinoDoc.ActiveDoc.ModelAngleToleranceRadians
# Initialize a list to store the curves
outline_crvs = []
for obj in objs:
rhino_obj = rs.coercegeometry(obj)
if rhino_obj is not None:
obj_outline = Rhino.Geometry.Silhouette.Compute(rhino_obj, st, cam_loc, tol, a_tol)
for silhouette in obj_outline:
crv = silhouette.Curve
outline_crvs.append(crv)
# Join the curves
if outline_crvs:
joined_crvs = Rhino.Geometry.Curve.JoinCurves(outline_crvs)
if joined_crvs:
return joined_crvs
return outline_crvs
def PreviewObjects():
selected_objects = rs.GetObjects("Select objects to preview")
if not selected_objects:
return
manager = PreviewManager()
attr = PreviewAttributes.Selected()
manager.preview_objects(selected_objects, attr, activate=True, silhouette=True)
rs.GetString("press enter or escape to end preview")
manager.clear_preview()
if __name__ == "__main__":
PreviewObjects()