On this page

Overview

Houdini lets you customize several menus, including the main menus, using XML configuration files.

  • Houdini loads the default menu files from the Houdini installation ($HFS/houdini), then any files with the same names from $HOUDINI_MENU_PATH. (The default path is $HOME/houdiniX.x/ and the current working directory.) This allows per-user menu customization.

  • By default $HOUDINI_MENU_PATH is the same as $HOUDINI_PATH.

  • Each definition file can add, rearrange, and remove menu items from files loaded earlier in the path.

  • Menus and menu items are identified by their id attribute. It is not an error to create a menu or item without an id attribute, but for various reasons every menu/item should have one.

  • For action and toggle menu items in the main menu files, the id value is also a hotkey symbol, allowing you to assign a hotkey to the item in the hotkey editor.

    Hotkey symbols must start with h., so if an item’s id doesn’t start with h. already, Houdini creates its hotkey symbol by prefixing the id with h..

  • Most of the menu items in the default files shipped with Houdini trigger built-in functionality based on the hotkey. However, action items can also trigger Python scripts. Additionally, you can create alias menu items. See the <scriptItem> and <actionId> tags below.

  • You can customize the context menu for nodes in the network editor using an $HOUDINI_MENU_PATH/OPmenu.xml.

Configuration files

The configuration files are in an XML format, using a schema defined in $HFS/houdini/menu.xsd.

Context menu files

Several context (right mouse button) menus are configurable using files similar to the main menu files.

File

Menu

id Prefix

CHGmenu.xml

Channel group context menu

h.pane.group.

OPmenu.xml

Node edit menu (i.e. RMB-click on a node in the network editor)

h.pane.wsheet.

PARMmenu.xml

Parameter context menu (in the parameter editor)

h.pane.parms.

ParmGearMenuenu.xml

The menu attached to the gear icon menu button in the parameter editor.

h.pane.parms.

PlaybarMenu.xml

Playbar context menu

h.

ShelfToolMenu.xml

Context menu for the tool buttons on the shelf

h.shelf.menu.

ShelfMenu.xml

Context menu for the shelf and shelf tabs

h.shelf.menu.

ShelfSetMenu.xml and ShelfSetPlusMenu.xml

Menu for the shelf set (drop-down arrow button in the upper right corner of the shelf and the plus button next to the tabs, respecitvely)

h.shelf.menu.

VOPFXmenu.xml

Motion FX menu to the right of parameters on VOP nodes

h.pane.parms.

The structure of context menu files is similar to main menu files, however the top-level element is <menuDocument> and the menu items are wrapped in a <menu> element.

The menu item labels for built-in functions are dynamically generated. Houdini will ignore any <label> and <labelExpression> element inside an <actionItem>.

<?xml version="1.0" encoding="UTF-8"?>
<menuDocument>
  <menu>
    <scriptItem id="create_myramp">
      <label>My First Item</label>
      <scriptPath>$HOME/scripts/first_script.py</scriptPath>
    </scriptItem>
  </menu>
</menuDocument>

The ids correspond to hotkeys with a certain fixed prefix (depending on the context menu being configured). For example, an item with id=create_ramp, refers to the h.pane.parms.vopeffects.create_ramp action in the hotkeys manager.

Pop-up menus also support a <context> XML element inside a <scriptItem> (and other) menu item elements. The <context> element specifies conditions that a menu item must meet to be included in the menu. The <context> element can contain:

<ownerMask>

Uses globbing to match owner name against the specified string. An owner is the entity that pops up the menu, e.g., a parameter or a shelf tool.

<scriptItem id="box_tool_item">
  <label>Box Tool Menu Item</label>
  <context>
    <ownerMask>*box*</ownerMask>
  </context>
</scriptItem>

<expression>

Is a python expression that allows the menu item if it evaluates to true (non-zero), and otherwise excludes it from the menu.

<expression>kwargs.get('name','') == 'sop_box'</expression>
<expression>hou.applicationVersion()[0] >= 12</expression>

Note

You can have multi-line expressions. The expression must return True or False.

For example, this expression returns True if the hou.Node object in kwargs exists and is an Object node:

<expression>
node = kwargs.get("node", None)
if node is None:
    return False

return node.type().category().name() == "Object"
</expression>

<parentNetworkType>

Specifies a list of network types in which the item can appear. It’s applicable to VOP FX menus only.

<parentNetworkType>vopsurface,vopmaterial</parentNetworkType>

VOP FX menu file

This is the menu displayed when you click the export menu button next to an input parameter in the parameter editor for a VOP node. This menu is used to attach "boilerplate" sets of VOP nodes to the parameter’s input to set up various effects. You can customize it using the VOPFXmenu.xml file.

The structure of VOPFXmenu.xml is similar to the context menu files, however you can use a <context> element inside a <scriptItem> or <separatorItem> element to specify what network types the menu item should be available in. Inside the <context> element, add a <parentNetworkType> element. The text of the element is a network type name (or comma-separated list of names).

<menuDocument>
  <menu>
    <actionItem id="create_parm"/>
        <scriptItem id="create_texture">
          <label>Texture Map</label>
      <context>
        <parentNetworkType>
        vopsurface,vopmaterial,rsl_vopmaterial
        </parentNetworkType>
      </context>
      <scriptCode><![CDATA[
        import voptoolutils

        effect = voptoolutils.parmVopEffect(kwargs, 'texture')
      ]]></scriptCode>
    </scriptItem>
    ...

The context type token is the node type of the parent network in which the node resides. For example, for a VOP surface shader, the token is vopsurface. For a VOP SOP node it is vopsop.

If you don’t specify a <context>, an item shows up in all VOP contexts.

The ids correspond to hotkeys with the h.pane.parms. prefix. For example, an item with id=create_ramp, refers to the h.pane.parms.vopeffects.create_ramp action in the hotkeys manager.

Reorganizing menus

To attach new items or sub-menus to existing menus, duplicate the existing parent structure and put your new items inside. Houdini will match up the duplicated structure to the defined structure.

For example, The default Edit ▸ Preferences sub-menu is defined like this:

<?xml version="1.0" encoding="UTF-8"?>
<mainMenu>
    <menuBar>
        ...
        <subMenu id="edit_menu">
            <label>Edit</label>
            ...
            <subMenu id="preferences_submenu">
                <label>Preferences</label>
                ...
            </subMenu>
            ...
        </subMenu>
        ...
    </menuBar>
</mainMenu>

To add a new item to the preferences sub-menu in an overlay file, do this:

<?xml version="1.0" encoding="UTF-8"?>
<mainMenu>
    <menuBar>
        <subMenu id="edit_menu">
            <subMenu id="preferences_submenu">
                <scriptItem id="h.mypref">
                    <label>My Preference</label>
                    <scriptPath>$HOME/scripts/generic_script.py</scriptPath>
                </scriptItem>
            </subMenu>
        </subMenu>
    </menuBar>
</mainMenu>

(See also the addScriptItem tag below for how to add a single item without duplicating structure.)

To fine-tune the position of your new script item within the menu you are attaching to, you can use the following sub-tags inside <scriptItem> or <actionItem>:

<insertBefore />

Insert this item at the beginning of the menu, before existing items.

<insertBefore>id</insertBefore>

Insert this item before the item with id="id".

<insertAfter />

Insert this item at the end of the menu, after existing items.

<insertAfter>id</insertAfter>

Insert this item after the item with id="id".

<insertAtIndex>num</insertAtIndex>

Insert this item at the nthe position in the menu.

So, to insert your new menu item before the 3D Viewports entry in the Preferences sub-menu, do this:

<?xml version="1.0" encoding="UTF-8"?>
<mainMenu>
    <menuBar>
        <subMenu id="edit_menu">
            <subMenu id="preferences_submenu">
                <scriptItem id="h.mypref">
                    <label>My Preference</label>
                    <insertBefore>h.prefs_viewports</insertBefore>

                    <scriptPath>$HOME/scripts/generic_script.py</scriptPath>
                </scriptItem>
            </subMenu>
        </subMenu>
    </menuBar>
</mainMenu>

<addScriptItem>

This tag inside <mainMenu> lets you add a single script item to an existing parent menu without duplicating the menu structure as shown above.

<mainMenu>
    <addScriptItem id="h.mypref">
        <label>My Preference</label>
        <parent>preferences_submenu</parent>
        <insertBefore>h.prefs_viewports</insertBefore>

        <scriptPath>$HOME/scripts/generic_script.py</scriptPath>
    </addScriptItem>
</mainMenu>

<parent>id</parent>

The id of the menu to attach this item to.

<modifyItem>

This tag inside <mainMenu> lets you relabel, rearrange, or re-parent an existing item.

A <modifyItem> element can contain <label>, <parent>, <insertAfter>, <insertBefore>, and <insertAtIndex> elements.

For example, the following XML code moves the Render ▸ Create Render Node ▸ Other Output Nodes ▸ Dynamics item one level up:

<mainMenu>
    <modifyItem id="h.create_dynamics">
        <label>Moved Dynamics</label>
        <parent>render_create_submenu</parent>
        <insertBefore>render_create_subsubmenu</insertBefore>
    </modifyItem>
</mainMenu>

You can also use the <modifyItem> tag within the <menuBar> definition structure to adjust any items previously defined.

<removeItem>

This tag inside <mainMenu> lets you remove an existing item.

For example, the following XML code moves the Desktop sub-menu to the top level and removes the unnecessary separator.

<mainMenu>
  <menuBar>
    <subMenu id="desktop_submenu">
      <insertBefore>help_menu</insertBefore>
    </subMenu>
  </menuBar>

  <removeItem id="windows_menu_sep_0"/>
</mainMenu>

Toggle and radio items

The <scriptToggleItem>, <scriptMenuStripRadio>, and <scriptRadioItem> tags let you create custom checkmark and radio menu items. The "states" of the items are stored in Houdini global variables.

Note

As of this writing, there is no way to get and set Houdini global variables in the optional scripts, so you will have to use hou.hscript() to access the HScript echo and set commands.

# Set the value of a global
hou.hscript("set -g MYGLOBAL = 0")

# Get the value of a global
value, err = hou.hscript("echo $MYGLOBAL")

Checkbox (toggle) items

You can create an item that is on or off using <scriptToggleItems>. The <variableName> sub-element specifies the name of the global variable controlled by this menu item. By default, choosing this menu item will change the value of the global variable between "0" and "1".

<scriptToggleItem id="myitem">
    <label>The text that appears in the menu</label>
    <variableName>MY_GLOBAL_VARIABLE</variableName>
</scriptToggleItem>

You can optionally specify a Python script inside the <scriptCode> sub-element or using a <scriptPath> reference. The script runs when the user chooses the item or the value of the global variable changes.

<subMenu id="edit_menu">
    <scriptToggleItem id="myvar_toggle_script">
        <label>MYVAR_TOGGLE Status With Script</label>
        <insertBefore>objects_submenu</insertBefore>
        <variableName>MYVAR_TOGGLE</variableName>
        <scriptCode><![CDATA[
        hou.hscript("""
if( $MYVAR_TOGGLE ) then
set -g MYVAR_TOGGLE = 0;
else
set -g MYVAR_TOGGLE = 1;
endif
        """)
        hou.hscript('varchange MYVAR_TOGGLE')
        (out, err) = hou.hscript('echo $MYVAR_TOGGLE')
        if( int(out) ):
            status = "on"
        else:
            status = "off"
        hou.ui.displayMessage( "MYVAR_TOGGLE has been turned " + status )
    ]]></scriptCode>
    </scriptToggleItem>
</subMenu>

Radio (mutually exclusive) items

The code should set the variable, but it does not have to. The radio items that don’t specify explicit script are equivalent to the following Python script:

The code, however, can do whatever it wants. Still, it is a good idea to at least call hou.hscript('varchange VAR') to restore the radio item index, otherwise the menu may get out of sync, if the variable is not set to the value corresponding to the clicked item.

You can use <scriptMenuStripRadio> in place of a regular menu item, to create a list of mutually exclusive items. The <variableName> sub-element specifies the name of the global variable controlled by this menu item. The <scriptMenuStripRadio> element should contain one or more <scriptRadioItem> elements which represent the items in the list. By default, choosing one of the menu item in the list will change the value of the global variable to the value inside the menu item’s <variableValue> sub-element.

<scriptMenuStripRadio>
    <variableName>MY_GLOBAL_VARIABLE</variableName>

    <scriptRadioItem id="my_item_1">
        <label>First item</label>
        <variableValue>value1</variableValue>
    </scriptRadioItem>
    ...
</scriptMenuStripRadio>

You can optionally specify a Python script inside the <scriptCode> sub-element or using a <scriptPath> reference. The script runs when the user chooses the item or the value of the global variable changes to the value in that item’s <variableValue> sub-element.

The default action is equivalent to the script:

hou.hscript('set -g VAR = val')
hou.hscript('varchange VAR')`

Technically, the script can do anything and is not required to use these commands, but to conform to user expectations about how radio menus work it should at some point set the global variable and then call varchange to update the menu UI.

<subMenu id="edit_menu">
    <subMenu id="myvar_variable">
        <label>Set MYVAR_RADIO</label>
        <insertBefore>objects_submenu</insertBefore>

        <scriptMenuStripRadio>
            <variableName>MYVAR_RADIO</variableName>
            <scriptRadioItem id="h.myvar_one">
                 <label>One</label>
                 <variableValue>value_of_one</variableValue>
            </scriptRadioItem>
            <scriptRadioItem id="h.myvar_two">
                 <label>Two</label>
                 <variableValue>two</variableValue>
            </scriptRadioItem>
            <scriptRadioItem id="h.myvar_three">
                 <label>Three</label>
                 <variableValue>3</variableValue>
            </scriptRadioItem>
            <scriptRadioItem id="h.myvar_four">
                 <label>Four</label>
                 <variableValue>fourth_value</variableValue>
                 <scriptCode><![CDATA[
                    message = 'Do you want to change the global variable MYVAR_RADIO?'
                    response = hou.ui.displayMessage( message, ('Yes', 'No'))
                    if response == 0:
                    hou.hscript("set -g MYVAR_RADIO = 'fourth_value'")
                    hou.hscript("varchange MYVAR_RADIO")
                    else:
                    # call varchange anyway to restore the radio strip UI to old radio index
                    hou.hscript("varchange MYVAR_RADIO")]]>
                </scriptCode>
            </scriptRadioItem>
        </scriptMenuStripRadio>

    </subMenu>
</subMenu>

Deferring execution of <scriptCode>

There are times when it is best to defer the execution of the <scriptCode> script instead of immediately running the script when the menu item is clicked.

For example, consider when the script contains code that pops up a message dialog or prompts for a selection from a choice dialog. In either case the script indefinitely runs until the dialog is closed. Immediately running such a script is undesirable as the menu does not fully close until the script completes. This can sometimes cause the menu to be visible and frozen while the dialog is open. It can also cause a crash in Houdini since opening a dialog can internally change the order of processed UI events.

To defer execution of the <scriptCode> script so that it runs after the menu has fully closed use the hdefereval module.

For example, suppose you had this script:

<scriptCode><![CDATA[
hou.ui.displayMessage("Hello World")
]]>
</scriptCode>

Then to defer execution change the script to this:

<scriptCode><![CDATA[
def runLater():
    hou.ui.displayMessage("Hello World")

import hdefereval
hdefereval.executeDeferred(runLater)
]]>
</scriptCode>

Basics

Getting started

Next steps

Customization

Guru-level