On this page |
|
Python viewer states
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:
-
Display handles and let the user manipulate the handles to change parameters or settings.
-
Interact with geometry to, for example, let you draw on a surface, or display information about what’s under the mouse cursor.
-
Display guide geometry.
-
Respond to user interaction with state parameters.
-
Let the user select geometry.
-
Display a custom right-click menu
-
Respond to low-level mouse, keyboard, and tablet events.
-
Wrap changes in undo blocks.
-
Run across multiple context-levels.
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 nameexamplecom::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.
-
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:
-
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
statedemo
, the Asset Label toState 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.
To quickly create a “blank” OBJ asset to experiment with:
-
At the Object level, 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
statedemo
, the Asset Label toState 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 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.
-
Open the Viewer State Browser window with the New Pane Tab Type ▸ Viewer State Browser menu.
-
Select the Object category from the toolbar.
-
Open the Viewer State Code Generator with the File ▸ New State… menu.
-
Enter
statedemo
as the state’s name in the Name field. -
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:
-
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
statedemo
, the Asset Label toState 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.
To quickly create a “blank” OBJ asset to experiment with:
-
At the Object level, 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
statedemo
, the Asset Label toState 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 Node tab.
-
Set the Default state field to the state name (for example, in the code above, this is
statedemo
). -
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.
-
-
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, 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:
Python viewer states
- Python state creating and editing nodes
- Python state nodeless states
- Python state selections
- Python state handles
- Python state parameters
- Python state guide geometry
- Python state user interface events
- Python state context menu
- Python state Drag and Drop
- Python states supporting undo
- Python state info panels
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 |
---|---|
|
Controls whether a mouse drag event (with the up) is generated or not. Mouse drag events are generated by default ( When If mouse drag events are not required by your state, consider setting 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 ... |
|
The flag triggers a redraw of the viewport on a mouse move or mouse wheel event when it’s set to By default the viewport always redraw. To reduce performance issues with large scenes, 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 ... |
|
When set to 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 ... |
|
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 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 |
---|---|---|
|
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. |
|
state is interrupted |
This method is called when:
Note: |
|
state is about to be exited |
This method is called when:
Note: |
|
interruption ends |
This method is called when:
Note: |
|
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 |
Additional notes:
-
Houdini calls either
onEnter
(if the state should work on an existing node) oronGenerate
(if the should create a new node) when the tool activates, never both. The dictionary passed toonEnter
has anode
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 anonMouseEvent
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 correspondingonResume
. If the user exits the state while it’s interrupted, you will not receive anonResume
call, justonExit
. -
If the user opens the state’s context menu, when the pointer moves over the menu and leaves the menu you will receive
onInterrupt
andonResume
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 |
---|---|---|
|
mouse moves/clicks |
See mouse handling. |
|
Mouse double clicks |
See mouse handling. |
|
mouse wheel scroll |
hou.UIEventDevice.mouseWheel returns See mouse wheel handling. |
|
For key events |
See reading the keyboard device for more. |
|
For key transition events |
See reading the keyboard device for more. |
|
context menu choice |
|
|
update menu state |
The handler |
|
State parameter events |
|
|
Action or change occurs on the state’s node |
Takes a dictionary argument similar to hou.OpNode.addEventCallback. The dictionary contains in particular:
The node event type
The state’s node object.
A hou.ParmTuple object containing the node parms (if any). This handler is required by hou.ViewerStateTemplate.bindNodeChangeEvent and hou.ViewerStateTemplate.bindNodeParmChangeEvent. |
|
Playbar change event occurs. |
Takes a dictionary argument containing the event information similar to hou.playbar.addEventCallback:
A float number that specifies the frame when the event took place.
A hou.channelListChangedReason is present only when
|
|
general purpose command events |
Called by invoking hou.SceneViewer.runStateCommand. The handler
The command string identifier.
A |
Additional notes:
-
onCommand
is used for implementing specific actions on the state. For instance, you can useonCommand
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 |
---|---|---|
|
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:
The string ID of the handle.
A dictionary containing the new handle parameter values.
A list of of the names of parameters that changed.
A dictionary containing the previous handle parameter values. This can be useful for computing deltas.
hou.UIEvent object to know about the handle status such as start dragging or stop dragging. |
|
node parameters change |
This lets you update handle parameters when node parameters change. The dictionary passed to this method contains the following extra items:
The string ID of the handle.
A dictionary containing the new node parameter values. |
|
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:
The string ID of the handle.
hou.UIEvent object to know about the handle status such as start dragging or stop dragging. |
|
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:
The string ID of the handle.
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 |
---|---|---|
|
user starts selecting |
The dictionary passed to this handler has the following item:
The name of the current active selector.
A list of drawable names representing selectable drawables. This entry is not available if the active selector is not a drawable selector. |
|
user selected geometry |
The dictionary passed to this handler has the following extra items:
A hou.GeometrySelection object representing the completed selection. This entry may not exist if the active selector is a drawable selector.
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.
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 |
|
user stops selecting |
This is called when the state “accepts” a selection by returning The dictionary passed to this handler has the following item:
The name of the current active selector. |
|
user located drawable geometry |
This handler is called when drawables are located with a drawable selector. The
A python dictionary object representing the located drawable components.
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 |
---|---|
|
Called when the user initiates a |
|
Called to build a list of |
|
Handles the selected |
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 |
---|---|---|
|
drawing events |
The method is called when:
|
|
drawing events |
The method is called when:
|
Additional notes:
-
onDraw
is required for advanced drawables. The handle returned bykwargs["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 thelog
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 useviewerstate.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("<", "<") msg = msg.replace(">", ">") 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 ¶
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 ¶
Here are a few of the HOM
API related to python viewer states.
Python viewer states