The Interactive & Immersive HQ

Python vs TouchDesigner Operators

Should you use Python or native TouchDesigner operators to perform a task in TouchDesigner? It feels like an eternal question with no clear answer! Some pros say “it doesn’t matter” and some say “operators always!” so which is it? In this post, I’ll dive into the thought process behind these decisions so you can make the best choice for your unique situation.

Preface: Python vs TouchDesigner Operators

As with anything in TouchDesigner, the context is important. There are tons and tons of ways of achieving any particular thing in TouchDesigner and often a bit of inefficiency is alright if it makes the overall workflow or project flow better, in my opinion. To help demonstrate the thought process that pros go through when deciding what to do in Python and what to do with native operators, I wanted to walk through how I would approach this in the context of generating a random noise value. This question stems from a discussion we had in The HQ PRO Facebook group and it was particularly fruitful to share.

The Good of Operators

Language of execution

Let’s start with the first line of answering this question, which is why do so many pros recommend doing as much as possible with just native operators? This stems from a simple fact that behind the scenes, all operators are either C++ or GLSL code with a nice set of parameters we can tweak. These two languages will generally be faster than Python when it comes to raw execution. This is especially the case when it comes to crunching numbers and doing signal processing with CHOPs, as the C++ code behind the scenes will generally be faster than writing a Python script to do the same thing.

Going back to our example, theoretically speaking, generating a noise value through a Noise CHOP would then be faster than generating that value with Python, because the Noise CHOP’s C++ backend would be able to do this faster.

Visual nature

On top of that, one of the things that can be taken for granted is the visual nature of native operators. I’ve seen countless Python scripts that new TouchDesigner developers try to write to do complicated things because the developer doesn’t feel confident in building a network of operators. More often then not, there’s an error somewhere in the code that they can’t track down. Does that sound familiar? We’ve all been there! More often than not, it’s a simple error that would have been tracked down easily if the same processing was built with native operators because then you would be able to see every step of the pipeline in their viewers. On top of that, parameters are amazing! They expose all kinds of useful functionality that we can access with sliders, buttons, and value fields. This makes exploration of functionality a fun and intuitive process.

In terms of our example of generating noise, this might not really be that big a factor, although I’d argue if I wanted to generate noise with a certain kind of movement or behaviour, doing so with the parameters would be easier than me reading Python documentation on noise generators.

Context crossing

The final piece of the puzzle that doesn’t get talked about enough is that it’s an expensive process to cross the Python <-> operator bridge. I feel that most developers imagine Python is all-encompassing inside of TouchDesigner because it runs everywhere and feels so native. A better way to imagine things is that Python is an infrastructure of it’s own running in the background and TouchDesigner is flawlessly moving data between itself and Python in real-time. This movement of data between the Python context and native TouchDesigner elements can be quite processor intensive. You’ll often hear pros mention that holding large amounts of data is more performant with Python Storage than it is writing all that data to a table. It’s not that the Table DAT is inherently slow, but the act of Python accessing the operator, accessing the cell, and updating it’s value is much more expensive (comparatively) than it is for Python to keep the same value inside of Python Storage and update it.

In the case of our example of generating noise, I would ask myself “what do I want to do with this generated noise value?” If the answer is to feed it into a CHOP chain that has further processing, I might consider doing the noise generating in a CHOP because then I don’t have pass my data from my Python context into a CHOP.

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.

The Good of Python Scripts

So far it sounds like native operators are head and shoulders better than writing Python scripts! Why would we ever want to use Python then?

Easier Logic

I think for many developers who are brand new to node-based development environments, it is much easier to program complex logic in a script than it is to create a network of nodes. This could be logic when it comes to how to process the data as there’s a world of StackOverflow articles about generating different kinds of noise data. This could be state machine logic for what to do based on the random number generated. Whatever the task is, it’s not uncommon that Python can get the job done faster for artists that are new to development in general (and it can help when creating trail effects too).

This is particularly true in the case of our noise example. If I wanted to generate a random value and overwrite a different parameter based on it’s value, this would be simple to do with an if statement in Python, whereas this could be a bit trickier to do in a network of operators.

Staying inside of Python

As you get further and further into your TouchDesigner career, you’ll likely notice your Python scripts start to get more and more complex. Especially as you create larger and larger projects and require different forms of state machines or communication pipes with the external world. Developers these days are turning to Python Extensions to house lots of related bits of code and functionality into a single place that is easier to manage and more intuitive to work with programmatically. Because of this, it can also make sense to sometimes to do things inside of Python just so that you can fully stay inside Python! This goes back to the bridge crossing we mentioned above. Extensions can start to become needlessly complex if every single method/function you’re writing needs to read/write from a different operator in the network. Especially if you start doing a lot of operations quickly, this can even lead to buggy software.

Generating noise is a perfect example of this. Even though we mentioned it was likely faster to generate noise with a Noise CHOP because of C++ (theoretically of course), if I’m generating the random value because I need it in the middle of an extension, it’s almost certainly better I just generate my value inside of Python as opposed to generating it in a Noise CHOP and referencing it from my extension.

Controlling operators

We mentioned previously that the context crossing can be an expensive process. Even with that said, in many cases it can be easier and more intuitive to control operators/parameters with Python than it can be with expressions. It can also have optimization benefits to not have Python references to operators active on operators that are cooking every frame if you only need to update the parameter values periodically. In those cases, having a Python script that writes a single constant value once to an operator’s parameter is more efficient.

In the case of our noise value generation, I’d ask myself “what do I want to do with this value?”. As we mentioned above, if it was feeding into a chain of operators, I’d keep it inside of the operator family, BUT if I’m not feeding that value into a series of other operators and it’s mostly being used for controlling my network or changing a parameter at a specific moment, I’d almost certainly use Python.

So what do I do? Python or TouchDesigner?

Don’t worry! All hope is not lost. At the end of the day, if a project is working well and holding a solid framerate, I’d consider it a success and I wouldn’t overthink things. But if you’re growing your skills and exploring new techniques and workflows with Python in TouchDesigner, the best thing you can do is think through things and discuss with your peers. Start to think about things like how your Python code might interact with the project, where the data you’re generating is going, and what your overall functionality goal is. Answering these questions while thinking about the topics we posted above will start to point your compass in the right direction, and that’s most important. Even pros will build something only to realize there’s a better way later, so as long as your compass is pointing in the right direction and you continue to grow your skills, you’ll move towards optimal workflows.

Wrap up

Whether you’re a Python pro or a new developer just getting into Python in TouchDesigner, you’ve likely asked yourself if you should build something with native operators or a Python script. Unfortunately, there’s no one magic answer that will be appropriate for every situation and my hope is this blog post will give you a behind-the-scenes look into some of the things pros are thinking as they’re evaluating the same tricky questions that you are. Enjoy!