Creating Nodeless States#
"""
State: 1 Course - Nodeless State Example
State type: course::nodeless_state_example::1.0
Description: Course::nodeless state example::1.0
Author: GuiSh
Date Created: January 23, 2025 - 02:44:23
"""
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
def createViewerStateTemplate():
""" Mandatory entry point to create and return the viewer state
template to register. """
#state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
state_typename = "course::nodeless_state_example::1.0"
state_label = "1 Course - Nodeless State Example"
state_cat = hou.objNodeTypeCategory()
template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
template.bindFactory(State)
# template.bindIcon(kwargs["type"].icon())
template.bindIcon("MISC_python")
return template
Creating Asset State#
import hou
import viewerstate.utils as su
class State(object):
MSG = "LMB to add points to the construction plane."
def __init__(self, state_name, scene_viewer):
self.state_name = state_name
self.scene_viewer = scene_viewer
self.pressed = False
self.index = 0
self.node = None
def pointCount(self):
""" This is how you get the number of instances
in a multiparm.
"""
try:
multiparm = self.node.parm("points")
return multiparm.evalAsInt()
except:
return 0
def start(self):
if not self.pressed:
self.scene_viewer.beginStateUndo("Add point")
self.index = self.pointCount()
multiparm = self.node.parm("points")
multiparm.insertMultiParmInstance(self.index)
self.pressed = True
def finish(self):
if self.pressed:
self.scene_viewer.endStateUndo()
self.pressed = False
def onEnter(self, kwargs):
self.node = kwargs["node"]
if not self.node:
raise
self.scene_viewer.setPromptMessage( State.MSG )
def onInterrupt(self,kwargs):
self.finish()
def onResume(self, kwargs):
self.scene_viewer.setPromptMessage( State.MSG )
def onMouseEvent(self, kwargs):
""" Find the position of the point to add by
intersecting the construction plane.
"""
ui_event = kwargs["ui_event"]
device = ui_event.device()
origin, direction = ui_event.ray()
position = su.cplaneIntersection(self.scene_viewer, origin, direction)
# Create/move point if LMB is down
if device.isLeftButton():
self.start()
# set the point position
self.node.parm("usept%d" % self.index).set(1)
self.node.parmTuple("pt%d" % self.index).set(position)
else:
self.finish()
return True
def createViewerStateTemplate():
""" Mandatory entry point to create and return the viewer state
template to register. """
state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
state_label = "Course::asset viewer state example::1.0"
state_cat = hou.sopNodeTypeCategory()
template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
template.bindFactory(State)
template.bindIcon(kwargs["type"].icon())
return template
UI Event Handlers#
Mouse events#
"""
State: 2 Course - Event Handlers
State type: course::event_handlers::1.0
Description: Course::event handlers::1.0
Author: GuiSh
Date Created: January 23, 2025 - 03:15:00
"""
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.node = None
self.mouseCoords = [0,0]
def onEnter(self,kwargs):
""" Called on node bound states when it starts
"""
self.node = kwargs["node"]
state_parms = kwargs["state_parms"]
# print kwargs in the viewer state console if "Debug log" is
# enabled
self.log("ENTERED STATE")
def onInterrupt(self, kwargs):
""" Called when the state is interrupted e.g when the mouse
moves outside the viewport
"""
self.log("INTERRUPTED STATE")
def onResume(self, kwargs):
""" Called when an interrupted state resumes
"""
self.log("RESUME STATE")
def onExit(self,kwargs):
""" Called when the state terminates
"""
state_parms = kwargs["state_parms"]
self.log("EXIT STATE")
def onMouseEvent(self, kwargs):
""" Process mouse events
"""
ui_event = kwargs["ui_event"]
dev = ui_event.device()
reason = ui_event.reason()
self.mouseCoords = [dev.mouseX(), dev.mouseY()]
modifier = 1000
# moved
# self.node.parmTuple("scale").set((self.mouseCoords[0]/modifier, self.mouseCoords[1]/modifier, 1))
if dev.isLeftButton():
modifier = 1000
elif dev.isRightButton():
modifier = 100
elif dev.isMiddleButton():
modifier = 10
#self.log("LMB pressed=", dev.isLeftButton())
#self.log("MMB pressed=", dev.isMiddleButton())
#self.log("RMB pressed=", dev.isRightButton())
# before Picked
if reason == hou.uiEventReason.Active:
self.log("LMB Click")
self.node.parmTuple("scale").set((self.mouseCoords[0]/modifier, self.mouseCoords[1]/modifier, 1))
#elif reason == hou.uiEventReason.Start:
# self.log("LMB was pressed down")
#elif reason == hou.uiEventReason.Active:
# self.log("Mouse dragged with LMB down")
#elif reason == hou.uiEventReason.Changed:
# self.log("LMB was released")
# self.log(dev)
#self.log("Mouse:", dev.mouseX(), dev.mouseY(), dev.isLeftButton())
# Must return True to consume the event
return False
def onMouseWheelEvent(self, kwargs):
""" Process a mouse wheel event
"""
ui_event = kwargs["ui_event"]
state_parms = kwargs["state_parms"]
dev = ui_event.device()
scrollamount = dev.mouseWheel()
#self.log("scrollamount:", scrollamount)
current_divisions = self.node.parm("divisions").evalAsInt()
current_divisions += int(scrollamount)
self.node.parm("divisions").set(current_divisions)
# Must return True to consume the event
return False
def createViewerStateTemplate():
""" Mandatory entry point to create and return the viewer state
template to register. """
state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
state_label = "2 Course - Event Handlers"
state_cat = hou.sopNodeTypeCategory()
template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
template.bindFactory(State)
template.bindIcon(kwargs["type"].icon())
return template
Keyboard events#
# in attrib wrangle / VEXpression
int iteration = detail(0, "iteration", 0) + 1;
i@ObjectID = chi("../object_id_" + itoa(iteration));
"""
State: 2 Course - UI Events 2
State type: course::ui_event_2::1.0
Description: Course::ui event 2::1.0
Author: GuiSh
Date Created: January 23, 2025 - 04:40:40
"""
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.node = None
def onEnter(self,kwargs):
""" Called on node bound states when it starts
"""
self.node = kwargs["node"]
state_parms = kwargs["state_parms"]
# print kwargs in the viewer state console if "Debug log" is
# enabled
self.log("onEnter=",kwargs)
def onKeyEvent(self, kwargs):
""" Called for processing a keyboard event
"""
ui_event = kwargs["ui_event"]
state_parms = kwargs["state_parms"]
#self.log('key string', ui_event.device().keyString())
#self.log('key value', ui_event.device().keyValue())
#self.log('key isAutoRepeat', ui_event.device().isAutoRepeat())
self.key_pressed = ui_event.device().keyString()
multiparm = self.node.parm("entries")
numentries = multiparm.evalAsInt()
if self.key_pressed in ('1', '2', '3'):
numentries += 1
multiparm.set(numentries)
self.node.parm("object_id_{}".format(numentries)).set(int(self.key_pressed)-1)
return True
# Must returns True to consume the event
return False
def onKeyTransitEvent(self, kwargs):
""" Called for processing a transitory key event
"""
ui_event = kwargs["ui_event"]
state_parms = kwargs["state_parms"]
self.log('key', ui_event.device().keyString())
self.log('key up', ui_event.device().isKeyUp())
self.log('key down', ui_event.device().isKeyDown())
# Must returns True to consume the event
return False
def createViewerStateTemplate():
""" Mandatory entry point to create and return the viewer state
template to register. """
state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
state_label = "2 Course - UI Events 2"
state_cat = hou.sopNodeTypeCategory()
template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
template.bindFactory(State)
template.bindIcon(kwargs["type"].icon())
return template
Drawable#
Basics#
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.node = None
# self.mysimpledrawable = hou.SimpleDrawable(
# self.scene_viewer,
# hou.drawablePrimitive.Tube, # .Sphere, .Circle
# "mysimpledrawable")
# self.mysimpledrawable.setDisplayMode(hou.drawableDisplayMode.CurrentViewportMode)
# self.mysimpledrawable.setDisplayMode(hou.drawableDisplayMode.WireframeMode)
# self.mysimpledrawable.setWireframeColor(hou.Color(0,1,0))
# self.mysimpledrawable.enable(True)
# self.mysimpledrawable.show(True)
def onEnter(self,kwargs):
""" Called on node bound states when it starts
"""
self.node = kwargs["node"]
state_parms = kwargs["state_parms"]
pigheadnode = self.node.node("GUIDE") # live reference
self.mysimpledrawable = hou.SimpleDrawable(
self.scene_viewer,
pigheadnode.geometry(),
"mysimpledrawable")
self.mysimpledrawable.setDisplayMode(hou.drawableDisplayMode.CurrentViewportMode)
# self.mysimpledrawable.setDisplayMode(hou.drawableDisplayMode.WireframeMode)
self.mysimpledrawable.setWireframeColor(hou.Color(0,1,0))
self.mysimpledrawable.enable(True)
self.mysimpledrawable.show(True)
def onInterrupt(self, kwargs):
""" Called when the state is interrupted e.g when the mouse
moves outside the viewport
"""
self.mysimpledrawable.show(False)
def onResume(self, kwargs):
""" Called when an interrupted state resumes
"""
self.mysimpledrawable.show(True)
def onMouseEvent(self, kwargs):
""" Process mouse events
"""
ui_event = kwargs["ui_event"]
dev = ui_event.device()
self.log("Mouse:", dev.mouseX(), dev.mouseY(), dev.isLeftButton())
# Must return True to consume the event
return False
def onMenuAction(self, kwargs):
""" Callback implementing the actions of a bound menu. Called
when a menu item has been selected.
"""
menu_item = kwargs["menu_item"]
state_parms = kwargs["state_parms"]
def onMenuPreOpen(self, kwargs):
""" Implement this callback to update the menu content before
it is drawn.
"""
menu_states = kwargs["menu_states"]
menu_item_states = kwargs["menu_item_states"]
def onDraw(self, kwargs):
""" Called for rendering a state e.g. required for
hou.AdvancedDrawable objects
"""
draw_handle = kwargs["draw_handle"]
def createViewerStateTemplate():
""" Mandatory entry point to create and return the viewer state
template to register. """
state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
state_label = "5 Course - Drawable"
state_cat = hou.sopNodeTypeCategory()
template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
template.bindFactory(State)
template.bindIcon(kwargs["type"].icon())
return template
State Geometry#
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.node = None
self.stategeo = hou.Geometry()
# self.mysimpledrawable = hou.SimpleDrawable(
# self.scene_viewer,
# hou.drawablePrimitive.Tube, # .Sphere, .Circle
# "mysimpledrawable")
# self.mysimpledrawable.setDisplayMode(hou.drawableDisplayMode.CurrentViewportMode)
# self.mysimpledrawable.setDisplayMode(hou.drawableDisplayMode.WireframeMode)
# self.mysimpledrawable.setWireframeColor(hou.Color(0,1,0))
# self.mysimpledrawable.enable(True)
# self.mysimpledrawable.show(True)
def onEnter(self,kwargs):
""" Called on node bound states when it starts
"""
self.node = kwargs["node"]
state_parms = kwargs["state_parms"]
pigheadnode = self.node.node("GUIDE") # live reference
pigheadgeo = pigheadnode.geometry()
polyextrude = hou.sopNodeTypeCategory().nodeVerb("polyextrude::2.0")
polyextrude.setParms(
{
"splittype":0,
"dist":0.1,
}
)
polyextrude.execute(self.stategeo, [pigheadgeo])
self.mysimpledrawable = hou.SimpleDrawable(
self.scene_viewer,
self.stategeo,
"mysimpledrawable")
self.mysimpledrawable.setDisplayMode(hou.drawableDisplayMode.CurrentViewportMode)
# self.mysimpledrawable.setDisplayMode(hou.drawableDisplayMode.WireframeMode)
self.mysimpledrawable.setWireframeColor(hou.Color(0,1,0))
self.mysimpledrawable.enable(True)
self.mysimpledrawable.show(True)
def onInterrupt(self, kwargs):
""" Called when the state is interrupted e.g when the mouse
moves outside the viewport
"""
self.mysimpledrawable.show(False)
def onResume(self, kwargs):
""" Called when an interrupted state resumes
"""
self.mysimpledrawable.show(True)
def onMouseEvent(self, kwargs):
""" Process mouse events
"""
ui_event = kwargs["ui_event"]
dev = ui_event.device()
self.log("Mouse:", dev.mouseX(), dev.mouseY(), dev.isLeftButton())
# Must return True to consume the event
return False
def onMenuAction(self, kwargs):
""" Callback implementing the actions of a bound menu. Called
when a menu item has been selected.
"""
menu_item = kwargs["menu_item"]
state_parms = kwargs["state_parms"]
def onMenuPreOpen(self, kwargs):
""" Implement this callback to update the menu content before
it is drawn.
"""
menu_states = kwargs["menu_states"]
menu_item_states = kwargs["menu_item_states"]
def onDraw(self, kwargs):
""" Called for rendering a state e.g. required for
hou.AdvancedDrawable objects
"""
draw_handle = kwargs["draw_handle"]
def createViewerStateTemplate():
""" Mandatory entry point to create and return the viewer state
template to register. """
state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
state_label = "5 Course - Drawable"
state_cat = hou.sopNodeTypeCategory()
template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
template.bindFactory(State)
template.bindIcon(kwargs["type"].icon())
return template
SOP Verbs#
node = hou.pwd()
geo = node.geometry()
box = hou.sopNodeTypeCategory().nodeVerb("box")
box.execute(geo, [])
polyextrude = hou.sopNodeTypeCategory().nodeVerb("polyextrude::2.0")
polyextrude.setParms(
{
"dist": 0.06,
"splittype":0
})
polyextrude.execute(geo, [geo])
node = hou.pwd()
geo = node.geometry()
boxgeo = hou.Geometry()
box = hou.sopNodeTypeCategory().nodeVerb("box")
box.execute(boxgeo, [])
spheregeo = hou.Geometry()
sphere = hou.sopNodeTypeCategory().nodeVerb("sphere")
sphere.setParms(
{
"type":2,
"scale":0.6,
}
)
sphere.execute(spheregeo, [])
boolean = hou.sopNodeTypeCategory().nodeVerb("boolean::2.0")
boolean.execute(geo, [boxgeo,spheregeo])
node = hou.pwd()
geo = node.geometry()
boxgeo = hou.Geometry()
box = hou.sopNodeTypeCategory().nodeVerb("box")
box.execute(boxgeo, [])
spheregeo = hou.Geometry()
sphere = hou.sopNodeTypeCategory().nodeVerb("sphere")
sphere.setParms(
{
"t":hou.Vector3(2,0,0),
}
)
sphere.execute(spheregeo, [])
geo.merge(boxgeo)
geo.merge(spheregeo)
Text Drawable#
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.node = None
self.text_drawable = hou.TextDrawable(self.scene_viewer, "mytextdrawable")
self.text_drawable.show(True)
self.mousepos = hou.Vector3(0,0,0)
def onEnter(self,kwargs):
""" Called on node bound states when it starts
"""
self.node = kwargs["node"]
state_parms = kwargs["state_parms"]
# print kwargs in the viewer state console if "Debug log" is
# enabled
self.log("onEnter=",kwargs)
def onInterrupt(self, kwargs):
""" Called when the state is interrupted e.g when the mouse
moves outside the viewport
"""
pass
def onResume(self, kwargs):
""" Called when an interrupted state resumes
"""
pass
def onMouseEvent(self, kwargs):
""" Process mouse events
"""
ui_event = kwargs["ui_event"]
dev = ui_event.device()
self.log("Mouse:", dev.mouseX(), dev.mouseY(), dev.isLeftButton())
self.mousepos = hou.Vector3(dev.mouseX(), dev.mouseY(), 0)
# Must return True to consume the event
return False
def onDraw(self, kwargs):
""" Called for rendering a state e.g. required for
hou.AdvancedDrawable objects
"""
handle = kwargs["draw_handle"]
textparms = {
"text": "hello world!",
"scale": hou.Vector3(3,3,3),
"color1":hou.Color(0,1,0),
"translate":self.mousepos,
}
self.text_drawable.draw(handle, textparms)
def createViewerStateTemplate():
""" Mandatory entry point to create and return the viewer state
template to register. """
state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
state_label = "GuiSh subnet1"
state_cat = hou.sopNodeTypeCategory()
template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
template.bindFactory(State)
template.bindIcon(kwargs["type"].icon())
Geo Drawable#
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.node = None
self.inputnode = None
self.inputgeo = hou.Geometry()
self.geometryintersector = None
self.text_drawable = hou.TextDrawable(self.scene_viewer, "mytextdrawable")
self.text_drawable.show(True)
self.geo_drawable = hou.GeometryDrawable(
self.scene_viewer,
hou.drawableGeometryType.Face,
"mygeodrawable",
label=None,
geometry=None,
params={
"color1" : hou.Color(1,0,0)
})
self.geo_drawable.show(True)
self.mousepos = hou.Vector3(0,0,0)
def onEnter(self,kwargs):
""" Called on node bound states when it starts
"""
self.node = kwargs["node"]
self.inputnode = self.node.node("INPUT_GEO")
self.inputgeo = self.inputnode.geometry()
self.geometryintersector = su.GeometryIntersector(
self.inputgeo,
scene_viewer=self.scene_viewer,
test_dist=0.5,
tolerance=0.03,
snap_options={},
pattern=None)
self.geo_drawable.setGeometry(self.input_geo)
state_parms = kwargs["state_parms"]
# print kwargs in the viewer state console if "Debug log" is
# enabled
self.log("onEnter=",kwargs)
def onInterrupt(self, kwargs):
""" Called when the state is interrupted e.g when the mouse
moves outside the viewport
"""
pass
def onResume(self, kwargs):
""" Called when an interrupted state resumes
"""
pass
def onMouseEvent(self, kwargs):
""" Process mouse events
"""
ui_event = kwargs["ui_event"]
dev = ui_event.device()
self.mousepos = hou.Vector3(dev.mouseX(), dev.mouseY(), 0)
ray_origin, ray_dir = ui_event.ray()
self.geometryintersector.intersect(
ray_origin,
ray_dir,
snapping=True,
min_hit=0.0,
max_hit=1e18)
# values of intersection result
print(self.geometryintersector.intersected)
print(self.geometryintersector.position)
print(self.geometryintersector.normal)
print(self.geometryintersector.uvw)
print(self.geometryintersector.prim_num)
print(self.geometryintersector.geometry)
print(self.geometryintersector.ray_origin)
print(self.geometryintersector.ray_dir)
if self.geometryintersector.prim_num != -1:
blast = hou.sopNodeTypeCategory().nodeVerb("blast")
blast.setParms(
{
"group": "!" + str(self.geometryintersector.prim_num),
"grouptype": 2
}
)
isolatedprim = hou.Geometry()
blast.execute(isolatedprim, [self.inputgeo])
self.geo_drawable.setGeometry(isolatedprim)
else:
self.geo_drawable.setGeometry(hou.Geometry())
# Must return True to consume the event
return False
def onDraw(self, kwargs):
""" Called for rendering a state e.g. required for
hou.AdvancedDrawable objects
"""
handle = kwargs["draw_handle"]
textvalue = "no hit"
if self.geometryintersector.intersected:
textvalue = "hit!"
textparms = {
"text": textvalue,
"scale": hou.Vector3(3,3,3),
"color1":hou.Color(0,1,0),
"translate":self.mousepos,
}
self.text_drawable.draw(handle, textparms)
self.geo_drawable.draw(handle)
def createViewerStateTemplate():
""" Mandatory entry point to create and return the viewer state
template to register. """
state_typename = kwargs["type"].definition().sections()["DefaultState"].contents()
state_label = "GuiSh subnet1"
state_cat = hou.sopNodeTypeCategory()
template = hou.ViewerStateTemplate(state_typename, state_label, state_cat)
template.bindFactory(State)
template.bindIcon(kwargs["type"].icon())
Selections#
Manual selections#
import stateutils
import soptoolutils
pane = stateutils.activePane(kwargs)
# 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
)
print(selections)
sourceselection = selections[0][0]
targetselection = selections[1][0]
print(sourceselection, targetselection)
# 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
)
newnode.parm("sourcegroup").set(sourceselection.selectionStrings()[0])
newnode.parm("targetgroup").set(targetselection.selectionStrings()[0])
# 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")