Houdini Object Model
Overview
The Houdini Object Model (HOM) is an application programming interface (API) that lets you get information from and control Houdini using the Python scripting language. HOM replaces the functionality of Houdini’s previous scripting solutions, the expression language and HScript.
In Python, the hou package is the top of a hierarchy of modules, functions, and classes that define the HOM. The hou module is automatically imported when you are writing expressions in the parameter editor and in the hython command-line shell.
If you're looking for the HOM reference documentation, you’ll find it here. If you're looking for starting examples, you’ll find them in the HOM cookbook.
Quick introduction
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 favor 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('/'))objcam1file1propertiesstandardoutpartchshopimgimg1vex
Learning Python
Before learning Houdini’s Python API, it’s necessary to understand at least a little of the Python language. Fortunately, Python is one of the easiest and most pleasant languages to learn. Teaching you the Python language itself is beyond the scope of the Houdini online help. Luckily, there are many excellent resources available online and in book form.
Python.org includes a fantastic tutorial that assumes no previous programming experience. If you read this tutorial, you should have no problem learning how to script Houdini.
Python.org also includes the standard library reference. Python has a large set of libraries, or modules, letting you perform a large variety of tasks in Python. This reference is invaluable even after you've learned the core Python language.
Python.org also contains links to other learning resources.
Mark Pilgrim’s Dive into Python book is available in print and free online. This is a very well-written and candid introduction to the language with many small projects to dive into. However, it may be slightly out-of-date with the latest versions of Python.
Many other introductory books are available for Python, such as O'Reilly's Learning Python.
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 + 24>>> 0.03 * 25.10.753>>> min(hou.frame(), 7) * 33.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.
Tip | 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. |
Tip | 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. |
Tip | You can’t use Ctrl+C to copy from the Python shell, since Ctrl+C will send a KeyboardInterrupt exception. So, to copy text from a Python shell, right-click and select Copy. |
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.Node object, or None if the path is invalid.
# Empty out the current session.>>> hou.hipFile.clear()>>> hou.node('/obj')<hou.Node at /obj>>>> # hou.node returned a hou.Node object corresponding to the /obj node>>> n = hou.node('/asdfasdf')>>> # The node path was invalid, so n will be the None object.>>> print nNone>>> g = hou.node('/obj').createNode('geo')>>> g<hou.ObjNode of type geo at /obj/geo1>>>> # g is hou.Node object corresponding to the newly created /obj/geo1 node.>>> # Note that g is actually a hou.ObjNode instance, which is a subclass of>>> # hou.Node.>>> # The parm method on hou.Node 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.Node.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)
Tip | 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. |
Tip | Use variables to store hou.Node, hou.Parm, and hou.ParmTuple objects instead of calling hou.node and hou.parm over and over again. |
Tip | Use the output from hou.Node.asCode to help learn the parts of the HOM API that create nodes and set parameters and keyframes. |
Exploring the hou Module
Python makes it easy to explore and search the HOM API. If you type hou. into Houdini’s Python shell, it will pop up a list of possible attribute completions (e.g. functions, classes, constants, etc.). Popup completion will work for any Python variable. For example, if n is a variable referring to a hou.Node object, typing n.ch will pop up a list of method completions (e.g. changeNodeType, children, childTypeCategory).
Tip | Press tab in Houdini’s Python shell to automatically complete what you're typing. Press Ctrl+tab in a Python source editor for completion. |
The Python popup help will also display the reference documentation for a function or method after you type an opening parenthesis (() after a function or method name. For example, typing hou.node will list attributes of the hou module starting with “node”, but typing hou.node( will display the help for the hou.node function.
Note | Popup help and tab completion will not work on results of functions. For example, you will get popup help for but you won’t get it for
>>> hou.node('/obj').parm( The reason is that the latter needs to evaluate hou.node('/obj') to determine what type of object it is, and that evaluation could have unintended side effects. For example, automatically evaluating file('~/.bashrc', 'w'). to list possible completions for the file object could have unintended effects. |
Depending on the method or function, Houdini’s Python shell may also list possible argument completions. For example, if you type hou.node('/obj/, you’ll get a list of the objects inside /obj.
The standard Python dir() and help() functions can also be very helpful to explore the hou module, and will work in a shell without popup completion. type dir(hou.ObjNode), for example, to list all the attributes of the hou.ObjNode class. Or, type dir(hou.node("/obj/geo1").type()) to list the attributes of the hou.NodeType class. To learn more about a particular attribute, use the help() function. For example, help(hou.ObjNode.worldTransform) will print the hou.ObjNode.worldTransform method’s help in the shell.
You can also explore the hou module’s reference documentation with the help browser.
Note | The HOM reference documentation also lists functions, methods, classes, and submodules that are not implemented in this version of Houdini but can be expected in future versions of Houdini. |
The hou module uses these naming conventions:
hou functions and class methods use lowerCamelCase.
Classes start use UpperCamelCase.
hou submodules use lowerCamelCase. There are two types of hou submodules
Submodules that contain enumeration values.
Submodules that contain a collection of related functions.
Parameter names use lower_case_with_underscores.
Methods and functions used internally by Houdini start with a single underscore.
The hou.session Module
The interactive Python shell is a great way to test and experiment with snippets of scripts. When you want to run the same sequence of statements multiple times, though, it’s tedious to enter those statements into the shell, especially if you need to iteratively modify a function definition. Instead of writing code in the interactive shell, a nicer solution is use an editor to write a Python function and call that function from the shell.
Houdini uses a special module named hou.session to store Python code that’s saved with the hip file. You can write functions in this module and access them from anywhere in Houdini.
Choose Windows > Python Source Editor to edit the contents of the hou.session module. For example, suppose you want to change all the parameter values starting with "/home/bob" to start with "$HIP". You can write some functions in the hou.session module and then modify and tweak them.
def fixFilePrefixes(node, from_prefix, to_prefix, node=None):"""For a node and all its children and grandchildren, change allnon-animated file name parameters starting with `from_prefix` so theyinstead start with `to_prefix`. If the node is not specified,the root node (/) will be used."""if node is None:node = hou.node('/')for parm in node.parms():template = parm.parmTemplate()if (template.type() == hou.parmTemplateType.String andtemplate.stringType() == hou.stringParmType.FileReference andparm.unexpandedString().startswith(from_prefix) andlen(parm.keyframes()) == 0):print "Fixing:", parm.path()parm.set(to_prefix + parm.unexpandedString()[len(from_prefix):])for child in node.children():fixFilePrefix(from_prefix, to_prefix, child)
Then, from the Python shell you would write:
>>> hou.session.fixFilePrefixes("/home/bob", "$HIP")Fixing: /obj/geo1/file1/file
Note | Don’t forget that the contents of hou.session change depending on which hip file is loaded. Don’t use hou.session for functions that need to be available for all sessions or that are for use by a digital asset. See below for other ways to create functions and classes. |
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 fixFilePrefixNameError: 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. |
Shelf Scripts
Each shelf tool has a script associated with it that’s run when you click on the tool. These scripts can be in hscript or Python, and the scripts that ship with Houdini are written in Python.
To see the script for a tool, right-click on the tool’s button and pick Edit Tool…_, then click on the Script tab. To create a new shelf too, right-click on an empty area of the shelf and select New Tool.
Before the script script runs, Houdini sets a variable named kwargs that the script can access. kwargs is a dict of strings with the following contents:
Key name | Value |
|---|---|
| Whether or not Shift was pressed when the tool was invoked |
| Whether or not Alt was pressed when the tool was invoked |
| Whether or not Ctrl was pressed when the tool was invoked |
| If invoked by right-clicking on a node’s input connector in the network editor, this is the index of that node’s input. Otherwise, it is -1. |
| If invoked by right-clicking on a node’s input connector in the network editor, this is the name of that node. (It will presumably be the output of a newly created node.) Otherwise, it is ''. |
| If invoked by right-clicking on a node’s output connector in the network editor, this is the index of that node’s output. Otherwise, it is -1. |
| If invoked by right-clicking on a node’s output connector in the network editor, this is the name of that node. (It will presumably be the input of a newly created node.) Otherwise, it is ''. |
| If invoked by middle-clicking on a node’s output connector in the network editor, this is True. Otherwise, it is False. |
| The name of the tool that was invoked. |
| If the tool was invoked from the shelf, it is None. If it was invoked from a viewer, it will be a hou.SceneViewer or other viewer pane tab instance. If it was invoked from the network editor, it will be a hou.NetworkEditor. |
| If the tool was invoked from the viewer, this is set to the hou.GeometryViewport where the tool was chosen. |
Tip | To display a message in a popup window from a shelf tool, use hou.ui.displayMessage. |
Tip | Houdini’s shelf scripts call functions in modules in $HFS/houdini/scripts/python. You can use these modules in your own shelf scripts, too. |
Note | When Houdini’s Python shell is open, print statements (or any output to sys.stdout or sys.stderr) will go to it. Otherwise, it will go to the console. |
Note | While running shelf scripts Houdini is still “Live”. This is most noticeable if you acquire DOP Objects and then change the DOP Network. After changing the DOP Network, the current frame will recook and invalidate your DOP Objects. To avoid recooking the current frame, one can call hou.setSimulationEnabled() to disable the simulation for the duration of your operation. Make sure you record the initial simulation state (hou.simulationEnabled()) and restore it, however. |
Session Independent Scripts
You can create a Python file that is always invoked whenever Houdini starts up. You might use this file to define helpful Python functions; create aliases for hou module classes, functions, and methods; or otherwise customize Houdini. Houdini will search in $HOUDINI_SCRIPT_PATH for files named python/pythonrc.py or python/.pythonrc.py. For example, if you want to create such a file that is customed for you, you would put it in $HOME/houdini/scripts/python/pythonrc.py. Similarly, you could put the file in $HSITE to have a site or project-wide configuration script.
Note that you can use pythonrc.py to add code to the hou.session module with hou.appendSessionModuleSource. Do not assign directly to hou.session (for example, don’t write hou.session.x = …), because attributes you assign directly to hou.session will not be saved with the hip file.
Houdini also supports Python versions of 123.cmd and 456.cmd, named 123.py and 456.py, respectively. Like 123.cmd, 123.py is invoked only when Houdini is started without a hip file. 456.py is invoked using the same rules as 456.cmd: Houdini runs it each time a file is loaded or the session is cleared.
Note that Houdini will run all the pythonrc.py files it finds in the path, but it will only run the first 123.py file in the path. Also, if 123.cmd and 123.py both exist, Houdini will only run 123.py.
Tip | You can run Python files in |
Tip | A good way of running your Python code is to import it as a module. Houdini will automatically append all the directories in |
Storing Python Scripts with Digital Assets
Create a PythonModule section section. Access it from the hou.Node or the hou.NodeType instance: hou.pwd().hdaModule() or hou.nodeType(hou.objNodeTypeCategory(), “hdaname”).hdaModule()
The return value of hdaModule() behaves like a module. So, if the PythonModule section defines a function named foo, you can call it from a callback button with hou.pwd().hdaModule().foo().
Until proper Python event handlers are implemented, you can invoke them from hscript with:
python -c "hou.node('$arg1').hdaModule().onCreated(hou.node('$arg1'))"and addingdef onCreated(node):to the PythonModule section.kwargs is automatically set in the PythonModule section – kwargs'type' contains the hou.NodeType for the HDA
To store multiple modules in an HDA
import toolutils bar = toolutils.createModuleFromSection(
'bar', kwargs'type', 'bar_PythonModule')
(where 'bar_PythonModule' is the section name)
bar.foo() from inside PythonModule section, hou.pwd().hdaModule().bar.foo() from outside
Command-Line Scripting
easily integrate Houdini into your existing Python-based scripts
importing the hou module into a regular Python shell will initialize the Houdini session
need to add $HFS/houdini/scripts/python to sys.path (can append to $PYTHONPATH to do that)
#!/usr/bin/python2.5 import sys, os sys.path.append(os.environ'HFS' + “/houdini/scripts/python”) import hou
hython
a Python interpreter with a few extra additions
automatically imports the hou module and sets up sys.path.
Can give .hip files on the command line and it will load them.
supports tab completion (double-tab to list possible completions).
can receive and handle openport commands while waiting for console input
controlling which license type is used from a regular Python shell (set HOUDINI_SCRIPT_LICENSE in os.environ before importing the hou module)
hou.releaseLicense()
Loading Hip Files
hou.hipFile
exceptions are raised from warnings, so catch them
Migrating from Hscript
replaces sections of HOM documentation
“replaced by” sections of command and expression help
use variables and write functions
hou.hscript(), hou.hscriptExpression(), hou.hscriptExpandString()
creating short aliases
Python Expressions in Parameters
The node’s default expression language
The preference for the default node expression language
mixing and matching expression languages in a node
single line for expressions, multiple lines for function bodies (so must return values)
“from hou import “ and ”from hou.session import ” are run automatically for expressions.
also import some functions from the math module for expressions (acos, asin, atan, atan2, ceil, cos, cosh, degrees, exp, fabs, floor, log, log10, pow, radians, sin, sinh, sqrt, tan, tanh).
curPoint() and curPrim() in sops
can interrupt expression evaluation by pressing escape
Working with Objects and Transformations
worldTransform(), setWorldTransform()
matrices, exploding
column vectors for transforms (p T1 T2), not (T2 T1 p)
Scripting with Python from the Browser
Review of RunHCommand()
RunPythonExpression(), RunPythonStatements()
can pass in a callback
callback is necessary when running statements that will control the browser (e.g. to open a new browser window)
Sending XML to an open port
JavaScript access to HOM via XMLRPC is in the works
Writing Python-Based SOPs
File → New Operator Type, edit Code section
introduction to geometry classes
how it works: hou.pwd() is set to node that cooks; grab geometry and modify
how to access parameter values in HDA: hou.ch
how to generate errors: raise an exception (e.g. hou.OperationFailed)
geo = hou.pwd().geometry()bbox = geo.boundingBox()cd_attrib = geo.addAttrib(hou.attribType.Point, "Cd", (1.0, 1.0, 1.0))for point in geo.points():dist_vec = hou.Vector3(point.position()) - bbox.center()color = [0.5 + dist_vec[i] / bbox.sizevec()[i] for i in range(3)]point.setAttribValue(cd_attrib, color)
Writing Python-Based Objects
how it works: hou.pwd() is set to node that cooks; setWorldTransform()
def centroid(prim):result = hou.Vector4()for v in prim.vertices():result += v.point().position()return hou.Vector3(result) * (1.0 / len(prim.vertices()))follow = hou.node(ch('follow'))geo = follow.displayNode().geometry()prim_num = hou.ch('prim_num') % len(geo.prims())prim = geo.prims()[prim_num]xform = hou.hmath.buildTranslate(centroid(prim)) * follow.worldTransform()hou.pwd().setWorldTransform(xform)
Controlling the Houdini Interface
hou.ui (a submodule)
displaying messages, prompting for input, file, node selection, list selection
advanced shelf tool examples
overview of toolutils
prompting for selections
Finding Python Errors
where to find error outputs
in the shelf/tab menu: in the details section of the popup window
in HDA callbacks: in the console
in 123.py/456.py: in the console
in parameters: MMB on node
in Python-based nodes: MMB on node
Places to Save Python Code
in a parameter: Python expressions
hip file: the hou.session module
across hip files
a module on disk (add to sys.path or set $PYTHONPATH and import the module)
a script (load with execfile, can use hou.findFile to read it)
the shelf
in a digital asset: the PythonModule section
in a Python-based node type
Interaction with System Installation of Python
Houdini will install a minimal copy of Python with it
Houdini will use the system’s version of Python if it’s installed
need Python 2.5
Highly recommended that you install a system version of Python – gives you full access to Python’s standard library of modules, and you can use the standard way of installing new modules and Houdini will have access to them
An Alternate Way to Navigate Nodes
nodewrap.py:
class NodeWrapper:def __init__(self, node):self.node = nodedef __getattr__(self, name):# Check for child nodes, parms, and attributes.child_node = self.node.node(name)if child_node != None:child_node = NodeWrapper(child_node)parm = self.node.parm(name)try:attribute = getattr(self.node, name)except AttributeError:attribute = Noneattribs = [x for x in (child_node, parm, attribute) if x is not None]# Make sure there is exactly one match.if len(attribs) == 0:raise AttributeError("'Node' object has no node, parm, or attribute " + name)if len(attribs) > 1:raise AttributeError("'%s' is an ambiguous name: it could be one of %s" %(name, attribs))return attribs[0]obj = NodeWrapper(hou.node("/obj"))root = NodeWrapper(hou.node("/"))
>>> from nodewrap import obj>>> obj.createNode("geo")<hou.ObjNode of type geo at /obj/geo1>>>> obj.geo1<hou.ObjNode of type geo at /obj/geo1>>>> obj.geo1.path()'/obj/geo1'>>> obj.geo1.tx<hou.Parm tx in /obj/geo1>>>> obj.geo1.tx.eval()0.0>>> obj.createNode("subnet")>>> obj.subnet1.createNode("tx")>>> obj.subnet1.txFile "<stdin>", line 1, in <module>File "nodewrap.py", line 27, in __getattr__(name, attribs))AttributeError: 'tx' is an ambiguous name: it could be one of [<hou.ObjNode of type geo at /obj/subnet1/tx>, <hou.Parm tx in /obj/subnet1>]>>> obj.subnet2.createNode("name")>>> obj.subnet1.nameTraceback (most recent call last):File "<stdin>", line 1, in <module>File "nodewrap.py", line 27, in __getattr__(name, attribs))AttributeError: 'name' is an ambiguous name: it could be one of [<hou.ObjNode of type geo at /obj/subnet1/name>, <bound method ObjNode.name of <hou.ObjNode of type subnet at /obj/subnet1>>]
Running a Separate Python Thread
can run concurrently with Houdini until it accesses something in Houdini (printing to the shell counts as accessing something)
Ctrl+C will raise a KeyboardInterrupt exception (unless you customize the SIGINT handling)
won’t work in all cases, because it sends signals to other threads – time.sleep() can’t be interrupted, for example
Houdini’s Python shell is actually running in a different thread
How multiple Python shell windows work (only one sys.stdout)
an example
Using RPC
| From server | houxmlrpc.run(port=8888) |
| From client | s = houxmlrpc.ServerProxy('http://localhost:8888') hou = s.hou |
Sending Python code to a port in Houdini.
Accessing HOM from C++
Writing the C++ equivalent of a Python-based SOP
Additional Learning Resources
The HOM reference documentation lists all modules, functions, and classes available under
hou.The HOM cookbook has several “recipes” showing how to accomplish useful scripting tasks with Python.
The
$HFS/houdini/scriptsdirectory contains scripts used by Houdini itself. It may contain useful snippets you can use in your own scripts.
