Blog

HDA Menu Scripts

minute read

A list of different menu scripts which make HDAs easier to use in Houdini. Note that you need to change the Menu Script Language to Python vs HScript for all of these. Updated as I create them.


Group Selection

Fairly straightforward. Generates a menu with the existing groups of a particular type. There are a couple variations of the code depending on if you want to use a different parameter to control the type being selected (which is how the group node works).

if kwargs["node"]:
    return kwargs["node"].geometry().generateGroupMenu()
else:
    return ["", "Invalid node"]
Copy Code to Clipboard

Attribute Selection

Simple Attribute Selection (Point Attributes)

The only modification needed is to select the type of attributes to find. Options listed here.

node = kwargs['node']
input = node.input(0)

if input:
    geo = input.geometry()
    menu = geo.generateAttribMenu(attrib_type=hou.attribType.Point)
    return menu
    
else:
    return ["", "No Input Connected"]
Copy Code to Clipboard

Full Attribute Selection

By default, the below script will select Point, Prim, and Vertex Attributes. The datatypes (options listed here) vary, as do the min and max sizes in order to demonstrate different ways to change or extend the code.

node = kwargs['node']
input = node.input(0)

if input:
    geo = input.geometry()
    menu = list(geo.generateAttribMenu(attrib_type=hou.attribType.Point, data_type=hou.attribData.Int, min_size=3))
    menu += list(geo.generateAttribMenu(attrib_type=hou.attribType.Prim, data_type=hou.attribData.String, max_size=1))
    menu += list(geo.generateAttribMenu(attrib_type=hou.attribType.Vertex, data_type=hou.attribData.Float, min_size=2, max_size=3))
    return menu

else:
    return ["", "No Input Connected"]
Copy Code to Clipboard

Float Attribute Selection with Separators

Similar to above, the below script will select Point, Prim, and Vertex Attributes. It only selects floats up to a size of 3, but puts a separator between different classes in a fairly elegant way.

from functools import reduce

node = kwargs['node']
input = node.input(0)

if input:
    geo = input.geometry()
    sep = ["_separator_", "_separator_"]
    pt_menu = list(geo.generateAttribMenu(attrib_type=hou.attribType.Point, data_type=hou.attribData.Float, max_size=3))
    prim_menu = list(geo.generateAttribMenu(attrib_type=hou.attribType.Prim, data_type=hou.attribData.Float, max_size=3))
    vtx_menu = list(geo.generateAttribMenu(attrib_type=hou.attribType.Vertex, data_type=hou.attribData.Float, max_size=3))
    menu2 = [pt_menu, prim_menu, vtx_menu]
    menu = reduce(lambda a,b:a+sep+b,menu2)
    return menu

else:
    return ["", "No Input Connected"]
Copy Code to Clipboard

Attribute Values as Menu

Once in a while, you need to create a menu of the values of a specific attribute. An example could be if you want certain transform attributes to only apply to say, the polygons with their variant attribute set to "dog". The expectation in the below code is that you have a string attribute (called "targetAttrib" in this case) being used to say which attribute to target, but you could also hard-set it by typing

attribName = "variant"

for example.

node = kwargs["node"]
attribName = node.parm('targetAttrib').evalAsString()
input = node.input(0)

menu = []

if node:
    geo = input.geometry()
    findAttrib = geo.findPointAttrib(attribName)

    if findAttrib:
        type = geo.findPointAttrib(attribName).dataType()
        list_menu = None
        if type == hou.attribData.String:
            list_menu = geo.pointStringAttribValues(attribName)
        elif type == hou.attribData.Int:
            list_menu = geo.pointIntAttribValues(attribName)

        list_menu = list(dict.fromkeys(list_menu))
        list_menu = sorted(list_menu)
            
        for value in list_menu:
            if value not in menu:
                menu.append(str(value))
                menu.append(str(value))

return menu
Copy Code to Clipboard

List all Parameters on a node (Int and Float only)

Setting up Multiple Action Buttons for 1 Parameter

This splits parameter names so they appear in an order designed to find "random" controls first. Sorts by parameters that have the following in their names, in this order: seed -> iter -> parameters with size 1 -> parameters with size 3.

FURTHERMORE, it is designed to work inside of a multiparm so you will see kwargs['script_multiparm_index'] which represents the mparm number. It also adds separators into the menu if you don't know how to do that.

def allParmTemplates(group_or_folder):
    """
    Creates a generator (iterator) I can use to access ALL parmTemplates in a folder, including the folders.
    
    """
    for parm_template in group_or_folder.parmTemplates():
        yield parm_template

    # Note that we don't want to return parm templates inside multiparm
    # blocks, so we verify that the folder parm template is actually
    # for a folder.
        if parm_template.type() == hou.parmTemplateType.Folder and parm_template.isActualFolder():
            for sub_parm_template in allParmTemplates(parm_template):
                yield sub_parm_template

hda = hou.pwd()
node = hda.parm(f"node_var{kwargs['script_multiparm_index']}").evalAsNode()
seeds = []
iters = []
parms = []
vectors = []
if node:
    for parm_template in allParmTemplates(node.parmTemplateGroup()):   
        
        if parm_template.type() in [hou.parmTemplateType.Int, hou.parmTemplateType.Float]:
            if parm_template.numComponents() > 1:
                parm_name = f"{parm_template.name()} "
                if parm_template.namingScheme() == hou.parmNamingScheme.Base1:
                    parm_name += "123 (Choose One)"
                if parm_template.namingScheme() == hou.parmNamingScheme.XYZW:
                    parm_name += "xyz (Choose One)"
                if parm_template.namingScheme() == hou.parmNamingScheme.XYWH:
                    parm_name += "xywh (Choose One)"
                if parm_template.namingScheme() == hou.parmNamingScheme.UVW:
                    parm_name += "uvw (Choose One)"
                if parm_template.namingScheme() == hou.parmNamingScheme.RGBA:
                    parm_name += "rgba (Choose One)"
                vectors.append(parm_name)
                vectors.append(parm_name)
            else:
                if parm_template.name().find("seed") != -1:
                    seeds.append(parm_template.name())
                    seeds.append(parm_template.name())
                elif parm_template.name().find("iter") != -1:
                    iters.append(parm_template.name())
                    iters.append(parm_template.name())
                else:
                    parms.append(parm_template.name())
                    parms.append(parm_template.name())
    if len(seeds) > 0 and len(iters+parms+vectors) > 0:
        seeds.append("_separator_")
        seeds.append("_separator_")
    if len(iters) > 0 and len(parms+vectors) > 0:
        iters.append("_separator_")
        iters.append("_separator_")
    if len(parms) > 0 and len(vectors) > 0:
        parms.append("_separator_")
        parms.append("_separator_")
    return seeds+iters+parms+vectors

else:
    return ("Node is invalid", "Node is invalid")
Copy Code to Clipboard