Houdini 17.0 Python Scripting

Extending the network editor

On this page

Almost all user interactions with the network editor are handled by python code defined in $HFS/houdini/python2.7libs, in a collection of modules with names that start with nodegraph. Every user action and various application generated events are sent to this python code for processing.

The basic organization of the code is that all events are passed to the current context module (nodegraph.py by default), to a function named handleEvent(). It is possible to change the current context by calling hou.NetworkEditor.pushEventContext(). This method is free to do whatever it wants with the event. The approach used in nodegraph.py is to pass this event to a python coroutine, which uses a yield statement inside a loop to effectively pause execution of the routine until the next event arrives. The use of a coroutine in this way allows the code to easily maintain state from one event to the next without using global variables. The use of the yield statement also lets the code be written as a simple loop, where the arrival of each event automatically resumes the code where it left off.

Within the event handling loop in the coroutine, certain special events (like hitting Escape, or events indicating the user has switched networks) are handled directly in the loop. User events (like pressing a button on the mouse) are sent to a function called createEventHandler. The purpose of this function is to identify the start of a user operation (such as connecting two nodes together, or moving a node), usually (but not necessarily) indicated by a keyboard event or a mouse button press. When a user operation is started, this function will return an EventHandler object (defined in nodegraphbase.py). Future events are sent directly to this event handler object’s handleEvent() method, which, like createEventHandler, also returns an EventHandler object. If the user operation is completed (often because the user releases the mouse button that started the operation), handleEvent will return None, and the next user event will call createEventHandler to look for the start of another user operation. If the user operation is still ongoing (often indicated by an event like a mouse drag), this method will return self, so that it continues to receive events. Or in some cases a different EventHandler object may be returned, indicating that the user operation has become more specialized, or morphed into a different operation. Whatever the reason, this new EventHandler will start receiving events as they come in.

Event types

Each event sent to the handleEvent function will be one of the event types defined in the $HFS/houdini/python2.7libs/canvaseventtypes.py module.

InitializationEvent

Sent once to each network editor when it is first created in the Houdini interface.

editor

The hou.NetworkEditor receiving the event.

eventtype

A string describing the event. initialization is the only value that appears in this class.

ContextEvent

Sent when the network editor changes to a different network location (such as moving from /obj to /obj/geo1).

editor

The hou.NetworkEditor receiving the event.

eventtype

A string describing the event. context is the only value that appears in this class.

oldcontext

A string containing the full path to the previous network.

context

A string containing the full path to the new network.

ContextClearEvent

Sent when the user clears the currently loaded hip file either by starting a new file or loading a different hip file. Gives the network editor a chance to clear any data that is specific to the hip file.

editor

The hou.NetworkEditor receiving the event.

eventtype

A string describing the event. contextclear is the only value that appears in this class.

context

A string containing the full path to the current network.

MouseEvent

Sent when the user moves the mouse, presses or releases a mouse button, moves the scroll wheel, or double clicks a mouse button.

editor

The hou.NetworkEditor receiving the event.

eventtype

A string describing the event.

mouseenter

The mouse cursor entered the network editor pane.

mousemove

The mouse cursor moved within the network editor pane.

mouseexit

The mouse cursor left the network editor pane.

mousedown

A mouse button was pressed.

mousedrag

The mouse cursor moved while the button is still pressed.

mouseup

The mouse button was released.

doubleclick

The mouse button was double clicked. This event will always follow a mouse down, mouse up, and another mouse down event. A second mouse up event will follow when the mouse button is released the second time.

mousewheel

The mouse wheel was turned.

mousepos

A hou.Vector2 object holding the current position of the mouse cursor in screen coordinates.

mousestartpos

If a mouse button is currently pressed, this is a /hom/Hom.hou.Vector2 object holding the position of the mouse when the mouse button was pressed.

mousestate

A MouseState object indicating the current state of the mouse buttons.

dragging

A bool value which will be True if a mouse button is pressed, and with the button pressed the mouse has moved far enough that the user can be assumed to be performing a drag operation. This value should remain False if the user presses a mouse button, and accidentally moves the mouse by a few pixels (a common problem with tablet input).

modifierstate

A ModifierState object indicating the current state of the keyboard modifier keys.

located

A NetworkComponent object that describes the user interface gadget under the mouse.

selected

If a mouse button is currently pressed, this is a NetworkComponent object that describes the user interface gadget under the mouse when the mouse button was pressed.

wheelvalue

An integer that will be zero unless this is a mousewheel event, in which case this value will indicate the magnitude and direction of the mouse wheel movement.

time

Returns the window system’s timestamp for this event. The timestamp is in seconds since some arbitrary point in time such as the time when the system started.

KeyboardEvent

Sent when the user hits a key on the keyboard. It is also possible to register certain keys to generate separate key up and key down events instead of key hit events, which is necessary to implement volatile states.

editor

The hou.NetworkEditor receiving the event.

eventtype

A string describing the event.

keyhit

A key has been pressed on the keyboard. If the key is held down long enough, a series of key hit events may be generated based on your configured key repeat rate.

menukeyhit

A menu item was selected, where the menu item has an associated hotkey symbol (even if no hotkey is assigned). This is how the network editor handles events from the menu at the top of the pane.

parentkeyhit

A special case of hitting a key or a menu item on the network editor pane when the pane is in List Mode. This means the network itself is not visible, but the key is still given the chance to be processed by the network editor code.

keydown

A key registered as a volatile key using hou.NetworkEditor.setVolatileKeys() has been pressed.

keyup

A key registered as a volatile key using hou.NetworkEditor.setVolatileKeys() has been released.

key

A string indicating the key that was pressed. This string may be a description of the key itself (Shift+H or Ctrl+T) if the event is generated by pressing an actual key on the keyboard. It may instead be a hotkey symbol (h.pane.wsheet.jump) if this event was generated by selecting a menu item. The nodegraphdisplay.setKeyPrompt() function is useful for testing keys against hotkey symbols (as well as prompting the user with text at the bottom of the pane indicating what key was hit).

modifierstate

A ModifierState object indicating the current state of the keyboard modifier keys.

located

A NetworkComponent object that describes the user interface gadget under the mouse when the key is pressed.

mousepos

A hou.Vector2 object holding the current position of the mouse cursor in screen coordinates.

mousestate

A MouseState object indicating the current state of the mouse buttons.

time

Returns the window system’s timestamp for this event. The timestamp is in seconds since some arbitrary point in time such as the time when the system started.

TimerEvent

Sent when the time interval elapses following a call to hou.NetworkEditor.scheduleTimerEvent().

editor

The hou.NetworkEditor receiving the event.

eventtype

A string describing the event. timer is the only value that appears in this class.

timerid

The unique integer identifier for the timer returned by the hou.NetworkEditor.scheduleTimerEvent() call that started the timer.

ValueEvent

Sent when the user finishes editing an input field brought up in the network editor through a call to hou.NetworkEditor.openNameEditor(), hou.NetworkEditor.openCommentEditor(), or hou.NetworkEditor.openNoteEditor().

editor

The hou.NetworkEditor receiving the event.

eventtype

A string describing the event. editvalue is the only value that appears in this class.

valueid

The unique integer identifier for the input field returned by the call that opened it.

value

The final string value in the input field.

ModalUIEvent

Sent when a Houdini UI gadget (generally the Tab menu) is opened or closed over the network editor.

editor

The hou.NetworkEditor receiving the event.

eventtype

A string describing the event.

startmodalui

The menu has just opened.

endmodalui

The menu has closed.

interfacename

The name of the interface gadget that is opened or closed. Currently the only value will be TabMenu.

Helper classes

Several of the events defined above make use of helper classes for describing interface components under the mouse, or the current state of mouse buttons. These classes are also defined in $HFS/houdini/python2.7libs/canvaseventtypes.py.

MouseState

Describes the state of all mouse buttons.

lmb

A bool value set to True if the left mouse button is pressed.

mmb

A bool value set to True if the middle mouse button is pressed.

rmb

A bool value set to True if the right mouse button is pressed.

ModifierState

Describes the state of all modifier keys on the keyboard.

alt

A bool value set to True if one of the Alt keys is pressed.

ctrl

A bool value set to True if one of the Ctrl keys is pressed.

shift

A bool value set to True if one of the Shift keys is pressed.

NetworkComponent

Describes a UI element that appears in the network editor pane.

item

The hou.NetworkItem of which the UI element is a part. This may be a hou.Node, hou.NodeConnection, or any other hou.NetworkItem subclass.

name

A string describing the specific UI element. The available values depend on the type of object in the item field.

index

An integer that further refines the UI element. For example, if the item is a hou.Node, and the name is input (indicating an input connector), the index will indicate which input connector.

Coordinate spaces

There are two coordinates systems used by the network editor. One is the “network” coordinates. This is the native coordinate system of a network. In this system, node tiles are generally about one unit wide. hou.Node.position returns the location of the lower left corner of the node in network coordinates.

The other coordinate system is the screen coordinate system. In this system, (0, 0) is the lower left corner of the network editor pane, and one unit is one pixel.

Almost all methods and data that don’t have “screen” or “mouse” in their name are expressed in network coordinates. And there are methods on hou.NetworkEditor to translate from one to the other. So hou.NetworkEditor.posToScreen() converts a position in network coordinates into the equivalent position in screen coordinates. hou.NetworkEditor.visibleBounds() returns the visible area of the network editor in network coordinates, and hou.NetworkEditor.screenBounds() returns the bounds in screen coordinates. When you pan around the network editor, your screen bounds are unchanged, but your visible bounds will change.

Intercepting events globally

The first opportunity for customizing the behavior of the network editor is provided by the createEventHandler function. Before even looking at the event, the event will be sent to nodegraphhooks.createEventHandler(). The default implementation of this function returns a value indicating that the events should be processed normally. However you can create a new nodegraphhooks module in your python path such that it is found before the one in $HFS/houdini/python2.7libs. You then have the opportunity to intercept any event, as long as there is no user operation in progress (if an EventHandler is already active, you do not have the chance to intercept those events).

This function is passed an event object, and a pending_actions parameter which can generally be ignored. It must return a tuple consisting of a EventHandler object or None, followed by a bool value indicating whether the event was handled. In the case of events like keyboard key presses, where the user operation is completed by the single event, you will return (None,True) to indicate that there is no EventHandler set up, but that further processing of the event should not occur.

In addition to custom handling of hotkeys, another common use for hooking would be to modify the behavior of certain operations (such as dragging nodes with certain modifier keys to do something other than simply moving the nodes). In this case you want most of the node behaviors to remain intact, but you want to customize the user operation under certain circumstances. The best way to accomplish this is by creating a subclass of one of the many EventHandler subclasses that already exist in the network editor control modules. Within the handleEvent method of the subclass, most events can defer to the base class, but specific events can be intercepted to change the behavior as desired.

Intercepting events for an HDA

You may also define a createEventHandler function inside the PythonModule of an HDA. This function takes the same parameters and returns the same values as the global hook function, but is only called when the event from the user is a mouse event on some part of a node tile for an instance of your HDA. This allows you to provide specialized behaviors when interacting with your HDA (such as customizing the double click event to dive to a specific location within the node). Because the PythonModule is part of the HDA definition, these custom behaviors will show up anywhere that you install the HDA without having to modify the global hook function.

Examples

The following code will modify the behavior of the hotkey for diving into a node so that it dives into the node under the mouse instead of the current node. This code should be placed in $HOUDINI_USER_PREF_DIR/python2.7libs/nodegraphhooks.py:

import hou
from canvaseventtypes import *
import nodegraphdisplay as display
import nodegraphview as view

def createEventHandler(uievent, pending_actions):
    if isinstance(uievent, KeyboardEvent) and \
       uievent.eventtype == 'keyhit':
        # This is a key hit event. Check for the 'dive in' key.
        editor = uievent.editor
        key = uievent.key
        eventtype = uievent.eventtype
        if display.setKeyPrompt(editor, key, 'h.pane.wsheet.jump', eventtype):
            # Find the node under the mouse.
            pos = uievent.mousepos
            items = editor.networkItemsInBox(pos, pos, for_select = True)
            for (item, name, index) in items:
                if isinstance(item, hou.Node) and item.isNetwork():
                    view.diveIntoNode(editor, item)
                    break
            # We handled this event, but don't need to return an event handler
            # because this is a one-off event. We don't care what happens next.
            return None, True

    return None, False

The following code will intercept the ⌃ Ctrl + ⇧ Shift + H key to set the network editor to a fixed zoom level, with the current node positioned under the mouse. This code should be placed in $HOUDINI_USER_PREF_DIR/python2.7libs/nodegraphhooks.py:

from canvaseventtypes import *

def createEventHandler(uievent, pending_actions):
    if isinstance(uievent, KeyboardEvent) and \
       uievent.eventtype == 'keyhit' and \
       uievent.key == 'Ctrl+Shift+H':
        # Get the current bounds.
        screenbounds = uievent.editor.screenBounds()
        bounds = uievent.editor.visibleBounds()
        # Figure out how much we need to scale the current bounds to get to
        # a zoom level of 100 pixels per network editor unit.
        currentzoom = screenbounds.size().x() / bounds.size().x()
        desiredzoom = 100.0
        scale = currentzoom / desiredzoom

        # Get the current node.
        currentnode = uievent.editor.currentNode()
        if currentnode is not None:
            mousepos = uievent.mousepos
            mousepos = uievent.editor.posFromScreen(mousepos)
            noderect = uievent.editor.itemRect(currentnode)
            # Do the zoom centered at the mouse position.
            zoomcenter = mousepos
            bounds.translate(-zoomcenter)
            bounds.scale((scale, scale))
            bounds.translate(zoomcenter)
            # Move the view so the current node is under the mouse.
            bounds.translate(noderect.center() - mousepos)
            # Set the new bounds.
            uievent.editor.setVisibleBounds(bounds)

        # We handled this event, but don't need to return an event handler
        # because this is a one-off event. We don't care what happens next.
        return None, True

    return None, False

The following code demonstrates the creation of an EventHandler subclass to override the behavior of a double click event on a node. Instead of diving into the node, it does nothing. This is unlikely to be a desirable change as a global hook, but might be useful as part of a PythonModule in a specific HDA:

import hou
import nodegraph
from canvaseventtypes import *

class NoDiveNodeMouseHandler(nodegraph.NodeMouseHandler):
    def handleEvent(self, uievent, pending_actions):
        if isinstance(uievent, MouseEvent) and \
           uievent.eventtype == 'doubleclick':
            return None

        return nodegraph.NodeMouseHandler.handleEvent(
            self, uievent, pending_actions)

def createEventHandler(uievent, pending_actions):
    if isinstance(uievent, MouseEvent) and \
       uievent.eventtype == 'mousedown' and \
       isinstance(uievent.selected.item, hou.Node):
        return NoDiveNodeMouseHandler(uievent), True

    return None, False

Python Scripting

Getting started

Next steps

Python viewer states

You can write viewer states in Python that let you customize user interaction in the viewport for your node.

Guru level

Reference

  • hou

    Module containing all the sub-modules, classes, and functions to access Houdini.

  • Alembic extension functions

    Utility functions for extracting information from Alembic files.