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"
        }
    ]
}

Nodes

Selection

  • Clear All

    • hou.clearAllSelected()
      • Clears the selected state for all nodes, network boxes, and other subclasses of hou.NetworkMovableItem in the Houdini session.
      • This function is equivalent to traversing the node hierarchy and calling setSelected(False) on every hou.NetworkMovableItem in the scene, but operates much much faster.
  • Selection

    • setCurrent(on, clear_all_selected=False)
      • Set or unset this node as the last selected one.
    • setSelected(on, clear_all_selected=False, show_asset_if_selected=False)
      • Select or deselect this item, optionally deselecting all other selected items in this network.
    • Select only the nodes in a list
      • [n.setCurrent(True, True) if i==0 else n.setCurrent(True, False) for i, n in enumerate(node_list)]

Class

for node in hou.selectedNodes():
        print(node.type().name() == 'geo') 
        print(type(node))
        print(isinstance(node, hou.ObjNode)) 

Flag

  • Set Flag
    • Bypass
      node.setGenericFlag(hou.nodeFlag.Bypass, True)
      
    • Comment
      node.setComment('Hello World')
      node.setGenericFlag(hou.nodeFlag.DisplayComment, True)
      

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

Add Parm Template

# string parm

string_parm = hou.StringParmTemplate('prefix', 'Prefix', 1)
wrangle_node.addSpareParmTuple(string_parm)

toggle_parm = hou.ToggleParmTemplate(name='toggle', label='Toggle', default_value=True)
wrangle_node.addSpareParmTuple(toggle_parm)

float_parm = hou.FloatParmTemplate(name='float', label='Float', num_components=1, default_value=(0,), min=-10, max=10.0)
wrangle_node.addSpareParmTuple(float_parm)

ramp_parm = hou.RampParmTemplate(name='ramp', label='Ramp', ramp_parm_type=hou.rampParmType.Float)
wrangle_node.addSpareParmTuple(ramp_parm)

# add conditional

your_parm.setConditional(hou.parmCondType.DisableWhen, "{ other_parm == 0 }")

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

UI

Custom Tool

  • Add new shelf tool
    • RMB click the shelf > New Tool
  • Options Tab
    • In the Name field type “my_tool”
    • In he Label filed type “My Tool” (This name will be used in the tab menu)
    • For a better UX
      • Add Icon, Keywords
  • Context tab
    • Network Pane tab
      • Enable the SOP checkbox (the context we want to create the tool in)
    • Viewer Pane tab
      • Enable the SOP checkbox (the context we want to create the tool in)
    • TAB Submenu Path
      • Type “My Tools”
  • Scrips tab
    • Add the following script
      import toolutils
      import hou
      
      def create_setup(parent, position):
                
          node = parent.createNode('null', 'Hello')
          node.setPosition(position)
      
      def doit():
      
          pane = kwargs.get('pane')
                
          if pane == None: # triggered form the shelf
      
              parent = toolutils.networkEditor().pwd()
      
          else:
              parent = pane.pwd()
      
          if isinstance(pane, hou.NetworkEditor):
              # only get the cursor pos if the tool was requested from the
      
              # network editor, if not the cursor pos will be quite useless
      
              position = pane.cursorPosition()
              # print(f'{pane=}, {parent=}, {position=}')
      
                    
          else:
              position = hou.Vector2(0, 0)
              # maybe use move to good location or what it is called?
      
      
          create_setup(parent, position)
      
      doit()
      
  • To Do
    • Look into the help tab
    • Look into add icon
    • Look into add keywords

hou.ui

Input Dialogs

ui.readMultiInput('mess', ('From', 'To'), initial_contents=('*_L', '*_R'))

The hou.ui module contains some nice user interface related functions. For instance:

  • Copy to clipboard
    • Instead of using Pyside to do this we can do:
hou.ui.copyTextToClipboard('Hello World!')
  • Select Node Dialog
    • To spawn a select node dialog with a node type filter and custom node filter we can do this:
def is_material_library(node):
    if node.type().name() == 'materiallibrary':
        return True
    return False

def select_lop_material_library_node():
    node = hou.ui.selectNode(node_type_filter=hou.nodeTypeFilter.Lop, custom_node_filter_callback=is_material_library)
    return node

Snippets

Selection

Select rop fbx nodes that has input geo and has prims with a path attrib with a value, with optional exclude set.

def set_current_if_has_geo(node_set):
    hou.clearAllSelected()
    for node in node_set:

        inputs = node.inputs()
        if not inputs:
            value = False
        else:
            input_geo = inputs[0].geometry()
            has_attrib_val = len(input_geo.findPrimAttrib('path').strings()) > 0
            value = True if has_attrib_val else False
       
        node.setCurrent(value, False)

exclude_set = {'rop_fbx1', 'rop_fbx2'}
parent_node = hou.node('/obj/geo1')
node_set = { node for node in parent_node.children() if node.type().name() == 'rop_fbx' and node.name() not in exclude_set }
set_current_if_has_geo(node_set)
callback += '\t\t\n'
callback += '\t\t

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

Copy to points

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


String

hou.text module

The hou.textModule contains Houdini-specific string manipulation methods.

Pattern find & replace

input_string = 'yolo_42_L'
pattern_find = '*_L'
pattern_replace = '*_R'

r = hou.text.patternRename(input_string, pattern_find, pattern_replace)
print(r)

Sanitize names

asset_name = hou.text.variableName(asset_name)

Regex

import re

REGEX_TRAILING_DIGITS = re.compile(r'^([a-zA-Z_]*?)[0-9]*$')

node = hou.pwd()
geo = node.geometry()

for prim in geo.prims():

    mat_name = prim.attribValue('shop_materialpath')
    m = REGEX_TRAILING_DIGITS.match(mat_name)
    if m:
        capt_grp = m.groups()[0]
        prim.setAttribValue('shop_materialpath', capt_grp)  

Parms

Add Script Callback

Add a button to a cop network that when it is pressed will render a child node called “rop_cop1”. It seems that the commented out line is not necessary? Seems to do the same as the 2 lines above. The lines are from when I run an asCode() on the node that had the setup I wanted.

snippet = "hou.node('.').node('rop_comp1').parm('execute').pressButton()"
parm_template_group = node.parmTemplateGroup()
parm_template = hou.ButtonParmTemplate("render", "Render")
parm_template.setScriptCallback(snippet)
parm_template.setScriptCallbackLanguage(hou.scriptLanguage.Python)
# parm_template.setTags({"script_callback": snippet, "script_callback_language": "python"})

parm_template_group.append(parm_template)
node.setParmTemplateGroup(parm_template_group)