On this page |
Overview ¶
(See Viewer handles for the basics of how to implement a Python handle.)
A Gadget is a geometry representing a visual component of a Python handle. For example, in a custom translate handle, a gadget could be used to represent the handle pivot for translating an object. In a slider handle, a gadget is a knob to change a parameter. A Python handle will typically use one or several gadgets to render its visuals.
Gadget drawable ¶
The hou.GadgetDrawable class represents a Python handle’s gadget. A gadget drawable is basically a specialized geometry drawable API with extra locating and picking functionalities that are essential for Python handles to identify which object the user is picking or which object under the cursor is pointing at.
Like geometry drawables, drawable gadgets can be set with any mesh geometries supported in Houdini, either by using verbs or even a geometry generated by a SOP network. However, for performance reasons, it’s best to assign Python handle gadgets with geometries defined through verbs.
Unlike geometry drawables, a gadget drawable must be registered first before use. With this information, Houdini can create the gadget instances of a Python handle by itself and perform the low-level picking and locating operations on the gadget’s underlying geometry.
For convenience, the gadget instances created by Houdini are stored in the handle_gadgets
attribute of the Python handle class.
Using gadget drawables ¶
Using gadget drawables is no different than using geometry drawables. Houdini makes it easier for the developer by creating the gadget objects automatically. A python handle implementation will typically set its gadgets with a geometry and a proper set of parameter values.
import resourceutils as ru def createViewerHandleTemplate(): handle_type = 'handle_example' handle_label = 'Handle example' handle_cat = [hou.sopNodeTypeCategory()] template = hou.ViewerHandleTemplate(handle_type, handle_label, handle_cat) template.bindFactory(Handle) template.bindIcon('$HFS/houdini/pic/minimizedicon.pic') # Register the gadgets template.bindGadget( hou.drawableGeometryType.Line, "line" ) template.bindGadget( hou.drawableGeometryType.Point, "point" ) return template class Handle(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) # Set the gadgets with the Houdini standard colors self.color_options = ru.ColorOptions(self.scene_viewer) # Set the "line" gadget sops = hou.sopNodeTypeCategory() verb = sops.nodeVerb("box") verb.setParms({"type" : 1, "divrate":(2,2,2), "size":(0.9,0.9,0.9)}) geo = hou.Geometry() verb.execute(geo, []) # Houdini uses `draw_color` for normal drawing, `pick_color` to draw the gadget when picking # is enabled and `locate_color` for highlighting the gadget. self.line = self.handle_gadgets["line"] self.line.setParams({"draw_color":self.color_options.colorFromName("YaxesColor")}) self.line.setParams({"pick_color":self.color_options.colorFromName("PickedHandleColor")}) self.line.setParams({"locate_color":self.color_options.colorFromName("HandleLocateColor", alpha_name="HandleLocateAlpha")}) self.line.setGeometry(geo) # Set the "point" gadget self.point = self.handle_gadgets["point"] verb.setParms({"type" : 1, "divrate":(2,2,2)}) verb.execute(geo, []) self.point.setParams({"draw_color":self.color_options.colorFromName("XaxesColor")}) self.point.setParams({"pick_color":self.color_options.colorFromName("PickedHandleColor")}) self.point.setParams({"locate_color":self.color_options.colorFromName("HandleLocateColor", alpha_name="HandleLocateAlpha")}) self.point.setParams({"radius":7.0}) self.point.setGeometry(geo) def onDraw(self, kwargs): # draw each gadget draw_handle = kwargs["draw_handle"] self.line.draw(draw_handle) self.point.draw(draw_handle)
The __init_
function uses a resourceutils.ColorOptions
attribute for setting gadget colors. ColorOptions
is a utility class for retrieving Houdini standard colors. It is strongly suggested to use this utility
for selecting color values and make your Python handle coherent with other Houdini builtin handles.
Colors fetched with resourceutils.ColorOptions
will also follow the viewport color scheme when the
background theme is modified.
Tip
The glow setting used for locating the handle component is set by default but can be customized with
the locate_glow
param, see hou.GadgetDrawable for details.
Gadgets and parameters binding ¶
As for any builtin handles, node parameters can be bound to python handles either interactively with the Handle Bindings dialog or programmatically with a python state. The gadgets used in a python handle implementation to interact visually with the bound parameter(s) will then be enabled for drawing, interacting, etc.
But what if the user chooses to bound only some of the python handle parameters and leaves the rest unbound ? The gadgets that require these “unbound
” parameters in
a python handle implementation are of no use by the user since they can’t modify parameters, we certainly don’t want the user to believe these gadgets are still usable in the
viewport.
What the python handle implementation should do in this case is to simply hide these "unbound"
gadgets. The way for a python handle to know if a gadget is available or not at
runtime is to provide a list of required parameters with hou.ViewerHandleTemplate.bindGadget. With this information in hand, Houdini will be able to make a gadget
available when all its required parameters are bound or make it unavailable otherwise.
Registering a gadget with its list of required handle parameters is optional but strongly recommended. It will make your handle more dynamic and friendly to users.
Here’s a more advanced example to demonstrate the implementation of a python handle with "binding aware"
gadgets.
Note
The example seems a bit intimidating but the code used in onMouseEvent
is very repetitive. This is done on purpose to better show the detail of a python handle implementation.
import resourceutils as ru def createViewerHandleTemplate(): handle_type = kwargs["type"].definition().sections()["ViewerHandleName"].contents() handle_label = 'Handle example' handle_cat = [hou.sopNodeTypeCategory()] template = hou.ViewerHandleTemplate(handle_type, handle_label, handle_cat) template.bindFactory(Handle) template.bindIcon('$HFS/houdini/pic/minimizedicon.pic') # Register the handle parameters template.bindParameter( hou.parmTemplateType.Float, name="X", default_value=0.0 ) template.bindParameter( hou.parmTemplateType.Float, name="Y", default_value=0.0 ) # Register the gadgets with a list of required handle parameters template.bindGadget( hou.drawableGeometryType.Line, "x_gadget", parms=["X"] ) template.bindGadget( hou.drawableGeometryType.Line, "y_gadget", parms=["Y"] ) # Export the parameters to Houdini template.exportParameters(["X","Y"]) return template class Handle(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) self.current_pos = hou.Vector3() self.xy_dragger = hou.ViewerHandleDragger("xy_dragger") # Set the gadgets with the Houdini standard colors self.color_options = ru.ColorOptions(self.scene_viewer) # Initialize the gadgets only if they are available sop_cat = hou.sopNodeTypeCategory() self.x_gadget = self._gadget("x_gadget") if self.x_gadget: verb = sop_cat.nodeVerb("line") verb.setParms( { "origin" : (0,0,0), "dir" : (1,0,0), "dist": 1.0, "points": 2 }) geo = hou.Geometry() verb.execute(geo, []) self.x_gadget.setParams({"draw_color":self.color_options.colorFromName("XaxesColor")}) self.x_gadget.setGeometry(geo) self.y_gadget = self._gadget("y_gadget") if self.y_gadget: verb = sop_cat.nodeVerb("line") verb.setParms( { "origin" : (0,0,0), "dir" : (0,1,0), "dist": 1.0, "points": 2 }) geo = hou.Geometry() verb.execute(geo, []) self.y_gadget.setParams({"draw_color":self.color_options.colorFromName("YaxesColor")}) self.y_gadget.setGeometry(geo) def _gadget(self, gadget_name): try: return self.handle_gadgets[gadget_name] except: return None def _showGadget(self, value): if self.x_gadget: self.x_gadget.show(value) if self.y_gadget: self.y_gadget.show(value) def onActivate(self, kwargs): self._showGadget.show(True) def onDeactivate(self, kwargs): self._showGadget.show(False) def onMouseEvent(self, kwargs): ui_event = kwargs["ui_event"] reason = ui_event.reason() consumed = False # current_gadget_name should be set to None if the gadget is not available. current_gadget_name = self.handle_context.gadget() current_gadget = self._gadget(current_gadget_name) if current_gadget_name in ["x_gadget"]: # Handle the x gadget if reason == hou.uiEventReason.Start: # Move along x axis self.xy_dragger.startDragAlongLine(ui_event, self.current_pos, hou.Vector3(1, 0, 0)) current_gadget.show(True) if self.y_gadget: self.y_gadget.show(False) elif reason in [hou.uiEventReason.Changed, hou.uiEventReason.Active]: drag_values = self.xy_dragger.drag(ui_event) delta_pos = drag_values["delta_position"] # update the handle parms while dragging self.current_pos += delta_pos self.handle_parms["X"]["value"] += delta_pos[0] # update the gadget transform xform = current_gadget.transform() * hou.hmath.buildTranslate(delta_pos) current_gadget.setTransform(xform) if reason == hou.uiEventReason.Changed: # ends dragging self.xy_dragger.endDrag() if self.y_gadget: xform = hou.hmath.buildTranslate(self.current_pos) self.y_gadget.setTransform(xform) self.y_gadget.show(True) consumed = True elif current_gadget_name in ["y_gadget"]: # Handle the y gadget if reason == hou.uiEventReason.Start: # Move along y axis self.xy_dragger.startDragAlongLine(ui_event, self.current_pos, hou.Vector3(0, 1, 0)) current_gadget.show(True) if self.x_gadget: self.x_gadget.show(False) elif reason in [hou.uiEventReason.Changed, hou.uiEventReason.Active]: drag_values = self.xy_dragger.drag(ui_event) delta_pos = drag_values["delta_position"] # update the handle parms while dragging self.current_pos += delta_pos self.handle_parms["Y"]["value"] += delta_pos[1] # update the gadget transform xform = current_gadget.transform() * hou.hmath.buildTranslate(delta_pos) current_gadget.setTransform(xform) if reason == hou.uiEventReason.Changed: # ends dragging self.xy_dragger self.xy_dragger.endDrag() # update the x gadget line if self.x_gadget: xform = hou.hmath.buildTranslate(self.current_pos) self.x_gadget.setTransform(xform) self.x_gadget.show(True) consumed = True return consumed def onDraw(self, kwargs): # draw each gadget draw_handle = kwargs["draw_handle"] if self.x_gadget: self.x_gadget.draw(draw_handle) if self.y_gadget: self.y_gadget.draw(draw_handle)
The following example uses a modular approach to simplify the implementation of the previous example.
import resourceutils as ru def createViewerHandleTemplate(): handle_type = kwargs["type"].definition().sections()["ViewerHandleName"].contents() handle_label = 'Handle modular example' handle_cat = [hou.sopNodeTypeCategory()] template = hou.ViewerHandleTemplate(handle_type, handle_label, handle_cat) template.bindFactory(Handle) template.bindIcon('$HFS/houdini/pic/minimizedicon.pic') # Register the handle parameters template.bindParameter( hou.parmTemplateType.Float, name="X", default_value=0.0 ) template.bindParameter( hou.parmTemplateType.Float, name="Y", default_value=0.0 ) # Register the gadgets with a list of required handle parameters template.bindGadget( hou.drawableGeometryType.Line, "x_gadget", parms=["X"] ) template.bindGadget( hou.drawableGeometryType.Line, "y_gadget", parms=["Y"] ) # Export the parameters to Houdini template.exportParameters(["X","Y"]) return template class Handle(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) self.xform = hou.Matrix4(1) self.gadgets = [] self.gadgets.append(LineGadget(self, self.scene_viewer, { "gadget_name": "x_gadget", "gadget_dir": hou.Vector3(1,0,0), "gadget_color": "XaxesColor", "parm_name": "X", "parm_index": 0 }) ) self.gadgets.append(LineGadget(self, self.scene_viewer, { "gadget_name": "y_gadget", "gadget_dir": hou.Vector3(0,1,0), "gadget_color": "YaxesColor", "parm_name": "Y", "parm_index": 1 }) ) def onActivate(self, kwargs): for gadget in self.gadgets: gadget.show(True) def onDeactivate(self, kwargs): for gadget in self.gadgets: gadget.show(False) def onDraw(self, kwargs): draw_handle = kwargs["draw_handle"] for gadget in self.gadgets: gadget.onDraw(draw_handle) def onMouseEvent(self, kwargs): ui_event = kwargs["ui_event"] reason = ui_event.reason() current_gadget_name = self.handle_context.gadget() for gadget in self.gadgets: if reason == hou.uiEventReason.Start: gadget.onBeginInteracting(kwargs, current_gadget_name) elif reason in [hou.uiEventReason.Changed, hou.uiEventReason.Active]: gadget.onInteracting(kwargs, current_gadget_name) if reason == hou.uiEventReason.Changed: gadget.onEndInteracting(kwargs, current_gadget_name) return True class LineGadget(object): """ Implement the support for a line dragging gadget. """ def __init__(self, handle, scene_viewer, parms): super(LineGadget,self).__init__() self.handle = handle self.scene_viewer = scene_viewer self.dragger = hou.ViewerHandleDragger("dragger") self.gadget_dir = parms["gadget_dir"] self.gadget_name = parms["gadget_name"] self.gadget_color = parms["gadget_color"] self.parm_name = parms["parm_name"] self.parm_index = parms["parm_index"] self.color_options = ru.ColorOptions(self.scene_viewer) # Initialize the gadget only if available try: self.gadget = self.handle.handle_gadgets[self.gadget_name] except: self.gadget = None if self.gadget: sop_cat = hou.sopNodeTypeCategory() verb = sop_cat.nodeVerb("line") verb.setParms( { "origin" : (0,0,0), "dir" : (self.gadget_dir[0],self.gadget_dir[1],self.gadget_dir[2]), "dist": 1.0, "points": 2 }) geo = hou.Geometry() verb.execute(geo, []) self.gadget.setParams({"draw_color": self.color_options.colorFromName(self.gadget_color)}) self.gadget.setGeometry(geo) def _accept(self, gadget_name): return gadget_name == self.gadget_name def show(self, value): try: self.gadget.show(value) except: pass def onBeginInteracting(self, kwargs, gadget_name): if not self._accept(gadget_name): self.show(False) return False ui_event = kwargs["ui_event"] current_pos = self.handle.xform.extractTranslates() self.dragger.startDragAlongLine(ui_event, current_pos, self.gadget_dir) self.show(True) return True def onInteracting(self, kwargs, gadget_name): if not self._accept(gadget_name): return False ui_event = kwargs["ui_event"] drag_values = self.dragger.drag(ui_event) delta_pos = drag_values["delta_position"] self.handle.handle_parms[self.parm_name]["value"] += delta_pos[self.parm_index] # update the gadget transform xform = self.gadget.transform() * hou.hmath.buildTranslate(delta_pos) self.gadget.setTransform(xform) self.handle.xform = xform return True def onEndInteracting(self, kwargs, gadget_name): if not self._accept(gadget_name): if self.gadget: current_pos = self.handle.xform.extractTranslates() xform = hou.hmath.buildTranslate(current_pos) self.gadget.setTransform(xform) self.gadget.show(True) return False self.dragger.endDrag() return True def onDraw(self, draw_handle): try: self.gadget.draw(draw_handle) except: pass