Python in Houdini

Python and Houdini Start Up Scripts – Part 3

We’re going to wrap up our Python and Houdini start up scripts series by addressing one last thing in our script. While our script worked upon launching Houdini, it did not appear to run when creating or opening an existing .hip file. We’ll address this issue in a couple of ways and introduce a few new concepts along the way. This is a bit dense so grab your Yoo-Hoo.

If you need to catch up, here are the posts:

The 456 Script

We did some refactoring of our script so everything is looking spiffy. However, the issue is the script only works when we start a new Houdini session. If we try to open an existing scene, our script doesn’t execute. If you recall, this is because Houdini executes two scripts. The first script, 123.py, runs when Houdini is first launched. The second script, 456.py, runs when creating or opening a .hip file. Your initial thoughts to solving this problem might have been to just copy over the code from 123 to 456 or just duplicating the file.

If you tried running Houdini with the 456.py in place, you should have noticed that you end up with duplicate nodes. Once for each individual script execution. If you tried opening an existing scene, the nodes get created even if they already exist. Maybe this is fine and you don’t mind the duplicate nodes, but what if you want the script to take into account that these nodes already exist in a scene? Not only that, we need to address the duplicate nodes upon launching Houdini. Enter globbing.

Globbing

Globbing is a term used in programming circles, especially Unix environments, that defines the use of wildcard characters for pathname expansion. WTF? It’s easier to use than to explain. First, what is a wildcard character?

Wildcard characters are characters such as an asterisk (*) or question mark (?). There are many more. These characters or wildcard characters are used to replace or represent one or more characters and perform actions on more than one file at a time. An * wildcard matches one or more characters and a ? any single character. This subject goes deep so we will leave at that here.

You’ve probably used pattern matching in Houdini without even knowing it. Suppose you want to delete some groups which have names like myGroup01, myGroup02, etc using a Delete SOP. You can type each individual name or you can use the pattern myGroup* with the * wildcard to delete all the groups beginning with myGroup. Since the asterisk matches one or more characters, it globs anything following the letter p in myGroup. Sort of like Pac-man. If you had a few hundred groups, you can see how this becomes extremely handy. Have a look at the following example done in Hscript. All I’m doing is listing the contents of the geometry object with opls and then I just glob for the foreach and repeat operators.

Globbing
Globbing

 

Searching for Nodes

So why am I talking about globbing and wildcard charaters? Well, this is what we are going to use to search for the camera and ROP nodes in an existing scene. If we find a match, we know the nodes exist and skip creating the nodes. Simple enough.

There are various ways of solving this problem. Some are better than others. This approach is more of a learning exercise than trying to show coolness and cleverness. It will also show you how sometimes you completely overlook certain things when programming. Our code so far looks like the following in the 123.py file:

# 123.py

# Create Camera - 1080
def create_camera():
    cam = hou.node("/obj").createNode("cam", "cam_1080")
    cam.setParms({"resx": 1920, "resy": 1080})
    cam.setDisplayFlag(False)

# Create Mantra - PBR driver
def mantra_driver():
    rop = hou.node("/out").createNode("ifd")
    rop.setParms({"vm_renderengine": "pbrraytrace", "override_camerares": True, "camera": "/obj/cam_1080"})

def main():
    create_camera()
    mantra_driver()

main()

Name Approach

Duplicate your 123.py script and rename it 456.py if you have not done so already. Place it in the same scripts folder alongside the 123.py file. Inside the main() function we are going to search for any nodes starting with the names cam and man in our scene. We’ll do this with some globbing functionality. Python being a language that has batteries included provides a glob module. However, we will be using Houdini’s HOM implementation of globbing using the glob() function.

The glob() function returns a tuple which is very similar to lists in that they are a sequence of objects. The main difference is that objects in a tuple are immutable. This means once you create the object you can’t change its state after the fact. For now, you can just treat them as lists.

Our function will return either a tuple with objects or a tuple that is empty. If our glob() function returns an empty tuple, we know our scene does not contain these nodes so we go ahead and create them. Not too hard right?

# 456.py

# Create Camera - 1080
def create_camera():
  node = hou.node("/obj").createNode("cam", "cam_1080")
  node.setParms({"resx": 1920, "resy": 1080})
  node.setDisplayFlag(False)

# Create Mantra - PBR driver
def mantra_driver():
  node = hou.node("/out")
  out = node.createNode("ifd")
  out.setParms({"vm_renderengine": "pbrraytrace", "override_camerares": True, "camera": "/obj/cam_1080"})

def main():
  if not hou.node('/obj').glob('cam*):
    create_camera()

  if not hou.node('/out').glob('man*'):
    mantra_driver()

main()

The highlighted lines of code show how we use the glob() function to search for the camera and mantra nodes. All I’m doing is chaining the functions together. We could have also written it out another way although a bit more verbose:

...

cam = hou.node('/obj')
cam_nodes = cam.glob('cam*')

if not cam_nodes:
  create_camera()

rop = hou.node('/out')
rop_nodes = rop.glob('man*')

if not rop_nodes:
 mantra_driver()

...

If you start a new session of Houdini and then attempt to create a new scene, everything should work as expected. Hopefully you see a glaring issue though. This code assumes the existing nodes start with the names cam and man. What happens if they don’t? Let’s try a different approach by checking the NodeType instead.

NodeType Approach

Nodes in Houdini have a node type. These node types are organized into NodeType categories such as an Object category, a Pop category, a Chop category, a Sop category, etc. These categories contain node types such as “box”, “cam”, “facet”, which is the internal name of a node type. This is the same internal name we use in hou.node.createNode(). We can use this name to check if a certain node type exists in our scene.

# 456.py

# Create Camera - 1080
def create_camera():
  node = hou.node("/obj").createNode("cam", "cam_1080")
  node.setParms({"resx": 1920, "resy": 1080})
  node.setDisplayFlag(False)

# Create Mantra - PBR driver
def mantra_driver():
  node = hou.node("/out")
  out = node.createNode("ifd")
  out.setParms({"vm_renderengine": "pbrraytrace", "override_camerares": True, "camera": "/obj/cam_1080"})

def main():
  # Check for camera node
  nodes = hou.node('/obj').glob('*')
  cam_exists = False

  for node in nodes:
    if node.type().name() == 'cam':
      cam_exists = True
      break
  
  if not cam_exists:
    create_camera()
  
  # Check for mantra node
  rops = hou.node('/out').glob('*')
  rop_exists = False

  for node in rops:
    if node.type().name() == 'ifd':
      rop_exists = True
      break

  if not rop_exists:
    mantra_driver() 

main()

First, we grab a reference to our '/obj' context and immediately chain our handy glob() function. Nothing new here. However, this time we are passing in just the asterisk (*) wildcard character. What I’m saying here is give me all the nodes at the Object level. There are no specific nodes we are after, just give me the whole pie. We store the results in the variable nodes.

We create a boolean variable called cam_exists and set it to False. As of right now, we make the assumption that no cameras exist in the scene. We’ll set it to True if we do find a camera.

Now we use a for loop to iterate over the tuple of object nodes our glob() function returned. For each node in our tuple we get the NodeType object by using the function type(). The type() function returns a NodeType object. In this case it will return an Object type. We then call the name() function on our NodeType object to get the internal name of the node type. We could have done this on separate lines but we are using our fancy chaining trick. Also, yes I know, this node type stuff is a bit confusing.

NodeType object
NodeType object

We compare the name we get back from name() with the internal name of a camera node type, which is ‘cam’. If we find a match, we set cam_exists to True and break out of the loop since we know at least one camera exists. There is not need to continue the loop.

Finally, we check if the variable cam_exists is not True. If it’s not true, we know no cameras are present so we create the camera by calling our create_camera() function that we previously defined. The search for a Mantra ROP follows the same process.

Completed Houdini Startup Script

So that was quite long. Hopefully I didn’t lose you along the way. One last thing, we can actually refactor the code in main() to just a few lines of code. However, as always, I will leave it to user exercise. Play with the code and add your own spin to it.

The last bit I want to change is how the scripts are executed. Right now at the end of the script we are just calling main(). I want to change that last line of code to the following:

if __name__ == '__main__':
  main()

You’ll see this code snippet in many Python scripts and programs. Basically, this checks whether the script is being imported by other scripts or being executed directly. The advantage of doing this check is you can have the code execute when you want to run it as a regular program—such as Houdini executing it— and not have it execute when you want to import it into other programs and call the individual functions themselves. So I can easily reuse the create_camera() in other scripts. Boom!

Go have another Yoo-Hoo. You deserve it! Have a look at the final code in my GitHub repository

Share:
990adjustments

990adjustments

I am a motion designer & developer based out of South Florida.
When not designing or animating pixels, I wrangle some code. If all else fails, I watch Twilight Zone, I Love Lucy, or Three’s Company reruns.