Houdini 17.5 Python Scripting

Python state selections

How to allow the user to select geometry as part of working with your SOP node state or to select objects with your OBJ node state .

On this page

Python viewer states

Overview

Often the workflow for a SOP filter node will allow the user to select components (such as polygons or points) for the node to work on. The tool may allow/require multiple selections, for example the "copy to points" tool takes the geometry to copy and the points to copy onto. These selections are then used to fill in "Group" parameters on the newly created node.

There are three ways to support selection for custom tools with Python states:

Manually scripting selection

This is traditional Houdini workflow where a shelf tool script (usually embedded with the asset it creates) asks for the selections using HOM scripting, then uses utility functions to create the node, then enters the node’s state.

See tool script for more information on writing tool scripts. See Python states and nodes for more information on writing a custom Python state for an asset type.

import stateutils
import soptoolutils

pane = stateutils.activePane()
# Only run the selector script if we are in a viewer (not putting the node)
# down in a network editor
if isinstance(pane, hou.SceneViewer):
    # First we'll ask for the primitive(s) to copy
    source = stateutils.Selector(
        name="select_polys",
        geometry_types=[hou.geometryType.Primitives],
        prompt="Select primitive(s) to copy, then press Enter",
        primitive_types=[hou.primType.Polygon],
        # Which paramerer to fill with the prim nums
        group_parm_name="sourcegroup",
        # Which input on the new node to wire this selection to
        input_index=0,
        input_required=True,
    )
    # Then, we'll ask for the points to copy onto
    target = stateutils.Selector(
        name="select_points",
        geometry_types=[hou.geometryType.Points],
        prompt="Select points to copy onto, then press Enter",
        group_parm_name="targetgroup",
        # Remember to wire each selection into the correct input :)
        input_index=1,
        input_required=True,
    )

    # This function takes the list of Selector objects and prompts the user for
    # each selection
    container, selections = stateutils.runSelectors(
        pane, [source, target], allow_obj_selection=True
    )

    # This function takes the container and selections from runSelectors() and
    # creates the new node, taking into account merges and create-in-context
    newnode = stateutils.createFilterSop(
        kwargs, "$HDA_NAME", container, selections
    )
    # Finally enter the node's state
    pane.enterCurrentNodeState()

else:
    # For interactions other than in a viewer, fall back to the low-level
    # function
    soptoolutils.genericTool(kwargs, "$HDA_NAME")

Binding a geometry selector

You can use hou.ViewerStateTemplate.bindGeometrySelector() to bind a single SOP-level selector that runs when the viewer enters your custom SOP state. When the user completes a selection, Houdini calls your state’s onSelection() callback method.

In the onSelection() method, you can get a "selection" item (a hou.GeometrySelection object) from the dictionary passed in. You can also get the current selector "name" from the dictionary. If you want to accept the selection, return True and the Houdini will end the selector and enter your state’s regular operation. You can return False to reject the selection, and the selector will continue to run.

This may be useful for nodeless states, for example a state that lets you select components from the display geometry then displays information about them.

Houdini lets you bind multiple geometry selectors, use hou.SceneViewer.triggerStateSelector() to start and stop individual selectors. Selectors are triggered by name which can be set at binding time.

class MyState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onSelection(self, kwargs):
        sel = kwargs["selection"]
        sel_name = kwargs["name"]
        if sel_name == "selector1":
            # Only accept the selection if it contains three polygons
            are_prims = sel.geometryType() == hou.geometryType.Primitive
            are_all_polys = all(pt == hou.primType.Polygon for pt in sel.primitiveTypes())
            selection = sel.selections()[0]
            count = selection.numSelected()

            return are_prims and are_all_polys and count == 3

        count = 0
        if sel_name == "selector2":
            # selector2 support
            selection = sel.selections()[0]
            count = selection.numSelected()
        return count > 0

template = hou.ViewerStateTemplate(
    "mystate.pystate", "My State",
    hou.sopNodeTypeCategory()
)
template.bindFactory(MyState)

# selector #1
template.bindGeometrySelector(
    name="selector1",
    prompt="Select three polygons",
    quick_select=False,
    use_existing_selection=True,
    geometry_types=hou.geometryType.Primitives,
    primitive_types=hou.primType.Polygon,
    allow_other_sops=False
)

# selector #2
template.bindGeometrySelector(
    name="selector2",
    prompt="Select a primitive",
    quick_select=True,
    geometry_types=hou.geometryType.Primitives
)

See the help for hou.ViewerStateTemplate.bindGeometrySelector() for information about the method’s arguments.

Binding an object selector

You can use hou.ViewerStateTemplate.bindObjectSelector() to bind a single OBJ-level selector that runs when the viewer enters your custom OBJ state. When the user completes a selection, Houdini calls your state’s onSelection() callback method.

In the onSelection() method, you can get a "selection" item (a list of hou.Node objects) from the dictionary passed in. If you want to accept the selection, return True and the Houdini will end the selector and enter your state’s regular operation. You can return False to reject the selection, and the selector will continue to run.

Houdini lets you bind multiple object selectors to your state, use hou.SceneViewer.triggerStateSelector() to start and stop individual selectors. Selectors are triggered by name which can be set at binding time.

class MyOBJState(object):
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

    def onSelection(self, kwargs):
        cameras = kwargs["selection"]
        print "Number of cameras selected: ", len(cameras)

        return True


template = hou.ViewerStateTemplate("myOBJstate", "My OBJ State", hou.objNodeTypeCategory())
template.bindFactory(MyOBJState)

# Add a selector to select camera objects only
template.bindObjectSelector(
    prompt="Select camera object(s) and press Enter",
    quick_select=False,
    use_existing_selection=True,
    allowed_types=('*cam*',))

See the help for hou.ViewerStateTemplate.bindObjectSelector() for information about the method’s arguments.

Binding entry selectors

This method is currently not recommended.

Theoretically, you can use hou.ViewerStateTemplate.bindSelector() to bind one or more component selectors to your state, similar to using the stateutils.Selector objects and stateutils.runSelectors() in the script above. The state would run through these selectors and then let you create the node in the state’s onGenerate() callback.

However, we recommend you manually script the selections and creating the node using utility functions as described above.

Python viewer states

Python Scripting

Getting started

Next steps

Python viewer states

You can write viewer states in Python that let you customize user interaction in the viewport for your node.

Guru level

Reference

  • hou

    Module containing all the sub-modules, classes, and functions to access Houdini.

  • Alembic extension functions

    Utility functions for extracting information from Alembic files.