Houdini 21.0 Python scripting

Python handle events

How to listen for and react to Viewer Handle events.

On this page

Python viewer handles

Overview

(See Viewer handles for the basics of how to implement a Python handle.)

To properly support node parameter changes interactively, Python handles should interpret various low-level events like mouse moves, mouse clicks and parameter changes. Other UI events support like keyboard and menu events is also possible, see the input events documentation for more details.

Mouse handling

Houdini lets you handle mouse events with three different handlers: onMouseEvent,onMouseIndirectEvent and onMouseWheelEvent.

onMouseEvent

The handler is used for implementing most of your Python handle interactive workflow.

  • onMouseEvent lets you handle and events when the mouse is over a handle gadget.

  • onMouseEvent is always processed before the active state’s onMouseEvent.

  • Use hou.ViewerEvent (kwargs["ui_event"]) to track down low-level mouse interactions, detect mouse downs, etc…

  • Use hou.ViewerHandleContext (self.handle_context) to access the state of a gadget.

  • Before calling onMouseEvent, Houdini does a pass to find out which geometry is under the mouse and updates self.handle_context with the (active) gadget associated to the geometry. There is no need to implement a geometry intersection to find the gadget or drawable by yourself.

  • The active gadget or drawable can then be accessed from self.handle_context in onMouseEvent to choose which action to execute.

  • For 3D Python handles, mouse interactions and actions can be implemented using hou.ViewerHandleDragger to constrain movements along axes, planes, or the construction grid.

  • drawable2d.Dragger2D is used to implement interactive mouse actions specifically for 2D COP Python handles.

3D Python handle

Here’s an example to demonstrate how to translate parameters with a handle dragger and gadget.

import viewerhandle.utils as hu

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)

    # Define the handle parameters
    template.bindParameter( hou.parmTemplateType.Float, name="tx", label="Tx", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="ty", label="Ty", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="tz", label="Tz", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )

    # Export all parameters
    template.exportParameters(["tx", "ty", "tz"])

    # Bind the pivot gadget
    template.bindGadget( hou.drawableGeometryType.Face, "pivot" )

    return template    

class Handle(base_class.Handle):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

        # Creates a dragger for translating the handle
        self.translate_dragger = hou.ViewerHandleDragger("translate_dragger")

        # Utility class to support the handle transform operations
        self.xform_aid = hu.TransformAid(self, parm_names={"translate":["tx","ty","tz"]})

def onMouseEvent( self, kwargs ):
    """ Called when a gadget is picked and dragged.
    """

    # Get the active gadget being located or picked.
    if self.handle_context.gadget() == "pivot":

        ui_event = kwargs["ui_event"]
        reason = ui_event.reason()

        # The pivot gadget is what the user drags to change the translate parameters.
        if reason == hou.uiEventReason.Start:
            # Get the handle tx,ty,tz parameters as a hou.Vector3 object
            handle_pos = self.xform_aid.parm3("translate")

            # Start the handle translate with a hou.ViewerHandleDragger object. 
            # The dragger has various methods for specialized operations such as 
            # dragging along a line or along a plane. Here we just move the pivot 
            # in world space.
            self.translate_dragger.startDrag(ui_event, handle_pos)

        elif reason in [hou.uiEventReason.Changed, hou.uiEventReason.Active]:
            # drag the pivot continously
            drag_values = self.translate_dragger.drag(ui_event)

            # Update the handle tx, ty, tz parameters with the position returned by the dragger.
            self.xform_aid.addToParm3("translate", drag_values["delta_position"])

            # Note: The transform will be computed in onDrawSetup.

            if reason == hou.uiEventReason.Changed:
                # We are done, exit the drag.
                self.translate_dragger.endDrag()                   

        # Consume the event
        return True

    return False

def onDraw(self, kwargs):
    self.pivot.draw(kwargs["draw_handle"])

This implementation pattern is pretty generic and can be reused for most of your Python handles. Since onMouseEvent is always called when a gadget is picked, there is no need to check if the mouse is down first, instead you check which gadget is active and acts upon it.

hou.uiEventReason.Start tells us the mouse was pressed down, you need to setup the dragger at this point by calling startDrag with the handle translate position (xyz parameters).

We should call the drag method when the UI event is set to hou.uiEventReason.Changed or hou.uiEventReason.Active, and update the handle’s translate parameters with the delta value returned by the dragger. Once the mouse has been released (hou.uiEventReason.Changed), we terminate the dragger with a call to endDrag.

You probably notice that self.handle_context is not explicitly created in the __init__ function. This is a class attribute created by Houdini, you don’t need to create one yourself. self.handle_context is set with a hou.ViewerHandleContext object which holds the state of gadgets with regards to picking and locating. This object is managed by Houdini to make sure it is constantly updated with the latest contextual information.

2D Python handle

This COP Python handle example demonstrates how to translate parameters using a drawable2d.Dragger2D and hou.Drawable2D objects.

import drawable2d as d2d
import resourceutils as ru
import viewerhandle.utils as hu

TX = "tx"
TY = "ty"

PARMS = [TX,TY]
PIVOT = "pivot"
PIVOT_C = [1.0,0.0,0.0]
PIVOT_R = 0.05

def createViewerHandleTemplate():
    handle_type = 'cop_handle_example'
    handle_label = 'COP Handle example'
    handle_cat = [hou.copNodeTypeCategory()]

    template = hou.ViewerHandleTemplate(handle_type, handle_label, handle_cat)
    template.bindFactory(Handle)

    # Define the handle parameters
    template.bindParameter(hou.parmTemplateType.Float, name=TX, label="TX", 
        min_limit=0.0, max_limit=10.0, default_value=0.0)
    template.bindParameter(hou.parmTemplateType.Float, name=TY, label="TY", 
        min_limit=0.0, max_limit=10.0, default_value=0.0)

    # Export all parameters
    template.exportParameters(PARMS)

    return template    

class Handle(object):
    def __init__(self, **kwargs):
        """ Called when creating an instance of the handle.
        """
        self.__dict__.update(kwargs)

        self.pivot = None

        # Handle all parameters
        self._dragger = d2d.Dragger2D(self.scene_viewer)       

def onActivate(self,kwargs):
    """ Called when the handle is activated.
        Creates all drawables needed for the handle.
    """
    # Create a circle drawable to represent the handle pivot 
    params={"color":PIVOT_C, "position": [0,0], "radius":PIVOT_R, "fill":1}
    self.pivot = hou.Drawable2D(self.scene_viewer, hou.drawable2DType.Circle, params=params, 
        name=PIVOT, pickable=True)

def onMouseEvent(self, kwargs):
    """ Use the mouse event to implement the handle interactive
        operations.
    """
    ui_event = kwargs["ui_event"]
    dev = ui_event.device()
    reason = ui_event.reason()

    if reason == hou.uiEventReason.Changed:
        # End the current drag operation on mouse up
        self._dragger.endDrag()
        return True

    # Update the dragger with the pivot drawable
    if not self._dragger.update(kwargs, self.pivot):
        # The dragger may fail if no drawable is located 
        return False

    consumed = True
    if reason == hou.uiEventReason.Start:
        self._dragger.startDrag(x=self.handle_parms[TX], y=self.handle_parms[TY])

    elif reason in [hou.uiEventReason.Active, hou.uiEventReason.Changed]:
        if drawable_name == self.handle_context.drawable():
            self._dragger.dragXY()

    return consumed

def onDraw(self, kwargs):
    self.pivot.draw(kwargs["draw_handle"])

onMouseIndirectEvent

The handler is triggered when is pressed in the viewport, regardless of whether a handle was clicked. It is typically used to enable freeform dragging interactions such as translate, rotate, or scale based on inferred input.

  • onMouseIndirectEvent is called only for the handle closest to the mouse, as determined by indirectMouseComputeDistance.

  • Use hou.ViewerEvent (kwargs["ui_event"]) to access low-level mouse interaction data.

  • Houdini only calls onMouseIndirectEvent for the following event reasons: hou.uiEventReason.Start, hou.uiEventReason.Active, hou.uiEventReason.Changed.

  • Dragging the mouse with or will not trigger this handler.

  • The handler must return True when the event is consumed.

  • If neither indirectMouseIsSupported nor indirectMouseComputeDistance is implemented, Houdini assumes the handle does not support indirect mouse handling. For HUD Handles, indirectMouseComputeDistance is not required.

3D Python Handle Example

import viewerhandle.utils as hu

class Handle(base_class.Handle):
    PIVOT_SIZE = 0.3

    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

        # Creates a dragger for translating the handle
        self.translate_dragger = hou.ViewerHandleDragger("translate_dragger")

        # Utility class to support the handle transform operations
        self.xform_aid = hu.TransformAid(self, parm_names={"translate":["tx","ty","tz"]})

        # Pivot gadget
        sops = hou.sopNodeTypeCategory()
        verb = sops.nodeVerb("box")
        psize = Handle.PIVOT_SIZE
        verb.setParms(
            {   "type" : 1,
                "size" : (psize,psize,psize),
                "divrate": (2,2,2)
            })
        pivot = hou.Geometry()
        verb.execute(pivot, [])
        self.pivot = self.handle_gadgets["pivot"]
        self.pivot.setGeometry(pivot)
        self.pivot.show(True)

def onMouseIndirectEvent(self, kwargs):
    """ Called when the mouse is dragged with MMB.
    """

    ui_event = kwargs["ui_event"]
    reason = ui_event.reason()

    consumed = False        

    # The example moves the pivot when MMB is down. 
    #
    # The implementation is not that different from `onMouseEvent`. 
    #
    # We don't need to check for the active gadget as the mouse cannot 
    # be over a gadget when onMouseInteractEvent is called.
    #
    # Using a "constraint free" dragger will compute the handle position 
    # relative to the world space position under the mouse.

    if reason == hou.uiEventReason.Start:
        # Get the handle tx,ty,tz parameters as a hou.Vector3 object
        handle_pos = self.xform_aid.parm3("translate")

        # init the dragger
        self.translate_dragger.startDrag(ui_event, handle_pos)

        consumed = True

    elif reason in [hou.uiEventReason.Changed, hou.uiEventReason.Active]:
        # drag the pivot continously
        drag_values = self.translate_dragger.drag(ui_event)

        # Update the handle tx, ty, tz parameters with the position returned by the dragger.
        self.xform_aid.addToParm3("translate", drag_values["delta_position"])

        # Note: The transform will be computed in onDrawSetup.

        if reason == hou.uiEventReason.Changed:
            # We are done, exit the drag.
            self.translate_dragger.endDrag()

        consumed = True

    return consumed

def indirectMouseComputeDistance(self, x, y):
    """ Compute the distance between the handle pivot and the
        xy point in screen-space.
    """
    p1 = hou.Vector2(x,y)

    screen_pos = self.scene_viewer.curViewport().mapToScreen(
        hou.Vector3(self.handle_parms["tx""]["value"], 
            self.handle_parms["ty"]["value"], 0))
    p2 = hou.Vector2(screen_pos.x(), screen_pos.y())

    return p1.distanceTo(p2)

def indirectMouseIsSupported(self):
    """ Returns True to support indirect mouse manipulation.
    """
    return True

def onDraw( self, kwargs ):
    self.pivot.draw(kwargs["draw_handle"])

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)

    # Define the handle parameters
    template.bindParameter( hou.parmTemplateType.Float, name="tx", label="Tx", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="ty", label="Ty", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="tz", label="Tz", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )

    # Export all parameters
    template.exportParameters(["tx", "ty", "tz"])

    # Bind the pivot gadget
    template.bindGadget( hou.drawableGeometryType.Face, "pivot" )

    return template    

2D Python Handle Example

import hou
import drawable2d as d2d
import viewerhandle.utils as hu

TX = "tx"
TY = "ty"
AXISX = "axisx"
AXISY = "axisy"
PIVOT = "pivot"
XAXIS_C = hu.HXAXIS_COLOR
YAXIS_C = hu.HYAXIS_COLOR
PIVOT_C = hu.HPIVOT_COLOR
AXIS_L = 0.1

class Handle(object):
    def __init__(self, **kwargs):
        """ Called when creating an instance of the handle.
        """
        self.__dict__.update(kwargs)

        self._dragger = d2d.Dragger2D(self.scene_viewer)

    def onActivate(self,kwargs):
        """ Called when the handle is activated.
            NOTE: all drawables needed for the handle must be 
            created here.
        """                    
        params={"color":XAXIS_C, "points": [-AXIS_L,0, AXIS_L,0], 
            "start_cap": hou.drawable2DCapStyle.Arrow, 
            "end_cap": hou.drawable2DCapStyle.Arrow}
        axisX = hou.Drawable2D(self.scene_viewer, hou.drawable2DType.Line, params=params, 
            name=AXISX, pickable=True)

        params={"color":YAXIS_C, "points": [0,AXIS_L, 0,-AXIS_L], 
            "start_cap": hou.drawable2DCapStyle.Arrow,
            "end_cap": hou.drawable2DCapStyle.Arrow}
        axisY = hou.Drawable2D(self.scene_viewer, hou.drawable2DType.Line, params=params, 
            name=AXISY, pickable=True)

        params={"color":PIVOT_C, "position": [0,0], "size":hou.drawable2DMarkerSize.Small, 
            "style": hou.drawable2DMarkerStyle.Dot}
        pivot = hou.Drawable2D(self.scene_viewer, hou.drawable2DType.Marker, params=params, 
            name=PIVOT, pickable=True)

        # Drawable grouping
        self._handle_group = d2d.Drawable2DGroup("drawables",[axisX, axisY, pivot])

        # Enable drawables based on the current state of the handle 
        # parameters.
        tx_enabled = self.handle_context.isParameterEnabled(TX)
        ty_enabled = self.handle_context.isParameterEnabled(TY)

        self._handle_group.enable(AXISX, tx_enabled)
        self._handle_group.enable(AXISY, ty_enabled)
        self._handle_group.enable(PIVOT, tx_enabled or ty_enabled)
        self._handle_group.show(True)

    def onDraw( self, kwargs ):
        """ Called for rendering the handle drawables.
        """
        self.handle_context.draw(kwargs["draw_handle"])

    def onMouseIndirectEvent(self, kwargs):
        """ Drag the pivot from the mouse position in the viewport.
        """
        ui_event = kwargs["ui_event"]
        dev = ui_event.device()
        reason = ui_event.reason()

        if reason == hou.uiEventReason.Changed:
            # End the current drag operation on mouse up
            self._dragger.endDrag()
            return True

        # Update the dragger with the pivot drawable
        pivot = self._handle_group.find(PIVOT)
        if not self._dragger.update(kwargs, self._handle_group, mapping_drawable=pivot):
            return False

        consumed = True
        if reason == hou.uiEventReason.Start:
            tx = self.handle_parms[TX]
            ty = self.handle_parms[TY]
            self._dragger.startDrag(p1=tx, p2=ty)

        elif reason in [hou.uiEventReason.Active]:
            # Drag the handle freely
            self._dragger.dragXY()

        else:
            consumed = False

        return consumed

    def indirectMouseComputeDistance(self, x, y):
        """ Compute the distance between the handle pivot and the
            xy point in screen-space.
        """
        p1 = hou.Vector2(x,y)

        spos = self.scene_viewer.curViewport().mapToScreen(
            hou.Vector3(self.handle_parms[TX]["value"], self.handle_parms[TY]["value"], 0))
        p2 = hou.Vector2(spos.x(), spos.y())
        return p1.distanceTo(p2)

    def indirectMouseIsSupported(self):
        """ Returns True to support indirect mouse manipulation.
        """
        return True

def createViewerHandleTemplate():
    """ Mandatory entry point to create and return the viewer handle 
        template to register. """

    handle_typename = "handle_example2d"
    handle_label = "Handle Example 2D"
    handle_cat = [hou.copNodeTypeCategory()]

    template = hou.ViewerHandleTemplate(handle_typename, handle_label, handle_cat)
    template.bindFactory(Handle)
    template.bindIcon("MISC_python")

    # Defines the handle parameters
    template.bindParameter( hou.parmTemplateType.Float, name=TX, label=TX.upper(), 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name=TY, label=TY.upper(), 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )

    # Tells Houdini what are the parameters to export. These parameters 
    # can be used by a state for binding the handle to a node.
    template.exportParameters([TX, TY])

    return template

onMouseWheelEvent

Responding to the mouse wheel is achieved with onMouseWheelEvent. This handler works pretty much the same as the Python state onMouseWheelEvent handler.

There is an important difference between the two however. The Python handle’s onMouseWheelEvent handler is only called on located drawables, hence the mouse cursor should always be over a drawable for a mouse wheel event to be fired. Sometimes, keeping the mouse over a located drawable can be finicky, so you might want to design your Python handle accordingly, with either a dedicated or a stationary drawable to enable a mouse wheel event.

Also, Houdini always call the Python handle’s onMouseWheelEvent before the Python state’s onMouseWheelEvent handler. Make sure the Python handle’s onMouseWheelEvent consumes the event to avoid processing the Python state’s handler by mistake.

Parameters

Parameters are an important part of a Python handle. Without parameters, a Python handle cannot be used for changing node parameters in the viewport.

Defining the handle parameters is part of the registration process, use hou.ViewerHandleTemplate.bindParameter to add parameters to a Python handle template. All defined parameters can then be manipulated in the viewport interactively only if they have been properly exported. Parameters are exported with hou.ViewerHandleTemplate.exportParameters, this step allows Python states to bind a Python handle parameters to a node parameters. If you choose not to export a specific parameter, it will not be available to the outside.

In addition to Python handle parameters, you can also add setting parameters to a Python handle with hou.ViewerHandleTemplate.bindSetting. These setting parameters are typically used as configuration settings for a Python handle. For instance, you might want to create a setting parameter to hold different color values for a gadget or a scaling value, etc… Python handle parameters and settings can be modified from the Handle Parameter Dialog and from the Python handle’s handle_parms class attribute. However, setting parameters are not exportable and therefore cannot be changed interactively from the viewport.

Note

Parameter and setting values are saved with the scene and restored to the last saved value when the scene is loaded back.

Responding to parameter changes

Use the onParmChangeEvent handler to respond to parameter changes. Houdini calls the handler after a Python handle’s parameter or setting parameter has been changed. For more details see onParmChangeEvent.

3D Python Handle

This sample demonstrates how to define the parameters of a Python handle and respond to parameter changes.

import viewerhandle.utils as hu

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)

    # Handle parameters are typically used for controlling gadgets and binding node parms.
    template.bindParameter( hou.parmTemplateType.Float, name="tx", label="Tx", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="ty", label="Ty", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="tz", label="Tz", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )

    # ... they also need to be exported, export only the ones you want Houdini to use. 
    # The exported parameters can be used in a python state for binding node parameters 
    # e.g. hou.ViewerStateTemplate.bindHandleStatic.
    template.exportParameters(["tx", "ty", "tz"])

    # Settings are parameters used for controlling the handle behavior.
    template.bindSetting( hou.parmTemplateType.Toggle, name="face", label="Face", default_value=True, align=True )
    template.bindSetting( hou.parmTemplateType.Toggle, name="wire", label="Wire", default_value=True, align=True )
    template.bindSetting( hou.parmTemplateType.Toggle, name="pivot", label="Pivot", default_value=True, align=True )
    template.bindSetting( hou.parmTemplateType.Toggle, name="knob", label="Knob", default_value=True, align=False )

    # Bind gadgets 
    template.bindGadget( hou.drawableGeometryType.Face, "pivot" )
    template.bindGadget( hou.drawableGeometryType.Face, "face" )
    template.bindGadget( hou.drawableGeometryType.Line, "wire" )
    template.bindGadget( hou.drawableGeometryType.Point, "knob" )

    return template    

class Handle(base_class.Handle):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)
        self.xform_aid = hu.TransformAid(self, parm_names={"translate":["tx","ty","tz"]})

        # skip gadget initialization for brevity

    def onParmChangeEvent(self, kwargs):
        """
        Called when a handle parameter or setting has changed.
        """
        parm_name = kwargs['parm_name']
        parm_value = kwargs['parm_value']

        if parm_name in ["tx","ty","tz"]:
            # update the handle transform with the new values
            self.xform_aid.updateTransform()
        elif parm_name == "face":
            kwargs["handle_gadgets"]["face"].show(parm_value)
        elif parm_name == "wire":
            kwargs["handle_gadgets"]["wire"].show(parm_value)
        elif parm_name == "pivot":
            kwargs["handle_gadgets"]["pivot"].show(parm_value)
        elif parm_name == "knob":
            kwargs["handle_gadgets"]["knob"].show(parm_value)

        # update the viewport
        self.scene_viewer.curViewport().draw()

2D Python Handle

Handling of Python handle parameters is pretty much the same for COP Python handles.

import resourceutils as ru
import viewerhandle.utils as hu

TX = "tx"
TY = "ty"

PARMS = [TX,TY]
PIVOT = "pivot"
PIVOT_C = [1.0,0.0,0.0]
PIVOT_R = 0.05

def createViewerHandleTemplate():
    handle_type = 'cop_handle_example'
    handle_label = 'COP Handle example'
    handle_cat = [hou.copNodeTypeCategory()]

    template = hou.ViewerHandleTemplate(handle_type, handle_label, handle_cat)
    template.bindFactory(Handle)

    # Define the handle parameters
    template.bindParameter(hou.parmTemplateType.Float, name=TX, label="TX", 
        min_limit=0.0, max_limit=10.0, default_value=0.0)
    template.bindParameter(hou.parmTemplateType.Float, name=TY, label="TY", 
        min_limit=0.0, max_limit=10.0, default_value=0.0)

    # Export all parameters
    template.exportParameters(PARMS)

    # Settings are parameters used for controlling the handle behavior.
    template.bindSetting(hou.parmTemplateType.Float, name="smoothness", label="Smoothness", default_value=0.5)

    return template    

class Handle(object):
    def __init__(self, **kwargs):
        """ Called when creating an instance of the handle.
        """
        self.__dict__.update(kwargs)
        self.pivot = None

    def onParmChangeEvent(self, kwargs):
        """
        Called when a handle parameter or setting has changed.
        """
        parm_name = kwargs['parm_name']
        parm_value = kwargs['parm_value']

        if parm_name in PARMS:
            # update the handle transform with the new values
            parms = self.handle_parms
            mat = ru.buildTransform2D(
                    x=parms[TX]["value"],
                    y=parms[TY]["value"])
            self.pivot.setTransform(mat)
            self.scene_viewer.curViewport().draw()

        elif parm_name == "smoothness":
            # Settings can be accessed from the kwargs too
            self.log("smoothness", parms["smoothness"]["value"])

def onActivate(self,kwargs):
    """ Called when the handle is activated.
        Creates all drawables needed for the handle.
    """
    # Create a circle drawable to represent the handle pivot 
    params={"color":PIVOT_C, "position": [0,0], "radius":PIVOT_R, "fill":1}
    self.pivot = hou.Drawable2D(self.scene_viewer, hou.drawable2DType.Circle, params=params, 
        name=PIVOT, pickable=True)

Accessing parameter values

Python handle’s parameters and settings can be accessed from the handle_parms dictionary. The dictionary is accessible from any Python handle handlers.

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)

    # Handle parameters.
    template.bindParameter( hou.parmTemplateType.Float, name="tx", label="Tx", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="ty", label="Ty", 
        min_limit=-10.0, max_limit=10.0, default_value=1.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="tz", label="Tz", 
        min_limit=-10.0, max_limit=10.0, default_value=0.0 )
    template.bindParameter( hou.parmTemplateType.Float, name="float3"", label="Float 3", 
        min_limit=-10.0, max_limit=10.0, default_value=[1.0,1.0,1.0], num_components=3 )

    # Handle settings.
    template.bindSetting( hou.parmTemplateType.Toggle, name="ui_guides", label="Draw UI guides", default_value=True)

    return template    

class Handle(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def onActivate(self, kwargs):
        self.log("Handle tx parm value", kwargs["handle_parms"]["tx"]["value"])
        self.log("Handle ty parm value", kwargs["handle_parms"]["ty"]["value"])
        self.log("Handle tz parm value", kwargs["handle_parms"]["tz"]["value"])
        self.log("Handle float3 parm value", kwargs["handle_parms"]["float3"]["value"])
        self.log("UI guides value", kwargs["handle_parms"]["ui_guides"]["value"])

# output
'Handle tx parm value' 0.0 
'Handle ty parm value' 1.0 
'Handle tz parm value' 0.0 
'Handle float3 parm value' [1.0, 1.0, 1.0] 
'UI guides value' 1 

Drawing

A Python handle utilizes the onDraw callback for rendering its visuals. onDrawSetup is also supported for 3D Python handles to compute their drawable scaling. Drawable scaling for hou.Drawable2D objects is managed by Houdini; therefore, COP Python handles do not need to compute their drawable scaling.

  • onDraw:

    Used for drawing the Python handle gadgets and other drawables. It is pretty straight forward to implement and should basically delegate to the handle gadgets and drawables draw method. The handler is similar to the Python states onDraw handler.

  • onDrawSetup:

    3D Python handles need to be scaled dynamically in order to maintain a fixed size independent of the current camera position. onDrawSetup is typically used for computing the handle scale with the help of hou.ViewerHandleContext.scaleFactor. With a newly computed scale value, Python handles should also update their transformation matrices along with their drawable guides transformation matrices.

def onDrawSetup(self, kwargs):
    # Use the current handle position to compute the scale value
    hpos = self.xform_aid.parm3("translate")

    # Compute the scale value 
    fixed_scale_value = 100.0
    scale = self.handle_context.scaleFactor(hpos)*fixed_scale_value

    # Rebuild the handle transform matrix with the new scale
    xform = self.xform_aid.updateTransform(s=[scale,scale,scale])

    # Update the gadgets transform
    kwargs["handle_gadgets"]["face"].setTransform(xform)
    kwargs["handle_gadgets"]["wire"].setTransform(xform)
    kwargs["handle_gadgets"]["pivot"].setTransform(xform)
    kwargs["handle_gadgets"]["knob"].setTransform(xform)

Python viewer handles

Python scripting

Getting started

Next steps

Reference

  • hou

    Module containing all the sub-modules, classes, and functions to access Houdini.

Guru level

Python viewer states

Python viewer handles

Plugin types