Externalize Operator Workflow
Tagging (includes save)
externalize_op tags the operator AND writes it to disk in one step (it calls Update() internally). No separate save is needed.
- Tag and externalize:
externalize_opon the operator (auto-detects type if omitted) - Verify:
get_externalization_statusto confirm dirty state and file path - Inspect: Verify file exists in
embody/via file inspection
Re-exporting After Changes
save_externalization force re-exports an already-externalized operator. Use it after modifying an operator in TD when you need to update its file on disk.
Excluding Runtime State from TDXN Exports (tdxn_exclude:<par> tags)
When a parameter holds runtime state (a session file path, a negotiated port, a live readout) rather than configuration, keep its value out of committed .tdxn files by tagging the operator itself - one tag per parameter, using the colon-suffixed form of the exclude tag:
op('deck_a').tags.add('tdxn_exclude:file')
The operator and its other parameters export normally; the named parameter's constant value is omitted (expressions/binds still export - references are configuration). The bare tdxn_exclude tag on a COMP is different: it makes the whole COMP invisible to TDXN. The suffixed tag round-trips in the .tdxn, so the omission is visible and survives reconstruction; it takes effect at the COMP's next export (Save tdxn, Update, or project save). A tag naming a nonexistent parameter logs a WARNING at export. Users can manage these visually: the tagger's Actions menu on a TDXN COMP has Exclude from tdxn, a drop-zone panel that toggles tdxn_exclude:<par> for dragged parameters and whole-COMP tdxn_exclude for dragged COMPs, listing every exclusion in the subtree with a per-row x to remove it.
A third form targets a DAT's live contents instead of a parameter: tdxn_exclude:dat_content (a reserved name, so it never trips the unknown-parameter warning). Use it for DATs whose rows are runtime state with no authored value -- a log ring buffer, a status readout. Those are normally force-captured even when Embed DATs is OFF, because Embody refuses to drop content that exists nowhere else on disk; this tag is the only sanctioned way past that net, so apply it only where losing the content is the intent. The operator itself still exports in full (parameters, position, wiring, tags) and returns empty on reconstruction, and the save-time content check reads the same rule, so it never flags the DAT or files it as its own file.
Creating Python Files for TouchDesigner
When creating Python files (scripts, extensions, test files, callbacks):
- Create the textDAT in TouchDesigner first (via MCP
create_opor in TD UI) - Write the Python code into the DAT (via MCP
set_dat_content) - Tag the DAT for externalization (
externalize_op) - Embody writes the.pyfile to disk
NEVER manually set the file and syncfile parameters - Embody handles all file path management.
Exporting a Portable Tox
Export any COMP as a self-contained .tox with all Embody metadata stripped:
- Via MCP:
execute_pythonwithop.Embody.ExportPortableTox(target=op('/path/to/comp'), save_path='/output/path.tox') - Via UI: Manager UI > Actions popup > "Export portable tox"
The exported .tox works in any TD project with no missing file errors.
Release hooks: a TEXT DAT named pre_release that is a DIRECT child of the target runs on a STAGED COPY in /sys/quiet - the live comp is never touched, file-sync is disabled on the copy (no write-through to source files), extensions are NOT initialized there and par callbacks don't fire (shape via direct par/DAT/op edits, or use hook_mode='live' for extension logic). A raise aborts the export and keeps the copy as <name>_release_failed for same-session inspection (/sys is not saved; post_release skipped). A direct-child Text DAT named post_release runs on the ORIGINAL after the save whenever pre_release completed - even when the save failed. Both hook DATs are deleted from the copy, so hook code never ships in the artifact. args[0] = resolved save path; post_release also gets args[1] = success. Hooks run synchronously on the main thread - keep them fast, defer uploads. Nested exports from inside a hook run plain (no hooks, no second copy). hook_mode='live' restores in-place semantics (mutations persist, hooks ship dormant in the tox); run_hooks=False skips hooks and ships them as-is (the self-updater's backup uses it). Exports whose target is - or contains - the live Embody COMP are never copy-staged (Embody-self runs live-mode; hooks on a container holding Embody are skipped with a warning in copy mode). op.Embody.ReleaseAll(root=None, out_dir=None) (or the Export All Release Toxes pulse) batch-exports every component that is BOTH Embody-tracked AND hook-bearing - tracked-and-hooked is the opt-in (third-party comps ship with their authors' hook DATs and are never batch-released); per-component failures log and continue.
Checking Status
get_externalizations- list all externalized operators with statusget_externalization_status- get dirty state, build number, timestamp, file path for a specific operator
TDXN Export - Palette COMP Handling
When exporting a TDXN-strategy COMP whose network contains TD palette components (e.g. abletonLink, Widget components, anything under Samples/Palette/), Embody consults the Tdxnpalettehandling par on the Embody COMP's TDXN page:
- Ask (default): On first encounter of each palette COMP, a four-button dialog appears - Black Box (this COMP), Full Export (this COMP), Black Box for All, Full Export for All. The per-COMP choice is stored via
comp.store('_tdn_palette_handling', ...)so repeated exports don't re-prompt. - Black Box: reference the palette only, emit
"palette_clone": true, skip internal children. Correct for stock palette COMPs. - Full Export: export all children as if the COMP were a regular user COMP. Use only when palette internals have been heavily customized.
Check and override programmatically: op.Embody.par.Tdxnpalettehandling = 'blackbox' | 'fullexport' | 'ask', or force a specific COMP: op('/path/to/comp').store('_tdn_palette_handling', 'fullexport').
TDXN Export - Locked Content
A TDXN export keeps the lock flag of a locked TOP, CHOP, SOP or POP but not its frozen data. Each export that writes a COMP's .tdxn file (externalize_op, save_externalization, or a save that re-exports a changed COMP) logs ONE WARNING per exported COMP (it rides back in _logs; an unchanged COMP at save time, autosave checkpoints and ad-hoc export_network snapshots of a tracked COMP skip it) listing path (FAMILY, source: none|unknown|recooks) and the exact remedy call. externalize_op, save_externalization, export_network, and the Autoexternalize step of create_op/copy_op/create_extension never show the locked-content dialog; execute_python code that calls Update or saveTDXN still can.
source: none: nothing wired in survives a rebuild. Never unlock it - unlocking leaves it empty.source: unknown: the source was not traced (a parameter reference, mixed inputs, a large scan). It may have none: treat it asnone.source: recooks: unlocking re-cooks it, but the frozen snapshot is replaced, not restored. Unlock only when a fresh cook is acceptable.- To keep the data, run the
externalize_op('<child COMP>', tag_type='tox')the WARNING names: that child is stored as a.toxand the parent.tdxnreferences it (tox_ref). In TD, the dialog's Switch to TOX button does the same.