Houdini 20.0 Python scripting

Writing custom viewer states in Python

A viewer state controls how to interpret mouse movements, clicks, keys, and so on in the viewer.

On this page

Overview

Viewer states control interaction in the viewport. For example, the Rotate tool is a view state. The Handles tool allows access to the viewer state associated with the current node. Houdini lets you create and register your own custom view states in Python.

A custom state can, for example:

A Houdini digital asset can specify a state (for the Handles tool to use when a node of that type is current). Eventually you will be able to programmatically enter a state by calling hou.SceneViewer.setCurrentState, for example in a shelf tool script (for tools that don’t create a node, such as an inspection tool).

To start playing with the code right away, see implementing a state below, which shows how to implement, register, and launch a minimal viewer state.

Tip

To see viewer states in action, check out the demo scene and asset files under $HH/viewer_states/examples. These samples provide a detailed coverage of the viewer state features.

Limitations

  • Currently Python states are available in the following contexts: SOP (geometry), OBJ, DOP and LOP levels.

  • Handles cannot be bound to nodeless states.

State names

A state has an internal name and a human readable label that appears in the UI. If you create a new custom state, the internal name you choose must be unique: if two authors use the same state name, one of the states will fail to register. If you consider that a state or asset you create might someday be shared with other users/studios or even sold as a product, you should take the time to ensure proper uniqueness.

There is the additional complication that Houdini automatically creates generic states for each asset, so you can’t use the name of an existing node type as a state name.

  • If the state is shared between more than one node, or not tied to a node, you must still ensure that the state name does not conflict with the generic state of any asset. The best way to do this is to incorporate the same namespace string you would use for an asset.

    For example, if you work at the Example.com movie studio and use examplecom:: as the namespace prefix for your assets, when you want to create a “scrub” state, you would use the state name examplecom::scrub.

  • State names do not have the same character restrictions as node type names and node names. A state name is more or less an arbitrary string.

  • You may need to use the name as a file/directory name (for example, if you're storing the code in a file)

Tool + state vs. self-contained state

Many, if not most “native” Houdini node states do not handle their own selection or create the node. Instead, they rely on a shelf tool script to ask for a selection and create the node. The state is then simply responsible for displaying handles.

You can use this workflow with a custom Python state as well, especially when the state is closely associated with an asset. You can write a Shelf tool script to ask for a selection and create the node (and filling in the node’s Group field with the selection).

Alternatively, your state can be self contained: it can create a node when invoked from the viewer and have its own selector.

(If your state doesn’t require a node (for example, an inspector-type tool), see writing nodeless state for more information.)

  • If you need to ask for more than one type of selections (for example, “Select some curves” followed by “Select some points on those curves”), use a tool script. Currently, a Python state can only accept a single type of selection.

  • If the code needs to know what’s in the selection before it creates the node, use a tool script.

See working with a node for more information on manipulating a node instance in a state.

Installing a state in Houdini

The most convenient and easiest way of creating and installing a viewer state in Houdini is to use the Viewer State Code Generator dialog. This code generator will get you a fully functional state in no time and will help you understand all the details involved in writing a viewer state from scratch.

There are currently two ways to make a custom state available in Houdini: embed the state’s code with an asset (HDA viewer state), or put a Python file containing the code in the right directory on the Houdini path.

Embedding a state in an asset

For creating HDA viewer states, use the code generator available in the Viewer State Editor tab of the Operator Type Properties window.

The following instructions create an asset and a minimal state so you can try out the code.

  1. Start with an asset you want to add a state to. To simplify the example, let’s start by using an OBJ asset.

    Note

    If you just want to start with a blank asset, you can to create a SOP asset or for an OBJ asset. The viewer state created with the code generator should work for both asset types.

    To quickly create a “blank” SOP asset to experiment with:

    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 statedemo, the Asset Label to State 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.

    To quickly create a “blank” OBJ asset to experiment with:

    1. At the Object level, use the ⇥ Tab menu to create a Subnetwork node.

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

    3. Set the Type Name to statedemo, the Asset Label to State 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.

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

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

  3. Click the Interactive|State Script tab.

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

  5. Select the onMouseEvent event handler and click Accept.

You should now have a functional state listed in the Viewer State Browser tree as Statedemo.

import hou
import viewerstate.utils as su

class State(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer


    def onMouseEvent(self, kwargs):
        """ Process mouse events
        """
        ui_event = kwargs["ui_event"]
        dev = ui_event.device()
        self.log("Mouse:", dev.mouseX(), dev.mouseY(), dev.isLeftButton())

        # Must return True to consume the event
        return False


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

    state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
    state_label = "Statedemo"
    state_cat = hou.objNodeTypeCategory()

    template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
    template.bindFactory(State)
    template.bindIcon(kwargs["type"].icon())

    return template

See implementing a state below for more information on adding functionality to the state class.

To test the state, select the asset in the network editor. Move the mouse into the scene viewer and press Enter. The mouse coordinates should be logged in the Viewer State Browser console as you move the mouse.

Loading a state from the Houdini path

For creating file viewer states, use the code generator in the Viewer State Browser window.

The following instructions create a file viewer state that can be “shared” between multiple assets. File viewer states are automatically registered by Houdini on startup.

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

  2. Select the Object category from the toolbar.

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

  4. Enter statedemo as the state’s name in the Name field.

  5. Select the onMouseEvent event handler and click Accept.

The new file viewer state should be saved as $HOUDINI_USER_PREF_DIR/viewer_states/statedemo.py, listed in the Viewer State Browser tree as Statedemo.

You should now have a functional state listed in the Viewer State Browser tree as Statedemo.

import hou
import viewerstate.utils as su

class State(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer


    def onMouseEvent(self, kwargs):
        """ Process mouse events
        """
        ui_event = kwargs["ui_event"]
        dev = ui_event.device()
        self.log("Mouse:", dev.mouseX(), dev.mouseY(), dev.isLeftButton())

        # Must return True to consume the event
        return False


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

    state_typename = "statedemo"
    state_label = "Statedemo"
    state_cat = hou.objNodeTypeCategory()

    template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
    template.bindFactory(State)
    template.bindIcon("MISC_python")

    return template

To test the state, find an asset you want to add a state to, or create a blank SOP asset with or for an OBJ asset. To simplify the example, create an OBJ asset.

To quickly create a “blank” SOP asset to experiment with:

  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 statedemo, the Asset Label to State 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.

To quickly create a “blank” OBJ asset to experiment with:

  1. At the Object level, use the ⇥ Tab menu to create a Subnetwork node.

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

  3. Set the Type Name to statedemo, the Asset Label to State 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.

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

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

  2. Click the Node tab.

  3. Set the Default state field to the state name (for example, in the code above, this is statedemo).

  4. At the bottom of the type properties window, click Accept.

    • If the asset was locked, Houdini will prompt you to unlock the asset so you can save your changes.

  5. To start testing, select the asset in the network editor. Move the mouse into the scene viewer and press Enter. The mouse coordinates should be logged in the Viewer State Browser console as you move the mouse.

    • The toolbar at the top of the viewer should display the state’s label on the left.

On startup, Houdini will call createViewerStateTemplate to access the state template and perform the registration. If omitted, Houdini will simply skip the state registration.

See implementing a state below for more information on adding functionality to the state class.

Tip

To reload a file state during a Houdini session, RMB on the state name listed in the Viewer State Browser tree and select Reload in the context menu. This will allow you to test changes to your state without restarting Houdini.

You can also reload the state with python by calling hou.ui.reloadViewerState with the name of the state.

Implementing the state

The following section shows a high-level overview of the methods you can add to the class implementing your state.

Note

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

For guides to implementing specific functionality, see the following pages:

Initializer (required)

def __init__(self, state_name, scene_viewer):

state_name

The state name string this state was registered under.

scene_viewer

A hou.SceneViewer object representing the scene viewer the tool is operating in. This object has many useful methods you can use to implement your state, for example hou.SceneViewer.currentGeometrySelection and hou.SceneViewer.setCurrentGeometrySelection.

In general, you’ll just want to store the arguments in object attributes in case other methods need them:

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    # Event handlers
    # ...

Event handlers

Event handlers are called with a single argument, a dictionary containing various useful items. The dictionary will typically (but not always) contain the following items:

node

Contains a hou.OpNode instance representing the node being operated on by the current state.

state_parms

Contains the names representing the state parameters tied to the current state. This dictionary is used for modifying the parameter states. See the details here.

state_flags

A dictionary containing various flags associated to the state. State flags can be set by all state handlers via their kwargs argument.

Flag

Notes

mouse_drag

Controls whether a mouse drag event (with the LMB up) is generated or not. Mouse drag events are generated by default (True).

When mouse_drag is False, no mouse drag event is generated. If a selector is active, the selectable elements will not be highlighted.

If mouse drag events are not required by your state, consider setting mouse_drag to False to reduce the overhead.

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onEnter( self, kwargs):
        kwargs['state_flags']['mouse_drag'] = False
        ...                

redraw

The flag triggers a redraw of the viewport on a mouse move or mouse wheel event when it’s set to True.

By default the viewport always redraw. To reduce performance issues with large scenes, redraw should be set to False when your state doesn’t require a redraw of the viewport.

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onMouseEvent( self, kwargs):
        kwargs['state_flags']['redraw'] = False
        if __some_redraw_test__:
            kwargs['state_flags']['redraw'] = True
        ...                

indirect_handle_drag

When set to True (the default), this flag enables the handle indirect dragging from the viewport (with MMB down). When the flag is set to False, indirect handle dragging is disabled.

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onEnter( self, kwargs):
                            # Disable indirect handle dragging. Handle parms can 
                            # no longer be modified by dragging the MMB in the
                            # viewport.
        kwargs['state_flags']['indirect_handle_drag'] = False
        ...                

exit_on_node_select

This flag determines whether the state is exited or remains active when a different node is selected. If set to True (which is the default behavior), the state exits when a different node is selected. If set to False, the state remains active even when a different node is selected. The exit_on_node_select flag can be used with both node-less and node-aware states.

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onGenerate( self, kwargs):
        # Set this node-less state to remains active when 
        # a node selection occurs.
        kwargs['state_flags']['exit_on_node_select'] = False
        ...                

interrupt_state

The name of the state that is interrupting the python state when a volatile state is activated, or an empty string.

The following tables list the event handlers you can define on your state class.

Lifecycle event handlers

Method name

Called by Houdini

Notes

onEnter

state is entered from the network

This method is called when the state is activated by the user creating a new node, or selecting an existing node and pressing Enter in the viewport.

onInterrupt

state is interrupted

This method is called when:

  • The window loses focus.

  • The pointer leaves the viewer (including moving over a menu).

  • The user pushes a “volatile” tool (for example, holding down S to enter the volatile selection tool).

Note: kwargs['interrupt_state'] contains the name of the volative state that is interrupting your state.

onExit

state is about to be exited

This method is called when:

  • The user or Houdini switches to a different tool.

  • The user closes the viewer or switches to a different pane tab.

  • The user exits Houdini.

Note: kwargs['node'] may not be present if the node has been deleted.

onResume

interruption ends

This method is called when:

  • The pointer re-enters the viewer.

  • The user pops back to this state from a “volatile” tool (for example, releasing S after using the volatile selection tool).

Note: kwargs['interrupt_state'] contains the name of the volative state that interrupted your state.

onGenerate

state is entered without an existing node

This method is called when the state is activated by the user entering the state not from an existing node, for example a shelf tool script calling hou.SceneViewer.setCurrentState. See nodeless states for information on states that work without a node.

For states associated with assets, you could theoretically create a node in this method, however we strongly recommend creating the node in the tool script instead. See working with a node in a Python state for more information.

The dictionary passed to this method does not contain the node item. You can get the network location corresponding to the viewer using hou.SceneViewer.pwd.

Additional notes:

  • Houdini calls either onEnter (if the state should work on an existing node) or onGenerate (if the should create a new node) when the tool activates, never both. The dictionary passed to onEnter has a node item to access the existing node as a hou.OpNode object. See editing nodes for more information.

  • Houdini will call onEnter/onGenerate when the state begins, even if the mouse pointer is not over the viewer at the time. If you want to display screen visuals around the cursor, wait for an onMouseEvent call to show them.

  • While the state is “interrupted” it does not receive UI events.

  • You cannot rely on every onInterrupt call being followed by a corresponding onResume. If the user exits the state while it’s interrupted, you will not receive an onResume call, just onExit.

  • If the user opens the state’s context menu, when the pointer moves over the menu and leaves the menu you will receive onInterrupt and onResume events.

  • If your state is current but interrupted because Houdini is in the background, and the user switches to Houdini, even if the mouse pointer is over the viewer, your state will not receive an onResume call until the user makes some input (such as moving the mouse).

UI event handlers

See UI event handling for more information.

The dictionary passed to these methods has the following extra item:

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

Called by Houdini

Notes

onMouseEvent

mouse moves/clicks

See mouse handling.

onMouseDoubleClickEvent

Mouse double clicks

See mouse handling.

onMouseWheelEvent

mouse wheel scroll

hou.UIEventDevice.mouseWheel returns -1 or 1 depending on the scroll direction.

See mouse wheel handling.

onKeyEvent

For key events

See reading the keyboard device for more.

onKeyTransitEvent

For key transition events

See reading the keyboard device for more.

onMenuAction

context menu choice

See context menu handling.

onMenuPreOpen

update menu state

See updating context menu.

The handler kwargs argument contains the following items:

onParmChangeEvent

State parameter events

See state parameter handling.

onNodeChangeEvent

Action or change occurs on the state’s node

Takes a dictionary argument similar to hou.OpNode.addEventCallback. The dictionary contains in particular:

event_type

node

The state’s node object.

parm_tuple

A hou.ParmTuple object containing the node parms (if any).

This handler is required by hou.ViewerStateTemplate.bindNodeChangeEvent and hou.ViewerStateTemplate.bindNodeParmChangeEvent.

onPlaybackChangeEvent

Playbar change event occurs.

Takes a dictionary argument containing the event information similar to hou.Playbar.addEventCallback:

event_type

frame

A float number the specifies the frame when the event took place.

reason

A hou.channelListChangedReason present only when event_type is hou.playbarEvent.ChannelListChanged. This will be either:

  • hou.channelListChangedReason.Replaced, when the list is replaced entirely, or

  • hou.channelListChangedReason.Filtered, when the list is only filtered.

onPlaybackChangeEvent is required by hou.ViewerStateTemplate.bindPlaybackChangeEvent.

onCommand

general purpose command events

Called by invoking hou.SceneViewer.runStateCommand. The handler kwargs argument contains the following items:

command

The command string identifier.

command_args

A python object holding the command specific arguments.

Additional notes:

  • onCommand is used for implementing specific actions on the state. For instance, you can use onCommand to set state parameters or to implement a custom notification mechanism. For details see hou.SceneViewer.runStateCommand.

Handle event handlers

Houdini will call these methods if you have bound dynamic handles to your state. See Python state handles for more information.

Method name

Called by Houdini

Notes

onHandleToState

user interaction with a handle

This lets you update node parameters (and/or the state/display) when a handle changes.

The dictionary passed to this method contains the following extra items:

handle

The string ID of the handle.

parms

A dictionary containing the new handle parameter values.

mod_parms

A list of of the names of parameters that changed.

prev_parms

A dictionary containing the previous handle parameter values. This can be useful for computing deltas.

ui_event

hou.UIEvent object to know about the handle status such as start dragging or stop dragging.

onStateToHandle

node parameters change

This lets you update handle parameters when node parameters change.

The dictionary passed to this method contains the following extra items:

handle

The string ID of the handle.

parms

A dictionary containing the new node parameter values.

onBeginHandleToState

start of user interaction with a handle

This lets you know when the user has started to manipulate a handle.

The dictionary passed to this method contains the following extra items:

handle

The string ID of the handle.

ui_event

hou.UIEvent object to know about the handle status such as start dragging or stop dragging.

onEndHandleToState

end of user interaction with a handle

This lets you know when the user has ended the manipulation a handle.

The dictionary passed to this method contains the following extra items:

handle

The string ID of the handle.

ui_event

hou.UIEvent object to know about the handle status such as start dragging or stop dragging.

Selection event handlers

Houdini will call these methods if you have bound selectors to your state. See selection handling for more information.

Method name

Called by Houdini

Notes

onStartSelection

user starts selecting

The dictionary passed to this handler has the following item:

name

The name of the current active selector.

drawable_mask

A list of drawable names representing selectable drawables. This entry is not available if the active selector is not a drawable selector.

onSelection

user selected geometry

The dictionary passed to this handler has the following extra items:

selection

A hou.GeometrySelection object representing the completed selection. This entry may not exist if the active selector is a drawable selector.

drawable_selection

If the active selector is a drawable selector, the entry contains a python dictionary object representing the selected drawable components. This entry is not available if the active selector is not a drawable selector.

name

The name of the current active selector.

You should Return True from this method to signal your state “accepts” the current selection and stop the selector. If the method returns any other value (or doesn’t contain a return statement), the selector will remain active.

onStopSelection

user stops selecting

This is called when the state “accepts” a selection by returning True from onSelection(), or if the state exits before accepting a selection.

The dictionary passed to this handler has the following item:

name

The name of the current active selector.

onLocateSelection

user located drawable geometry

This handler is called when drawables are located with a drawable selector. The kwargs dictionary passed to this handler has the following extra items:

drawable_selection

A python dictionary object representing the located drawable components.

name

The name of the current active selector.

Drag and Drop event handlers

Houdini calls the methods below for handling the drag drop events occurring in the viewer when a python state is active. See Drag and Drop handling for more.

Method name

Note

onDragTest

Called when the user initiates a drag event.

onDropGetOptions

Called to build a list of drop options to let the user chooses one.

onDropAccept

Handles the selected drop option.

Drawing event handlers

These methods are called when drawing events are generated. For instance, these methods would be implemented for processing hou.AdvancedDrawable objects.

Method name

Called by Houdini

Notes

onDraw

drawing events

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.

onDrawInterrupt

drawing events

The method is called when:

  • The user interrupts the state like for orbiting the view or scrubbing the timeline.

  • The mouse pointer leaves the viewer.

  • The window loses focus.

Additional notes:

  • onDraw is required for advanced drawables. The handle returned by kwargs["draw_handle"] must be passed to drawables for performing the render operation.

    def onDraw(self, kwargs):
        handle = kwargs['draw_handle']
    
        params = { 'translate': hou.Vector3(self.mouse_pos[0],self.mouse_pos[1],self.mouse_pos[2]) }
        self.cursor.render(handle, params )
    
        params = { 
            'translate': hou.Vector3(self.translate[0],self.translate[1],self.translate[2]),
            'color1': hou.Color(self.rgba[0],self.rgba[1],self.rgba[2],self.rgba[3]),
            'blur_width': self.blur_width
        }
        self.face_drawable.render(handle, params)
    
  • onDrawInterrupt is optional and only used with advanced drawables when drawing is required during a state interruption.

Inspecting viewer states

Houdini lets you view all registered viewer states with the Viewer State Browser window. The browser can be opened via the Python Panel menu.

Debugging tips

Houdini provides some support for debugging python viewer states. The most useful is the Viewer State Browser which offers integrated tools for displaying debugging information in the browser console window. You can still use a more basic solution like the python print function, but the browser offers more functionality with regards to debugging.

Tracing Python states execution

Tracing is a builtin tool that allows you to print debugging information in the browser console window during the execution of a python state. Tracing can be turn on from the browser toolbar, and configured with the Trace Options dialog in the File|Debug menu. The options dialog let you choose the python state handlers you want to trace and the data to print as the handlers get executed.

  • The tracing support of the viewer state browser is a useful way of debugging python states without adding a single line of code to your python state.

log method

log is a method on your python state class and is similar to the python print function. Instead of printing to the standard output, it prints messages to the Viewer State Browser console. The methods has a severity argument you can use to print the message with a different background and text color.

  • The browser lets you control when to log messages by toggling the Debug log button. This allows you to keep the log method call in your code instead of commenting out when debugging is not required.

  • Houdini adds the log method dynamically to the python state after the object was created. log is therefore not yet available when __init__ is called. The workaround is to use viewerstate.utils.log from __init__ for logging messages in the Viewer State Browser console.

import traceback

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onMouseEvent(self, kwargs):
        # Log the mouse position
        ui_event = kwargs["ui_event"]
        device = ui_event.device()
        self.log("Mouse position x=", device.mouseX(), "y=", device.mouseY(), 
            severity=hou.severityType.ImportantMessage)

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

The browser console is Qt base and supports rich text formatting. With the use of HTLM-style tags, self.log can display text with a specific font, font size or color.

msg = "Lorem ipsum dolor sit amet."
self.log("<span style='color:#F0FF00; background:orange; font-size:50px;" \
    "font-family:arial narrow'>{}</span>".format(msg))

# Some characters are rich text sensitive. If you want to display 
# these characters without rich text parsing them, make sure to 
# use their escape forms to disable them.
msg = "<< Lorem ipsum dolor sit amet. >>"
msg = msg.replace("<", "&lt;")
msg = msg.replace(">", "&gt;")
self.log("<font color='#FF00FF'><b><i><u>{}<u/><i/></b><font/>".format(msg))

DebugAid

The utility class viewerstate.utils.DebugAid provides debugging support for viewer states. This utility provides a logging mechanism like self.log and all the debugging features currently available in the viewer state browser as methods:

  • Inspect the active viewer state.

  • Add markers.

  • Enable a debug trace.

  • Enable logging console

  • Reload the running viewer state.

See also python handle DebugAid.

import traceback
from viewerstate.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 state
        self.dbg.marker()
        # inspect this viewer sate
        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()))

Python print function

The most basic but still useful form of debugging is to print information, such as the contents of variables as the script runs.

  • The main advantage of using prints is that you can output a lot of information, including multi-line blocks of text (such as the current Python call stack), and scroll back to read it later.

  • The python state code needs to be edited in order to add the print outs.

The output appears in a console window, or Houdini’s Python shell window, or the shell you started Houdini from, depending on how you started Houdini and what windows are open.

Tip

It’s a good idea to use from __future__ import print to get used to using print as a function instead of as a statement. The function is easier to use and has more functionality.

from __future__ import print
import traceback

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onMouseEvent(self, kwargs):
        # Print the mouse position
        ui_event = kwargs["ui_event"]
        device = ui_event.device()
        print("Mouse position x=", device.mouseX(), "y=", device.mouseY())

        # Print the current Python call stack
        traceback.print_stack()

hou.SceneViewer.setPromptMessage

The hou.SceneViewer.setPromptMessage and hou.SceneViewer.clearPromptMessage functions let you display prompts for user interaction in the status line at the bottom of the main Houdini window. You can “abuse” these functions to display debugging information.

  • The advantage of this method is that the information is front-and-center in the window as you interact with Houdini.

  • The disadvantage is that the status line can only show one line of information at a time and when you change it the previous message is lost.

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onMouseEvent(self, kwargs):
        # Display the mouse position in the status line
        ui_event = kwargs["ui_event"]
        device = ui_event.device()
        message = "Mouse x=%d y=%d" % (device.mouseX(), device.mouseY())
        self.scene_viewer.setPromptMessage(message)

hou.ui.displayMessage

The hou.ui.displayMessage function pops up a message window and waits for the user to click OK or Cancel.

  • This has a few advantages for debugging. One is that it pauses the script while it waits, which may be useful if you're trying to investigate changes that happen very quickly. It also lets you give some feedback to the script based on which button you click. Another is that the function has keyword arguments that let you attach a block of “details” text that is hidden by default but can be expanded. This is useful, for example, for displaying the current Python call stack.

  • You probably don’t want to use this function in a loop, where it would be annoying to have to click to dismiss multiple message windows.

import traceback

import hou

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onMouseEvent(self, kwargs):
        ui_event = kwargs["ui_event"]
        device = ui_event.device()
        # Only diplay the message on a click
        if device.isLeftButton():
            message = "Mouse x=%d y=%d" % (device.mouseX(), device.mouseY())

            # Get the call stack at this point in the script and format it
            # in a string so we can attach it to the message as "details"
            details = "".join(traceback.format_stack())

            # Display a message and wait for the user to click a button in
            # the message window
            clicked = hou.ui.displayMessage(
                message, buttons=("OK", "Error"),
                details_label="Current call stack",
                details=details
            )

            # If user clicked the "Error" button, raise an error
            if clicked == 1:
                raise Exception("Don't blame me!")

Reloading states

For states defined on disk, if the files on disk change, you can tell Houdini to reload the state using hou.ui.reloadViewerState. Use the Viewer State Editor to reload HDA embedded states.

Debug context menu

You can optionally use the viewerstate.utils.createDebugMenu utility for binding a debug menu to your viewer state. The debug menu gives access to the Viewer State Browser functionalities such as inspecting the active viewer state and logging debug messages in the browser console.

The debug context menu contains the following items:

  • Logging

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

  • Clear

    Clear the logging console.

  • Inspect

    Dump the content of the active viewer state object in the console.

  • Trace

    Enable or disable a handler trace. If enabled, a trace of the active viewer state handlers are logged in the console.

  • Marker

    Add a marker in the console to discriminate messages.

Note

The debug menu can only be used when an instance of the Viewer State Browser is active, otherwise the menu is disabled when it opens.

Here’s an example generated by the Viewer State Code Generator to demonstrate how to integrate the debug menu to your viewer state.

import hou
import viewerstate.utils as su

class State(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer


    def onMenuAction(self, kwargs):
        """ Callback implementing the actions of a bound menu. Called 
        when a menu item has been selected. 
        """

        menu_item = kwargs["menu_item"]
        state_parms = kwargs["state_parms"]

        # viewerstate.utils.onDebugMenuAction is mandatory to respond to 
        # the debug menu actions.
        su.onDebugMenuAction(kwargs)

    def onMenuPreOpen(self, kwargs):
        """ Implement this callback to update the menu content before 
        it is drawn. 
        """
        menu_states = kwargs["menu_states"]
        menu_item_states = kwargs["menu_item_states"]

        # viewerstate.utils.updateDebugMenu disables the debug menu if 
        # the Viewer State Browser is inactive.
        su.updateDebugMenu(kwargs)


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

    state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
    state_label = "Empty selection"
    state_cat = hou.sopNodeTypeCategory()

    template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
    template.bindFactory(State)
    template.bindIcon(kwargs["type"].icon())

    # Bind the state debug menu.
    template.bindMenu(su.createDebugMenu(state_typename, state_cat))

    return template

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 createViewerStateTemplate like this:

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

# Replace this ...
menu.addMenu(su.createDebugMenu(state_typename, state_cat))

# ... with this code
su.addDebugEntriesToMenu(state_typename, state_cat, menu)

...

Examples

The python state example scenes are distributed with the viewer_state_demo package, they are located under $HFS/packages/viewer_state_demo/scenes. You must load the package into Houdini before using the scenes by clicking the File|Load Examples menu in the Viewer State Browser

You can load the example scenes either with the Demo Viewer State shelf tools or with the File|Open… menu of the main menubar.

HOM API

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