com um clique
td-api-reference
MUST READ before writing TD Python via execute_python, set_dat_content, or edit_dat_content. API reference for parameters, storage, operators, threading.
Menu
MUST READ before writing TD Python via execute_python, set_dat_content, or edit_dat_content. API reference for parameters, storage, operators, threading.
MUST READ before first MCP tool call in a session. Complete Envoy tool catalog with parameters and usage.
Run Embody's test suite and write new tests (Embody development)
MUST READ before calling create_op. Contains required verification, positioning, and error-checking steps.
Workflow for running multiple TD instances with Envoy, switching between them, and understanding the instance registry.
MUST READ before calling externalize_op or save_externalization. Required workflow steps.
MUST READ before calling create_extension. Required parameters, lifecycle methods, and wiring steps.
| 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. |
Always research TD features on the wiki before writing code. Assumptions about TD's Python API are frequently wrong.
# CORRECT — always use .eval() for the current runtime value:
value = op('geo1').par.tx.eval()
# WRONG — .val only works in constant mode:
value = op('geo1').par.tx.val
# Setting values:
op('geo1').par.tx = 5
op('geo1').par.tx.val = 5 # CAUTION: silently switches to CONSTANT mode
# Menu parameters accept string name or index:
op('geo1').par.xord = 'trs' # by name
op('geo1').par.xord = 5 # by index
# Type casting requires explicit .eval():
me.par.tx.eval().hex() # CORRECT
me.par.tx.hex() # WRONG
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') # Returns ParGroup
p = pg[0] # Get the actual Par
p.default = 0.5
p.normMin = 0; p.normMax = 2 # Slider range
p.min = 0; p.clampMin = True # Hard clamp
p.help = "Playback speed multiplier." # Tooltip on hover
p.startSection = True # Draw separator line above
# Other methods:
page.appendInt('Count')
page.appendToggle('Active')
page.appendStr('Label')
page.appendMenu('Mode') # Creates EMPTY menu — set .menuNames/.menuLabels separately
page.appendPulse('Reset')
page.appendRGB('Color') # Creates Color1r, Color1g, Color1b
page.appendXYZ('Pos') # Creates Pos1, Pos2, Pos3
page.appendOP('Target')
page.appendFile('Path')
# Post-creation properties:
p.help = "Tooltip text shown on hover" # ALWAYS set this
p.startSection = True # Draw separator line above this parameter
p.order = 11.5 # Insert between order 11 and 12
p.readOnly = True # Display-only parameter
# Cleanup:
comp.destroyCustomPars() # Remove ALL custom pars
par.Speed.destroy() # Remove single custom par
Naming rule: First letter MUST be uppercase, rest lowercase/numbers. No underscores.
.claude/rules/parameters.md for full conventionsop() vs opex()node = op('/nonexistent/path') # Returns None — silent failure
node = opex('/nonexistent/path') # Raises tdError — clear message
all_noises = ops('noise*') # Returns a LIST (supports wildcards)
Use op() only when None is an acceptable result.
debug() vs print()debug('value is', x) # "myScript line 42: value is 42" (with source info)
print('value is', x) # "value is 42" (no source info)
Never call op(), parent(), or access TD objects at module level. They execute during import, before the network is ready.
# WRONG:
my_op = op('base1') # May be None during init
# CORRECT — defer to methods:
class MyExt:
def doSomething(self):
my_op = op('base1') # Resolved at call time
TD searches for DATs by name before sys.path. A DAT named json shadows Python's json module.
mod() for Module Access# In a parameter expression (import not available):
mod.utils.myFunction()
# In a script (decreasing performance):
import utils # Fastest
m = mod.utils; m.func() # OK — cache the reference
mod.utils.func() # Slowest — re-resolves every call
# Access by path:
mod('/project1/utils').myFunction()
# Direct module property:
op('myDat').module.myFunction()
extensionsReady Guard# In a parameter expression:
parent().MyExtensionProperty if parent().extensionsReady else 0
op('base1').store('count', 42)
val = op('base1').fetch('count', 0) # 0 is default
op('base1').unstore('count')
op('base1').storeStartupValue('version', 1) # Restored on project load
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 Valuesdep = tdu.Dependency(0)
dep.val = 5 # CORRECT — triggers recooks
dep = 5 # WRONG — destroys the Dependency object
current = dep.peekVal # Read without creating dependency
dep.val = [1, 2, 3]
dep.val.append(4) # Does NOT trigger update
dep.modified() # Required
tdu Utility Functionstdu.clamp(val, min, max)
tdu.remap(val, fromMin, fromMax, toMin, toMax)
tdu.rand(seed) # Deterministic [0.0, 1.0)
tdu.base('noise3') # 'noise'
tdu.digits('noise3') # 3
tdu.validName('my op!') # 'my_op_'
tdu.match('noise*', ['noise1', 'c1']) # ['noise1']
tdu.expand('A[1-3]') # ['A1', 'A2', 'A3']
tdu.tryExcept(expr, fallback)
All DAT cells are internally strings. Auto-cast to numbers in expression contexts.
n = op('table1')
n[1,2] + 1 # 4 (auto-cast)
n[1,2].val + 1 # TypeError: str + int
dat.text — tab/newline delimited; strips multi-line cell content. Use dat.csv for cells with newlinesdat.jsonObject — parses as JSON directly (no json.loads() needed)dat.module — access as Python moduledat.write(content) — appends (does not overwrite)ch = op('noise1')['chan1'] # By name — NO wildcard
chs = op('noise1').chans('tx*') # Pattern matching
val = ch.eval() # Current value
ch[0], ch[10] # Sample by index
ch.evalFrame(30) # At specific frame
arr = op('noise1').numpyArray() # Shape: (numChans, numSamples)
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.
# sample() uses TD texture coords: y=0 is BOTTOM of image
r, g, b, a = op('noise1').sample(x=0.5, y=0.5) # Center of texture
r, g, b, a = op('noise1').sample(x=0, y=0) # Bottom-left corner
# numpyArray() returns rows TOP-to-BOTTOM (opposite of TD texture coords)
arr = op('noise1').numpyArray() # [height, width, channels] — NOT [width, height]
# arr[0] is the TOP of the image (highest TD Y)
# arr[-1] is the BOTTOM of the image (TD y=0)
# Flip to match TD bottom-up order:
arr_td = np.flipud(arr)
POPs process 3D geometry on the GPU (analogous to SOPs but GPU-accelerated).
grid = parent.create(gridPOP, 'grid1')
n = pop_op.numPoints(delayed=True) # Non-blocking
pts = pop_op.points('P') # Downloads (blocks GPU)
bounds = pop_op.bounds(delayed=True) # Non-blocking
attrs = pop_op.pointAttributes # Set of attribute names
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 Executionrun("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)
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, # Main thread
ExceptHook=on_error, # Main thread
RefreshHook=on_refresh, # Main thread, every frame
)
op.TDResources.ThreadManager.EnqueueTask(task)
op.TDResources.ThreadManager.EnqueueTask(task, standalone=True) # Dedicated thread
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).
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.