Ahmed Hindy

Pipeline TD / VFX Pipeline Developer

Setting Multiple Houdini Parameters with hou.Node.setParms()

A practical pattern for configuring Houdini nodes from Python dictionaries, including parameter tuples, validation, and animated parameters.

  • Houdini
  • Python
  • Pipeline
On this page

This started as a note to myself after another Houdini setup script turned into a wall of repeated parameter calls:

    
    node.parm("tx").set(1.0)
node.parm("ty").set(2.0)
node.parm("tz").set(3.0)
node.parm("scale").set(1.25)

  

For two or three values, that is perfectly fine. Once a tool configures most of a node, a dictionary passed to setParms() is much easier for me to scan and change.

    
    values = {
    "tx": 1.0,
    "ty": 2.0,
    "tz": 3.0,
    "scale": 1.25,
}

node.setParms(values)

  

The dictionary keys are Houdini parameter names, not their labels in the parameter editor.

Parameter tuples work too#

You do not have to address every component separately. Houdini also accepts the base name of a parameter tuple.

    
    node = hou.node("/obj/geo1")

if node is None:
    raise hou.NodeError("Could not find /obj/geo1")

node.setParms({
    "t": (0.0, 1.0, 0.0),
    "r": (0.0, 45.0, 0.0),
    "s": (1.25, 1.25, 1.25),
})

  

Here, t, r, and s refer to the translate, rotate, and scale tuples on the object node. The same call can mix tuple values and individual scalar parameters.

    
    node.setParms({
    "t": (0.0, 1.0, 0.0),
    "ry": 90.0,
    "scale": 1.5,
})

  

I generally pick one style per tuple. Mixing r and ry in the same dictionary makes the result depend on assignment order and is harder to review.

The production use case: building a node preset#

The pattern becomes more useful when the values come from a preset, project configuration, or UI option.

    
    import hou

geo = hou.node("/obj").createNode("geo", "pyro_preset_example")
simulation_node = geo.createNode("pyrosolver", "pyro_solver1")

simulation_settings = {
    "startframe": int(hou.playbar.frameRange()[0]),
    "substeps": 2,
    "timescale": 1.0,
}

simulation_node.setParms(simulation_settings)

  

Houdini 21 Pyro Solver with Start Frame, Time Scale, and Max Substeps visible

The Houdini 21 Pyro Solver SOP using the values from this example: Start Frame 1001, Time Scale 1, and Max Substeps 2.

Because the settings are data rather than a sequence of calls, I can merge overrides, print the final state for debugging, or save it elsewhere.

    
    base_settings = {
    "substeps": 2,
    "timescale": 1.0,
}

shot_overrides = {
    "substeps": 4,
}

final_settings = {**base_settings, **shot_overrides}
simulation_node.setParms(final_settings)

  

The override is visible instead of being hidden several lines later in the function.

Validate parameter names before setting them#

setParms() raises hou.OperationFailed when a key is not a valid parameter or parameter tuple. That catches the mistake, but in a production tool I still want the error to name the missing fields and the node that received them.

One important detail: setParms() is not atomic. If an invalid key appears after valid keys in the dictionary, Houdini can apply the earlier values before raising the exception. Validate the complete dictionary first whenever a partial update would be unsafe.

I usually validate the dictionary first:

    
    from typing import Mapping

import hou


def set_existing_parameters(node: hou.OpNode, values: Mapping[str, object]) -> None:
    """Set parameter values after checking that every name exists.

    Args:
        node: Houdini node to configure.
        values: Mapping of parameter or parameter-tuple names to values.

    Raises:
        ValueError: If one or more parameter names do not exist on the node.
    """
    missing_names = sorted(
        name
        for name in values
        if node.parm(name) is None and node.parmTuple(name) is None
    )

    if missing_names:
        missing_text = ", ".join(missing_names)
        raise ValueError(f"Missing parameters on {node.path()}: {missing_text}")

    node.setParms(dict(values))

  

Usage stays simple:

    
    values = {
    "t": (0.0, 1.0, 0.0),
    "ry": 45.0,
}

set_existing_parameters(node, values)

  

This has saved time when a node interface changed between Houdini versions, and when a callback was handed the wrong node type. For example, the settings above are real parameter names on Houdini 21's Pyro Solver SOP: Start Frame, Max Substeps, and Time Scale.

Be deliberate with animated parameters#

My original note came from a Pyro Solver setup script that did this before calling setParms():

    
    pyro_solver.parm("startframe").deleteAllKeyframes()

  

That line is easy to overlook. Setting a value is not the same as saying, "This parameter should no longer be animated." On an animated parameter, setParms() keeps the channel and adds or updates a key at the current frame.

If the tool is supposed to replace animation with a static value, remove the keyframes explicitly first.

    
    start_frame = pyro_solver.parm("startframe")

if start_frame is None:
    raise hou.NodeError(f"{pyro_solver.path()} has no startframe parameter")

start_frame.deleteAllKeyframes()
pyro_solver.setParms({"startframe": int(hou.playbar.frameRange()[0])})

  

deleteAllKeyframes() leaves the parameter at the value it evaluates to on the current frame. After that, setParms() applies a static value rather than modifying an animated channel.

For a tuple, use parmTuple() and clear the complete tuple:

    
    translate = node.parmTuple("t")

if translate is None:
    raise hou.NodeError(f"{node.path()} has no translate tuple")

translate.deleteAllKeyframes()
node.setParms({"t": (0.0, 0.0, 0.0)})

  

Do not clear animation as a generic safety step. A pipeline utility should only remove keyframes when that is part of its stated behavior.

setParms() is for values, not expressions#

For parameter expressions, use setParmExpressions() instead.

    
    node.setParmExpressions({
    "tx": 'ch("ty")',
    "sy": "sin($F)",
})

  

Keeping values and expressions in separate dictionaries makes the intent clearer.

    
    static_values = {
    "tz": 2.0,
}

expressions = {
    "tx": 'ch("ty")',
}

node.setParms(static_values)
node.setParmExpressions(expressions)

  

A small helper for tool code#

This is the version I would actually keep in a shared Houdini utility module:

    
    from typing import Mapping, Sequence

import hou


def configure_node(
    node: hou.OpNode, values: Mapping[str, object], clear_animation: Sequence[str] = ()
) -> None:
    """Apply a validated parameter preset to a Houdini node.

    Args:
        node: Houdini node to configure.
        values: Static parameter or parameter-tuple values.
        clear_animation: Names whose keyframes should be removed first.

    Raises:
        ValueError: If a requested parameter name does not exist.
    """
    requested_names = set(values) | set(clear_animation)
    missing_names = sorted(
        name
        for name in requested_names
        if node.parm(name) is None and node.parmTuple(name) is None
    )

    if missing_names:
        missing_text = ", ".join(missing_names)
        raise ValueError(f"Missing parameters on {node.path()}: {missing_text}")

    for name in clear_animation:
        parm_tuple = node.parmTuple(name)
        if parm_tuple is not None:
            parm_tuple.deleteAllKeyframes()
            continue

        parm = node.parm(name)
        if parm is not None:
            parm.deleteAllKeyframes()

    node.setParms(dict(values))

  

Example:

    
    values = {
    "t": (0.0, 1.0, 0.0),
    "ry": 45.0,
}

configure_node(node, values, clear_animation=("t", "ry"))

  

I keep this helper deliberately boring. It does not swallow Houdini exceptions, skip unknown names, or guess whether animation should be removed. The calling tool should make those decisions explicitly.

When I would not use it#

I still use individual parm().set() calls when:

  • I need different error handling for each parameter.
  • Later values depend on evaluating earlier changes.
  • I am setting ramp data or other values that need special preparation.
  • The code is only changing one or two parameters.
  • I need to preserve or deliberately manage channel references.

For ordinary node presets and setup scripts, a dictionary passed to setParms() is usually the clearest picture of the state I want the node to end up in.

WIP checklist#

  • Double check everything.
  • Add a Pyro Solver preset example with current parameter names.
  • Confirm and document the behavior when one dictionary key is invalid.
  • Add a Pyro Solver screenshot showing the UI labels and configured values.

References#

View all blog posts →