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:
-
Draw, pick, highlight handle components with drawable gadgets.
-
Extend mouse control with handle draggers.
-
Define parameters with Houdini parameters, choose to export specific parameters to Python states.
-
Create guide geometries with geometry drawables.
-
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.
Limitations
-
Python handles can only be implemented in Python module files, they cannot be implemented yet in a Houdini digital asset.
Installing a viewer handle in Houdini
Python handles are installed in Houdini via a registration callback similar to the one used for
registering Python states. A callback named createViewerHandleTemplate
must be implemented in
a Python file containing the handle code and the file must be located in the right directory on the
Houdini path.
The purpose of this callback is to describe 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.
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.
Loading a handle from the Houdini path
To load a Python handle in Houdini you need to a registration function. The preferred way to create such function is to use the Python handle code generator from the Viewer Handle Browser. 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 following instructions create a file Python handle that can be used by multiple Python states. The new Python handle file will be automatically registered by Houdini on startup.
-
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 option and click Accept.
The new Python handle file should be saved as $HOUDINI_USER_PREF_DIR/viewer_handlers/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 import viewerstate.utils as su #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, find a SOP
asset with a Python state, or create a blank SOP asset with .
To quickly create a "blank" SOP asset to experiment with:
-
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 Create digital asset. -
Set the Operator name to
state handle demo
, the Operator Label toState Handle Demo
, and Save to library toEmbedded
.Setting the library location to
Embedded
saves the asset with the current scene file instead of in an asset library.
-
Open the type properties window for the asset. (Right click an instance of the asset type and choose Type properties).
-
Click the Interactive|Viewer State 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
Implementing a Python handle from scratch can be error prone and challenging. Consider using the Viewer Handle Code Generator to create your initial Python handle. The dialog provides a variety of code samples from which you can choose to generate the Python handle code.
The following sections gives an overview of the methods you can use for implementing a Python handle.
For more details about implementing specific functionality, see the following pages:
Python viewer handles
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 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().
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 to query the dictionary.
handle_parms
A dictionary of Python handle parameter values. These parameters are set during the registration with hou.ViewerHandleTemplate.bindParameters and hou.ViewerHandleTemplate.bindSettings.
Note
The code generator generates an __init__
function that stores the content of the kwargs
as
attributes to the class. This allows you to access these entries directly as class attributes in your code.
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. This cache is also available as a class attribute.
<menu item name>
Entries containing the states associated with menu items, using the menu item name as the key.
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 |
---|---|
|
Called when a Python handle gadget is picked and dragged. See mouse handling. |
|
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.
|
Drawing
Method name |
Notes |
---|
These methods are called when drawing events are generated. See handle drawing.
|
Called when Houdini needs to redraw the Python handle. The method is called when:
|
|
Called before |
Lifecycle
Method name |
Notes |
---|---|
|
Called when the handle is set to active, which can happen when:
|
|
Called when the handle gets deactivated, which can happen when:
|
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. Utilities
such as ColorOptions
and HUD
, for example, 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. -
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.HUD
:Utility class for displaying textual information in the viewport in the form of a HUD display.
-
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 also experiment with python handles through the example scenes.
These examples are distributed with the viewer_handle_demo
package located under $HFS/package/viewer_handle_demo/scenes
.
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 example code is 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 aswe 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 ones available for the Python states, see the Python states 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 MyState(object): def __init__(self, state_name, scene_viewer): self.state_name = state_name self.scene_viewer = scene_viewer 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.
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