Common Mistakes¶
A comprehensive list of the most frequent TouchDesigner Python pitfalls and how to avoid them.
Parameter Access¶
1. Using .val instead of .eval()¶
.val only returns the constant-mode value. If the parameter is in expression or bind mode, .val gives you the wrong answer.
2. Setting .val destroys expressions¶
Setting .val implicitly switches the parameter to constant mode, silently killing any active expression.
# This destroys the expression:
op('geo1').par.tx.val = 5
# If you need to set a constant value, this is equivalent but more explicit:
op('geo1').par.tx = 5
3. Toggle parameters with wrong types¶
Toggle parameters use 0/1, not "True"/"False" strings.
# WRONG:
set_parameter(op_path="/base1", par_name="active", value="True")
# CORRECT:
set_parameter(op_path="/base1", par_name="active", value="1")
Module-Level Code¶
4. op() at module scope¶
Never call op(), parent(), or access any TD objects at the top level of a .py file. Module-level code executes during import, potentially before the network is ready.
# WRONG — executes during import:
my_op = op('base1') # May be None
class MyExt:
def __init__(self, ownerComp):
self.ownerComp = ownerComp
# CORRECT — defer to methods:
class MyExt:
def __init__(self, ownerComp):
self.ownerComp = ownerComp
def doSomething(self):
my_op = op('base1') # Resolved at call time
5. Import shadowing¶
A DAT named json will shadow Python's stdlib json module. Name DATs carefully to avoid conflicts.
Operator References¶
6. Using op() when opex() would catch errors¶
op() returns None silently. opex() raises an exception immediately with a clear error message.
# Silent failure — hard to debug:
node = op('/nonexistent')
node.par.tx = 5 # AttributeError: NoneType
# Immediate failure — clear error:
node = opex('/nonexistent') # Raises tdError
7. Relying on op.id for tracking¶
Operator IDs change across sessions, copy/paste, and undo. Never use them as persistent identifiers.
Extension Pitfalls¶
8. Caching extension references¶
Extension instances become stale when TD reinitializes them (e.g., when externalized .py files change on disk).
# WRONG — cached ref goes stale:
ext = self.ownerComp.ext.Embody
ext.SomeMethod()
# CORRECT — always call inline:
self.ownerComp.ext.Embody.SomeMethod()
9. Missing extensionsReady guard¶
Parameter expressions referencing promoted attributes need a guard:
Thread Safety¶
10. Accessing ThreadManager from a worker thread¶
op.TDResources.ThreadManager is a TD COMP — accessing it from a worker thread triggers a THREAD CONFLICT. Use plain threading.Thread for sub-tasks inside workers.
11. TD imports in worker threads¶
Never import or call TouchDesigner modules in worker thread code. All TD access must route through main-thread hooks.
Data Access¶
12. fetch() without search=False¶
By default, fetch() searches up the parent hierarchy, which may return a parent's value.
# May return a parent's value:
val = op('base1').fetch('key', 0)
# Local-only lookup:
val = op('base1').fetch('key', 0, search=False)
13. mod.name in loops¶
mod.name re-resolves the DAT lookup every call. Cache the reference:
# SLOW — resolves every iteration:
for i in range(100):
mod.utils.func(i)
# FAST — cached reference:
m = mod.utils
for i in range(100):
m.func(i)
14. Assigning to tdu.Dependency¶
dep = tdu.Dependency(0)
dep.val = 5 # CORRECT — updates the value
dep = 5 # WRONG — destroys the Dependency object!
Performance¶
15. TOP.sample() in loops¶
Downloads the entire texture from GPU to CPU per call. Use numpyArray() for batch access.
# WRONG — downloads entire texture per pixel:
for x in range(100):
r, g, b, a = op('noise1').sample(x=x/100, y=0.5)
# CORRECT — single download:
arr = op('noise1').numpyArray()
16. addError()/addWarning() outside cook callbacks¶
These silently do nothing outside a cook callback. Use addScriptError() from extension methods.
Operator Operations¶
17. changeType() without capturing return value¶
The original operator reference becomes invalid after changeType():
# WRONG:
old_op.changeType(waveCHOP)
old_op.par.tx = 5 # Invalid reference!
# CORRECT:
new_op = old_op.changeType(waveCHOP)
new_op.par.tx = 5
18. COMP.copy() for multiple connected operators¶
Connections between copied operators are lost. Use copyOPs([list]) to preserve wiring:
# Connections between a, b are lost:
new_a = comp.copy(a)
new_b = comp.copy(b)
# Connections preserved:
new_ops = comp.copyOPs([a, b])
File Paths¶
19. Backslashes in paths¶
Always use forward slashes for cross-platform compatibility:
Network Layout¶
20. Creating operators without setting positions¶
All new operators default to [0, 0] and stack on top of each other. Always set explicit positions after creating operators.
21. Relying on layout()¶
TD's layout() method produces unreadable layouts with no logical grouping. Calculate explicit positions for production networks.