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'))"

Parameter Callback Scripts

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.

The keys in the kwargs dictionary have the same names as the variables available to Hscript callback scripts, except that it contains the extra keys node and parm. This dictionary contains the following entries:

parm

The hou.Parm object whose callback script was invoked.

node

The hou.Node object containing the parameter.

parm_name

The name of the hou.Parm. This value is the same as kwargs['parm'].name().

script_parm

The name of the hou.ParmTuple containing the parameter. This value is the same as kwargs['parm'].tuple().name(). This key is called script_parm instead of something more meaningful like parm_tuple_name for backwards compatibility with Hscript.

script_value

The value of the parameter. This entry is the same as kwargs['parm'].eval().

script_value0, script_value1, …

The values of the parameter tuple. These entries are the same as [p.eval() for p in kwargs['parm'].tuple()].

script_multiparm_index

The number of the multiparm, if the parameter is an instance of a multiparm. Note that the “First Instance” parameter on the multiparm’s folder block list determines where to start numbering multiparms. Typically, the first multiparm is numbered 1, the second 2, and so on.

If this parameter is not an instance of a multiparm, this value is -1.

script_multiparm_nesting

If this parameter is not an instance of a multiparm, this value is 0. If it is a multiparm instance but the instance is not contained inside other multiparm instances, the value is 1. Otherwise, if the multiparm instance is nested inside one level of multiparm instances it returns the number of nesting levels.

script_multiparm_index2, …, script_multiparm_indexN

These values are only available if script_multiparm_nesting is 2 or higher. These values correspond to the multiparm instance numbers of the outer multiparm instances. For example, if multiparm instance 3 is nested inside instance 5 which is inside instance 4, script_multiparm_index will be 3, script_multiparm_index2 will be 5, and script_multiparm_index3 will be 4.

Parameter Expressions

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.

Parameter Menu Scripts

Menu parameters whose contents are generated via a script can also use PythonModule functions to generate the menu. You can write these menu scripts using either Python or Hscript. In Hscript, these scripts output a whitespace-separated list of strings, with two strings per menu entry. Each pair of strings contains the internal name for the menu entry in the first part of the pair and the label in the second part. In Python you write code that evaluates to a sequence of strings. 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”.

If a Python menu script contains only one line, Houdini evaluates it as a Python expression. If it contains multiple lines, Houdini evaluates it as a function body. This behavior is the same for Python expressions in parameters.

The following code illustrates how to use Python to generate a menu of Object-level digital asset names:

result = []
for tool in hou.shelves.tools().values():
    node_type_name = tool.toolMenuOpType(hou.paneTabType.NetworkEditor)
    if (node_type_name.startswith("Object/") and
            'Digital Assets' in tool.toolMenuLocations()):
        result.append(tool.name())
        result.append(tool.label())
return result

Note that you can also put the code that generates the menu into a function in the PythonModule section (e.g. a generateMenu function). You would then call that function with hou.pwd().hdaModule().generateMenu().

Node Initialization Scripts

Houdini supports both Hscript and Python versions of node initialization scripts. Hscript initialization scripts have always been supported. For example, when you put down a geometry object, Houdini searches $HOUDINI_PATH for Hscript script files named scripts/obj/geo.cmd and runs the first one it finds.

These initialization scripts may also be written in Python, and Houdini will also search for scripts/obj/geo.py and run it if it finds it. If Houdini finds both an Hscript (.cmd) version and a Python (.py) version of a node initialization script, it will run the Python version.

Python node initialization scripts can use the kwargs dictionary. It contains one entry: kwargs['node'] contains the hou.Node object that was just created.

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()