Houdini - HDA

The notes below are based on info found in the Houdini docs as well as other sources.

Digital Asset

For a really nice and streamlined workflow of creating HDA you can use the SideFx Labs Digital Asset RMB menu.

Preferences

Before you create an HDA it is a good idea to configure the preferences. Below is the way I have setup my prefs, but that is depending on your workflow etc.

  • RMB click the subnet > Digital Asset > Preferences
  • Digital Asset Save Preferences
    • Name Construction
      • Author Entries
        Here I am using the reverse domain name notation, which in my case is:
        • se.petfactory
      • Athor Namespace : On
      • Branch Namespace : Off
      • Versioning : On
    • Tab Menu
      • Menu Entry : Digital Assets
      • Asset Label : Automatic
      • Display Branch in Label : Off
    • Save To
      • Library Directory : Custom Preference -> path/to/your/dir
      • Library File Name Automatic
      • Prefix Type Category : On

Create New

  • Select the node(s) you want to create a digital asset from.
  • Click the create subnet button (in the top of the node graph tool bar)
    • Or Ctrl + C
  • RMB click the subnet > Digital Asset > Create New
  • If you setup the preferences the way you like, the only thing you need to set is Type Name. (Possible the version number)

Custom HDA Path

Set custom path to HDA

Parameters

  • Add Parms to HDA
    • Alt MMB click the parm of a node (in the HDA subnet) with the Operator Type Properties window open
  • Conditional display (docs)
    • { parm_name [operator] value …} …
    • Available operators: ==, !=, <, >, >=, <=, =~ (matches pattern), !~ (doesn’t match pattern).
    • { enablefeature == 1 count > 10 }
    • Note if you want to use inside a multiparm remember to suffix the parm name with an #
      • { use_prefix# == 1}

Viewport Selection

  • Add Vieport Geometry Selection parameter
    • Go to Edit Parameter Interface
    • Add a string parameter to your node
    • Under the “Action Button” tab of the string parameter enter the following python snippet:
      import soputils 
      kwargs['geometrytype'] = (hou.geometryType.Points,) 
      kwargs['inputindex'] = 0
      kwargs['ordered'] = True
      soputils.selectGroupParm(kwargs)
      
      • Note that you can change the kwargs[‘inputindex’] to the input index of the node that you want to select geo from. You can also change geometry type.
      • Note that you can skip the “ordered” key in the kwargs dict. This will be the default behaviour (like it is in the select field of a group node). If the “ordered” key is not present, and if you select all points the select field will be set to empty (which in a group node means select all) which might not be what you want.
  • Process the selected points
    You might want to do something with the selected points, to do this:
    • In the “Action Button” tab (of the string parameter) append the following code to call a script in your hda python module.
      kwargs['node'].hdaModule().process_points(kwargs)
      
    • In your HDA Python Module add a function that will be called from the callback script.
      def process_points(kwargs):
      
          node = kwargs['node']
          parm_name = kwargs['parmtuple'].name()
          pattern = kwargs['node'].parm(parm_name).eval()
      
          # the index of the input node   
      
          input_index = kwargs['inputindex']
          input_node = node.input(input_index)
      
          # the index of the multiparm
      
          script_multiparm_index = kwargs['script_multiparm_index']
      
          try:
              pnts = input_node.geometry().globPoints(pattern)
              # do somethig with the points...
      
              print(pnts)
      
          except hou.OperationFailed as e:
              print(e)
      

  • Get data from the selected geo
    I wanted to convert the ptnum from the viewport selection and replace the ptnum with the name attr stored on the point.
    • first

  • Add KineFX point group selector
    • You can also use the snippet that the secondary motion tool uses in its action button.
      from kinefx.ui import rigtreeutils
      rigtreeutils.selectPointGroupParm(kwargs)
      
      
      

Extra Files

Lets add an extra file to an HDA.

  • RMB click the HDA > Type Properties and select the Extra Files tab.
  • Click the Filename file chooser amnd navigate to the file.
  • Click Add File and press Accept to close the type properties widget.

Now the file has been saved inside the HDA on disc. So lets load the file within the HDA.

  • On the file node (inside the HDA) click the geometry file chooser.
  • On the left hand side, in the Locations section of the file chooser widget select opdef:/
  • Navigate to the HDA that we just saved our file to.
    • Note we will find the file in a directory of the HDA type. If we created a sop HDA it will be inside the sop dir. If we created the HDA with a namespace the directory of the HDA will be prefixed with this namespace as well. If we have different versions, the versions will be stored in different dirs.

Icon

It can be fun to add your own icon to the HDA. It is a good idea to use a .svg. To add an icon follow the steps outlined above (extra files). Note if you add an icon to the asset, you can also use it in the help card.

HDA Python Scripts

PythonModule

HDAModule User-defined Python module containing functions, classes, and constants that are stored with and accessed from a digital asset.

  • RMB click the HDA Allow Editing of Contents
  • RMB click the HDA Type Properites
  • On the scripts tab select Python Module from the Event Handler combobox. We now get a python module “automatically” added to our HDA.

Lets write som test code and execute it from a button on the HDA.

  • On the scripts tab, with the PythonModule selected enter the some code to be called
def speak(kwargs):
	node = kwargs['node']
    print('speak was called from PythonModule', node)
  • Add a button to the HDA and as a python callback script we add the following snippet:
hou.phm().speak(kwargs)
  • Note
    • phm() → hou.HDAModule
      • This shortcut lets you write hou.phm() instead of hou.pwd().hdaModule(). You are most likely to use this shortcut from event handlers, button callbacks, and menu generation scripts in digital assets.
    • To make this work the python module must be named PythonModule (which it is if we add it this way)

Additional Modules

If you find that a digital asset has too much Python code to store in one module, it’s possible to create submodules. Lets create a separate python module and call it from our “main” python module.

  • RMB click the HDA > Type Properties and select the Scripts tab
  • In the Event Handler combobox select Custom Script
  • Select the python script to include (with the filebrowser labeled “filename”)
  • This will autopopulate the section name with the file name (which is fine i guess, you can remove the .py extension if you want)
  • Click Add File to add the custom script to the HDA.
  • Now we need to import the custom module in our main python module (we also have a speak method in out custom module, which we also call)
  • NOTE
    • New to Houdini 18.0, the createModuleFromSection function expects the code in the HDA section to have Python 3 style print statements.
# toolutils.createModuleFromSection(module_name, node_type, section_name)

import toolutils
custom_module = toolutils.createModuleFromSection("custom_module", kwargs["type"], "custom_module.py")

def speak():
    print('called "speak" from PythonModule')
    custom_module.speak()

Help

It is a really good idea to add some help notes. Both for yourself down the line and if you share the HDA. There is a help tab on the HDA where you can add some notes. Read the docs here

Expressions

String parm python expression

In an HDA I was building I wanted a way to construct the filepath of a rop file output node inside a cop network. To get an idea of how I could approach this I had a look inside the labs map baker.

  • My HDA had a “Output Directory” chooser on the top level of the HDA.
  • If we step inside the HDA we are inside a geomery context, in this context I had two detail attributes (which in this case were generated by a python node, but the attrs could of course have been generated by some other node). I wanted theese detail attrs to be part of the file path
    • udim_s
    • matpath_s
  • Inside the geometry context there was a cop network and inside this I had the rop file output which output picture parm I wanted to control.

To setup the expression

  • Put the cursor in the string field you want to add the expression to
  • Press Alt E to open up a script editor
  • Write your expression, or paste one you written elsewhere (I found this to be a bit finicky, where it would error out if I got some syntax wrong and after that would not behave… took some trial and errors to get it right)
  • If you want it to run as Python, in the string field RMB click > Expression > Change Language to Python. (You do not need to change it at the top menu bar of the node)

Here is an example of the script I ended up using.

import hou

out_dir = hou.pwd().parent().parent().parm("output_directory").evalAsString()

udim = hou.pwd().parent().parent().node('python').geometry().attribValue('udim_s')
matpath = hou.pwd().parent().parent().node('python').geometry().attribValue('matpath_s')

file_path = '{}/{}.{}.jpg'.format(out_dir, matpath, udim)

return file_path

Extra

Asset name

The general form of an asset’s internal name is

[namespace::]node_name[::version]

The namespace and version are both optional. You can have a name with both a namespace and a version, or just a namespace, or just a version, or neither.

Namespaces

The namespace identifier lets you name your assets without worrying about using the same name as a built-in Houdini node or as a third-party asset you might use someday. (Note that this only applies to the internal name of the node… you can always use any string you want for the human readable label that appears in the user interface.) A useful convention to ensure you use a unique namespace name is to reverse the DNS address of your website. For example, if the creator´s website is at houdini.bacon.org, they would use org.bacon.houdini as the namespace for their assets.

Versions

The version string allows you to create multiple independent versions of an asset without having to change the “main name”. Instances of the old version will still work and use the old implementation, while users placing a new node will get the latest version. The version can only contain numbers and periods (.). For example, myasset::2, myasset::2.1, myasset::19.1.3, but not myasset::2a or myasset::alpha.

Asset name & scripting

Some scripting commands require the node category and node name together (for example Object/geo, Sop/copy, Dop/popsolver). To use namespaced names with these commands, use the form [namespace::]node_category/node_name[::version] For example, com.sundae::Sop/copy::2.0