Having the flexibility to write expressions in reference fields for an operator is an incredibly powerful feature in TouchDesigner. This can free you from the necessity of doing computation outside of your reference, making for much tidier networks and programming. However, if you’ve ever tried to write a long expression, or a multi line logical test you might find yourself scratching your head about how to best make that work. Today we’ll look at how you can write a function as a module which you can then call in a parameter. First we’ll take a look at the basic pieces of this idea, and then we’ll explore what that might mean as a more complex exploration of this kind of idea.
One of the primary ingredients to this approach is to understand the basics of writing your own functions in Python. If this is new to you, start by look over these other examples:
Before we get in too deep, let’s first write a simple function in a text DAT, and see how that works in TouchDesigner. Let’s open up a new network, and add a Text DAT to our empty project. Now we’ll write a simple function. Write this following, or copy and paste, into your text DAT:
def simple(): print( 'This is Hard coded text' ) return
Great, but what is all of this? First we defined our function with the name simple and then we indicated that there aren’t any parameters that we want to change in the function by leaving our parentheses empty. Inside of our function we’ve written a print statement that is hard coded into our function – that is to say that we can only change this text by re-writing our function. If you try to run this text DAT as is, nothing should appear in your text port. What gives? Well, we’ve defined our function, but we haven’t actually asked TouchDesigner to run it – to do that, we need to call the function. We can do that by adding one more line of code to our text DAT:
def simple(): print( 'This is Hard coded text' ) return simple()
Our last line calls the function we just defined. Now, if you run this DAT, you should see our text print in the text port.
Congratulations, you’ve written your first Python Function here in TouchDesigner. This is all well and good, but lets push a little harder. Let’s change this to be a simple multiplication operation. Let’s make a simple function that converts feet to centimeters. If we do a little googling, we can quickly find that 1 foot is equal to 30.48 centimeters. Imagine we want to feed this function feet as a unit, and get centimeters back.
This time we can write a function that looks like this:
def simple( feet ): centimeters = feet * 30.48 print( centimeters ) return
This prints out centimeters, when we input feet:
This is pretty good, but let’s look at two other ways to get information out of this function. Rather than just printing out a float (floating point number, a value with numbers after the decimal), what if we wanted this to print out a full statement like, “X feet is Y centimeters.” Let’s change our function so we can make that happen:
def simple( feet ): centimeters = feet * 30.48 print( '%s feet is %s centimeters' %(feet, centimeters) ) return
Alright, this is all well and good, but let’s consider that we just want to return a value, that we can then plug into something else. To do this, we’ll change our function, and then write a print command that integrates our new function. Here’s our new function:
def simple( feet ): centimeters = feet * 30.480000 return centimeters
We can read this as completing the operation, and then returning the variable centimeters. We can now add the following line to our text DAT to actually print out our conversion string:
print( '%s feet is %s centimeters' %( 2, simple(2) ) )
Alright this is an awful lot of simple function writing, but not much integration work, what gives? Now that we have some fundamental pieces of function writing under our belt, let’s look at what this means for using functions in parameter references.
Let’s start with a simple case where we hard code a value into our function. Let’s start by adding a text DAT to our network, lets also make sure we give it a unique name – I’m going to call mine simpleMod1. Here’s our simple function:
def simple(): a = 100 return a
We should have something like looks like this in our network:
Great, now we just need to reference this in a parameter. Let’s add a constant CHOP into our network, and write the following reference:
Here we can see the syntax is:
- me.mod – we’re asking for a module
- simleMod1 – the name of the text DAT we’re referencing
- simple() – the name of the function in the text DAT
Looking at this we should be excited to realize that we could write multiple functions in a single text DAT, and call them by name – we wont’ look at that here today, but feel free to experiment on your own. As long as everything’s working, you should now see that you’ve successfully retrieved your value from your function in your constant CHOP. You should have something that looks like this:
Whew! Alright, we’ve now successfully written a function, that we’re retrieving in a constant CHOP. This, however, is highly impractical. You would probably never retrieve a constant value from a module like this – that said, it’s a helpful for us to see some of the fundamental elements that go into making something like this work. Now that we have some general concepts down, lets look at a few more complex examples.
We might imagine a circumstance where we want to perform a set of computations on values in a table, and then return them in a parameter. Let’s start by laying out all of the operators we need, First let’s add a Table DAT, a text DAT, and a constant CHOP. I’m going to name my text DAT simpleMod10. Let’s keep our initial function, but make one small change. Let’s change it to accept a parameter called val. That means our function should now look like this:
def simple(val): a = 10 * val return a
Now we’re ready to start cooking. In our table, let’s add a second row, and place two values in the two rows. You should have something like this:
Now, we’ll write an expression in a constant CHOP that passes in a reference to these two cells to our function. In our constant CHOP, let’s write the following expression:
me.mod.simpleMod10.simple( op('table2')[0,0] )
Looking at what this might mean in English, we see that we’re calling the module simpleMod10 and the function called simple() we can also see that we’re passing it a value from table2 and the cell in row 0, column 0. Let’s do the same thing for the next row in our table. This means we should have something that looks like this:
We’ve got simple referencing and value passing down, now let’s consider the circumstance where we want to use a slider in a control interface, but instead of a constant float we instead want to only get 4 values. From 0 – 0.25 we want the value 10, from 0.25 – 0.5 we want the value 15, from 0.5 – 0.75 we want the value 20, and from 0.75 – 1.0 we want 25. First let’s start by adding a few things to our network. First we’ll need a text DAT, a slider COMP, and a constant CHOP. Let’s write our function in our Text DAT. I’m going to name my Text DAT simpleMod8. We can use some simple if and else if statements return different values based on our ranges. With that in mind our function should look something like this:
def simple(val): if val < 0.25: a = 10 elif val < 0.5: a = 15 elif val < 0.75: a = 20 elif val <= 1.0: a = 25 return a
Next, in our constant we’ll use the following expression to make sure that we pass in the value from our slider:
me.mod.simpleMod8.simple( op( 'slider1/out1' )[ 'v1' ] )
There we go, we should now end up with values that we’ve specified based on a logical test in our function. You should have something that looks like this:
Returning values is excellent, but we can also return strings using this same method. Let’s set up another similar example. In this one we’ll need a slider COMP, a Text DAT, an Eval DAT, and a Text TOP. Let’s start with our function from the last example, but change our returned values to strings.
def simple(val): if val < 0.25: a = 'apple' elif val < 0.5: a = 'kiwi' elif val < 0.75: a = 'orange' elif val <= 1.0: a = 'grape' return a
Now in both the text field of our Text TOP, and in the expression field of our Eval DAT let’s use the following expression:
me.mod.simpleMod9.simple( op('slider2/out1')['v1'] )
We should have something that looks like this, and as the slider moves returns strings:
Alright, last but not least, let’s look at how we might use this same idea to test strings and return values. This time around we’ll want a Text DAT, a field COMP, and a constant CHOP. I’m going to name my Text DAT simpleMod7. Using the same ideas we’ve explored already, let’s write a function that tests our field COMP’s output and returns an integer.
def simple( val ): if val == 'kiwi': a = 15 elif val == 'apple': a = 10 else: a = 0 return a
This should feel like a pretty routine drill now, let’s set our constant to use the module and pass in our field value with the following expression:
me.mod.simpleMod7.simple(val = op( 'field1/out1' )[ 0,0 ])
With everything up and working, you should see something like this:
The way our function works, if we type ‘apple’ or ‘kiwi’ into our field, we’ll see 10 or 15 returned. In all other cases, we’ll see 0.
If you want to see a set of examples head over to GitHub and look through the source examples.