| name | td-api-reference |
| description | MUST READ before writing TD Python via execute_python, set_dat_content, or edit_dat_content. API reference for parameters, storage, operators, threading. |
TouchDesigner Python API Reference
Always research TD features on the wiki before writing code. Assumptions about TD's Python API are frequently wrong.
Parameter Access Patterns
value = op('geo1').par.tx.eval()
value = op('geo1').par.tx.val
op('geo1').par.tx = 5
op('geo1').par.tx.val = 5
op('geo1').par.xord = 'trs'
op('geo1').par.xord = 5
me.par.tx.eval().hex()
me.par.tx.hex()
Creating Custom Parameters
All append* methods return a ParGroup (tuple-like), not a single Par — always index with [0].
page = comp.appendCustomPage('Controls')
pg = page.appendFloat('Speed', label='Speed')
p = pg[0]
p.default = 0.5
p.normMin = 0; p.normMax = 2
p.min = 0; p.clampMin = True
p.help = "Playback speed multiplier."
p.startSection = True
page.appendInt('Count')
page.appendToggle('Active')
page.appendStr('Label')
page.appendMenu('Mode')
page.appendPulse('Reset')
page.appendRGB('Color')
page.appendXYZ('Pos')
page.appendOP('Target')
page.appendFile('Path')
p.help = "Tooltip text shown on hover"
p.startSection = True
p.order = 11.5
p.readOnly = True
comp.destroyCustomPars()
par.Speed.destroy()
Naming rule: First letter MUST be uppercase, rest lowercase/numbers. No underscores.
op() vs opex()
node = op('/nonexistent/path')
node = opex('/nonexistent/path')
all_noises = ops('noise*')
Use op() only when None is an acceptable result.
debug() vs print()
debug('value is', x)
print('value is', x)
Module-Level Code Hazard
Never call op(), parent(), or access TD objects at module level. They execute during import, before the network is ready.
my_op = op('base1')
class MyExt:
def doSomething(self):
my_op = op('base1')
Import Shadowing
TD searches for DATs by name before sys.path. A DAT named json shadows Python's json module.
mod() for Module Access
mod.utils.myFunction()
import utils
m = mod.utils; m.func()
mod.utils.func()
mod('/project1/utils').myFunction()
op('myDat').module.myFunction()
extensionsReady Guard
parent().MyExtensionProperty if parent().extensionsReady else 0
Operator Storage
op('base1').store('count', 42)
val = op('base1').fetch('count', 0)
op('base1').unstore('count')
op('base1').storeStartupValue('version', 1)
Gotchas: fetch() searches UP hierarchy by default — use search=False for local-only. store() triggers recooks. Cannot store TD operator references — use path strings.
tdu.Dependency for Reactive Values
dep = tdu.Dependency(0)
dep.val = 5
dep = 5
current = dep.peekVal
dep.val = [1, 2, 3]
dep.val.append(4)
dep.modified()
tdu Utility Functions
tdu.clamp(val, min, max)
tdu.remap(val, fromMin, fromMax, toMin, toMax)
tdu.rand(seed)
tdu.base('noise3')
tdu.digits('noise3')
tdu.validName('my op!')
tdu.match('noise*', ['noise1', 'c1'])
tdu.expand('A[1-3]')
tdu.tryExcept(expr, fallback)
DAT Cell and Text Behavior
All DAT cells are internally strings. Auto-cast to numbers in expression contexts.
n = op('table1')
n[1,2] + 1
n[1,2].val + 1
dat.text — tab/newline delimited; strips multi-line cell content. Use dat.csv for cells with newlines
dat.jsonObject — parses as JSON directly (no json.loads() needed)
dat.module — access as Python module
dat.write(content) — appends (does not overwrite)
- Docs: https://docs.derivative.ca/DAT_Class
CHOP Channel Access
ch = op('noise1')['chan1']
chs = op('noise1').chans('tx*')
val = ch.eval()
ch[0], ch[10]
ch.evalFrame(30)
arr = op('noise1').numpyArray()
TOP Pixel Access
Coordinate system: TD places (0, 0) at the bottom-left, with Y increasing upward for all texture operations. TOP.numpyArray() is the exception: it returns rows top-to-bottom (numpy convention).
TOP.sample(x, y) downloads the entire texture from GPU — extremely expensive. Never in loops.
r, g, b, a = op('noise1').sample(x=0.5, y=0.5)
r, g, b, a = op('noise1').sample(x=0, y=0)
arr = op('noise1').numpyArray()
arr_td = np.flipud(arr)
POPs — GPU-Accelerated Point Operators
POPs process 3D geometry on the GPU (analogous to SOPs but GPU-accelerated).
grid = parent.create(gridPOP, 'grid1')
n = pop_op.numPoints(delayed=True)
pts = pop_op.points('P')
bounds = pop_op.bounds(delayed=True)
attrs = pop_op.pointAttributes
Common types: gridPOP, noisePOP, transformPOP, particlePOP, spherePOP, linePOP, mergePOP, nullPOP, selectPOP, mathPOP, cachePOP, glslPOP. For files: fileinPOP (File In POP — meshes/geometry) vs pointfileinPOP (Point File In POP — 3D point clouds: .ply/.pts/.xyz/.e57, Gaussian splats) are distinct operators.
run() — Delayed Code Execution
run("op('/project1/base1').cook(force=True)", delayFrames=1)
run("print('done')", delayMilliSeconds=500)
run("op.Embody.Update()", endFrame=True)
run(myFunction, arg1, arg2, delayFrames=5)
run("me.cook(force=True)", fromOP=op('/project1/base1'), delayFrames=1)
Thread Manager
Long-running Python must run in background threads. Use op.TDResources.ThreadManager.
task = op.TDResources.ThreadManager.TDTask(
target=my_background_function,
args=(arg1, arg2),
SuccessHook=on_success,
ExceptHook=on_error,
RefreshHook=on_refresh,
)
op.TDResources.ThreadManager.EnqueueTask(task)
op.TDResources.ThreadManager.EnqueueTask(task, standalone=True)
Key: standalone=True for long-lived tasks. Worker pool = 4 threads. InfoQueue for worker → main communication. Never call EnqueueTask() from a worker thread (THREAD CONFLICT).
Explicit Type Conversion
TD parameters auto-cast in expression contexts but remain TD objects. Convert with int(), float(), str() for standard Python functions. Use repr() to reveal actual type.