Python in Houdini

Python Parameter Expressions II: Custom Functions

Did you know Aaron Paul from Breaking Bad fame once appeared on the game show The Price is Right? No joke. It just goes to show anything is possible. I wish I could be on The Price is Right. You know what else is possible? Using your own custom Python functions in Houdini parameter fields. How might this be handy? Well, imagine you created a custom random or noise algorithm or maybe you developed a set of custom utility functions. Sounds helpful doesn’t it? Let’s see how we can go about creating them.

You can find the other parts here:

Setup

So we have a line that we want to jitter the points on. Simple enough. There are various ways to do this in Houdini but we will create a custom function that you can impress your friends with at the Homecoming dance. Who knows, you might even get selected as the Homecoming king!

Again, we’ll do something contrived just to demonstrate the idea. This is by no means award winning or the best way to accomplish the task. The best way to learn how to code is by actually coding. You can’t learn this stuff through osmosis.

Drop a Line node with about 100 points in the X direction. Then, attach a Point node to it.1

Line setup
Line setup

 

The idea is we want to randomize the points in the Y direction but do not want to affect the first and last points. These points should remain with a Y value of zeroRight-mouse click on the the Y position parameter field and convert the expression to a Python expression. While your mouse cursor is still on that parameter field, open up the Expression Editor by pressing Command(CTRL) + e.

We want randomness, so the best place to start is just by importing the Python random module. Python has done the hard work for us already. Why paint a yellow banana yellow again. Let’s play around with the different Python random algorithms.

Getting Help

One of the nice things about using Python in Houdini is that you have access to the Python Standard Library. This is an extensive list of Python modules that offer a wide range of facilities. Want to do object serialization inside Houdini? No problem, import the pickle module. Hold the mustard. For now, let’s just play with something simple.

Start by first importing the random module. You should have the Houdini Expression Editor open. Type in the following:

import random

The random module is fairly simple to use. All you have to do is call random.random() and you will get back a random number in the range [0.0, 1.0). This means the random number will be between 0.0 and 1.0. It will include 0.0 but never include 1.0. We can leave it at that and head out to the homecoming dance but we are going to dig a bit deeper. After all, it’s custom ™

We’re going to play around with some of the different methods in the random class. Namely normalvariate() and gammavariate(). You may be asking where did I find those and what do they do? To answer the first question, open a Python shell by going to Window then selecting Python Shell.

Houdini Python Shell
Houdini Python Shell

 

Type in the same import statement in the shell as you did in the Expression Editor, import random, then hit Enter. If you recall in the last post, we used the dir() method to inspect the module. I’m going to introduce another method that gives you more information. Type the following statement after import random:

help(random)
Python help function
Python help function

 

You will see a bunch of text scroll by. Scroll through it and you will see the various classes and methods the random class provides along with documentation. The help() function just invokes the built-in help system. Just remember this function is intended for interactive use.

Python Random Module

So back to our Random class. Why did I pick these two particular methods? No reason. It’s just to experiment and see if something different came out of these methods. The normalvariate() and gammavariate() methods are used for non-uniform distribution applications that usually model real world situations. For instance, normalvariate() is used for the analysis of astronomical data. Is this overkill? Of course it is, but again, experimentation can sometimes lead to wonderful discoveries.

Ok, let’s head back to the Expression Editor. One thing you want to remember is any Python code you write in the editor is treated as a function that needs to return a value. Type the following code after your import statement then hit apply.

return(random.random())
Python random() function
Python random() function

 

Hey look! It works! That’s cool but let’s try out those other methods we talked about. Replace the previous return statement with the following:

return(random.normalvariate(1,2))
Python normalvariate() function
Python normalvariate() function

 

Now lets try using the gammavariate() method passing in the previous values.

return(random.gammavariate(1, 2)
Python gammavariate() function
Python gammavariate() function

 

I’m just entering arbitrary numbers but try entering different values and you’ll get interesting results. The method gammavariate() sends our points upward in the positive direction and normalvariate() gives us points going in positive and negative directions. Let’s bring our points back down a bit by dividing by the number of points. We can use our the handy local variable $NPT which returns the total number of points. Remember how to use local variables in Python? We need to use the method lvar() and pass in the local variable. You don’t have to use $NPT, experiment with different values if you like. I like the output of the gammavariate() method so let’s stick with it. Type in the following:

import random

return(random.gammavariate(1,2)/lvar("NPT"))
Modifying our random function
Modifying our random function

 

So we have this working! Notice that every time we hit apply in the Expression Editor you will get back a different result. Now we want to keep the first point and the last point from being affected by the noise. We can code this by checking what point is being processed and skipping it but here us an alternate way. Drop a Group node before the Point node. Give the group a name and set the Entity to Points. In the Pattern field enter:

0 $N

This gives us a Point group containing the first and last point. Why didn’t we just enter 0 and 99? Well, what happens if we add more points to the line? The Group node won’t contain the last point anymore. You would have to remember to go in and change the pattern values. By using the local variable $N, which gives us the number of points – 1, we keep things procedural.

Now here is the trick! On your Point node, set the Group parameter field to whatever you called your group. Notice how this is totally not what we want. We want the inverse. Add an exclamation mark to the front of the group name and like magic we clamped our first and last points.

!firstLast

Wait, what does this step have to do with Python? Absolutely nothing. I was just adding a little spice to the task. Sort of like when you add curry to your Quinoa. Try changing the number of points on your line and see some procedural goodness. Basic but still very cool. It’s definitely cooler than that 90s remake of The Twilight Zone.

Setting the group
Setting the group

 

Our jittered line ready for action
Our jittered line ready for action

The Future

In our next installment we will learn how to save these custom these functions to a library and simply call them from our Parameter Fields while being able to pass a parameter or two. Keep it procedural!

1. Yes, we can use Wrangles and VEX and it would be better.

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.