Home Lesson 1 Lesson 2 Lesson 3 Lesson 4 Lesson 5

In this lesson we'll get an overview of how Python works in Houdini and learn some simple functions and workflows.

The potential for scripting within houdini is pretty vast, and well explained here (with examples):

http://www.sidefx.com/docs/houdini11.0/hom/cookbook/

If you want an example, open: "C:\Program Files\Side Effects Software\Houdini 12.1.125\houdini\help\hom\cookbook\feed\part3.hip" and write hou.session.createObjects() in the python text editor.

This creates a selectable news feed in houdini

-----------------------------------------------------------------------------------------------------

Who Are You......................... Hou are You?:  

The 'hou' module is the houdini equivalent of maya.cmds. It is the module that contains all houdini specific commands. For example:

print 'the frame is %s'%hou.frame() will print the current frame.

-----------------------------------------------------------------------------------------------------

Where you can run scripts. There are a few different options:

--- You can use the python shell in houdini, which is similar to the IDLE python shell in that it gives you quick feedback but is not great for multiline coding

--- I like to create buttons on the houdini shelf by right-clicking and selecting 'new tool'. You can then enter in multi-line code in the 'script' section and test the code by clicking the button

--- You can also import external .py files just like in Maya (for a file called blahblah.py import blahblah for the first import, reload(blahblah) for any subsequant loads) by placing them in a recongised houdini script path. 

My machine has "C:\houdiniScripts\python" set as script path in my Houdini.env file

---You can create python sops, that will execute your code every time the node is cooked, File/New Operator Type with Operator Style = Python Type; Network Type = whatever you want (Geometry is all I use)

---You can also use python to set parameters on nodes or in digital assets, it is especially useful to run call back scripts from buttons

-----------------------------------------------------------------------------------------------------

How to Access/Affect Nodes in Houdini:

Nodes are referenced using hou.node('/path/to/node') . This command returns a 'Node Object' That can be queried or altered or used as a path to create/delete other nodes.

For example, we can store the '/obj' node (the default obj context path) inside a variable called objectRoot and get an array of all nodes within this root using the '.children' command:

objectRoot = hou.node('/obj')
for child in objectRoot.children():
    print child

Before I create any nodes, I get only 'ipr_camera' as the result of these 3 lines. After I create nodes in the obj context, they are also printed.

-----------------------------------------------------------------------------------------------------

Getting/Setting Parameters:

You can retrieve a parameter from any node using the following syntax:

parameter = hou.node('/path/to/node').parm('name of parm').eval() 

This could be split into 3 stages:

newNode = hou.node('/path/to/node') here we are declaring a variable newNode and setting it to the houdini node object returned by 'hou.node('/path/to/node')

parm = newNode.parm('name of parm') here we are declaring a variable parm and setting it to the houdini parameter object returned by 'hou.node('/path/to/node').parm('name of parm')

parmValue = parm.eval() create a variable named parmValue that holds the parameter's value

A shorcut for accesing parms is:

hou.parm('/path/to/node/name of parm')

Once we have the correct parm object, we can set its value using '.set':

hou.parm('/path/to/node/name of parm').set(45)

-----------------------------------------------------------------------------------------------------

Creating/Deleting Nodes:  

If you have the path of the parent node as a hou.node() object. You can create nodes using '.createNode(nodeType)':

objectRoot = hou.node('/obj')
newGeo = objectRoot.createNode('geo') 

here we create a new geo node at the root level (note we could do it in one line with: newGeo = hou.node('/obj').createNode('geo'))

We could set the new node's name with: newGeo.setName('newGeo')

We can delete this new node by running the '.destroy()' method:

hou.node('/obj/newGeo').destroy()

-----------------------------------------------------------------------------------------------------

Setting Inputs / Ouputs:

Let's create a geo called 'parent geo', delete its default 'file' node, and within this geo, create a box feeding into a scatter:

objectRoot = hou.node('/obj')
masterGeo = objectRoot.createNode('geo')
masterGeo.setName("masterGeo") 
for child in masterGeo.children():
  child.destroy()
scatterSop = masterGeo.createNode('scatter')
scatterSop.setPosition([0, 0])
boxSop = masterGeo.createNode('box')
boxSop.setPosition([0, 2])
scatterSop.setFirstInput(boxSop) 

Here there are 2 new commands: setPosition([vector2]) sets the position of a node in the node graph

node1.setFirstInput(node2) will connect the first input of node1 to the output of node2 

-----------------------------------------------------------------------------------------------------

A longer example of scattered time: (the full code is downloadable below)

In this example we will create a 'font' node with the current time of day, and scatter points within in, with spheres of random size and colour parented to the scatter points. We will need the time module and we will need to record when this script started running:

import time
begTime = time.time()

Start by creating a scatter and a font node within a geo node, we have already bascially covered this:

objectRoot = hou.node('/obj')
masterFont = objectRoot.createNode('geo')
masterFont.setName("masterFont") 
for child in masterFont.children():
    child.destroy()
scatterSop = masterFont.createNode('scatter')
scatterSop.setPosition([0, 1])
fontSop = masterFont.createNode('font')
fontSop.setPosition([0, 4])

I haven't connected them together as we will want nodes between them.

We should now set the text of the font node to the time of day (which we can get from the 'time' module using 'time.gmtime()' which will return the time in an array of the form: [year, month, day, hour, minute, second]:

gmt = time.gmtime()
fontSop.parm('text').set('%s:%s:%s, %s/%s/%s'%(gmt[3], gmt[4], gmt[5], gmt[2], gmt[1], gmt[0]))

Ok, so we have the font set. To get the scattering as I want it, I need an extrude node and an isoOffset between the scatter and the font nodes:

extrudeSop = masterFont.createNode('extrude')
extrudeSop.parm('depthscale').set(0.165)
extrudeSop.setPosition([0, 2])
isoOffsetSop = masterFont.createNode('isooffset')
isoOffsetSop.setPosition([0, 1])
isoOffsetSop.parm('samplediv').set(300)
extrudeSop.setFirstInput(fontSop)
isoOffsetSop.setFirstInput(extrudeSop)
scatterSop.setFirstInput(isoOffsetSop)

Here there were a couple commands to set the uniform sampling on the isoOffset and the extrude distance.

Let's use a copy sop to instance font nodes to all of our points. We will set the text on the font nodes to the milliseconds of the current time using 'time.time()%1':

copySop = masterFont.createNode('copy')
copySop.setPosition([-1, 0])
copySop.parm('stamp').set(1)
copySop.parm('param1').set('pointNum')
copySop.parm('val1').setExpression('$PT')

fontCopySop = masterFont.createNode('font')
fontCopySop.parm('sx').set(0.005)
fontCopySop.parm('sy').set(0.005)
fontCopySop.setPosition([-2,1])

copySop.setInput(0, fontCopySop)
copySop.setInput(1, scatterSop)

Here we have created a copy sop, moved it to a reasonable position and created a stamp variable called 'pointNum' which holds $PT. We use the setExpression method to set the value of the stamp variable because we are setting it to a local houdini variable $PT

We also create a new font sop, scale it down and set its position.

We then set the inputs to the copy sop. Instead of using setFirstInput, we use the more general expression setInput which takes as its arguments the input number to be set and the input node to set it to respectively.

Finally we are going to do something a bit jazzy and set the text of the font node equal to the timepassed since the beginning of running the code:

endTime = time.time()
fontCopySop.parm('text').set( '`%s + (stamp("../copy1", "pointNum", 0)/5000 ) * %s`'%((begTime%1), (endTime-begTime)) )

Finally we will set the display flag to be on the copy sop:

copySop.setDisplayFlag(True)

-----------------------------------------------------------------------------------------------------

scatteredTime.py