Skip to main content

td-api-reference

MUST READ before writing TD Python via execute_python, set_dat_content, or edit_dat_content, and before any heavy build. Parameters, storage, operators, referencing, threading, cook model, heavy-build safety caps.

Quellinformationen

Repository
dylanroscover/Embody
Letzte Quellaktivität
26. September 2026 um 06:32
Erkannte Sprache von SKILL.md
Englisch
Sterne
191
Forks
10

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
3 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
td-api-reference
description
MUST READ before writing TD Python via execute_python, set_dat_content, or edit_dat_content, and before any heavy build. Parameters, storage, operators, referencing, threading, cook model, heavy-build safety caps.
<!-- Generated by Embody/Envoy - Do not remove this comment - sha:1517fd24f8397a92 --> # TouchDesigner Python API Reference Verify unfamiliar TD behavior before asserting it: `get_docs('<Operator or Class>')` reads the offline help mirror first, the wiki (https://docs.derivative.ca: OP Class, COMP Class, Par Class, Extensions, Cook) second, and `describe_op_type` gives an operator type's real parameter names. Assumptions about TD's Python API are frequently wrong. Two companions in this skill's folder hold the long tail: `references/background-work.md` (the Web Client DAT and Thread Manager code) and `references/heavy-build-safety.md` (crash causes and caps). Read them before any fetch, thread or heavy build; without a skills folder, `get_guidance(topic='td-api-reference/background-work')`. ## Parameter Access Patterns ```python value = op('geo1').par.tx.eval() # CORRECT: the current runtime value value = op('geo1').par.tx.val # WRONG: constant-mode value only op('geo1').par.tx = 5 # set; .val = 5 also silently switches the mode to CONSTANT op('geo1').par.xord = 'trs' # menus take the name or the index me.par.tx.eval().hex() # cast AFTER eval(); me.par.tx.hex() is wrong ``` ## Creating Custom Parameters All `append*` methods return a **ParGroup** (tuple-like), never a single Par: index with `[0]`. ```python page = comp.appendCustomPage('Controls') p = page.appendFloat('Speed', label='Speed')[0] p.default = 0.5; p.val = p.default # .default never sets the value p.normMin = 0; p.normMax = 2 # slider range p.min = 0; p.clampMin = True # hard clamp p.help = "Playback speed multiplier." # ALWAYS set help p.startSection = True # separator above # appendInt / appendToggle / appendStr / appendMenu (EMPTY menu: set .menuNames/.menuLabels) # appendPulse / appendRGB ('Color' -> Colorr/g/b) / appendXYZ ('Pos' -> Pos1/2/3) / appendOP / appendFile ``` Names start uppercase, then lowercase letters and digits, no underscores. Get-or-create (never a blind `append*`), callbacks, styles, ranges, help text: `/parameter-design`. Docs: https://docs.derivative.ca/Custom_Parameters ## `op()` vs `opex()` `op('/missing')` returns `None` silently; `opex('/missing')` raises `tdError` with a clear message. Use `op()` only when `None` is an acceptable result. `ops('noise*')` returns a list (wildcards). `opex('level1').asType(levelTOP, checkType=True)` is the typed, runtime-asserted one-liner for a known type. ## Operator Referencing Patterns Absolute paths (`op('/project1/...')`) are always wrong, in code, expressions AND parameter values. Take the highest rung that resolves from where the code runs: | Rung | Pattern | Use when | Prerequisite | |---|---|---|---| | 1 | `self.ownerComp` | Anywhere inside an extension; capture it once in `__init__` | none | | 2 | `parent.CompName` | Inside the COMP but outside the extension: callback DATs, parameter expressions | the COMP sets `par.parentshortcut` | | 3 | `op('sibling')`, `op('./child')`, `op('../up_one')` | The target is in the same network or nearby | none | | 3b | `iop.Name` / `ipar.Name` | An internal op or parameter the COMP publishes for its own descendants | Internal OP shortcuts on the Common page | | 4 | `op.CompName` | A project-wide singleton no parent shortcut can reach | the COMP sets `par.opshortcut` | - `parent.CompName` searches UP by shortcut name and survives renesting; `parent(2)` encodes the depth and breaks the moment anything moves. Set `par.parentshortcut` when you create a reusable COMP. - The bare global `parent()` / `parent(N)` is banned inside an extension class body: use `self.ownerComp`. Exemptions: a COMP's own callback DAT (where `parent()` IS the owner), generic tooling inside a COMP it did not author, parameter expressions on replicated or cloned widgets. `some_op.parent()` is documented OP-class API and untouched. - `iop.Name` names one internal operator, `ipar.Name` a parameter-holding COMP; inside-only, and an operator-valued internal par needs `op(ipar.X.Opname)`. Reserve them for stable internal addresses. - `op.CompName` is globally unique: assigning a name in use steals it from the previous holder. Use it for singleton services (`op.Embody.Log(...)`), otherwise prefer `parent.CompName`. - Always verify a reference resolves from the calling context; a reference that returns `None` (or finds the wrong operator) is a latent bug. - After ANY rename (operator, method or parameter) sweep every live surface, not just DAT text on disk: DAT text inside the `.toe` (`findChildren(type=DAT)` and read `.text`), `par.expr`, `par.bindExpr`, constant-mode OP-reference values (`par.val`), and `run()` strings composed at call time (issue #94: an agent swept DAT content only and left stale expressions and binds behind). `findChildren(depth=N)` matches EXACTLY depth N, a silent all-clear for anything shallower; walk with no depth argument or `maxDepth=N`. ## Type Hints Annotate every `def` (arguments AND return) and let a checker read them; Python ignores them at runtime. TD's classes are the types (`COMP`, `DAT`, `OP`, `Par`, `constantCHOP`, ...); put `from __future__ import annotations` at the top of a module DAT. `x: str = None` declares a `str` that is `None`: write `Optional[str] = None`. A callback returns `-> None`; a query that can miss returns `-> Optional[COMP]`. The hint is the soft check; `asType(..., checkType=True)` is the runtime assertion. Check softly, in CI, with a baseline (pyright `basic`, failing only on NEW errors). ## Everyday Essentials - `debug('value is', x)` prints with the source DAT and line number; `print()` does not. - Never call `op()`, `parent()` or touch TD objects at module level: module code runs at import, before the network is ready. Defer to methods. - A DAT named `json` shadows Python's `json`: TD searches DATs by name before `sys.path`. - `mod.utils.func()` re-resolves the DAT every call; in a loop cache it (`m = mod.utils`). In a parameter expression `import` is unavailable, `mod.utils.f()` works. `mod('utils')` resolves relative to the current op, never an absolute path. - Expressions that read a promoted attribute guard with `parent().X if parent().extensionsReady else 0`. - `changeType()` returns the NEW op (capture it); `copyOPs([list])` preserves wires, `COMP.copy()` does not; `addError()`/`addWarning()` work only in cook callbacks (use `addScriptError()` from extension code). ## onInitTD and TDXN Import Timing Inside a TDXN-strategy COMP, `onInitTD` fires BEFORE the import that rebuilds the children (project open around frame 60, every Ctrl+S strip/restore, every manual reimport with `clear_first=True`), so anything it creates, sets or stores inside the COMP is wiped. Defer network-dependent setup with `run('args[0].postInit()', self, delayFrames=5)`, keep it idempotent (it runs several times), null-check children (`op('child')` is `None` during the strip window), and keep durable state OUTSIDE the boundary or in `store()` on the COMP. ## Parameter, Storage, or Dependency? User-facing state is a **custom parameter**; hidden durable bookkeeping is **`storage`** (lives on the COMP, survives an extension reinit, wiped when the COMP's contents are replaced by a tox reload or TDXN reconstruction); a derived value that must recook its readers is a **`tdu.Dependency`** (dies with the extension instance on every source save, rebuilt in `__init__`). Publish once and let readers pull; never push values into consumers. The lifetime table, what actually notifies dependents, and the storage mechanics are measured in `/parameter-design` and its `references/state-lifetimes.md`. ```python op('base1').store('count', 42) # write THROUGH store(); comp.storage[k] = v notifies nobody val = op('base1').fetch('count', 0) # no default -> a miss RAISES; fetch() searches UP the parents op('base1').unstore('count') # glob-matched: never a computed key op('base1').storeStartupValue('version', 1) # a separate dict restored on load dep = tdu.Dependency(0) dep.val = 5 # CORRECT: recooks readers. dep = 5 destroys the Dependency dep.val.append(4); dep.modified() # in-place mutation needs modified() current = dep.peekVal # read without subscribing ``` `tdu.clamp`, `tdu.remap`, `tdu.rand(seed)`, `tdu.base('noise3')` -> `'noise'`, `tdu.digits`, `tdu.validName`, `tdu.match`, `tdu.expand('A[1-3]')`, `tdu.tryExcept(expr, fallback)`. ## DAT, CHOP and TOP Access - DAT cells are strings; `n[1,2] + 1` auto-casts, `n[1,2].val + 1` raises. `dat.text` strips multi-line cells (`dat.csv` keeps them); `dat.jsonObject` parses directly; `dat.write()` APPENDS; `dat.module` imports it. - CHOP: `op('noise1')['chan1']` by name (no wildcard), `.chans('tx*')` for patterns, `ch.eval()`, `ch[10]`, `ch.evalFrame(30)`, `numpyArray()` shaped `(numChans, numSamples)`. - TOP coordinates: `TOP.sample(x, y)`, GLSL `gl_FragCoord`, UVs and Crop/Transform parameters put (0, 0) at the BOTTOM-LEFT with Y up. `TOP.numpyArray()` follows the same y-up order: row 0 is y = 0, the bottom of the image (shape `[height, width, channels]`; measured 2025.33230 against `sample()` on a vertical ramp). PIL/OpenCV images and panel screen coordinates are top-down, so `np.flipud` converts between the two. `sample()` downloads the whole texture: never in a loop. `numpyArray()` returns linear-light floats, not the sRGB bytes a file decode gives: compare same-domain only. - POPs: `pop.numPoints(delayed=True)` and `bounds(delayed=True)` are non-blocking; `pop.points('P')` downloads from the GPU. `fileinPOP` (meshes) and `pointfileinPOP` (point clouds, splats) are distinct operators. - `run("me.cook(force=True)", fromOP=op('base1'), delayFrames=1)`, `run(fn, arg, delayMilliSeconds=500)`, `run("...", endFrame=True)`; resolve ops through `fromOP` or shortcuts, never absolute paths. ## Cook Model Gotchas - `cook(force=True)` does not advance a feedback loop within a frame: evolution needs real frames with the chain demanded (`run(..., delayFrames=1)` or an Execute DAT `onFrameStart`), never a `for` loop. - A Movie File In reload (`par.file`, `reloadpulse`) lands only across a real frame advance, and ops downstream in the same forced pass can still serve the pre-reload texture. Verify content at the point of capture and let each reload settle (`/movie-export`). - A CHOP Execute DAT is a per-frame demand source: its watched CHOP cooks every frame (measured 2025.33230). An Execute DAT's `framestart` toggle is OFF by default, so a frame driver that was never enabled demands nothing. - Animate cheaply: make a heavy generator static (no time reference, cooks once) and put the motion in a cheap downstream op that animates the SAMPLING. Confirm with `cookedThisFrame`. ## Background and Long-Running Work **Ironclad rule: a worker thread touches NO TD object, and a read counts as a write.** No `op()`/`opex()`, no `Par` (not even `.eval()`), no DAT/CHOP/SOP/TOP content, no `storage`, no `tdu.Dependency`, no `debug()`/`print()`, and never `run()`/`td.run()` from a worker: it does not raise, the scheduled code even runs, and the crash lands later far from the call site (Derivative-confirmed 2026-08-17). A worker gets pure Python, `tdu` math utilities, values resolved on the main thread and passed in, `queue`/`threading` primitives, `td.isMainThread()`, and the Thread Manager's `InfoQueue`/`SafeLogger`; it hands plain data back for a main-thread callback to apply. Match the rung to the problem; threading is the LAST resort: 0. Prototype a fetch synchronously once to prove URL, auth and parse; never ship it. 1. Fast, TD-only, no I/O: inline. Any network, disk or subprocess call is never this rung. 2. Fetch data with a native operator: Web Client DAT (`request(url, 'GET', timeout=8000)` returns at once; `onResponse` runs on the MAIN thread), WebSocket DAT, Web Server DAT, OSC, File In / Folder DAT. Parse with a JSON DAT, bridge with DAT to CHOP. 3. Long TD-touching work: chunk with `run(delayFrames=N)`; `run()` controls WHEN, not HOW MUCH. 4. Blocking pure-Python work: the Thread Manager (Palette Thread Manager Client preferred), a `target` with zero TD access, results applied in main-thread hooks. Never `EnqueueTask()` from a worker. 5. Long-lived loop or server: ThreadManager `standalone=True` or a top-level `threading.Thread` with zero TD access draining a `queue.Queue` from a main-thread callback armed BEFORE the worker starts. Trigger a fetch from a Pulse parameter or a Timer CHOP (cycle on, limit off), never a sleep loop, a self-rescheduling `run()` poller or an auto-fetch on open. Gate with primary evidence: fps and `droppedFrames` held (`get_project_performance`) AND the result actually arrived. `requests` has no default timeout and blocks the frame: `timeout=(2, 8)`, and only ever inside a worker. Code patterns: `references/background-work.md`. ## Heavy-Build Safety Load `references/heavy-build-safety.md` (crash causes with their warning metrics and mitigations) before render chains, feedback loops, instancing, large geometry, GLSL, high-resolution TOPs or many operators at once; the gating protocol and stop conditions in `performance.md` always apply. The caps in one breath: TOPs <= 1920x1080 in 8/16-bit fixed formats unless asked; feedback loops bounded (fixed resolution, decay below 1, reset wired, bypassed while wiring, terminated in a Null); heavy sources bypassed while wiring; GPU instancing or POPs over Copy SOP, counts ramped with metrics (CPU particles ~10k max); Time Slicing on and small buffers for CHOPs; constant-bounded GLSL loops with `TD_NUM_*` index guards; short non-blocking `execute_python` calls with no sleep, no `TOP.sample()` in loops, and large builds chunked across frames. `allowCooking = False` is COMP-only; gate other operators with `bypass`. ## Pre-Installed Packages and Types `numpy` and `cv2` are documented as bundled; `requests`, `yaml`, `cryptography`, `attrs` are commonly present (verify in your build). Auto-imported stdlib: `math`, `re`, `sys`, `collections`, `enum`, `inspect`, `traceback`, `warnings`. TD parameters auto-cast in expressions but stay TD objects: convert with `int()`, `float()`, `str()` before handing them to standard Python; `repr()` reveals the real type.
Auf GitHub ansehen