Choose File > New operator type.
Set the Operator style to
python, then set Network type to Geometry operator.
Use the Save to library option to set an OTL library file to save the new node type into.
The type properties window appears.
Use the options in the type properties window to define the interface for your new node type.
Click the Code tab to view and edit the Python script that defines the SOP’s behavior.
If you need to edit the script after closing the type properties window, right-click an instance of the node and choose Type properties.
Writing the code
To get the node’s incoming geometry, use
geo = hou.pwd().geometry()
hou.pwd() function returns the currently cooking Node, and the
geometry() method returns a hou.Geometry object. If an input SOP
is connected to the first input of the Python SOP, Houdini copies the input
SOP’s geometry into the Python SOP’s geometry before running the Python code.
To get the geometry from an input other than the first, use the
inputs() method of Node to get the input nodes, and then the
geometry() method of one of those nodes to get its geometry.
this_node = hou.pwd() inputs = this_node.inputs() # Get the geometry from the second input # (first input=0, second input=1, third=2, etc.) second_input_geo = inputs.geometry()
You can then call methods on the geometry object (
geo in this example) to modify the outgoing geometry. See the documentation for the hou.Geometry object for how to manipulate the geometry.
For example, to add a polygon to the geometry:
poly = geo.createPolygon() for position in (0,0,0), (1,0,0), (0,1,0): point = geo.createPoint() point.setPosition(position) poly.addVertex(point)
If you want to create a “source” node rather than a “filter” node, simply set the node’s number of inputs to 0 in the type properties. Calling
hou.pwd().geometry() will return an empty geometry you can add to.
Making the SOP interruptible
If your Python surface node takes a long time to cook when given large input geometry, you may want to be able to interrupt its cooking by pressing Escape. To make your SOP interruptible, periodically call hou.updateProgressAndCheckForInterrupt. For example, you can press Escape to stop this SOP from cooking further:
geo = hou.pwd().geometry() # Evaluate the "t" parameter to see how much to translate each point. translation = hou.Vector3(hou.parmTuple("t").eval()) for point in geo.points(): # Set the new position for each point. point.setPosition(point.position() + translation) # Check if the user pressed Escape. if hou.updateProgressAndCheckForInterrupt(): break
SOP Errors and Warnings
If your Python surface node generates an exception, the node will turn red with an error and you can view the stack trace of the error by middle-clicking on it.
If you would like to generate an error message to the user that doesn’t contain a Python stack trace, raise a hou.NodeError exception. For example, running
raise hou.NodeError("Invalid parameter settings")
will turn the node red with an error message of
"Invalid parameter settings".
Similarly, you can add node warnings by raising instances of
Creating local variables for new attributes
hou.Geometry.addAttrib contains a parameter to control whether Houdini creates a local variable for newly added attributes. There is no need to modify the varmap attribute directly.
Profiling the SOP code
You can profile your Python surface node with Python’s
If you are on Ubuntu, use
aptitude to install
python-profiler since the standard library is missing pstats in Ubuntu.
You should write your surface node’s script so the work is done inside a function, instead of a bunch of statements at the top level, for example:
def cook(): poly = geo.createPolygon() for position in (0,0,0), (1,0,0), (0,1,0): point = geo.createPoint() point.setPosition(position) poly.addVertex(point) cook()
cProfile and use
cProfile.runctx to call your function instead of calling it directly:
def cook(): poly = geo.createPolygon() for position in (0,0,0), (1,0,0), (0,1,0): point = geo.createPoint() point.setPosition(position) poly.addVertex(point) cProfile.runctx('cook()', globals(), locals())
This will print a summary of your script’s run time to the Python shell when the node cooks.
Writing Part of the SOP in C++
See Extending HOM with C++ to see how to easily write a portion of your SOP in C++.
Storing your Code Outside a Digital Asset
You may want to store your source code outside of your digital asset in
order to manage it under a version control system. It is possible to use
the Type Properties dialog to set up your asset’s parameters and then inject
the Python source code into the
You can use the following function to update your digital asset to use the Python source code from a file:
def loadPythonSourceIntoAsset(otl_file_path, node_type_name, source_file_path): # Load the Python source code. source_file = open(source_file_path, "rb") source = source_file.read() source_file.close() # Find the asset definition in the otl file. definitions = [definition for definition in hou.hda.definitionsInFile(otl_file_path) if definition.nodeTypeName() == node_type_name] assert(len(definitions) == 1) definition = definitions # Store the source code into the PythonCook section of the asset. definition.addSection("PythonCook", source)
TODO: Document how to use geo.globPoints(), geo.globPrims(), etc. to implement