Of course! This is a fantastic question. Using Python in TouchDesigner is a powerful way to extend its capabilities, automate tasks, and create complex, custom logic that would be difficult or impossible with the built-in nodes alone.

Here’s a comprehensive guide to Python in TouchDesigner, broken down into key concepts, where to use it, and practical examples.
Why Use Python in TouchDesigner?
TouchDesigner's node-based paradigm is incredibly visual and fast for prototyping and many common tasks. However, Python excels where you need:
- Complex Logic: Handling intricate state machines, complex mathematical calculations, or parsing data structures.
- External Communication: Easily integrating with web APIs (like OpenAI, weather services), databases, or other software over TCP/UDP or serial ports.
- Text Processing: Manipulating strings, parsing JSON/XML, or reading/writing files.
- Automation: Creating custom tools, managing large networks, or automating repetitive tasks.
- Object-Oriented Programming: Organizing complex logic into reusable classes and objects.
- Leveraging Libraries: Using the vast ecosystem of Python libraries like
requests,numpy,Pillow, oropencv-python.
Where to Run Python in TouchDesigner
TouchDesigner provides several places to run Python, each with its own use case.
| Component | Where to Find It | Best For... | Execution Context |
|---|---|---|---|
python TOP |
In the network editor. | Real-time, per-frame execution. Manipulating pixel data, running a loop for each pixel. | Runs on the GPU (if available) or CPU. Very fast for pixel operations. |
script CHOP |
In the network editor. | Channel-based data. Reading/writing channels, analyzing or generating audio/MIDI data. | Runs on the CPU. |
text DAT |
In the network editor. | General-purpose scripting. Good for organizing code, constants, or helper functions. | Runs on the CPU. |
panel Execute |
Right-click a panel > Run Script. |
UI-specific interactions. Customizing buttons, sliders, and other widgets in a panel. | Runs on the CPU. |
op Execute |
Right-click any operator > Run Script. |
Quick, one-off scripts to modify that specific operator or its parameters. | Runs on the CPU. |
| Python Editor | Ctrl+Shift+P or Window > Python Editor. |
Debugging, testing code snippets, and running longer scripts without a UI component. | Runs on the CPU. |
| TouchDesigner Python Module | From an external Python script or terminal. | Batch processing, rendering animations, or building tools outside of the real-time environment. | Runs in a standalone Python interpreter. |
The Core: The op() and parent() Objects
The most important concept to understand is how your Python script talks to the TouchDesigner network. You do this through two main objects:

op('operator_path')
This is your primary tool. It lets you get a reference to any operator in your network, just like you would type its path into a parameter.
Syntax: op('parent_name/child_name')
Examples:
# Get a reference to a movie player TOP
movie_player = op('movie_in1')
# Get a reference to a null container
my_container = op('my_container')
# Get a reference to a CHOP with the path /project1/audio
audio_chop = op('/project1/audio')
parent()
This is a shortcut for op('.'). It returns the operator that the script is currently living inside.

Example:
If you have a script CHOP named my_script inside a container named logic, then inside my_script:
# These two lines are equivalent
my_parent = parent() # Gets the 'logic' container
my_parent_alt = op('.') # Also gets the 'logic' container
# You can chain them
# If 'logic' contains a null named 'settings', you can get it like this:
settings_null = parent().op('settings')
Common Python Tasks in TouchDesigner
A. Reading and Writing Parameters
This is the most frequent operation. You use the .par attribute.
# Assuming my_script is inside a container named 'logic'
# and 'logic' contains a movie player named 'movie_in1'
# Get a reference to the movie player
movie_player = parent().op('movie_in1')
# --- Reading a parameter ---
current_file = movie_player.par.filename # Get the value of the 'filename' parameter
print(f"Current movie file: {current_file}")
# --- Writing a parameter ---
# Set the filename to a new movie
movie_player.par.filename = 'C:/path/to/my/new_movie.mp4'
# You can also use the .val shortcut for simple parameters
movie_player.par.play.val = True # Press play
print(f"Play state is: {movie_player.par.play.val}")
B. Working with TOPs (Pixel Data)
The python TOP is designed for this. It gives you a NumPy array of the pixel data.
# This code would live inside a 'python' TOP # 'prev' refers to the top connected to the input of the python TOP # Get the pixel data as a NumPy array # It will have shape (height, width, channels) for RGBA images pixels = prev.SliceNumComponents(4) # Get RGBA data # --- Manipulate the pixels --- # Example: Invert the colors pixels[:, :, 0:3] = 255 - pixels[:, :, 0:3] # Invert RGB channels # --- Set the output --- # The 'dst' object is the output of the python TOP # You must assign a NumPy array to it dst[:] = pixels
C. Working with CHOPs (Channel Data)
The script CHOP is perfect for this. It gives you a list of channels.
# This code would live inside a 'script' CHOP
# 'me' is a reference to the script CHOP itself
# --- Read channel data ---
# Get a list of all channel names in the first input CHOP
chop_names = me.inputs[0].colnames
print(f"Input channels: {chop_names}")
# Get the value of a specific channel at the current sample
x_value = me.inputs[0]['chan1'][0] # [0] gets the current sample
# --- Write channel data ---
# Create a new channel named 'output'
me.appendChan('output')
# Set a value for the current sample on the 'output' channel
me['output'][0] = x_value * 2 # Double the input value
D. External Communication (e.g., a Web API)
This is where Python shines. You can use the requests library.
Setup:
- Install the
requestslibrary in your Python environment.- Go to
Edit > Preferences > Python. - In the "Packages" section, click and type
requests. ClickInstall.
- Go to
- Create a
scriptDAT to run the code.
# This code lives in a 'script' DAT
import requests
import json
# The op() to our UI panel where we'll display the result
ui_panel = op('ui_text')
def get_weather(api_key, city):
url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}&units=metric"
try:
response = requests.get(url)
response.raise_for_status() # Raise an exception for bad status codes
data = response.json()
temp = data['main']['temp']
description = data['weather'][0]['description']
return f"It's {temp}°C and {description} in {city}."
except requests.exceptions.RequestException as e:
return f"Error fetching weather: {e}"
except (KeyError, json.JSONDecodeError):
return "Error parsing weather data."
# --- Call the function ---
# Replace 'YOUR_API_KEY' and 'London' with real values
api_key = 'YOUR_API_KEY'
city = 'London'
weather_report = get_weather(api_key, city)
# Display the result in a text DAT
ui_panel.text = weather_report
Best Practices and Tips
-
Organize Code in
textDATs: For complex scripts, write your functions in atextDAT and thenexec()orimportthem in yourscriptCHOP orpythonTOP. This keeps your network clean and your code reusable.textDAT (namedmy_functions):def add_numbers(a, b): return a + bscriptCHOP:# Execute the code from the text DAT exec(op('my_functions').text) # Now you can use the function result = add_numbers(5, 7) print(f"The result
