Houdini 20.5 Python scripting

Writing custom viewer handles in Python

Viewer handles are custom handles for changing parameters interactively.

On this page

Python viewer handles

Overview

Houdini lets you implement viewer handles in Python with extended capabilities to customize the workflow of Python states:

Use the Viewer Handle Code Generator to create source code from various sample sources. With the Python handle source code in place and registered in Houdini, Python states can use them in a generic fashion through handle bindings.

Installing a viewer handle in Houdini

Python handles can be coded in Houdini with a Python file module or embedded in an HDA. Both of these methods require the Python handles to be installed in Houdini via a registration callback similar to the one used for registering Python states.

The callback named createViewerHandleTemplate describes the content of the Python handle: its handle type name, its parameters, its gadgets and so on. Houdini will use this information to create instances of Python handles when they are needed by Python states. See hou.ViewerHandleTemplate for details about how to describe the content of a Python handle.

Use the Viewer Handle Code Generator to create the handle skeleton code along with the registration function. The code generator is available from the Viewer Handle Browser window or from the Handle script tab for viewer handles embedded in an HDA.

You can still create the code by yourself from scratch though, but the code generator will let you get your Python handle up and running in no time with no syntax errors.

The registration process takes place either on startup or when using a registration API such as hou.ui.registerViewerHandle and hou.ui.reloadViewerHandle.

A typical registration callback would look like this:

def createViewerHandleTemplate():
    """ This entry-point is mandatory for registering a viewer handle 
        in Houdini.
    """
    handle_type = 'viewer_handle_intro1'
    handle_label = 'Viewer Handle Intro1'
    handle_cat = [hou.sopNodeTypeCategory()]

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

    # other bindings go here
    return template    

Note

Python handles and Python states get installed pretty much at the same time when Houdini starts up, but the order of registration between the two is not important. Houdini doesn’t require Python handles to be installed before Python states can bind them. The registration phase only collects the binding information between the Python handle parameters and the Python state node parameters. These bindings are used later by Houdini when the Python state is entered at runtime, at this point the proper validation kicks in and binding errors get processed if any.

Embedding a handle in an asset

These instructions demonstrate how to create a SOP asset for embedding a Python viewer handle.

  1. At the Object level, use the ⇥ Tab menu to create a Geo object.

  2. Double-click the geo1 node to dive into the Geometry network inside.

  3. Use the ⇥ Tab menu to create a Subnetwork node.

  4. Right-click the subnet1 node and choose Digital Asset → Create New.

  5. Set the Type Name to handle_demo, the Asset Label to Handle Demo, and Library Path to Embedded in .hip File.

    Setting the library location to Embedded saves the asset with the current scene file instead of in an asset library.

  6. Uncheck the Author, Branch and Version check box controls.

  7. Open the type properties window for the asset. (Right click an instance of the asset type and choose Type properties).

  8. Click the Interactive|Handle Script tab.

  9. Click the New… button to generate the handle code.

  10. Select the Rotate sample event handler and click Accept.

  11. Click the Apply button to register the new handle.

The Handle script editor should display the code for a Python handle capable of rotating a geometry around an axis. The new handle should be listed under the Handle Demo node in the the Viewer Handle Browser tree.

To test the new handle, you need to create a viewer state to bind the handle.

  1. Click the Interactive|State Script tab.

  2. Click the New… button to generate the state code.

  3. Enter state_rotate_demo as the state’s name in the Name field.

  4. Select the Static Handle sample event handler and click Accept.

  5. Replace this line

    HANDLE_TYPENAME = "unknown"

    with

    HANDLE_TYPENAME = "handle_demo"

  6. Replace this line

    template.bindHandleStatic( HANDLE_TYPENAME, HANDLE_NAME, [] )

    with

    template.bindHandleStatic( HANDLE_TYPENAME, HANDLE_NAME, [("ry","ry")] )

  7. Add a float parameter in the asset called ry.

  8. Click Accept.

The new state state_rotate_demo should be listed in the Viewer State Browser window. You can see the new handle_demo entry by expanding the state browser’s node.

Now create a box geometry and add this channel ch("../subnet1/ry") to the box’s Rotate Y field.

Select the demo asset node in the network editor. Move the mouse into the scene viewer and press Enter. The handle should be drawn as a green ring, pick and drag the ring to rotate the box.

Loading a handle from the Houdini path

The following instructions demonstrate how to create a Python handle module that automatically get registered by Houdini on startup.

  1. Open the Viewer Handle Browser window with the New Pane Tab Type ▸ Inspectors ▸ Viewer Handle Browser menu.

  2. Select the Sop category from the list menu in the browser toolbar.

  3. Open the Viewer Handle Code Generator with the File ▸ New Handle… menu.

  4. Enter handle_demo as the handle’s name in the Name field.

  5. Select the gadget sample and click Accept.

A new Python handle file with sample code should be saved as $HOUDINI_USER_PREF_DIR/viewer_handles/handle_demo.py and listed in the Viewer Handle Browser tree as Handle demo.

import hou
import resourceutils as ru
import viewerhandle.utils as hu

#Usage: Simple handle that does nothing but draws a handle gadget.

GADGET_PIVOT = "pivot"
SCALE = 250.0

class Handle(object):

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

        # Utility class to support handle transform operations.
        self.xform_aid = hu.TransformAid(self,kwargs)

        # Here we store the gadget as a class attribute. This is 
        # optional though as gadgets are always available through the 
        # self.handle_gadgets attribute.
        color_options = ru.ColorOptions(self.scene_viewer)
        self.pivot = self.handle_gadgets[GADGET_PIVOT]
        self.pivot.setParams({"draw_color":color_options.colorFromName("HandlePivotColor")})

        # Assign a box geometry to the pivot gadget        
        sops = hou.sopNodeTypeCategory()
        verb = sops.nodeVerb("box")
        verb.setParms({"type":1, "scale":0.1, "divrate":(2,2,2)})
        pivot = hou.Geometry()
        verb.execute(pivot, [])
        self.pivot.setGeometry(pivot)
        self.pivot.show(True)

    def onDraw( self, kwargs ):        
        draw_handle = kwargs["draw_handle"]

        # draw the pivot gadget
        self.pivot.draw(draw_handle)

    def onDrawSetup(self, kwargs):
        """ Called prior to perform drawing, picking or locate operations. 
        """    

        # Scale the pivot gadget with a scale factor independent from the 
        # camera position.
        origin = hou.Vector3()
        scale = self.handle_context.scaleFactor(origin)*SCALE
        scales = [scale]*3

        xform = self.xform_aid.updateTransform(s=scales)
        self.pivot.setTransform(xform)

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

    handle_type = "handle_demo"
    handle_label = "Handle demo"
    handle_cat = [hou.sopNodeTypeCategory()]

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

    # Bind the drawable gadget to the handle.
    # Gadgets are basically geometry drawables used to define the handle 
    # components that can be manipulated interactively in the viewport.
    #
    # The gadgets are used by Houdini to perform common handle operations 
    # such as mouse locating and picking. 
    #
    # Gadget instances are created by Houdini and are made available to 
    # the Handle object via this data member:
    # self.handle_gadgets: dictionary of gadgets keyable by name.
    template.bindGadget( hou.drawableGeometryType.Face, GADGET_PIVOT )

    return template

To test the handle, follow these steps:

  1. At the Object level, use the ⇥ Tab menu to create a Geo object.

  2. Double-click the geo1 node to dive into the Geometry network inside.

  3. Use the ⇥ Tab menu to create a Subnetwork node.

  4. Right-click the subnet1 node and choose Digital Asset → Create New.

  5. Set the Type Name to state handle demo, the Asset Label to State Handle Demo, and Library Path to Embedded in .hip File.

    Setting the library location to Embedded saves the asset with the current scene file instead of in an asset library.

  6. Uncheck the Author, Branch and Version check box controls.

  7. Open the type properties window for the asset. (Right click an instance of the asset type and choose Type properties).

  8. Click the Interactive|State Script tab.

  9. Click the New… button to generate the state code.

  10. Select the Static Handle sample option and click Accept.

  11. In the new state source code, set HANDLE_TYPENAME with “handle_demo” and click Accept.

  12. Select the state handle demo node and hit enter in the viewer.

You should now have the Python handle demo activated with the pivot gadget at the origin.

Implementing the handle

This section cover the details for implementing Python handles. Implementing from scratch can be error prone and challenging, consider using the Viewer Handle Code Generator to create your Python handle. The code generator provides a variety of code samples you can use to generate the skeleton code of a Python handle.

Creating a Python handle consists of implementing a python class, the next sections give an overview of the supported class methods.

Note

Houdini provides a python module called viewerhandle.utils containing various documented utility functions and classes to support the installation of viewer handles and to help you implementing your own handles. The module is located under $HHP/viewerhandle folder.

Tip

The $HHP environment variable points to the sub-directory in Houdini’s installation containing its Python libraries, $HFS/houdini/pythonX.Ylibs/.

For more details about implementing specific functionalities, see the following pages:

Initializer

def __init__(self, **kwargs)

The Python class __init__ method is mandatory to initialize the Python handle class. Houdini expects the method to take a kwargs dictionary as argument. The kwargs dictionary is pre-filled with the following key entries:

handle_name

The viewer handle type name set at registration.

handle_label

The Python handle label set at registration.

handle_instance_name

The handle instance name as specified by the Python state when binding the handle with hou.ViewerStateTemplate.bindHandle or hou.ViewerStateTemplate.bindHandleStatic. Mostly used to initialize an instance of hou.Handle to reference a python handle implementation. See the SideFx HUD handles for an example.

scene_viewer

An instance of hou.SceneViewer representing the scene viewer the handle is running in.

handle_context

A context object for accessing various contextual information about the active handle.

handle_gadgets

A dictionary of gadget objects as defined with hou.ViewerHandleTemplate.bindGadget. Use the gadget’s name as dictionary key to query the gadget object.

handle_parms

A dictionary containing the Python handle parameter and setting values. The dictionary is populated with the information supplied with hou.ViewerHandleTemplate.bindParameters and hou.ViewerHandleTemplate.bindSettings. Use the parameter or setting’s name to query the dictionary values.

Note

You can store the content of the kwargs as attributes to the class in __init__. This allows you to access these entries directly in your code without querying the kwargs.

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

def onActivate(self, kwargs):
    self.log("Handle parameters", self.handle_parms)

Event handlers

Several handlers are supported to react to Python handle events. All these event handlers are called with a single dictionary argument (kwargs) containing specific values related to the event.

Here are the kwargs entries common to all handlers:

handle_context

A context object for accessing various information about the active handle. This cache is also available as a class attribute.

handle_parms

A writable dictionary containing the names representing the parameters and settings bound to the handle.

The following tables list all event handlers by category along with specific kwargs entries if any.

UI

Here are the kwargs entries common to all UI event handlers:

ui_event

Contains a hou.ViewerEvent instance with information about the event (for example, for a mouse event, the current mouse coordinates and whether a button was clicked).

Method name

Notes

onMouseEvent

Called when a Python handle gadget is located or picked and dragged. See mouse handling.

onMouseIndirectEvent

Called when the mouse is dragged with from anywhere in the viewport. See indirect mouse handling.

onMouseWheelEvent

Called when a a mouse scroll occurs. hou.UIEventDevice.mouseWheel returns -1 or 1 depending on the scroll direction. See mouse wheel handling.

onKeyEvent

Called when a key is pressed. See reading the keyboard device for more.

onKeyTransitEvent

Called when a key transition event occurs. See reading the keyboard device for more.

onMenuAction

Called when the user selects a context menu item. See Python state context menus for handling context menus.

menu_item: Contains the selected menu item name.

Note

This handler doesn’t get a ui_event entry in its kwargs.

onMenuPreOpen

Called before a context menu is opened. See updating context menu.

menu_states: The root or sub menu state value.

menu_item_states: All menu item state values of the menu about to open.

onParmChangeEvent

Called when a handle parameter or setting has been changed. See handle parameter handling.

parm_name: The parameter or setting name that changed.

parm_value: The new parameter or setting value. For multiple component parameters, parm_value is set as a list.

Drawing

Method name

Notes

These methods are called when drawing events are generated. See handle drawing.

onDraw

Called when Houdini needs to redraw the Python handle. The method is called when:

  • The user causes an interactive event such as a mouse move and a mouse-click.

  • Houdini is forced to redraw the current viewport.

onDrawSetup

Called before onDraw. Typically used to scale the handle geometry components before drawing.

Lifecycle

Method name

Notes

onActivate

Called when the handle is set to active, which can happen when:

  • the handle has been created.

  • the viewport gets the focus back.

  • after using the camera tool.

onDeactivate

Called when the handle gets deactivated, which can happen when:

  • a different state is selected or the user exits Houdini.

  • the user starts using the camera tool or the volatile selector.

  • the viewport loses focus.

onLoadSettings

Called when the handle settings have been loaded by Houdini and are ready to be processed. This method is typically used for processing all settings in batch as opposed to onParmChangeEvent which is called when a change happens on a single setting. onLoadSettings is called after OnActivate.

Undo support

Houdini automatically generates items on the undo stack for handle and node parameter changes. However, if custom undo support is needed you can use the hou.undos module and undo methods on hou.SceneViewer to group undoable operations into one single entry on the undo stack. See state undo support for details.

Utilities

Houdini provides various utility classes to help with the Python handle implementation. For example, utilities such as ColorOptions and hou.SceneViewer.hudInfo are designed to be coherant with the Houdini standard, it is strongly suggested to use them. See the Python handle demos for examples how to use them.

  • viewerhandle.utils.TransformAid:

    The TransformAid handles transform operations for Python handles. Methods such as updateTransform and toScreen will compensate for the handle’s object transform when the viewport is set to world space.

  • viewerhandle.utils.DebugAid:

    A utility class to help debugging Python handles.

  • resourceutils.ColorOptions:

    Utility class for accessing the Houdini color options. See gadget drawables for more details.

  • resourceutils.DisplayGroup:

    This class allows drawables to be displayed as groups in the viewport. For instance, this can be useful if you want to make a group of handle gadgets visible in a cycling fashion, similar to the Houdini transform modes key Y.

  • resourceutils.CursorLabel:

    Uses this class when you want to display some text label around the cursor. This is pretty handy for Python handles, have a look at the move_tool_demo handle for details how to use this class.

  • resourceutils.DebugMenu:

    Used by Houdini for creating a debug menu entry in a Python handle context menu. The Viewer Handle Code Generator actually uses it to create the handle’s context menu.

Examples

In addition to the code generator samples, you can experiment with python handles through example scenes. These scenes are distributed with the viewer_handle_demo package and are located under $HFS/package/viewer_handle_demo/scenes. Load the package into Houdini before using the scenes by clicking the File|Load Examples menu in the Viewer Handle Browser

The example scenes can be loaded from the Demo Viewer Handle shelf tools or with the File|Open… menu in the main menubar.

The viewer_handle_intro.hip scene gives an introduction to python handle implementation, the scene will guide you through the basic features of python handles.

The move_tool_demo.hip scene provides a much more complete and complex implementation example of a python handle for translating/rotating/scaling a geometry.

The python handle examples are available in the Viewer Handle Browser, make sure to look them up.

Using viewer handles

There is almost no difference between the workflow used for controlling Python handles and built-in handles. For instance, users should be able to use the UI features below when using either built-in or Python handles:

  • button to display Python handles.

  • Handle Parameter Dialog to access parameters and settings.

  • Persistent Handle Editor to enable Python handles as persistent.

  • dialog to set snapping options for draggers.

  • Camera zooming to adjust the Python handle scaling.

  • Animate Python handle parameters with key frames .

Note

The Handle Preference dialog, however, is not meant for Python handles as this dialog was designed to control the Houdini built-in handle preferences. Some of these preferences could be exposed for Python handles in the future as we see fit.

Inspecting viewer handles

Houdini lets you view all registered Python handles with the Viewer Handle Browser window. The browser is similar to the Viewer State Browser and offers pretty much the same features:

  • Tree for browsing registered handles.

  • Console for logging messages.

  • Various operations for dumping the active handle class attributes.

  • Viewer Handle Code Generator

  • Support for editing and loading Python handles.

Debugging tips

The debugging capabilities available for Python handles are pretty much similar to the Python states ones, see the debugging tips section to learn more about these features.

DebugAid

The utility class viewerhandle.utils.DebugAid provides debug support for Python handles. This utility is convenient for logging messages in the Python handle browser but also provides all the logging features currently available in the browser as methods:

  • Inspect the active Python handle.

  • Add markers in the console.

  • Enable a debug trace.

  • Enable logging console

  • Reload the running Python handle.

import traceback
from viewerhandle.utils import DebugAid

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

    def onEnter(self, kwargs):
        # Inspect the handle
        self.dbg.marker()
        self.dbg.inspect()

        # start a trace
        self.dbg.trace()

    def onMouseEvent(self, kwargs):
        # Log the mouse position
        ui_event = kwargs["ui_event"]
        device = ui_event.device()

        self.dbg.marker()
        self.dbg.log("Mouse position x=", device.mouseX(), "y=", device.mouseY())

        # Log the current Python call stack
        self.dbg.log(''.join(traceback.format_stack()))

Reloading handles

Reloading Python handle modules from Houdini improves the development cycle. There are several ways to reload a Python handle:

Note

Reloading a Python handle will automatically exit the running Python state if an instance of the handle is being used.

Debug context menu

Another way of adding debugging capabilities to your Python handle is to use a debug context menu. The code generator has an option to generate a debug menu which gives access to the following Viewer Handle Browser debugging features:

  • Logging

    Enable or disable console logging. If enabled, messages logged with self.log or with the DebugAid utility are sent to the console.

  • Clear

    Clear the logging console.

  • Inspect

    Dump the content of the underlying Python handle in the console.

  • Trace

    Enable or disable a debug trace. If enabled, a trace of the active Python handle are logged in the console.

  • Marker

    Add a marker in the console to discriminate messages.

The code generated will add the debug menu as a sub-menu. If you wish to add the debug menu as a flat menu, you can modify the code in createViewerHandleTemplate like this:

menu = hou.ViewerStateMenu(handle_type + "_menu", handle_label)
...

# Replace this ...
menu.addMenu(hu.createDebugMenu(handle_type))

# ... with this code
hu.addDebugEntriesToMenu(handle_type, menu)

...

HOM API

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