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:
-
Manually script asking the user for each selection. This is the traditional way that existing shelf tools work. We strongly suggest you use this method
-
Bind persistent geometry selectors. This makes the active Python state ask for a selection. When the user completes a selection you can accept the selection by returning
True
from theonSelection()
callback. One or many geometry selectors can be added. -
Bind persistent object selectors. Similar to geometry selectors but expect object selections instead. One or many object selectors can be added.
-
Bind drawable selectors. Enable the python state to select geometries set on drawables.
-
Bind one or more entry selectors. This is similar to manually scripting selections. This method is currently not recommended.
Advanced selection features for Python states:
-
Handle volatile selection. Support for accessing a volatile selection when a python state is active.
-
Handle existing selection. Accessing an existing selection when a python state is activated.
-
Secure selection support. Allows selectors to obey the viewer’s secure selection or not.
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: