Troubleshooting Python In TouchDesigner

We’ve all been there. Deep in a few scripts and barely hanging on to what we understand about Python as we try to wrangle a strange set of features together. And then BAM. Errors. Sometimes these errors are easy to deal with, but often times they are elusive and tricky to debug without a few helpful bits of info. In this post, I’ll share a troubleshooting tip I’ve learned over the years to help you move past your bugs more quickly and easily.

Know your type

This is probably the most useful thing to keep in your back pocket. It’s also the troubleshooting strategy I see used least frequently. It all revolves around printing the type of the data you’re working with. Why would this be helpful? Well let’s look at a quick example.

Example setup

Below I have a simple example of a script that is taking a custom string parameter where I’ve typed a Python list into, and then uses a nice built in library called ast to convert the string representation of the list into an actual Python list (and not just a string that looks like a list). It’s a pretty common procedure when working with data that moves through different systems. It looks like this:

The code in the script is below for reference:

import ast

my_val = op('container1').par.Myvalue
new_list = ast.literal_eval(my_val)
print(new_list)

Seems pretty straight forward. We import ast. Then we get the parameter we want the string from. We feed that to the ast.literal_eval() function to convert it back into a real Python object. Then we want to print it. The problem is that if you run this you get a long error that says:

Traceback (most recent call last):
  File "</project1/text1:op('/project1/text1').run()>", line 1
td.Error: File "/project1/text1", line 4
  File "C:\Program Files\Derivative\TouchDesigner.2021.13610\bin\lib\ast.py", line 91, in literal_eval
    return _convert(node_or_string)
  File "C:\Program Files\Derivative\TouchDesigner.2021.13610\bin\lib\ast.py", line 90, in _convert
    return _convert_signed_num(node)
  File "C:\Program Files\Derivative\TouchDesigner.2021.13610\bin\lib\ast.py", line 63, in _convert_signed_num
    return _convert_num(node)
  File "C:\Program Files\Derivative\TouchDesigner.2021.13610\bin\lib\ast.py", line 55, in _convert_num
    raise ValueError('malformed node or string: ' + repr(node))
ValueError: malformed node or string: type:Par name:Myvalue owner:/project1/container1 value:[0, 1, 2]
Results of run operation resulted in exception.

What could be happening here?

Everything in our code seems straight forward enough. If we add a print statement before our ast.literal_eval() line so that it looks like this:

import ast

my_val = op('container1').par.Myvalue
print(my_val)
new_list = ast.literal_eval(my_val)
print(new_list)

We would see that my_val is printed as [0, 1, 2] in our textport. Whenever I encounter an issue like this. My first thought is to check the type of the data and ensure I’m holding what I actually think I’m holding. I can edit the print(my_val) to be print( type( my_val ) ) like below so we can actually check what kind of data is in there:

import ast

my_val = op('container1').par.Myvalue
print( type( my_val) )
new_list = ast.literal_eval(my_val)
print(new_list)

When we run this, our result for the first print is now <class ‘td.Par’>. What is that? Shouldn’t it be a string? Since after all it’s a string parameter and every time we want to access that value in TouchDesigner we get the string returned to us. Why not now?

Get Our 7 Core TouchDesigner Templates, FREE

We’re making our 7 core project file templates available – for free.

These templates shed light into the most useful and sometimes obtuse features of TouchDesigner.

They’re designed to be immediately applicable for the complete TouchDesigner beginner, while also providing inspiration for the advanced user.

What’s a td.Par?

In this particular case it comes down to the automatic casting that happens behind the scenes in TouchDesigner. Technically, when we type something like op(‘container1’).par.Myvalue we aren’t accessing a value, we are accessing a parameter class object, or par class for short. This par class object actually have a ton of data inside of it which you can see if you go to the wiki page for par class below:

https://docs.derivative.ca/Par_Class

What’s nice about TouchDesigner is that it’s generally smart enough to know that even when you only use something like op(‘container1’).par.Myvalue that likely what you want is the value of it, so it automatically returns the value. But the trouble here is that this isn’t completely bullet proof. You might see these kind of issues when it comes to working between TouchDesigner’s Python implementation and the Python libraries or 3rd party libraries you might be importing into scripts. In a lot of these cases the automatic casting doesn’t quite happen. So instead of us getting the string value and feeding it to ast.literal_eval(), we’re feeding it a par class object that ast has no idea how to deal with.

Solution?

There’s clean cut solution to this unfortunately. One suggestion is to try to be as explicit as possible when you’re programming in Python in TouchDesigner. For example, in the cases where we want the value of a parameter, instead of making it a habit to just use:

op('container1').par.Myvalue

We could instead access one of the many members of the par class which specifically and absolutely target and return the value to us, which you’ve likely seen used here and there:

op('container1').par.Myvalue.val

Yup! Just adding a .val could save you a lot of trouble in the future! I’m notoriously picky about my code being as explicit as possible. While the TouchDesigner automatic casting is great for new users and helping making working with Python a bit easier, once you get into heavier project work, working with external libraries, and similar, you’ll likely want to be more explicit.

What about the type() though?

After all that is said and done, why is type() so important to get it’s own blog post? Well, it’s part of your detective toolkit. Most developers aren’t going to know the deep intricate ins and outs of how different platforms deal with data conversions behind the scenes. So the only way you could even start to pick up on these patterns and solve your actual issues when they happen is by having tricks in your toolkit that can help you determine why you have an error. For a lot of the common tricky errors I see people encounter in TouchDesigner, type() is a great tool to have in your detective kit.

Wrap up

We all know there’s nothing more annoying that trying to debug strange Python errors. While there are many pieces of knowledge advanced developers have to get around them, for many general developers there aren’t a lot of resources or tools you learn to use in your practice. Checking the type of your data as it’s passing through the different areas of your code can be an easy and quick way to get easy issues like data types out of your way.