HOM Storing Python Scripts in digital assets

Python Module per Digital Asset

Each Houdini Digital Asset (HDA) may have its own Python module. This module corresponds to the asset type, and is independent of the number of instances of the asset. Typically, one would use this module to store functions, classes, constants, etc. to do with the HDA.

The HDA’s Python module is stored in the PythonModule section. To create this module, create a “Python Module” event handler in the Scripts page of the Operator Type Properties dialog.

Because the module is associated with the HDA’s node type, you can access the Python module object via hou.NodeType.hdaModule. For example, you could write hou.nodeType(hou.objNodeTypeCategory(), 'hdaname').hdaModule() to access the module for the hdaname HDA. Most of the time, however, you're dealing with hou.Node objects, so you can use the convenience method hou.Node.hdaModule. If n is a Node object, calling n.hdaModule() is equivalent to calling n.type().hdaModule().

The return value of hdaModule() behaves like a Python module. So, if the PythonModule section contains:

def foo():
    print "hello"
and /obj/hda1 is an instance of that HDA, you could write hou.node('/obj/hda1').hdaModule().foo().

Python Event Handlers

HDA event handlers can be written in Hscript or Python. Simply change the “Edit as” menu to tell Houdini which language the code is in.

Parameters to Python event handlers are passed in via a global dictionary variable named kwargs, similar to how they're passed into shelf tool scripts. For example, in an Hscript OnCreated event handler you would use $arg1 to get the path to the new instance of the HDA. In a Python OnCreated handler, you would use kwargs['node'] to get the hou.Node for the new HDA instance.

The following table lists the contents of the kwargs dictionary for the various event handler types:

Key name

Value

Event Handlers

node

The hou.Node instance for the digital asset.

OnCreated, OnUpdated, OnInputChanged, OnNameChanged, OnDeleted

type

The hou.NodeType defined by the digital asset.

OnLoaded, PreFirstCreate, OnCreated, OnUpdated, OnInputChanged, OnNameChanged, OnDeleted, PostLastDelete, PythonModule

old_name

A string containing the the old name of the HDA instance.

OnNameChanged

input_index

The index of the input that was connected/disconnected.

OnInputChanged

Note

Note that kwargs['type'] is also available in the PythonModule section.

Accessing the Python Module

There are a number of places where you will commonly call functions in the PythonModule, including Python and Hscript event handlers, parameters in nodes inside the HDA, HDA button callbacks, and menu generation scripts.

To call a function in PythonModule from a Python event handler, simply use kwargs['node'] to access the node and from the node you can access the module. For example, if you had the following function in the PythonModule section:

def onCreated(node):
    hou.ui.displayMessage("You created " + node.path())
you could call it from the OnCreated Python event handler with:
kwargs['node'].hdaModule().onCreated(kwargs['node'])
You could also call this method from an Hscript version of the OnCreated section using the python Hscript command:
python -c "hou.node('$arg1').hdaModule().onCreated(hou.node('$arg1'))"

When Houdini runs a Python parameter callback script, it also passes its arguments via a kwargs dictionary. The most useful values in this dictionary are kwargs['node'] and kwargs['parm'], containing the hou.Node and hou.Parm objects corresponding to the parameter. So, you could write

kwargs['node'].hdaModule().onButtonPress()
to call the onButtonPress() function in the PythonModule section.

Note

You might see the above code written as hou.pwd().hdaModule().onButtonPress(). Using hou.pwd, or hou.node('.'), is also a valid way to access the PythonModule section, since Houdini changes the current node to the HDA instance when it invokes a parameter callback.

Python expressions in nodes inside the HDA can easily access the PythonModule via relative references. For example, if “/obj/hda1” is an instance of the HDA and “/obj/hda1/geo1” is a node inside it, a Python expression on one of geo1’s parameters could contain, for example, hou.node('..').hdaModule().bar() to call the bar() function in PythonModule.

Menu parameters whose contents are generated via a script can also use PythonModule functions to generate the menu. In the current version of Houdini, menu scripts cannot be written directly in Python. Instead, you must use invoke the Python code from Hscript and use Hscript to echo the menu contents. Recall that Hscript menu generation scripts output a whitespace-separate list of strings, with two strings per menu entry. Each subsequent pair of strings contain the internal name for the menu entry in the first part of the pair, and the label in the second part. For example, "one Apples two 'Cream Pie'" would correspond to two menu entries with internal names “one” and “two” and labels “Apples” and “Cream Pie”.

The following code illustrates how to use Python to generate the menu. The following functions would go in the PythonModule section:

def flattenMenuForHoudini(menu):
    """Take a sequence of pairs and return a space-separated string that
       Houdini understands."""
    return " ".join(repr(e[0]) + " " + repr(e[1]) for e in menu)

def generateMenu():
    """This function is called from the hscript menu generation script."""
    return flattenMenuForHoudini((('one', 'Apples'), ('two', 'Cream Pie')))

This Hscript menu script calls the functions above. Note that the strreplace prevents Hscript from stripping single quotes for when the menu labels contain spaces.

echo `strreplace(pythonexprs("hou.pwd().hdaModule().generateMenu()"), "'", "\'")`

Multiple Python Modules in a Digital Asset

If you want to break up your Python code into multiple modules, you can use other HDA sections to store those modules. For example, suppose you want to create a (sub)module named bar. You could put the following code in an HDA section named “bar_PythonModule”:

def foo():
    print "This is bar.foo()!"

Then, in the PythonModule section, you would write:

import toolutils
bar = toolutils.createModuleFromSection(
    'bar', kwargs['type'], 'bar_PythonModule')

bar now appears as a submodule of hdaModule(). So, for example, you could write the following in a button callback to call the foo function:

hou.pwd().hdaModule().bar.foo()