Featured Artist: Erwin Santacruz, Florida

Python in Houdini

Building Python Shelf Tools

“Never be afraid to try something new. Remember that a lone amateur built the Ark. A large group of professionals built the Titanic.”

— Dave Barry

 

INTRODUCTION

 

My Journey

Unlike most Houdini users, I come to Houdini from a non-traditional route. My background has been mainly motion design in the broadcast world, but over the years I have worked on game development, interactive, UI design/animation, web design and development, film, commercials, music videos, and mobile development. Recently, I’ve been exploring the world of AR and VR. I’ve been fortunate to have worked with clients such as Nike, ExxonMobil, Adidas, Coca Cola, Vislogix & ATT.

One of the primary reasons that has allowed me to branch out to other fields was making an early decision to learn software development. While I enjoy the visual side of creating content, the programming side provides a different perspective from which to approach my work. In hindsight, I think learning how to code was one of best decisions I made. While I eventually went back for a Master’s degree to fill in knowledge gaps, much of what I learned came from books and online resources. When it came time to learn Houdini, having the programming background made it much easier to grasp some of the more complex aspects of the application. While it’s not necessary to be able to code to use Houdini, you will find it opens up a world of possibilities. 

 
Hello Python!

Python is a workhorse, plain and simple. It’s an interpreted, high-level programming language used for a plethora of programming applications. Python is known for its “batteries included” philosophy. This is because it includes a rich library that provides a wealth of functionality right out of box. Add to this the large repository of third-party libraries that help you write sophisticated code in less time. This is one of the many reasons I always recommend Python to those who are interested in learning how to code.

Python, believe it or not, is one of the easier programming languages to learn. Now, “easier” does not necessarily mean easy if you have never written a line of code in your life before. But among the various programming languages, Python is quite easy to pick up. The great thing about learning Python is that a plethora of software packages provide a Python API. Nuke, Maya, RealFlow, Cinema 4D, and of course our favorite application - Houdini. Moreover, Python provides a gentle introduction to core programming concepts. You can build some really sophisticated software with Python. It equips you with a strong foundation if you move up to other languages such as C++, C#, Swift, etc.

The best way to get started learning how to write Python scripts for Houdini or any application for that matter, is to actually learn some basic Python. More importantly, let’s start by learning a bit about programming fundamentals. Yes I know, fundamentals are not glamorous, let’s just jump into the cool stuff, right?

Once you learn the underlying principles, learning the syntax of most programming languages is the easy part. When you understand how to assemble programs using various libraries or pieces of code and program flow jumping to another language is not all that hard. Take for instance learning 3D as an example. Once you learn about X, Y, Z, navigation in 3D space and the terminology associated with 3D you can migrate to another 3D package quite easily. Yes, you have to learn the nuances of each program, but that’s usually not your biggest obstacle.

Houdini API

Houdini provides a powerful Python based API known as the Houdini Object Model or HOM.  The HOM provides the hou package which is the central module that exposes many modules, classes, functions and global variables. They give you the ability to extract data from Houdini as well as manipulate all sort of things within the application.

If you’re just starting out and have little or no programming experience, an API is a tough thing to chew through. The APIs are mostly written for developers, but Side Effects has really done a tremendous job with the HOM documentation. Like the rest of Houdini’s help files, the HOM API is beginner friendly. It also provides various examples to help you get started tweaking code.

One thing that is important to point out is that while Python in Houdini is nicely integrated, it’s best suited for automation and tool building. While You can manipulate geometry with Python, but if your intention is to wrangle or create geometry, it is recommended you look into VEX for these tasks. Having said that, once you learn Python, learning VEX is not all that difficult.

Houdini Tool Scripts

Python Tools

Python excels in automating tasks and creating tools. This is one area where you can make extensive use of the power of Python in Houdini. Houdini 16 introduced the ability to save .hip binary files as text in addition to being able to use file comparison tools such as Beyond Compare and Kaleidoscope or Git and Subversion for version control. Think about the ability to use GitHub to collaborate on Houdini projects. Using Python, you can create tools that help integrate these applications into new or existing pipelines or just help automate your own everyday workflow. Imagine creating a Python tool that integrates GridMarkets into your pipeline. From shelf tools to Digital Assets, there is almost nothing you can’t create in Houdini through Python. Let us look at an example on how to create a simple shelf tool using Python.

Setting The Camera View

When you manipulate the camera in Houdini, the camera position is not recorded unless you lock the camera to the view. This is a source of frustration to new users of Houdini. You select your camera and set the perfect viewpoint only to find out you never actually set the camera’s position. Even if you are an experienced Houdini user, sometimes you forget this important bit. Let’s see how to go about creating a Python tool that takes the current view’s transformation matrix and applies it to a camera. In other words, take whatever you see in the viewport and record it to the camera node at the click of a button.

Creating A Shelf Tool

Creating shelf tools in Houdini is quite simple. Head on over to the shelf area and click on the plus sign and select New Shelf.

 

Give the shelf a name and a label. The Label parameter is what your shelf tool will display and the Name parameter is the internal name. This has to be a unique name across your shelves. The Tools tab shows all the Houdini tools available to your script which you can add by selecting any one of them. We won’t worry about this section since we will be creating a custom tool.

By default, the shelf will be saved to a .shelf file in your Houdini preferences folder. This means the shelf will be available only to you. If you plan on sharing your shelf tools or you want to keep things separate and organized, you can save it to a unique .shelf file. As a side note, this .shelf file is an XML document which you can manipulate with Python. ;) Click accept after filling in the parameters. Now we have a nice empty shelf we can populate with different tools.

Adding A Tool

Right click on the empty area of the shelf tab that was just created and select the New Tool… option. This will pop up a new window with a few more tabs. The Options tab has similar parameters to the ones when creating a shelf with the addition of being able to add an icon. The icon should be a square sized 256x256 or 512x512 png, pic or SVG graphic. I find SVG to be the best option.

You will also see the Script tab, which is the most important because this is where we will be adding our Python code. Make sure the script language at the bottom of the window is set to Python. The Help tab allows you to add documentation and enable the tooltip that appears when you hover over the tool. The Context tab controls in which network contexts your tool is available. Finally, the Hotkeys tab is where you can assign a hotkey to your tool. Again, notice you can save the tool to your default .shelf file or to a .shelf of your choosing.

 

Hit Accept to make sure you save out the file and switch over to the Script tab. We are ready to add some Python code. You will be surprised that with just a few lines of code you can create some useful tools. This is due to the great HOM API Side Effects has provided.

Scripting The Tool

The Python Shell

While we can start typing code in the Script tab, Houdini provides an interactive Python Shell where we can test out code snippets to see if things work as planned or to test out a few ideas. To access a shell choose Windows ▸ Python Shell to open an interactive Python Shell window.

The nice thing about using the Python shell is that it provides code completion and the ability to drag and drop a node from the network editor into the shell to paste a fully written hou.node expression.  We will use the shell to try out our code before we commit the code to our shelf tool.

Desktops

First thing we want to do is get a reference to the current Desktop. This is the Desktop layout Houdini is using such as Build, Games, Technical etc. It can also be a custom layout. We do this with the following line of Python code:

desktop = hou.ui.curDesktop()

The curDesktop() function in the hou.ui module returns the desktop Houdini is currently in. We store this object in the desktop variable. If we print out the value of our variable using a print() statement, we can see that it is indeed a Desktop object along with the name of the layout. While there are more robust ways to debug Python code, using the print() function is a quick and dirty way to see what is happening with our code and checking if we’re getting the values we expect.

Pane, PaneTabs, Views & Viewports

Now that we have the desktop, we want to drill down into the interface. This Desktop we just accessed is composed of one or more panes. The panes themselves can contain one or several pane tabs of various types such as the Network Editor, Parameters tab, Spreadsheet or the Scene Viewer. We need to access the Scene Viewer to get information about the view.

You can Visualize this by going to Windows ▸ New Floating Panel. You’re going to see a panel open up that contains a Network View tab. Add a new tab or change the existing tab to a Scene View tab. The view within this tab is what we are after.  In order to do that we need to use the method associated with our desktop object named:

 

paneTabOfType(type, index=0)

This method belonging to the Desktop class takes two arguments and returns a pane tab of a specific type. The first argument is the type of tab we are looking for and the second is the index of the tab since there may be several of the same tabs. We will leave out the second argument since a default is provided. Our first argument will be an Enumerator. An Enumerator is a list of named values. Here we are grabbing an Enumeration of pane tab types Houdini provides. Our code should look like this:

desktop = hou.ui.curDesktop()

scene_viewer = desktop.paneTabOfType(hou.paneTabType.SceneViewer)

Print the scene_viewer object’s type to verify that you did get back a pane tab of type SceneViewer. This can be accomplished with the following line of code:

 

scene_viewer.type()

You should get back paneTabType.SceneViewer. The SceneViewer class contains various methods that deal with the prompting and selecting of geometry, snapping, and other functions relating to views.

What we’re are after here is the viewer’s current viewport. This viewport is usually the viewport under the mouse cursor. If we scan our documentation for the hou.SceneViewer class you will find the curViewport(self) method. This looks like what we need. The code so far looks like the following:

desktop = hou.ui.curDesktop()

scene_viewer = desktop.paneTabOfType(hou.paneTabType.SceneViewer)

viewport = scene_viewer.curViewport()

So far we have accessed the Houdini Desktop, dove in to access a pane with a Scene View tab and finally grabbed the current viewport. You are basically traveling down a hierarchy. There’s one last object we need to access. This object provides the methods that allow us to manipulate the viewport. If you inspect the viewport variable by printing it out in the console, you will see it now holds a reference to an object of type GeometryViewport.  Take some time to browse through the GeometryViewport class documentation.

Saving View State

Now that we have most of the pieces in place we are ready to write our line of code that does the magic. The method that should stick out is appropriately called saveViewToCamera(self, camera_node). This method saves the viewport’s current view transform matrix and applies it to the camera’s transform.

There is one important thing we need to do first. Can you guess what it is from the arguments in the saveViewToCamera(self, camera_node) method? Yes, we need to get a reference to our camera. Currently, we have no reference to a camera in our code. We don’t even have a camera in the scene. Drop a camera down anywhere in the viewport. Now our task is to access the camera in order to use it as an argument for our method.

Selecting Nodes

There are various ways to deal with the camera issue. We are doing a basic setup that will require the user to select the camera node in order to have the viewport's current view transformation transferred over to it. You can optionally prompt the user to pick and select a camera if you have several in the scene. I’ll leave that for reader exercise.

In order to select the camera, we will use the hou.selectedNodes() HOM function. This function returns a tuple of all the selected nodes in the scene. It will return an empty tuple if no nodes are selected. Think of a tuple as an box containing various items. If you wanted to loop or go through each selected node and print its name, you can write the following:

nodes = hou.selectedNodes()

for i in nodes:

    print(i.name())

If you wanted to get fancy and filter out one of the cameras in a scene you can use Python’s list comprehension feature.

cam = [i for i in nodes if i.type().name() == “cam”][0]

We don’t have to do any looping or anything fancy for our tool since we are selecting only one camera. Notice in the code below I’m extracting the first item from the tuple by adding the [0] right after the function call. This is a neat little trick you will see often in Python code. Just one problem when doing it this way. Try out the script for yourself. If you have a camera selected it will work as promised but if you don’t have a camera selected, Houdini will scream at you.

desktop = hou.ui.curDesktop()

scene_viewer = desktop.paneTabOfType(hou.paneTabType.SceneViewer)

viewport = scene_viewer.curViewport()

cam = hou.selectedNodes()[0]

So the question may be, how do you avoid this situation? Again, this task can be solved in several ways but we will us a common technique known as a try/except block to handle the possibility of the user forgetting to select a camera and crashing our script.

Error Handling

If you tried the script above without a camera selected, Houdini generated an Index Out of Range error while it was running. In other words, you are trying to access a value that does not exist. This type of error that occurs during execution is called an Exception.

Error handling in Python uses exceptions to catch errors through a try block and are handled in an except block. The idea behind a try/except block in Python or any other language for that matter, is to decide on how to proceed if your script or application crashes. A try/except block allows your script to clean up after itself and exit gracefully or continue running. Imagine if a user is working with your application and it crashes. I’m sure you’re familiar with this scenario. The user could lose all their work. However, if you safeguard your code, you can give the user the ability to save before the application crashes. That’s a great user experience despite the application crashing.

The try portion of the block handles the code that will be executed if everything goes as planned. The except portion will only execute if the code in the try portion fails. You can add the type of error you are handling right after the except statement. If you are handling several error types, you can separate them with a comma.

Have a look at the code so far with the added try/except safeguard. Notice how I snuck in a new function. The displayMessage function takes several arguments, however, You can just provide the text you want the user to see and call it a day. The rest of the arguments are handled by defaults. If you want to do a bit more customization or custom handling, then have a look at the docs for the hou.ui class. Try this version of the code with the added error handling. It should display a message if you forget to select a camera. Neat!

desktop = hou.ui.curDesktop()

scene_viewer = desktop.paneTabOfType(hou.paneTabType.SceneViewer)

viewport = scene_viewer.curViewport()

try:

  cam = hou.selectedNodes()[0]

  viewport.saveViewToCamera(cam)

except IndexError:

  hou.ui.displayMessage("Please select a camera.")

Cleaning Up

Finally, let’s clean the code up a bit by wrapping those first few lines of code inside a function and passing the selected camera as an argument. Functions group instructions or lines of code together by name. They help prevent duplication of code because the instructions are only found in one place. This allows us to execute those instructions at any time by calling the name given to that set of instructions.

Doing this is not necessary but it helps keep our code organized and modular. For example, we can pull this function out of this script and perhaps use it as part of another script that requires the same functionality. We can call the function that does the work inside our try statement.

The other thing I would suggest is to add some comments. Comments are not executed by Python. These are notes to ourselves and other folks who might be working with the code. Trust me, you will come back six month later to your code and not remember how it even works. In a simple script like this it’s not a big deal, but as your code gets larger and more complex, it’s a good idea to start commenting code. I think we are ready to transfer the code over to the Script tab. You can find the source code for the above tool on my GitHub.

def saveViewportToCam(camera_node):

    desktop = hou.ui.curDesktop()

    scene_viewer = desktop.paneTabOfType(hou.paneTabType.SceneViewer)

    viewport = scene_viewer.curViewport()

    # set camera

    viewport.saveViewToCamera(camera_node)

try:

    # camera selection

    cam = hou.selectedNodes()[0]

    saveViewportToCam(cam)

    hou.ui.setStatusMessage("Camera view set", severity=hou.severityType.Message)

except IndexError:

      hou.ui.displayMessage("Please select a camera.", severity=hou.severityType.Error, title="No camera selected”)

 

Conclusion

I know this was a quick run through with some advanced concepts using Python in Houdini, but hopefully it will give you an idea of what is possible. If you are serious about learning Python, I encourage you to study some of the resources available online and be persistent. Start simple and work your way up to more complex examples. Python is a great tool to have under your belt. One last note: You will read about installing IDEs and all kinds of fancy software to get started, but don't worry about any of that for now. Houdini provides its own built in Python interpreter. No need to install anything.

If you want to dig a bit deeper, Python and a simple text editor is all you need. If you are on Mac OS X, then you are all set. Just open up a Terminal and type in python. On Windows, install Python if you don't have it already installed, and open up a command prompt. If you need a text editor, Sublime Text or Atom are available for OS X and Windows. Make sure you don’t use something like MS Word. Good luck and thanks for making it this far!

If you want some more Houdini resources, visit my website HoudiniTricks.com, where I post various Houdini tips, or follow the @houdiniTricks Twitter account.

You can also follow my personal Twitter account @990adjustments or visit my personal Website 990adjustments.com.

Erwin Santacruz 2017

 

By: Patricia Cornet
GridMarkets marketing

GridMarkets USA
Presidio of San Francisco
P.O. Box 29920
San Francisco, CA  94129

LINKS

SERVICES

ABOUT

SOCIAL MEDIA

  • White Facebook Icon
  • White Twitter Icon
  • LinkedIn
  • White YouTube Icon
  • White Pinterest Icon
  • White Instagram Icon

© Copyright 2019 by GridMarkets All Right Reserved.