Pixels2GenAI
Path ii Continuum
M 10 · 10.1.2 · conceptual

10.1.2 Python Integration — op() and Parameter Access

Wire Python into a TD network. Learn where Python lives (Script DAT/CHOP/TOP), how `op('name')` references any operator, and how `.par.X` reads or writes parameters — turning your network into a programmable instrument.

Duration30–36 min
Levelintermediate
Load3 conceptual pieces
Prereqs10.1.1 (node networks), Python basics

Big Question — how do you mix the visual and textual programming worlds?

Visual programming is exhilarating for the first hour and constricting by the third. Some logic is genuinely sequential — “if the audio level is high for more than two seconds, switch to scene 3 and reset the fader” — and trying to express that as a node chain takes more nodes than it saves. The pragmatic answer in every modern node-based environment is the same: embed a real text-based programming language inside the visual one. TouchDesigner embeds Python. The same Python you have written for Modules 0-9 runs inside TouchDesigner’s own interpreter, with one new function — op('name') — that returns a live reference to any operator in your network. From there your script can read parameters, change them, watch values, write data to operators, and react to events. The visual and textual paradigms become a unified instrument, where each is used for what it does best.

Diagram showing how Python code inside a Script DAT executes within TouchDesigner's frame-rate loop, reading and writing parameters on surrounding operators every frame
Fig. 1 TouchDesigner embeds Python in every Script operator. Code runs inside the cooking loop, sees the entire network, and can read or write any parameter — all at 60 fps.

Learning objectives

  1. Identify where Python code lives in TouchDesigner — Script DAT, Script CHOP, Script TOP — and which to pick for which job.
  2. Use op('name') to reference any operator by name, and me to reference the current operator.
  3. Read parameters with .par.X.eval(); write parameters with .par.X = value; set per-frame expressions with .par.X.expr = '...'.
  4. Apply absolute and relative paths to reference operators in different containers.

Part 1 — Where Python lives

Three operators host Python in TouchDesigner:

  • Script DAT — the workhorse. Edits as a text file with full Python access. Runs once when triggered, or on parameter change, or on every frame if you wire a clock to it. Use this for control logic, automation, parameter modification, and external data integration. Default choice.
  • Script CHOP — outputs numeric channels. Your code returns floats; TD displays them as a CHOP signal. Use for custom animation curves, signal generators, audio-domain math.
  • Script TOP — outputs pixel data. Your code returns a NumPy array; TD shows it as an image. Use to bring your Modules 1-6 NumPy code directly into a TD network.
Three-panel diagram showing the differences between Script DAT (text output), Script CHOP (numeric channels), and Script TOP (pixel array)
Fig. 2 The three script operator types. Script DAT is the most versatile and the right default; the other two specialise in numeric or pixel output.

The Python interpreter is full Python 3.11 — import numpy as np, import json, import requests all work. Your code can use any package shipped with TD, plus any package you install into TD’s bundled Python environment.

Part 2 — op() and me: navigation in the network

The single most important addition to Python in TouchDesigner is the op() function. It takes a name (or path) and returns a live reference to the named operator:

python · op_basics.py
blur = op('blur1')                # operator in the same container
noise = op('/project1/noise1')    # absolute path from root
parent_container = op('..')       # go up one level

The returned reference is live — reading blur.width returns the current width right now, not a snapshot from when you ran the script. Setting blur.par.size = 10 changes the parameter and triggers downstream cooking instantly.

Inside any script operator, the variable me refers to the script itself:

python · me_basics.py
print(me.name)        # 'script1'
print(me.type)        # 'textDAT'
print(me.path)        # '/project1/script1'
print(me.parent())    # the container that holds this script
print(me.time.seconds)  # current TD clock time in seconds

me is invaluable for code that wants to act relative to its own position. A control script in container A and the same script in container B can both reference siblings as me.parent() without caring about absolute paths.

Network hierarchy diagram showing nested containers — project1 holds container1, which holds noise1 and blur1; container2 holds level1. Arrows show absolute paths (/project1/container1/noise1) and relative paths (../container2/level1) for cross-container references.
Fig. 3 Operator hierarchy is a tree. Absolute paths start with `/`; relative paths use `..` (parent) and operator names (children). Always prefer relative paths inside reusable components.

Part 3 — Reading, writing, and animating parameters

Every operator’s parameters are accessible through .par.X where X is the parameter’s lowercase internal name. Hover over a parameter in TD to see its internal name; the visible label and the internal name can differ.

Read with .eval():

python · read_params.py
blur = op('blur1')
size = blur.par.size.eval()       # current numeric value
filter_type = blur.par.filtertype.eval()  # current string menu choice

Write by assignment:

python · write_params.py
blur.par.size = 10
blur.par.filtertype = 'gauss'

# multi-component parameters (RGB, XYZ) use suffixes:
noise = op('noise1')
noise.par.tx = 0.5    # x-translation
noise.par.colorr = 1.0  # red channel of color
noise.par.colorg = 0.5
noise.par.colorb = 0.0

Animate with .expr — sets an expression that re-evaluates every frame:

python · expression_params.py
blur.par.size.expr = "absTime.seconds * 5"           # blur grows over time
noise.par.tx.expr = "sin(absTime.seconds * 2) * 0.3"  # x oscillates

The expression string is in TD’s expression language (almost Python — supports math, no statements, no imports). Once set, you do not run the script again; the expression cooks every frame on its own.

Synthesis project — Python-driven parameter modulation

Build a small TD project where one Script DAT reads time, computes a handful of sine-wave values, and writes them to parameters across five different TOPs — Noise period, Noise amplitude, Blur size, Level brightness, HSV hue offset. The result: rhythmic, coordinated animation driven by a single Python script you can edit live.

Reflection questions

  • Why is using .par.X.expr = "..." usually preferable to writing a Script DAT that re-sets .par.X every frame?
  • What happens if you op('blur1').par.size = 10 from a script that itself was triggered by the blur cooking?
  • Why use relative paths (me.parent()) instead of absolute paths inside a reusable component?

Make it your own

  • External control surface: read MIDI from an OSC input, map controller values to TD parameters via a Script DAT. The DJ controller becomes a live patch interface.
  • Web-driven: a Web DAT periodically fetches a JSON file; a Script DAT parses it and updates parameters. The network is now controllable from a web page.
  • Network-of-networks: a control container holds Python scripts that manage state; multiple visual containers expose their parameters via custom parameters; the control container reads and writes to all of them.

Summary

Common pitfalls to avoid

  • Writing per-frame Python when an expression would do. Expressions are much cheaper.
  • Hard-coding absolute paths inside components. Breaks the moment the component is instantiated elsewhere.
  • Mistaking the parameter’s visible label for its internal name. Hover to confirm; internal names are lowercase and have no spaces.
  • Re-cooking an operator inside its own script (write-after-read on the same operator). Creates an infinite cook loop in older TD versions.
  • Forgetting .eval() when reading. blur.par.size is the parameter object; .eval() returns its current value.

References

  1. [1] Derivative Inc. (2024). TouchDesigner Python Documentation. derivative.ca/UserGuide/Python
  2. [2] Lutz, M. (2013). Learning Python (5th ed.). O’Reilly Media.
  3. [3] Hils, D. D. (1992). Visual languages and computing survey: Data flow visual programming languages. Journal of Visual Languages and Computing, 3(1), 69–101.
  4. [4] Burnett, M. M. (2001). Visual programming. Encyclopedia of Computer Science and Engineering. Wiley.
  5. [5] McCarthy, M., & Wright, M. (2014). Programming Interactivity (2nd ed.). O’Reilly Media.
  6. [6] Reas, C., & McWilliams, C. (2010). Form+Code in Design, Art, and Architecture. Princeton Architectural Press.