arrow_forward_ios

HouPy Wiki

Support

When working with a custom HDA, one might find themselves wanting to add more functionality to the parameters, especially if you are handing off this asset to a team who arent interested in digging around, and just need easy access as quick as possible. This is an example of how to utilize the Scripts portion of an HDA.

In my example, I have built an empty HDA with 2 parameters, one file input and a button with an icon and no label.

We can right click on the node and click on 'Type Properties' and go the 'Scripts' tab. At the bottom left, we can click the 'Event Handler' dropdown and select 'Python Module,' this is the event handler that deals with actions on the HDA.

In the script box on the right, add a function like this: def OpenDir(kwargs):

If you've read the other pages, you will know that we can use kwargs to get all the extra information from the node.

so if we print(kwargs), we get this beautiful mess:

{'node': <hou.SopNode of type lukevan::Test_HDA::1.0 at /obj/geo1/Test_HDA>, 'parm':
 <hou.Parm openBtn in /obj/geo1/Test_HDA>, 'script_multiparm_index': '-1', 'script_v
alue0': '0', 'script_value': '0', 'parm_name': 'openBtn', 'script_multiparm_nesting'
: '0', 'script_parm': 'openBtn'}

If we fish through this, we can see a few bits of use, namely 'node'. So in our script, we can now use the node to get any parm on the node. From here, we have a basic hou.node case here, where the docs can guide us.

So now, our updated function looks like this:

def OpenDir(kwargs):
    node = kwargs['node']
    input = node.parm('file_input').eval()

Note the use of eval() here, otherise we are just getting a reference to the parm. We specifically want to get the value of it, that is why we use eval().

Our initial goal was to open the path in our native explorer, so let's add that last line into our function.

def OpenDir(kwargs):
    node = kwargs['node']
    input = node.parm('file_input').eval()
    hou.ui.showInFileBrowser(input)

The last step is to add a callback function to our buttom, to run the script when we press it.

We use the function hou.phm() which is a shorthand for hou.pwd().hdaModule(). So with that, we can set the callback function to:

hou.phm().OpenDir(kwargs)

So now our button will open the location selected in the file input parameter.

Contributed by:
Picture of the author

How to create multiparm blocks, set and access them through the Python API/HOM.

To create a multiparm block, you actually need to grab the "Folder" parameter. Then, set the "Folder Type" to one of the 'Multiparm Block' types.

1

All 3 types are essentially the same, only the UI differs. I tend to use the 'List' type most often but that's personal preference.

You can now create a parameter of your choosing inside the block. In this example, I created a string parameter. Each time you press the '+' button on the block, it will create an instance of your parameter(s). In my case, it creates a string parameter. Note the '#' character in my parm name parm_#. This refers to the instance of the parameter. If you look at at the image below, you can see my parms are labelled: Parm 1, Parm 2, Parm 3.

1

Creating a Multiparm block and setting its instances with Python

To create a parameter on a node, you need node.addSpareParmTuple() which requires a hou.ParmTemplate. Now that you know that a multiparm block is a folder, we can use the hou.FolderParmTemplate class to create our multiparm block.

We need to initialize our FolderParmTemplate to have the folder_type argument as one of the MultiparmBlock types. In this case we will use the hou.folderType.MultiparmBlock.

We will now create a parameter inside the multiparm.

First we create the parm template, choosing for this example the StringParmTemplate class.

We can now call addParmTemplate() on the multiparm template to add the StringParmTemplate to it.

Then we use addSpareParmTuple(template) on the target node to create the multiparm block.

1

Say you want to set a float parameter to 5.25; you would use the set() method on your parameter object, like so: node.parm('my_float_parm').set(5.25)

Multiparms work in a similar fashion. The set() method will accept an integer as an argument. For example: 1

So, let's now call parm.set(15). This will create 15 instances of the string parameter we added to the block.

Great! You've set your multiparm to the desired number of instances. If you want to clear it, simply set it to 0: parm.set(0)

We've now created a multiparm block that holds a string parameter, and created 15 instances of said string parameter. Let's see what that snippet looks like:

node = hou.node('/obj/geo1/ME') #this is just a null inside of sops
template = hou.FolderParmTemplate("my_block","My Block",folder_type = hou.folderType.MultiparmBlock)
stemplate = hou.StringParmTemplate("my_instance_#","My Instance #",1)
template.addParmTemplate(stemplate)
parmtuple = node.addSpareParmTuple(template)
node.parm('my_block').set(15)

Accessing the multiparm instances

You can use a for loop range(multiparm.evalAsInt() to iterate through all items.

Here's the catch: when creating a multiparm, there's an option to decide how the instances are numbered, the "First Instance" option.

1

As you can see, our parms start at 1. That's because we have 'First Instance' set to 1. This means that when looping through the parms, we'll have to add 1 to the iterator.

1

To get the Parm object, we're using node.parm('parm_'+str(i+1)), that's because 'First Instance' is set to 1 but iterators in loops start at 0 (you probably already know this).

This is fine but it's something you have to keep in mind.

Don't want to have to deal with that? Set 'First Instance' to 0, then you won't have to add 1 to your iterator. However that will also influence the look of your UI : the first parm is now labelled 'Parm 0'.

1

Earlier we created a multiparm block with Python. What if you want to set 'First Instance' using Python too?

When creating the parm template, you can specify a 'tags' argument that will let you set that 'First Instance' to whatever your heart desires ( as long as it is an unsigned int, aka x >=0.). Well I say uint but you then have to convert it to a string. You'll most likely only ever set it to '0' or '1'.

Let's take our previous snippet and specify the 'multistartoffset' tag to set the 'First Instance' option through Python.

node = hou.node('/obj/geo1/ME') #this is just a null inside of sops
tags = dict()
tags['multistartoffset'] = '0' #note that 0 is a string
template = hou.FolderParmTemplate("my_block","My Block",folder_type = hou.folderType.MultiparmBlock,tags=tags)
stemplate = hou.StringParmTemplate("my_instance_#","My Instance #",1)
template.addParmTemplate(stemplate)
parmtuple = node.addSpareParmTuple(template)
node.parm('my_block').set(15)

Say you've rebuilt this setup, let's provide you with an example snippet that sets your multiparm block to 15 instances and sets each of them to some random gibberish.

In the snippet below, index == str(i). This works because we had set 'First Instance' to 0! Don't forget that by default 'First Instance' is equal to 1 which means you would have to set index to be str(i+1).

Here's what the code looks like:

import random
import string
import hou #depending on where you write this you might not need this import

def create_rand_string(length):
	characters = string.ascii_letters + string.digits + string.punctuation
	rstring = ''.join(random.choice(characters) for i in range(length))
	return rstring

node = hou.node('/obj/geo1/ME')
parm = node.parm('my_block')

instances = 15

parm.set(15)

for i in range(instances):

    index = str(i)

    parm = node.parm('my_instance_'+index)

    randint = random.randint(i+1,20)

    parm.set(create_rand_string(randint))

That's it! Set all the instances within a multiparm block using python.

Contributed by:
Picture of the authorPicture of the author

When using nodes in Houdini you'll often come across these handy menus that usually come with string parameters.

Let's look at how we can DIY that.

Let's create a new string parameter and head to the menu tab of the parameter description.

We'll tick Use Menu and set it to Replace (Field + Selection Menu). You can experiment with other options but this is usually the one you want.

Now you can write your own menu by setting a token then a label. That's cool and all but this is Houdini, we'd like something procedural. Let's use the menu script tab to write some python instead !

The script is pretty much a callback that runs every time you click the button. The callback needs to return a list that's built exactly like a regular menu: (token1,value1,token2,value2,token3,value3)

In this example we'll get a list of directories. Let's say my hip is /home/what/projects/pythonstuff.hip. I want to get all the directories that live in /home/what/projects, my hip's parent directory.

Let's look at our code

import os
from pathlib import Path

basedir = hou.expandString("$HIP")

parent = Path(basedir).parent

menuitems = list()

dirs = os.listdir(parent)

for dir in dirs:

    menuitems.append(dir)
    menuitems.append(dir)

return menuitems

We're getting the path to the HIP with hou.expandString('$HIP') . We could do the same with hou.getenv('HIP'). Then we're getting the parent directory with Path.parent. We could also use os.path.split()[-1]. Many ways to skin a cat ; this example focuses on how you can create the menu, not so much on which methods/packages you should use. We're then creating a empty list. os.listdir() gives us a list of the directories contained within parent. We then iterate through the list and add the item to the menu. Why twice ? A menu needs a token and a label. In this case, we want them to have the same value. Let's illustrate this by reworking the loop.

for index,dir in enumerate(dirs):

	menuitems.append(str(index))
	menuitems.append(dir)

In this example, the token would be : 0,1,2,... and the value would be each directory. So clicking on the menu item would return :0,1,2.

This might be something you want but most of the time you want the values to match.

If you want to check how more menus are implemented, the Labs or SideFx HDAs are a good place to start.

Contributed by:
Picture of the author