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
- 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.
- Create a directory called packages in the $HOUDINI_USER_PREF_DIR.
- Add a file (the name does not matter) but make sure the extension is .json
- Add the snippet below to that file and save.
- 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.
- hou.clearAllSelected()
-
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)]
- setCurrent(on, clear_all_selected=False)
Class
for node in hou.selectedNodes():
print(node.type().name() == 'geo')
print(type(node))
print(isinstance(node, hou.ObjNode))
Type
All the node types in Houdini are organized into categories, and a node type is uniquely identified by its category and node type name. For example, objects, SOPs, POPs, etc. are node type categories. You can also access a NodeType object from a category with hou.NodeTypeCategory.nodeTypes. Similarly, you can call hou.nodeType() with the category and node type name.
- nameComponents() → tuple of str
- Returns a tuple of node type name components that constitute the full node type name. The components in the tuple appear in the following order:
- Network scope network type
- Namespace node type namespace
- Name node type core name
- Version
- Returns a tuple of node type name components that constitute the full node type name. The components in the tuple appear in the following order:
node = hou.node('/obj/geo1/copytopoints1')
print(node.type())
# <hou.SopNodeType for Sop copytopoints::2.0>
print(node.type().nameComponents())
# ('', '', 'copytopoints', '2.0')
Flag
- Set Flag
- Bypass
node.setGenericFlag(hou.nodeFlag.Bypass, True)
- Comment
node.setComment('Hello World') node.setGenericFlag(hou.nodeFlag.DisplayComment, True)
- Bypass
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
Icons
hou.qt.Icon An icon generated from a Houdini icon name.
- init(icon_name, width=None, height=None)
- Create and return a new icon for the specified Houdini icon name.
- You can discover Houdini icons using the Houdini file chooser dialog and diving into the “hicon://” path. The name of the icon file without the .svg extension is the icon’s name. For example, the icon name for “hicon://SVGIcons.index/BUTTONS_help.svg” is “BUTTONS_help”.
- Example
node = hou.node('/obj/geo1/merge1') icon = hou.qt.Icon(node.type().icon()) icon = hou.qt.Icon(node.type().icon(), width=64, height=64)
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
- If you want it accessible from here also:
- Enable the SOP checkbox (the context we want to create the tool in)
- If you want it accessible from here also:
- TAB Submenu Path
- The name of the tab sub menu
- Type “My Tools”
- Hotkeys
- We might want ti assign it to a hot key
- On the right hand side of the Network Pane click the edit an add a hot key
- Network Pane tab
- 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()
- Add the following script
- To Do
- Look into how to create the nodes, and lay the down on the node graph when the LMB is clicked.
- 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
Camera Viewport
If you want to add comments to the camera viewport you can do this by the steps below. Thank to TyHoudinifx for the tip.
- Viewport Comments
- Add Viewport Comment parameter
- On the camera click the Edit Parameter Interface
- 1 - regular String parm
- You can use a regular string parameter as long as you give it the name vcomment
- 2 - from Render Properties
- But you can also add it from the render properties tab that has the name already setup
- Swicth to the Render Properies tab
- Type “comment” in the filter and hit enter
- Drag the Viewport Display and drop it onb the Existing Parameters
- Edit parm properties
- Select the added parm in the Existing Parameters treeview
- On the Parameter tab enable the Multiline String
- Switch to the Channels tab
- On the first row you will click on the third icon and set it from Literal to Python
- Click accept on the edit parameter interface
- Set Comments with Python
- With the camera selected, navigate to the parm we just added
- Click once on the Viewport Comment parameter. The background color will turn to a lighter grey. This means we can start writing Python
- Click in the line edit and press enter one time. For some reason the script fials if code is enetred on the first line.
- So on line 2 write return ‘Hello World!’
- Display Custom node Info
- If we want to display custom info from a node in the network we could add a Operator Path parameter to the OpenGL View section.
- Open the cameras Edit Parameter Interface and drag in a Operator Path parameter. Give it a good name and label.
- Then we can acces the data found on the node we specify with the operator path parm.
- Python Example
- Here is an example script that will output some data to the viewport
- Note that the example below expects a detail dict attribute called “metadata” to be present on the node (that we specified with the operator path)
import hou cam_node = hou.pwd() s = f'hip : {hou.expandString("$HIPFILE")}\n' s += f'cam : {cam_node.name()}\n' # if the parm or node is not specified try: node_path = cam_node.parm('node').evalAsString() node = hou.node(node_path) geo = node.geometry() # if the dict attrib does not exist try: meta_dict = geo.dictAttribValue('metadata') for key, value in meta_dict.items(): s += f'{key} : {value}\n' except: pass except: pass return s
- Add Viewport Comment parameter
Snippets
Lops
Create a Karma Material Builder
import voptoolutils
dst_node = hou.node('/obj/geo1/lopnet1/matnet1')
subnet_node = None
name = 'karmamaterial'
mask = voptoolutils.KARMAMTLX_TAB_MASK
folder_label= 'Karma Material Builder'
render_context = "kma"
mat_builder = voptoolutils._setupMtlXBuilderSubnet(
subnet_node=subnet_node,
destination_node=dst_node,
name=name,
mask=mask,
folder_label=folder_label,
render_context=render_context)
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)