On this page

Overview

Here’s a quick example of how to use HOM to accomplish a simple task in Houdini. Don’t worry if you don’t understand the details of this example – it will give you a flavor of what scripting Houdini is like.

Choose Windows ▸ Python Shell to open an interactive Python Shell window.

# Print out a tree of all the nodes in the scene:
>>> def print_tree(node, indent=0):
...     for child in node.children():
...         print (" " * indent + child.name())
...         print_tree(child, indent + 3)
... # Press Enter to finish the definition

>>> print_tree(hou.node('/'))
obj
   cam1
      file1
      properties
         standard
out
part
ch
shop
img
   img1
vex

Getting Started

When you open Houdini’s Python shell, you’ll notice it greets you with the >>> prompt and waits for you to enter Python expressions or statements. Even if you don’t plan on writing large Python scripts, the Python shell is invaluable as a handy calculator:

>>> 2 + 2
4
>>> 0.03 * 25.1
0.753
>>> min(hou.frame(), 7) * 3
3.0

What is hou.frame(), you might ask? Houdini’s Python API is implemented in a module named hou, short for Houdini. Just like os.getcwd is a function in the os module, hou.frame is a function in the hou module, and it returns the current frame number. Note that you don’t need to write import hou to use the hou module, since Houdini automatically imports the hou module when it starts up.

Press Ctrl+D to close a floating Python shell window. See the main menu for the shortcut to open the floating Python shell window.

Python shells can be inside panes if you don’t want to use a floating window.

In the Python shell, Home and ⌃ Ctrl + A will move to the beginning of the line, End and ⌃ Ctrl + E will move to the end, and up and down will navigate through the history.

You can’t use ⌃ Ctrl + C to copy from the Python shell, since Ctrl+C will send a KeyboardInterrupt exception. To copy text from a Python shell, right-click and select Copy.

Loading Hip Files

Use the hou.hipFile submodule to save/load the current session to/from hip files. Note that hou.hipFile.load will throw a hou.LoadWarning exception if there were warnings, even though the file was loaded successfully. The following code will print out warnings and continue the rest of the script.

# Print out load warnings, but continue on a successful load.
try:
    hou.hipFile.load("myfile.hip")
except hou.LoadWarning as e:
    print(e)

Accessing Nodes

Because Houdini is designed around nodes (e.g. SOPs, DOPs, Object nodes, etc.), you're likely to manipulate them in scripts. Here’s a brief primer to get started.

The hou.node function takes a path to a node and returns a hou.OpNode object, or None if the path is invalid.

# Empty out the current session.
>>> hou.hipFile.clear()

>>> hou.node('/obj')
<hou.OpNode at /obj>
>>> # hou.node returned a hou.OpNode object corresponding to the /obj node

>>> n = hou.node('/asdfasdf')
>>> # The node path was invalid, so n will be the None object.
>>> print(n)
None

>>> g = hou.node('/obj').createNode('geo')
>>> g
<hou.ObjNode of type geo at /obj/geo1>
>>> # g is hou.OpNode object corresponding to the newly created /obj/geo1 node.
>>> # Note that g is actually a hou.ObjNode instance, which is a subclass of
>>> # hou.OpNode.

>>> # The parm method on hou.OpNode objects returns a hou.Parm object (or None
>>> # if the parameter name is invalid).
>>> tx = g.parm('tx')
>>> tx
<hou.Parm tx in /obj/geo1>

>>> # Evaluate the parameter and change its value.
>>> tx.eval()
0.0
>>> tx.set(3.5)
>>> tx.eval()
3.5

>>> hou.node('/obj/geo1').parm('tx').eval()
3.5
>>> # hou.parm is a shortcut to access a parm directly.
>>> hou.parm('/obj/geo1/tx').eval()
3.5
>>> # hou.evalParm is a shortcut to evaluate a parameter.
>>> hou.evalParm('/obj/geo1/tx')
3.5
>>> # hou.ch is exactly the same as hou.evalParm.
>>> hou.ch('/obj/geo1/tx')
3.5

>>> # hou.Parm.name returns the name of the parameter, and hou.OpNode.parms
>>> # Returns a tuple of all the Node's parameters.
>>> [p.name() for p in g.parms()]
['stdswitcher1', 'stdswitcher2', 'stdswitcher3', 'stdswitcher4', 'keeppos',
'pre_xform', 'xOrd', 'rOrd', 'tx', 'ty', 'tz', 'rx', 'ry', 'rz', 'sx', 'sy',
'sz', 'px', 'py', 'pz', 'scale', 'lookatpath', 'lookup', 'pathobjpath',
'roll', 'pos', 'uparmtype', 'pathorient', 'upx', 'upy', 'upz', 'bank',
'shop_materialpath', 'shop_materialopts', 'tdisplay', 'display',
'use_dcolor', 'dcolorr', 'dcolorg', 'dcolorb', 'picking', 'pickscript',
'caching', 'vport_shadeopen', 'vport_displayassubdiv', 'vm_phantom',
'vm_renderable', 'folder01', 'folder02', 'folder03', 'folder04',
'categories', 'reflectmask', 'lightmask', 'geo_velocityblur',
'vm_shadingquality', 'vm_rayshadingquality', 'vm_rmbackface',
'shop_geometrypath', 'vm_rendersubd', 'vm_renderpoints', 'vm_metavolume',
'vm_coving', 'vm_computeN']

>>> # hou.Parm tuples correspond to parameter groupings:
>>> t = g.parmTuple('t')
>>> t
<hou.ParmTuple t in /obj/geo1>
>>> tuple(t)
(<hou.Parm tx in /obj/geo1>, <hou.Parm ty in /obj/geo1>, <hou.Parm tz in /obj/geo1>)
>>> t.eval()
(3.5, 0.0, 0.0)
>>> t.set((1.0, 2.0, 3.0))
>>> t.eval()
(1.0, 2.0, 3.0)

>>> # Build a simple sop network.
>>> hou.hipFile.clear()
>>> geo = hou.node('/obj').createNode('geo')
>>> box = geo.createNode('box')
>>> subd = geo.createNode('subdivide')
>>> subd.parm('iterations').set(3)
>>> subd.setFirstInput(box)
>>> subd.moveToGoodPosition() # Move the node tiles to avoid overlaps.
>>> subd.setDisplayFlag(True)
>>> subd.setRenderFlag(True)
>>> subd.setCurrent(True, clear_all_selected=True)

Working with Animated Parameters and Keyframes

When you hear the term “animated parameter”, you typically think of keyframed values and bezier curves and the animation graph editor. Recall from earlier, though, that parameters with expressions are also considered animated parameters. All animated parameters have at least one keyframe, and each keyframe has an expression. Typical parameters with expressions simply have one keyframe whose expression is something like sin($F) or cos(time()), while typical animation curves have multiple keyframes whose expressions are something like bezier().

So how does a function like bezier() evaluate to different values at different times? Clearly there are no parameters passed to bezier that vary from time to time, and there are no keyframe or slope values passed in. The answer is that keyframes store more than just an expression. A keyframe stores those values, slopes, and accelerations, and certain functions, like bezier, access those values for the current keyframe and the next one. For keyframes with expressions like sin($F), those extra values are not set and are not used.

Each keyframe has an associated time. Using that time and the number of frames per second, you can derive the keyframe’s frame. You can think of the expression as being active between keyframes: Houdini evaluates the expression between its keyframe and the next keyframe. If there is no next keyframe, most animation functions (e.g. bezier, cubic, etc.) simply evaluate to their keyframe’s value. For the times before the first keyframe, the parameter evaluates to the value at the first keyframe’s time.

hou.Parm.keyframes values, slopes, and accelerations

  • If you set the in value and the (out) value is not set, it will be set to the same value. Setting the in value breaks the tie between the values. If neither of the in or (out) values are set, they are considered tied.

  • for example, to set a keyframe with the current value and slope, do not set the value or slope in the keyframe

  • or, to automatically determine the slopes, set a keyframe with the slope not set

  • times and expressions

  • in and out/values

  • tied values

  • asCode()

  • same syntax between Hscript expressions and Python

Working with Objects and Transformations

  • worldTransform(), setWorldTransform()

  • matrices, exploding

  • column vectors for transforms (p T1 T2), not (T2 T1 p)

  • see the object_xform cookbook example

Where to find error messages

  • For the shelf/tab menu: in the details section of the popup window.

  • For HDA callbacks: in the console.

  • For 123.py/456.py: in the console.

  • For parameters: on node.

  • For Python-based nodes: on node.

Interpreting Python error messages

When Python code generates an unhandled exception, it will display a traceback of the call stack where the exception was raised. By looking at the last entry in the traceback, you can find the line of code that raised the exception.

For example, if there was spelling error in the implementation of fixFilePrefixes, you might see the following traceback.

>>> hou.session.fixFilePrefix(hou.node('/'), '/home/luke/project', '$HIP')
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "hou.session", line 12, in fixFilePrefix
NameError: global name 'to_prefix' is not defined

The last line of the traceback displays the string representation of the exception. The most common exceptions the hou module is likely to raise are hou.OperationFailed, hou.PermissionError, hou.LoadWarning, and hou.ObjectWasDeleted. A list of all exception types defined in the hou module can be found in the reference documentation.

Tip

The Python source editor, multi-line expression editor, shelf script editor, and HDA script editors show the line number in the bottom-right corner, helping to locate the line that raised an exception.

Tips

  • Drag a node from the network editor into the Python shell to paste a hou.node expression. You may find this easier if the Python shell is inside a pane.

  • Use variables to store hou.OpNode, hou.Parm, and hou.ParmTuple objects instead of calling hou.node and hou.parm over and over again.

  • Use the output from hou.OpNode.asCode to help learn the parts of the HOM API that create nodes and set parameters and keyframes.

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