On this page |
|
Python viewer handles
Overview ¶
Houdini provides a framework for implementing custom viewer handles in Python, offering extensive capabilities to tailor the workflow of your Python states.
-
You can draw, pick, and highlight handle components using either drawable gadgets or drawable 2D.
-
You can extend mouse control using either hou.ViewerHandleDragger or
drawable2d.Dragger2D
specifically for COP Python handles. -
Define parameters with Houdini parameters, choose to export specific parameters to Python states.
-
Create guide geometries using either hou.GeometryDrawable or hou.Drawable2D.
-
Respond to low-level mouse, keyboard, and tablet events.
-
Respond to user interaction with parameter settings.
-
Display custom context menus.
-
Run in various context-levels.
-
Wrap changes in undo blocks.
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 in Houdini can be implemented either within a Python file module or embedded directly within an HDA. Both methods require a registration callback to install the Python handle in Houdini, similar to the process used for registering Python states.
The registration callback, named createViewerHandleTemplate
, defines the structure and behavior of the Python handle. This includes its
handle type name, parameters, gadgets, and more. Houdini uses this information to instantiate Python handle objects as needed by Python states.
For comprehensive details on defining the content of a Python handle, refer to hou.ViewerHandleTemplate.
To expedite the creation of Python handles, you can utilize the Viewer Handle Code Generator. This tool generates the basic handle code along with the necessary registration function. You can access the code generator through the Viewer Handle Browser window or the Handle script tab in the Operator Type Properties window for viewer handles embedded in an HDA.
While manual creation of the code is possible, the code generator accelerates the development process to quickly get your Python handle working without syntax errors.
The registration of Python handles occurs either during Houdini’s startup or through the use of registration APIs such as hou.ui.registerViewerHandle and hou.ui.reloadViewerHandle.
A typical registration callback looks like the following:
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 are installed about 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 which point the proper validation kicks in and binding errors (if any) are processed.
Embedding a handle in an asset ¶
These instructions demonstrate how to create a SOP asset for embedding a Python viewer handle.
-
At the Object level, use the ⇥ Tab menu to create a
Geo object.
-
Double-click the
geo1
node to dive into the Geometry network inside. -
Use the ⇥ Tab menu to create a Subnetwork node.
-
Right-click the
subnet1
node and choose Digital Asset → Create New. -
Set the Type Name to
handle_demo
, the Asset Label toHandle Demo
, and Library Path toEmbedded in .hip File
.Setting the library location to
Embedded
saves the asset with the current scene file instead of in an asset library. -
Uncheck the Author, Branch and Version check box controls.
-
Open the type properties window for the asset. (Right click an instance of the asset type and choose Type properties).
-
Click the Interactive|Handle Script tab.
-
Click the New… button to generate the handle code.
-
Select the Rotate sample event handler and click Accept.
-
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 Viewer Handle Browser tree.
To test the new handle, you need to create a viewer state to bind the handle.
-
Click the Interactive|State Script tab.
-
Click the New… button to generate the state code.
-
Enter
state_rotate_demo
as the state’s name in the Name field. -
Select the Static Handle sample event handler and click Accept.
-
Replace this line
HANDLE_TYPENAME = "unknown"
with
HANDLE_TYPENAME = "handle_demo"
-
Replace this line
template.bindHandleStatic( HANDLE_TYPENAME, HANDLE_NAME, [] )
with
template.bindHandleStatic( HANDLE_TYPENAME, HANDLE_NAME, [("ry","ry")] )
-
Add a float parameter in the asset called
ry
. -
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 is automatically registered by Houdini at startup. This example is valid for all Python handle contexts; however, some samples provided by the Code Generator may not be available in the COP Python handle context, such as the gadget sample shown below.
-
Open the Viewer Handle Browser window with the New Pane Tab Type ▸ Inspectors ▸ Viewer Handle Browser menu.
-
Select the
Sop
category from the list menu in the browser toolbar. -
Open the Viewer Handle Code Generator with the File ▸ New Handle… menu.
-
Enter
handle_demo
as the handle’s name in the Name field. -
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
.
The gadget sample generated code:
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:
-
At the Object level, use the ⇥ Tab menu to create a
Geo object.
-
Double-click the
geo1
node to dive into the Geometry network inside. -
Use the ⇥ Tab menu to create a Subnetwork node.
-
Right-click the
subnet1
node and choose Digital Asset → Create New. -
Set the Type Name to
state handle demo
, the Asset Label toState Handle Demo
, and Library Path toEmbedded in .hip File
.Setting the library location to
Embedded
saves the asset with the current scene file instead of in an asset library. -
Uncheck the Author, Branch and Version check box controls.
-
Open the type properties window for the asset. (Right click an instance of the asset type and choose Type properties).
-
Click the Interactive|State Script tab.
-
Click the New… button to generate the state code.
-
Select the Static Handle sample option and click Accept.
-
In the new state source code, set HANDLE_TYPENAME with “handle_demo” and click Accept.
-
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 details the implementation of Python handles. Creating them from scratch can be challenging and prone to errors. To streamline this process, consider using the Viewer Handle Code Generator. This tool offers a variety of code samples that you can leverage to generate the basic structure of your Python handle.
Implementing a Python handle involves creating a Python class. The following sections provide 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:
Python viewer handles
3D Python Handles ¶
3D Python handles possess 3D manipulation capabilities and operate within the Scene Viewer. You can define these handles in contexts such as SOP, LOP, and OBJ nodes.
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)
2D Python Handles ¶
2D Python handles are designed to operate within the Compositing Viewer and the Scene Viewer.
Initializer ¶
def __init__(self, **kwargs)
For 2D Python handles, the Python class __init__
method is mandatory to initialize the handle class. Houdini expects this method to
accept a kwargs
dictionary as its argument. This kwargs
dictionary is prepopulated with the following key entries:
handle_name
The viewer handle type name as set during registration.
handle_label
The Python handle label as set during registration.
handle_instance_name
The unique name of this handle instance, as specified by the Python state when binding the handle using hou.ViewerStateTemplate.bindHandle or hou.ViewerStateTemplate.bindHandleStatic. This is often used to initialize a hou.Handle instance that references this Python handle implementation.
scene_viewer
An instance of hou.CompositorViewer representing the Compositing Viewer in which the handle is running.
handle_context
A hou.ViewerHandleContext object providing access to various contextual information about the active handle.
handle_parms
A dictionary containing the Python handle’s parameter and setting values. This dictionary is populated with information supplied through hou.ViewerHandleTemplate.bindParameters and hou.ViewerHandleTemplate.bindSettings. Use the parameter or setting’s name as the key to access its value in the dictionary.
Note
You can conveniently store the contents of the kwargs
dictionary as attributes of your handle class within the
__init__
method. This allows you to access these entries directly as self.attribute_name
in your code, avoiding
repeated dictionary lookups.
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. Most of 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.
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).
This table lists all event handlers along with specific kwargs
entries if any.
Method name |
Notes |
---|---|
|
This callback is invoked when a 3D Python handle’s hou.GadgetDrawable or a 2D Python handle’s hou.Drawable2D is located, picked, and dragged. For more information, see mouse handling. |
|
Called when the mouse is dragged with |
|
Indicates whether the handle supports indirect mouse manipulation. Must return True to enable support, or False otherwise. If this handler is not implemented, Houdini assumes indirect manipulation is not supported. |
|
Computes the distance between the handle and the mouse position. Takes the mouse position as input and returns the distance as a float in screen space. Houdini uses this value to determine which of the active handles in the viewport receives the indirect manipulation event - the closest handle wins. If unimplemented, Houdini assumes the handle does not support mouse indirect handling. Note You do not need to implement this handler for HUD handles; Houdini computes the distance automatically. |
|
Called when a a mouse scroll occurs. hou.UIEventDevice.mouseWheel returns |
|
Called when a key is pressed. See reading the keyboard device for more. |
|
Called when a key transition event occurs. See reading the keyboard device for more. |
|
Called when the user selects a context menu item. See Python state context menus for handling context menus.
Note This handler doesn’t get a |
|
Called before a context menu is opened. See updating context menu.
|
|
Called when a handle parameter or setting has been changed. See handle parameter handling.
|
|
Called when Houdini needs to redraw the Python handle. See handle drawing for details. The method is called when:
|
|
Called before Note
|
|
Called when the handle is set to active, which can happen when:
|
|
Called when the handle gets deactivated, which can happen when:
|
|
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 |
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.
Note
Some of the hou.SceneViewer undo methods are not supported in hou.CompositorViewer.
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 asupdateTransform
andtoScreen
will compensate for the handle’s object transform when the viewport is set to world space.Note
While
TransformAid
is primarily designed for 3D Python handles and some of its methods might seem applicable to 2D handles, it is recommended to usedrawable2d.Dragger2D
for handling 2D-specific operations within 2D Python handles instead. -
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
. -
drawable2d.Dragger2D
:A utility class for interactive mouse-based manipulation of hou.Drawable2D objects in 2D image space. This is primarily used in COP Python handles or states, where user interaction occurs within a normalized model space ranging from
[-1, -1] to [1, 1]
. -
drawable2d.Drawable2DGroup
:This class covers the drawable grouping functionality for hou.Drawable2D objects.
-
resourceutils.CursorLabel
:Use this class to display a text label that follows the cursor’s movement. This can be useful for both 2D and 3D Python handles to provide contextual information. Refer to the
move_tool_demo
andcop_state_drawable_demo
handle examples for detailed usage. -
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.
3D Python Handles ¶
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.
2D Python Handles ¶
The cop_viewer_handle_demo.hip
scene offers an introduction to creating Python handles specifically for COPs. By exploring this example,
you will learn the fundamental features and implementation techniques for COP Python handles.
Using Python 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 (not compatible with COP Python handles).
-
dialog to set snapping options for draggers (not compatible with COP Python handles).
-
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:
-
If your source code is opened with the Houdini Python file editor, hit the
Accept
button. -
From one of the Viewer Handle Browser menus such as the
Reload
context menu in the browser tree. -
From the Python Handle editor if your handle is embedded in an HDA.
Note
Reloading a Python handle will automatically exit the running Python state if an instance of the handle is being used.
Debug context menu ¶
HOM API ¶
Here are a few of the HOM
API related to Python handles.
Python viewer handles