Skip to content
The Technical ArtistArt. Code. Pipelines. Games.
Home/Houdini/Digital Assets (HDAs)
houdini - working
hdas
how tas ship tools

Houdini Digital Assets

Workingtutorial~8 min read

An HDA packages a node network into a reusable tool with its own interface. This page follows the practical steps: create the asset, expose the controls artists need, edit it safely, then version and share it with the team.

On this page What An HDA Actually Is Create Your First HDA The Parameter Interface Editing An HDA Safely Versioning Using & Sharing Design Guidelines

What An HDA Actually Is

Two ideas are useful before you start. First: an HDA turns a node network into a node type - after you create one, "rock scatter" sits in the TAB menu next to Scatter and PolyExtrude, indistinguishable from a built-in. Second, and more important: the definition (the network inside, stored in a .hda file) is separate from the instances (every copy placed in every scene). Instances don't contain the network; they reference the definition. Fix a bug in the definition once, and every instance in every scene on every artist's machine picks it up on load.

That shared definition is useful, but changes can affect existing scenes - it's why HDAs are how studios ship Houdini tools, and why the Editing Section below exists.

HDA workflow: build a SOP network, collapse to a subnet, create a digital asset, promote parameters and share the .hda file.
fig 21 - the hda lifecycle

Create Your First HDA

The worked example for this page: a rock scatterer - geometry in, rocks scattered on it, artist controls for density, scale, and seed. Any network works; this one exercises everything.

  1. Build a network that someone else can inspect. Inside a Geometry object: your input geometry flows through scatter -> attribrandomize (scale/orient) -> copytopoints (with a rock in the second input). End with a Null named OUT - the see-through habit from the workflows page matters double inside an HDA.
  2. Collapse it into a subnet. Select the nodes -> Shift+C (or right-click -> Collapse Selected Into Subnet). Check the subnet's inputs: input 1 should be the surface, input 2 the rock geometry. Wire order here becomes the public input order of your tool - get it right now, changing it after shipping breaks scenes.
  3. Create the asset. Right-click the subnet -> Create Digital Asset... Three fields matter:
    • Operator Name - use the namespaced form from day one: studio::rock_scatter::1.0 (author/studio :: tool :: version). Renaming an operator later is a scene-breaking event; namespacing now is free.
    • Operator Label - what artists see in the TAB menu: "Rock Scatter".
    • Save to Library - the .hda file path. For real tools, a version-controlled shared location (see Sharing); for experiments, $HOME/houdini/otls is fine.
  4. Accept, and the Type Properties window opens. That's the parameter interface editor - the next section. You can close it and come back anytime (right-click the node -> Type Properties).

Hit TAB in a fresh geometry network and type "rock" - there's your tool, in the menu, like it always belonged there.

Placeholder with instructions to screenshot the TAB menu showing your custom HDA entry
Screenshot placeholder: the HDA in the TAB menu

The Parameter Interface - Exposing What Artists Touch

A fresh HDA has no parameters: the network inside works, but every control is buried where artists shouldn't dig. Promotion lifts chosen parameters from internal nodes up to the HDA's own interface - the tool's public API. Open Type Properties -> Parameters tab. Three ways to promote, in order of everyday usefulness:

  1. Drag and drop. With Type Properties open, drag any parameter from any node inside the HDA straight into the parameter list. Houdini creates the interface parameter and links the internal one to it (the internal parm turns into a channel reference - you'll see it go green).
  2. Right-click a parameter -> Promote Parameter. Same result, no window juggling - fastest when you're inside the network and spot something artists will need.
  3. Build manually. In the Parameters tab, use the "Create Parameters" palette (float, int, ramp, menu, button...) to add an interface parm, then reference it from inside with ch("../paramname"). This is how you expose one slider that drives several internal values - often the mark of a well-designed tool.
Placeholder with instructions to screenshot the Type Properties parameters tab during a parameter promotion
Screenshot placeholder: promote parameters in Type Properties

For the rock scatter, promote: Scatter's Force Total Count (relabel it "Rock Count"), the randomise ranges as a "Scale Min/Max" pair, and the Global Seed. Then organise the interface around how the artist will use it:

  • Folders group, labels translate. Create folder parms (Placement / Variation / Advanced) and drag parameters into them. Rename every label into artist language - the internal name stays stable for scripting; the label is UX.
  • Ranges and defaults are design decisions. Set slider ranges to values that can't look broken, and defaults that demo well on first drop - the same rule as Geometry Nodes groups and master materials, because it's the same discipline.
  • Tooltips on everything promoted. The Help field in each parameter's settings. Future-you is the main beneficiary.
  • Conditional UI: in a parameter's settings, Disable When { use_custom_rocks == 0 } greys it out; Hide When removes it. Show the controls that apply to the current settings so the artist can see what needs attention.
  • Use ramps and menus where they fit the task. A ramp parameter (referenced with chramp() in a wrangle - see the VEX sheet) gives artists a whole art-directable curve; an Ordered Menu beats a mystery integer every time.
  • Buttons run callbacks. A button parm with a Python callback is how "Bake to Disk" and "Reset Seed" happen:
Python - button callback (in the parameter's Callback Script field)
# kwargs["node"] is the HDA instance whose button was pressed
node = kwargs["node"]
node.parm("seed").set(__import__("random").randint(0, 99999))
print(f"{node.name()}: rerolled seed")
Placeholder with instructions to screenshot the finished HDA parameter interface next to the viewport result
Screenshot placeholder: the finished HDA controls and viewport result

Editing An HDA Safely

Place an instance and try to dive inside - it's locked. That's the definition/instance separation protecting you: instances are read-only views of the definition. The edit loop:

  1. Unlock: right-click the instance -> Allow Editing of Contents. The node badge changes; this instance is now editable and diverged from the definition.
  2. Make your changes - fix the bug, add the node, adjust the internal wiring.
  3. Commit: right-click -> Save Node Type. Your edits become the new definition; the instance re-locks; everyone downstream gets the fix. Change of heart instead? Match Current Definition throws your edits away and re-syncs to the saved version - the closest thing node-land has to git checkout --.
Check For Unsaved Definition Changes

Editing an unlocked instance for a week and never saving the node type. Your scene works; every other scene still runs the old definition; and your changes live in exactly one file, unversioned. If a node shows the "unlocked" badge at the end of the day, either Save Node Type or Match Current Definition - never leave it drifting. (And the .hda file itself belongs in version control, where a bad definition save is a revert instead of requiring the tool to be rebuilt.)

Versioning - The Namespace Payoff

Remember studio::rock_scatter::1.0? Here's the payoff. When a change would break existing scenes - removed parameters, different input meanings, changed defaults that alter output - don't overwrite: Type Properties -> Basic tab -> Save As, bump to ::2.0. Both versions now coexist: old scenes keep cooking with 1.0 exactly as they were lit and approved; new work gets 2.0 from the TAB menu (Houdini offers the newest by default). Safe changes - bug fixes, added parameters with harmless defaults, speedups - just save into the current version.

The judgement call in one line: "would a scene built last month look different after this change?" Yes -> new version. No -> same version. When in doubt, bump; disk space is cheaper than re-approving forty shots.

Using & Sharing HDAs

An .hda file becomes available three ways, depending on how you need to distribute it:

  • Install from file: File -> Import -> Houdini Digital Asset, or drop the file into $HOME/houdiniXX.X/otls. Fine for personal tools and trying things.
  • Shared path: put your studio's otls/ folder on $HOUDINI_OTLSCAN_PATH (usually via a packages JSON). Every artist who launches Houdini gets the whole toolbox automatically - this plus version control is the standard studio setup.
  • Scripted install - for pipeline launchers and render farms:
Python - install & inspect from code
import hou

hou.hda.installFile(r"P:/studio/otls/studio.rock_scatter.1.0.hda")

# what's installed, and which definitions live in a file?
for defn in hou.hda.definitionsInFile(r"P:/studio/otls/studio.rock_scatter.1.0.hda"):
 print(defn.nodeTypeName(), "-", defn.description())

Two distribution details to check: embedded HDAs (saved inside the .hip, "Embedded" in the library dropdown) are for one-off experiments only - they silently fork from the library version and produce the "works in my scene" genre of bug. And if your tool targets engines, the same .hda loads in Unreal/Unity via Houdini Engine - your promoted parameters become editor UI there too, which quietly doubles the audience of every interface decision you made above.

Design Guidelines (The Checklist)

  • Define clear inputs and outputs - label them in Type Properties ("Base Geometry", "Guide Curves"); end the internal network at a Null named OUT.
  • Promote few, promote well - expose the controls the task needs and add more when there’s a clear reason.
  • Help tab + tooltips - fill in the Help tab in Type Properties; an HDA with real help text feels like something you can actually ship.
  • Version with namespaces - studio::tool::X.Y, bump on breaking changes, keep old versions installed.
  • Validate inputs loudly - explain which input is invalid and how the artist can fix it:
VEX - input validation wrangle (Detail mode, first node inside)
int npts = npoints(0);

if (npts == 0)
 error("No input geometry. Connect a surface to input 1.");

if (!hasattrib(0, "point", "N"))
 warning("No normals on input - rocks will orient to world up.");

// stash metadata for downstream nodes
setdetailattrib(0, "_input_ptcount", npts);
HDA Best Practice

Store team HDAs in a shared $HOUDINI_PATH library under version control. Embedded HDAs are for project one-offs only. And treat the parameter interface like API surface - because scenes, scripts, and Houdini Engine sessions all depend on those names staying put.