Houdini - Python

Here I will gather information that I find useful.

Environment variables

Houdini uses environment variables for various purposes, such as defining paths (sets of directories in which Houdini looks for certain types of files), commonly used directories (such as $HFS, the install location of Houdini) and for obscure or highly technical settings.

See the list of environment variables in Houdini.

Inspect variables

To print the value of an environment variable you can use the Houdini Python Shell:

hou.getenv('HOUDINI_OTLSCAN_PATH')

You can also use the Textport:

echo $HOUDINI_OTLSCAN_PATH

Environment config

Traditionally, the way to set the values of environment variables in Houdini was to edit the houdini.env file. The houdini.env file are found in the $HOUDINI_USER_PREF_DIR (see notes below). The problem is that not only the user wants to set environment variables, but also studios, plug-in authors, and so on. Multiple parties would end up rewriting or appending to the houdini.env file, sometimes causing errors or redundancy.

The new solution for this kind of set-up is packages. Packages are .json files in HOUDINI_PATH/packages. Each package file contains a specification for how to modify the environment (including the Houdini Path and other variables).

Houdini Packages

Docs

  • Package files must be valid JSON and must have the .json extension.
  • Houdini will scan a number directories on startup looking for packages (see docs for which)
    • $HOUDINI_USER_PREF_DIR/packages

The $HOUDINI_USER_PREF_DIR is located here

  • Mac
    • ~/Library/Preferences/houdini/X.X/
  • Windows
    • %HOME%/houdiniX.X/

Lets add a package to set a custom directory for our HDA. We will use the houdini user prefs dir for this.

  1. Create a directory called packages in the $HOUDINI_USER_PREF_DIR.
  2. Add a file (the name does not matter) but make sure the extension is .json
  3. Add the snippet below to that file and save.
  4. Launch Houdini, to verify you can inspect the variable using the methods listed under Inspect variables.

Example:

{
    "env": [
        {
            "HOUDINI_OTLSCAN_PATH": "$HOME/path/to/HDA"
        }
    ]
}

Python Path

We can also use the same package file to add a custom python path

{
    "env": [
        {
            "HOUDINI_OTLSCAN_PATH": "$HOME/path/to/HDA"
        },
        {
        	"PYTHONPATH": "$HOME/path/to/scripts"
        }
    ]
}

Parms

Here is a quick one liner to print all the parms of the selected node aswell as node specifed with a path.

print('\n'.join(p.name() for p in hou.selectedNodes()[0].parms()))

node = hou.node('/obj/copytopoints')
print('\n'.join(p.name() for p in node.parms()))

Parm template

A ParmTemplates describes a parameter and the type of data it holds, but it does not store the current value of a parameter. only hou.Parm objects inside hou.ParmTuple objects actually store parameter values. You can think of a hou.ParmTuple as an instance of a ParmTemplate.

For example, the “t” parm tuple on geometry objects can be described by a hou.FloatParmTemplate – it has a label of “Transform”, a data type of hou.parmData.Float, 3 components, a naming scheme of hou.parmNamingScheme.XYZW, etc.

node.parm('ty').parmTemplate()
# <hou.FloatParmTemplate name='t' label='Translate' length=3 ...

node.parm('ty').parmTemplate().type()
# parmTemplateType.Float

Multiparm

Here is an example of how to create a multiparm on a attrib wrangle node. The parm will be added to the root of the node (this is also where parms are added when the “auto add spare parms” is clicked)

parent_node = hou.node('/obj').createNode('geo')
wrangle_node = parent_node.createNode('attribwrangle')
wrangle_node.parm('class').set(0)

parm_template_group = wrangle_node.parmTemplateGroup()

multi_folder_parm_template = hou.FolderParmTemplate("positions", "Positions", folder_type=hou.folderType.MultiparmBlock, default_value=0, ends_tab_group=False)
float_parm_template = hou.FloatParmTemplate("pos_#", "Pos", 3, default_value=([0, 0, 0]), min=0, max=1)
multi_folder_parm_template.addParmTemplate(float_parm_template)
parm_template_group.append(multi_folder_parm_template)

snippet ='''
int num = chi('positions');
int i, pnt;
vector p;

for(i=0; i<num; i++){
    p = chv(sprintf('pos_%i', i+1));
    pnt = addpoint(0, p);
}
'''
wrangle_node.parm('snippet').set(snippet)
wrangle_node.setParmTemplateGroup(parm_template_group)

Parm tuple

The ParmTuple class behaves like a Python sequence, so you can index into it using square brackets, iterate over it, call len on it, etc. The elements inside the parameter tuple are hou.Parm objects.

A parameter tuple’s name may only contain letters, numbers, and underscores. For example, objects contain a parameter tuple named “t” that contains three float parameters. The names of the parameters inside the tuple are determined from the parameter tuple’s name and its naming scheme. For example, the “t” parameter uses the XYZW naming scheme, so the three parameters inside it are named “tx”, “ty”, and “tz”. Note that if the parameter tuple only contains one parameter, the tuple and the parameter inside it may have the same name

In addition to a name, a parameter tuple also has a label that is displayed to the user in the parameter dialog. For example, the “t” parameter’s label is “Translate”. The label may contain spaces and punctuation characters.

node.parmTuple('t')
# <hou.ParmTuple t in /obj/geo1>

node.parmTuple('t').name()
# 't'

node.parmTuple('t').description()
# 'Translate'

node.parmTuple('t').parmTemplate()
# <hou.FloatParmTemplate name='t' label='Translate' length=3 ...

node.parmTuple('t').eval()
# (0.0, 0.0, 0.0)

So the “t” parm tuple holds a reference to the x, y & z float parms.

node.parmTuple('t')[0]
# hou.Parm tx in /obj/geo1>

node.parmTuple('t')[0] == node.parm('tx')
# True

To find the parm template used for a specific parm we get it with the parmtemplate method:

node.parm('tx').parmTemplate()
# <hou.FloatParmTemplate name='t' label='Translate' length=3 ...

Parms in Folder

If you want to get all the parms in a specific folder you can use the parmsInFolder function.

Lets say that you want all the parms in the PostFX folder (that are inside the Redshift Camera folder of the camera node) note that we need to specify the entire “hierarchy” to the destination folder.

src_node.parmsInFolder(("Redshift Camera", "PostFX"))

Copy to points

Attributes from target

New to copy to points node in Houdini 18 is that we need to add attribs from target via a multiparm attribute.

# create a copytopoints node

geo = hou.node('/obj').createNode('geo')
copy = geo.createNode('copytopoints')

# we want to add 1 attributes from target parm

copy.parm('targetattribs').set(1)

# lets set the attr

c.parm('applyattribs1').set('my_attr')

Misc

Undo

Returns a context manager, within which all changes to Houdini are recorded as a single action on the undo stack. For example:

with hou.undos.group("Move all nodes to the left"):
    for n in hou.node("/obj").children():
        n.move(hou.Vector2(-1, 0))

asCode()

When you are scripting it is sometimes useful to get the python code that produces a specific node. The asCode methdod prints the Python code necessary to recreate a node. The snippet belows copies the result to the clipboard.

from PySide2 import QtWidgets

sel_list = hou.selectedNodes()
if len(sel_list)<1:
    print('Nothing is selected')
else:
    clipboard = QtWidgets.QApplication.clipboard()
    clipboard.setText(sel_list[0].asCode())