Creating Attributes with Python in Houdini

Creating Attributes with Python in Houdini

A few questions have rolled in regarding the Mograph Multishader #QuickTip. Remember we were grabbing a bunch of images from a folder to randomly texture some geometry. One question in particular was how to have Houdini automatically determine the number of images in a folder? This would require we query the folder somehow and store the data in some variable or attribute. What better way to do this than with our friendly neighborhood programming language, Python. In this #QuickTip, we will be creating attributes with Python in Houdini to help us determine the number of objects in a folder and read this information in a Attribute Wrangle.

Quick Recap

Recall Mograph Multishader Setup in Houdini, we had some VEX code taking in the number of images and creating a random value to feed into our Material SOP.  Although we were able to set the number of images, we still had keep track of them. This is not really an ideal way of handling things especially if you were handing off the scene to someone else. What if you add more images or objects to the folder? You have to remember to update the value. It just becomes a mess.

Enter Python

What we want to do is have Houdini automatically read the number of images or objects in a directory. We can create a detail attribute to store this value and use it in the VEX snippet. The reason we want a detail attribute as opposed to a point or primitive attribute is because we just need a single computed integer value. In other words, we want a global attribute. That’s it? Yeah, easy enough. However… How do you read file stats with VEX?

 

I searched a bit to see if there were VEX functions to read file stats and found a few but not exactly what I needed to make this work. Sure, you can load in some C headers and do it that way but we won’t do that today. Way too much work. In Houdini we have access to a Python SOP so why not use the vast modules Python provides. We will use the Python node to read the directory and create an attribute to store this value. Then we can use the attribute in our VEX code. Yay!

Yay!
Yay!

Getting The Number of Images with Python

Drop down a Python SOP. You will need this node before the Wrangle. By default, there is some Python code already in the node. The first line of code is referring to the currently cooking node. The second line uses this object reference and grabs the nodes geometry object. The next few lines are just comments. Let’s write some code.

Default Python SOP
Default Python SOP

 

We need file I/O functionality from Python. We can do this by importing the OS module. This module provides a nice and portable way of dealing with operating system dependent functionality. I’ve deleted all the default code in the node since I’m going to create a little shortcut to access the geometry object. Here is our first line of code:

import os

Technically, the two lines Houdini gives you are fine. You can use those two lines and it will work as well. I’m just going to do the same thing in one line. Fancy, I know. This is a preference. The default code is considered better style since it is easier to understand. I’ll stick to my one line. The difference is that I’m chaining the function calls. So after I get back the object returned by pwd(), I immediately call its geometry() method.

import os

geo = hou.pwd().geometry()

Next, I create a String variable to store the directory path where the images or objects are located. Notice I’m using the $HIP environment variable. Your project should already be set for this to work.

import os

geo = hou.pwd().geometry()

dir = "$HIP/tex"

Now we need to get the objects in the folder. This is where our OS module comes in. Using the OS module’s listdir(path) method, we can get a list containing the names of the entries in the directory given by path. Path is going to be the dir variable we created storing the path to the images or objects.

import os

geo = hou.pwd().geometry()

dir = "$HIP/tex"
images = os.listdir(dir)

The images variable stores a list of images. However, this does not tell us how many images are in the list. Not a problem. Just use Python’s len(s) function to get the length (the number of items) of an object. This will tell us how many images are in the folder. We can do this in one line to avoid creating another variable. I also renamed my variable to reflect its purpose.

import os

geo = hou.pwd().geometry()

dir = "$HIP/tex"
numimgs = len(os.listdir(dir))

# One way of skipping mac data files using list comprehension
#numimgs = len([name for name in os.listdir(dir) if name != ".DS_Store"])

We can now move on to creating a detail attribute that we can use in our VEX wrangle. The last thing I want to point out is we are not taking into consideration issues such as the type of images or objects or if anything actually exists in the folder. Also, on a OS X, you get an invisible metadata file called a .DS_Store. Reading these in, which our code does currently, will skew the values. I’ll leave error handling as a user exercise. In a production environment you want to make sure you address these various issues. In the code above, it shows one way to deal with .DS_Store files on OS X.

Handle your errors!
Handle your errors!

Creating Attributes with Python

Now for the good stuff. Creating attributes with VEX is easy. In Python it’s not as straightforward as writing something like  [email protected]. Creating attributes requires we use some methods provided by the hou.Geometry class. The method we are after is addAttrib(type, name, default_value, transform_as_normal=False, create_local_variable=True). I know, it’s a long one but most of the arguments have defaults, so you only have to worry about a few of them. This method will create a new point, primitive, vertex, or detail attribute. The detail attribute is also known as a “global”.

The main arguments you want to address are the type of attribute, the name of your attribute and the value. Those are the first three arguments in our method respectively. This method is called on a reference to a geometry object. We already have one in our variable geo that we got back by the line hou.pwd().geometry().

import os

geo = hou.pwd().geometry()

dir = "$HIP/tex"
numimgs = len(os.listdir(dir))

# One way of skipping mac data files using list comprehension
#numimgs = len([name for name in os.listdir(dir) if name != ".DS_Store"])

#Create global or detail attribute
geo.addAttrib(hou.attribType.Global, "numberOfImages", numimgs)

You can see we pass in the type of attribute which is hou.attribType.Global, the name we want to give this attribute which is numberOfImages, and the default value which is the number of objects in the folder obtained through our variable numimgs. That’s it! Your global attribute is created. Check the Geometry Spreadsheet under the Detail tab to make sure you are getting the value you expect.

Detail attribute
Detail attribute

 

Accessing The Global Variable in VEX

Last thing we do is replace all the instances of the hardcoded or slider value with our Python created attribute. You don’t have to do anything special. Just use the regular attribute syntax. You can also access the attribute in parameter expressions if so desired.

// numimgs is the detail attribute we created via the Python SOP
[email protected] = int(fit01(rand(@primnum * imgSeed), 0, numimgs) % numimgs);

I’ll try to figure as an exercise for myself how to stat the files with pure C. Should be a fun exercise but probably not practical when we have the power of Python and just makes thing much easier. Until next time. I need to go buy some Journey concert tickets now!

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.