Houdini 20.0 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

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 several ways to support selection for custom tools with Python states:

Advanced selection features for 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.

See Selection event handlers for details on the different selection handlers.

import hou

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", "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.OpNode 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 a drawable selector

A drawable selector allows you to select hou.GeometryDrawable and hou.SimpleDrawable entities created by a python state. Use hou.ViewerStateTemplate.bindDrawableSelector to bind a drawable selector to the python state.

A drawable selector is similar to a geometry or object selector in many aspects:

  • Drawable components can be selected or located with the Houdini select tool options such as box selection and lasso selection.

  • The selection and locating of drawable components is detected by Houdini.

  • Can be bound to python states at registration time.

  • The onSelection handler is called when a drawable selection occurs.

But it also differs from a regular selector:

  • Python states are responsible for drawing the located and selected drawable elements.

  • The onLocateSelection handler is called when drawable components are located (mouse is hovering over), this allows the python state to highlight a geometry component.

  • Can only select drawable geometries, other scene geometries are just ignored when hovering the mouse over.

  • Any drawable components can be selected regardless of the Select tool geometry mode menu.

  • Secure selection setting doesn’t apply to a drawable selector.

Like for selectors such as geometry and object locators, the python state plug-in can implement the onSelection method to receive new selections. onSelection gets the new selection formatted as a python dictionary containing the selected component indices. The onLocateSelection handler can be implemented by the python state plug-in to receive new drawable locate information when the mouse hover over drawables. The new selections and locate information are stored in the drawable_selection entry of each handler kwargs dictionary.

Drawable selection format

The onSelection handler is called whenever drawable components are selected. The onLocateSelection handler is called whenever drawable components are located. Both handlers receive the drawable component information in a python dictionary stored in the drawable_selection entry of the kwargs. The information is formatted as follows:

Drawable dictionary

Component

Data

drawable name

face

list of primitive indices on the drawable geometry.

line

list of point indices on the drawable geometry. First and second indices describes a line, third and fourth another line and so on.

point

list of point indices on the drawable geometry.

A drawable_selection entry example:

{
    "box_lines" : {
          "line" : [5, 7, 6, 4, 7, 6, 7, 2, 3, 6],
    },
    "simple_tube" : {
          "face" : [3, 8, 7, 6, 5, 4],
          "line" : [4, 19, 3, 4, 5, 20, 4, 5, 6, 21, 5, 6, 7, 22, 6, 7],
          "point" : [8, 4, 5, 6, 7],
    },
    "tube_lines" : {
          "line" : [1, 16, 0, 1, 15, 0, 1, 2, 17, 16, 14, 29, 13, 14, 14, 0],
    },
    "tube_points" : {
          "point" : [17, 14, 2, 1, 0, 16],
    }
}

This example uses a drawable selector to highlight the points of located lines.

import hou

def createViewerStateTemplate():
    state_typename = "highlight_line_points"
    state_label = "Highlight Line Points Demo"
    state_cat = hou.sopNodeTypeCategory()

    template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
    template.bindFactory(State)
    template.bindIcon("MISC_python")

    template.bindDrawableSelector("Select a drawable component", 
        name="my_drawable_selector",
        auto_start=True, 
        drawable_mask=["box_lines"])

    return template

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

        # A wireframe drawable box to locate
        self.box_lines = hou.GeometryDrawable(self.scene_viewer, 
            hou.drawableGeometryType.Line, "box_lines")
        self.box_lines.setParams({"color1":hou.Color(1,1,0)})

        # Drawable to highlight the points of the located lines.
        self.box_points_h = hou.GeometryDrawable(self.scene_viewer, 
            hou.drawableGeometryType.Point, "box_points_h")
        self.box_points_h.setParams({"color1":hou.Color(1,0,0), "radius":7, 
            "style":hou.drawableGeometryPointStyle.LinearSquare})

    def onEnter(self, kwargs):
        verb = hou.sopNodeTypeCategory().nodeVerb("box")
        verb.setParms({"type" : 1, "divrate":(5,5,5), "size":(3,3,3), "t": (0,1,0) })
        geo = hou.Geometry()
        verb.execute(geo, [])

        self.box_lines.setGeometry(geo)
        self.box_points_h.setGeometry(geo)

        self.box_lines.show(True)

    def onDraw( self, kwargs ):
        handle = kwargs["draw_handle"]
        self.box_lines.draw(handle)    
        self.box_points_h.draw(handle)    

    def onLocatedSelection(self, kwargs):
        self.box_points_h.show(False)

        # Iterate over the selection to set the points to highlight.
        for k,v in kwargs["drawable_selection"].items():

            if k == "box_lines":
                if "line" in v:
                    indices = v["line"]
                    self.box_points_h.setParams({"indices":indices})
                    self.box_points_h.show(True)

    def onStopSelection(self, kwargs):
        selector_name = kwargs["name"]

        if selector_name == "my_drawable_selector":
            # Clear the located components
            self.box_points_h.show(False)

See Selection event handlers for details on the drawable selector handlers.

Your python state can also react to selection modifiers as selected in the Select tool menu (such as Add, Toggle and Remove). This example colorizes the located components with regards to the selected modifier by using the viewerstate.utils.DrawableSelectorColors class which provides the standard Houdini colors for modifiers.

import hou
import viewerstate.utils as su

class State(object):

    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer
        self.selection = None

        # Manage the selector colors
        self.colors = su.DrawableSelectorColors(self.scene_viewer)            

        # Face drawable for picking/selection
        self.faces = hou.GeometryDrawable(self.scene_viewer, hou.drawableGeometryType.Face, 
                                        "faces")
        self.faces.setParams({"color1":(0,0,0,0.5)})

        # Face for highlights
        self.faces_h = hou.GeometryDrawable(self.scene_viewer, hou.drawableGeometryType.Face, 
                                        "faces_h")
        # Face for selection
        self.faces_s = hou.GeometryDrawable(self.scene_viewer, hou.drawableGeometryType.Face, 
                                        "faces_s")
        self.faces_s.setParams({"color1":self.colors["s"]})

        # Non-selectable line drawable
        self.lines = hou.GeometryDrawable(self.scene_viewer, hou.drawableGeometryType.Line, 
                                        "lines")
        self.lines.setParams({"color1":(1,1,1,0.5)})

    def onEnter(self, kwargs):
        verb = hou.sopNodeTypeCategory().nodeVerb('sphere')
        verb.setParms({"type" : 2, "rad":(1,1,1), "rows": 50, "cols": 50})
        geo = hou.Geometry()
        verb.execute(geo, [])

        self.faces.setGeometry(geo)        
        self.faces_h.setGeometry(geo)
        self.faces_s.setGeometry(geo)
        self.faces.show(True)

        self.lines.setGeometry(geo)
        self.lines.show(True)

    def onDraw( self, kwargs ):
        handle = kwargs["draw_handle"]
        self.faces.draw(handle)    
        self.faces_h.draw(handle)    
        self.faces_s.draw(handle)    

        self.lines.draw(handle)

    def onLocatedSelection(self, kwargs):        
        self.faces_h.show(False)

        # Iterate over the located faces to highlight.
        for k,v in kwargs["drawable_selection"].items():
            if k == "faces" and "face" in v:
                indices = v["face"]

                # Set the drawable with the face color to match the selected modifier
                self.faces_h.setParams({"indices":indices, "color1":self.colors["face"]})
                self.faces_h.show(True)

    def onSelection(self, kwargs):
        if "drawable_selection" in kwargs:
            self.selection = kwargs["drawable_selection"]

            self.faces_h.show(False)
            self.faces_s.show(False)

            if not self.selection:
                return

            # Iterate over the selected drawables to draw the selected 
            # faces.
            for k,v in self.selection.items():

                if k == "faces" and "face" in v:
                    indices = v["face"]
                    if len(indices):
                        self.faces_s.setParams({"indices":indices})
                        self.faces_s.show(True)

        return False

def createViewerStateTemplate():
    state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
    state_label = "Drawable selector modifier demo"
    state_cat = hou.sopNodeTypeCategory()

    template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
    template.bindFactory(State)
    template.bindIcon(kwargs["type"].icon())

    hk1 = su.hotkey(state_typename, "drawable selector", "1")

    template.bindDrawableSelector("Select a drawable component", 
        name="my_drawable_selector",
        auto_start=True, 
        drawable_mask=["faces"],
        hotkey=hk1)    

    return template

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.

Handle volatile selection

Volatile selection (i.e. selection via the select state) is supported with viewer states by simply implementing the onSelection handler. This is the same handler you would normally implement when binding a viewer state selector of a given category or a drawable selector. But there is no need to bind any selectors to support the volatile selection. Details about selection handlers can be found here.

When you terminate the volatile selection by tapping the S key, onSelection will be called with the selected elements as input. Implementing the onStartSelection handler is fruitless for a volatile selection as this callback is designed for python state selectors only and won’t get called before the volatile selector has ended, same goes for the onStopSelection.

Handle existing selection

It is possible to access the current selection when a state is entered. If a selection already exists when the state is entered, Houdini will pass the selected element(s) to the onSelection handler and will consume the selection automatically.

Like for a volatile selection, there is no need to bind a selector for handling an existing selection, just implementing onSelection will suffice. If one or more selectors are bound, Houdini will use either the auto_start selector or the first one set with use_existing_selector=True.

Secure selection support

You can configure selectors to obey or change the current viewer’s secure selection when a selector starts. Secure selection can be useful to prevent accidentally changing the selection when the user meant to click a handle for instance.

Selectors can be registered with one of the following secure selection option:

  • Obey: The selector follows the current viewer’s secure selection setting. This is the default option.

  • On: The selector sets the current viewer’s secure selection to On when it starts. With this option, the previous viewer’s secure selection setting is restored when the state exits.

  • Off: The selector sets the current viewer’s secure selection to Off when it starts. With this option, the previous viewer’s secure selection setting is restored when the state exits.

  • Ignore: With this option a selector ignores the viewer’s secure selection setting and can always select elements.

When secure selection is on, the active selector is passive and the user cannot change the current selection. When secure selection is off, the user can change the current selection.

For more details see:

Python scripting

Getting started

Next steps

Reference

  • hou

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

Guru level

Python viewer states

Python viewer handles

Plugin types