Python in Houdini

Python and Houdini Start Up Scripts – Part 2

Previously on the Six Million Dollar Man we talked about Python and Houdini start up scripts and how to go about creating them. If you need to catch up, have a look at part 1 for details. In this second part we are going to focus on refactoring the code and working a bit with Python functions.

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

Refactoring

Once you get your scripts or programs working and everything looks good, you should take the time to go over the code again and see what can be improved or cleaned up. We don’t necessarily want to change the behavior of the program but improve the internal structure. Code refactoring is a discipline in software engineering that helps improve the code and makes it easier to work with. This could mean just adding comments or rewriting the algorithm or syntax that produces the same or similar results. There are many techniques to refactoring but that’s beyond the scope of this article. We’ll do basic refactoring.

Our previous code looked like the following:

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

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

First thing we want to do is tighten up some of the code and make it more concise. Now, this is a preference of mine and does not necessarily mean it’s the right way. At the end of the day it’s about making things easier to read. Being too clever with your code can be a detriment.

Command Chaining

I want to combine the first two lines of code and create one object as opposed to two. What I am doing here is chaining function calls.

#Our previous code creates two objects
#obj = hou.node("/obj")
#cam = obj.createNode("cam", "cam_1080")

# Our combined lines
cam = hou.node("/obj").createNode("cam", "cam_1080") # Notice the dot between the two functions
cam.setParms({"resx": 1920, "resy": 1080})
cam.setDisplayFlag(False)

You may be thinking if you can also chain the setParms({"resx": 1920, "resy": 1080}) function call. You can chain the command and a camera will be created but let’s see what results are if you do this.

Chaining Commands
Chaining Commands

Notice the type of object that is being created and assigned to our cam variable. When we chain the setParms() function, we get back a NoneType object. However, when we omit the setParms() function we get back a node of type ObjNode. Why is this happening and what does it mean?

This NoneType is the type for the None object. It’s an object that indicates there is no value and therefore does not have any of the functionality an ObjNode has such as the  method setDisplayFlag(). Remember createNode() returns a hou.Node and setParms() does not return anything. Its only job is setting our object’s parameters. Since it’s the last function call in the chain, our cam variable receives nothing or None.

Lets check the difference between an objNode and NoneType with the python dir() function.

ObjNode and NoneType
ObjNode and NoneType

 

The key takeaway is if you are going to start chaining commands, be aware of the type of object or data you are getting back. Also, at a certain point, chaining can become unreadable. You have been warned. That being said, let’s do the same with the ROP node code.

#Our previous code creates two objects
#out = hou.node("/out")
#rop = out.createNode("ifd")

# Our combined lines
rop = hou.node("/out").createNode("ifd")
rop.setParms({"vm_renderengine": "pbrraytrace", "override_camerares": True, "camera": "/obj/cam_1080"})

Functions

Now that we have some more compact code, we can start to make the code a bit more modular and portable. We are going to take the code that creates and sets parameters on the camera and ROP node and place it in individual functions.

A function is a type of device that packages up a bunch of statements and allows you to run the code more than once and as many times as you want. Functions go by various names. Functions, subroutines, methods1, or procedures. At the end of the day, they accomplish the same thing. They are little black boxes that do some work. Imagine if you are programming a game and write the code to fire a weapon. You can copy and paste that code a million times or you can create a function with the code and call with one line. If you need to change the function code, you do it in one place as opposed to a billion places. I’ll cover functions more in depth in a future article.

Our functions will look like the following:

# 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"})

In Python, you use the keyword def followed by the name you want to give your function, opening and closing parentheses, a colon, and underneath, all your code indented at least one space 2. Please be aware indentation in Python is important. You usually use four spaces in Python.

If I wanted to create a function called move_all_points it would look like this:

def move_all_points():
    print('Hello Houdini! Move all your points.')

Everything looks dandy! If you were to start up Houdini now, it would appear we broke the script. Have a look at the code again and try to figure out why it’s not working. The code is perfectly valid. I’ll watch some TV while you figure it out.

Steve Austin

 

Calling Functions

Hopefully you saw the problem. Now that you wrapped up your code in functions, you need to call them. Houdini does not automatically call them for you. This is as simple as writing create_camera() and mantra_driver() after our function definitions.

# 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"})

create_camera()
mantra_driver()

Let’s go one step further since we are talking about functions. Why don’t we wrap our two function calls into a function called main() then call main()?

# 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()

Please keep in mind in a script this simple, this might be overkill, however, as your code gets more complex you want to think about how to structure your code. This not only applies to Python. This concept can also be used when working with VEX code. Have a look at some of the Entagma tutorials and see how they make use of functions in their code.

Pending Issues

If you start a new session of Houdini, our refactored script does its magic. Very nice. Have a drink to celebrate. Try creating a new scene. Wait! WTF! The script is broken. No, the script is fine. If you recall what I mentioned in part I, 123 scripts execute when you first launch a new Houdini session. Here we are creating a new file. The 123 script does not run when creating a new scene if Houdini is already running. For this we need to use the 456 start up script.

You’re going to be tempted to copy and paste the code to this 456 file. Go ahead. I’ll catch you in part III to discuss.

1. Methods are usually associated with Class objects but we are keeping it simple here.
2. Please Refer to Python best coding practices regarding this.

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.