Read, Import & Export¶
Reading a Network (no disk I/O)¶
MCP Tool¶
Use the read_tdn tool to return the live network as a TDXN dict without writing anything to disk. This is the preferred read path for LLM workflows exploring networks of more than ~3 operators — typically 20-90× fewer tokens than walking the same subtree with get_op + query_network because of default-omission, type_defaults, and par_templates compaction.
| Parameter | Default | Description |
|---|---|---|
comp_path |
"/" |
Starting COMP path |
include_dat_content |
Toggle setting | Include DAT text/table content |
max_depth |
null (unlimited) |
Cap recursion on large roots |
embed_all |
false |
Recurse into TDXN-tagged COMPs instead of skipping their children |
Works in all three Tdnmode values (Off / Export-on-Save / Roundtrip) — read_tdn reads live state, not .tdxn files on disk.
When NOT to use read_tdn¶
For these, reach for the runtime-state MCP tools instead:
| Need | Use |
|---|---|
| Evaluated-expression runtime values | get_parameter |
| Cook errors / warnings | get_op_errors |
| DAT / CHOP / TOP output data | get_dat_content, capture_top |
| Cook timing | get_op_performance |
| Flag state after runtime mutation | get_op_flags |
Exporting a Network¶
Keyboard Shortcuts¶
- Ctrl+Shift+E — Export the entire project to a single
.tdxnfile - Ctrl+Alt+E — Export just the current COMP to a
.tdxnfile
MCP Tool¶
Use the export_network tool with these options:
| Parameter | Default | Description |
|---|---|---|
root_path |
"/" |
Starting COMP path |
include_dat_content |
Toggle setting | Include DAT text/table content |
output_file |
null |
File path (use "auto" for automatic naming, null for dict-only). A path other than the COMP's tracked file writes a snapshot: the tracked file and its table row are left untouched, and no stale-file cleanup runs. A relative path is anchored at the project folder. |
max_depth |
null (unlimited) |
Maximum recursion depth |
embed_all |
false |
Recurse into TDXN-tagged COMPs instead of writing tdn_ref pointers, producing a self-contained export |
What Gets Exported¶
- All operators under the root path (recursively)
- Only non-default parameter values
- Connections between operators
- Custom parameter definitions
- Built-in parameter sequences with non-default block counts or values (
sequences) - Flags, positions, sizes, colors, comments, tags
- Serializable operator storage entries (unless disabled per-COMP or globally)
- Annotations
- Optionally: DAT text/table content
What Gets Excluded¶
- System paths (
/local,/sys,/perform,/ui) - Pulse, Momentary, and Header parameters (no persistent state)
- Read-only parameters
- COMP externalization parameters (
externaltox,enableexternaltox, etc.) - Children of palette clones (TD recreates them from the clone source)
Importing a Network¶
Use the import_network MCP tool:
| Parameter | Description |
|---|---|
target_path |
Destination COMP path |
tdn |
The TDXN document, parsed object (full document or operators array) |
clear_first |
Delete existing children before importing |
Import Phases¶
The import process runs in a pre-phase plus the ordered phases below. This ordering ensures dependencies are satisfied:
| Phase | Action | Details |
|---|---|---|
| Pre | Resolve templates and defaults | Expand $t references and merge type_defaults into operators. With clear_first, excluded COMPs (the tdn_exclude tag) are preserved, not destroyed. |
| 1 | Create operators | Depth-first creation. COMPs first so children can be placed inside. |
| 2 | Create custom parameters | Pages, types, ranges, menu entries, defaults. |
| 2.5 | Expand sequences | Resizable parameter blocks (sequences on ops like mathmixPOP, glslPOP, constantCHOP) have their sequence parameters created before any values are set. |
| 3 | Set parameter values | Both built-in and custom. = prefix → expression, ~ prefix → bind. |
| 4 | Set flags | Array entries without - → true; with - → false. |
| 4a | Warn about locked non-DATs | Locked TOP/CHOP/SOP operators are flagged (lock preserved, frozen data is not). |
| 5 | Wire connections | Resolve sources (sibling name first, then full path). |
| 6 | Set DAT content | Text or table data loaded into DATs. |
| 6a | Restore storage | Storage key-value pairs restored via op.store(); $type wrappers deserialized. |
| 7 | Set positions | Positions, sizes, colors, comments applied (later phases still follow). |
| 7b | Set docking | Docking relationships restored between operators. |
| 7a | Create annotations | Annotations created with utility=True. |
| 8 | Restore file links | File/syncfile parameters restored on externalized DATs. |
| 8.5 | Restore TOX content | .tox content loaded into tox_ref shells. |
| 8.6 | Restore nested TDXN content | tdn_ref shells filled from their own .tdxn files in the same import (recursive, with an ancestor-chain cycle guard). Skipped by startup reconstruction and the post-save restore, whose own depth-sorted loops import every tracked TDXN COMP exactly once. |
| 9 | Apply target COMP properties | The target COMP's own type, parameters, flags, color, tags applied last. |
Version Compatibility¶
The importer checks metadata for compatibility:
version: Warning only when the file's version is newer than the running build (the risky direction — the file may use schema this build does not understand). Equal or older versions import silently.td_build: Info message if TD version differs (parameter defaults may vary)build: Logged for informational purposes
These checks are non-blocking — import always proceeds.
Diffing a Network¶
diff_tdn answers the question git cannot: what have I changed but not saved? It compares the live in-memory network against its on-disk .tdxn — the unsaved window, which git never sees (git only reads files on disk, not TouchDesigner's live state).
| Parameter | Default | Description |
|---|---|---|
target |
(optional) | A COMP path, or a .tdxn file path / bare filename (e.g. "mixer.tdxn"). Omit it for a whole-project summary across every live TDXN COMP. |
max_changed_ops |
200 |
Cap on the total changed entries returned (added + removed + modified combined, modified filled first). The reported counts always reflect the true totals; entries beyond the cap are dropped from the payload and counted in dropped. |
max_bytes |
60000 |
Cap on the output size. |
The comparison is semantic, not byte-level: both sides normalize through the same type_defaults / par_templates expansion, and the volatile export header (build, generator, td_build, exported_at, source_file) is ignored — so a no-op re-export shows nothing. Each change is {old, new} (old = disk, new = live), tagged root, op, or annotation.
Git integration: the .tdxn textconv driver¶
diff_tdn covers the unsaved window; for the committed view, Embody installs a git textconv driver so git diff / git log -p / git show on a .tdxn show only real network changes, not export-header churn. It is auto-configured on Envoy startup (.gitattributes *.tdxn diff=tdn, .embody/tdn_textconv.py, and git config diff.tdn.textconv). Use diff_tdn for what you have not saved; use git diff for what you have committed.
Crash safety and .embody_backup¶
Every .tdxn write is atomic — the content goes to a temp file in the same directory, is flushed and fsynced, then os.replace()s the target. A crash or power loss mid-write leaves either the complete old file or the complete new one, never a half-written one.
Before each write, Embody also rotates two generations of the previous content into a .embody_backup/ folder beside your .toe, mirroring the network file's relative path:
my-project/
├── embody/
│ └── Foo/
│ └── bar.tdxn ← current
└── .embody_backup/
└── embody/
└── Foo/
├── bar.tdxn.bak ← previous write
└── bar.tdxn.bak2 ← the one before that
After each write the file is read back and re-parsed; if that validation fails, the newest surviving backup is restored automatically and the log names the exact file it came from. ext.Embody.reconstructTDNComps and the post-save export roll back the same way if reconstruction fails. Recovery tries .bak first, then .bak2.
The folder holds backups of both .tdxn and .tdn files — a COMP externalized before v6.1.0 keeps writing .tdn forever — which is why the name carries no format token.
Renamed in v6.1.6
This folder was called .tdn_backup/ before v6.1.6. Nothing moves or deletes an existing one: new backups are written to .embody_backup/, and recovery still reads .tdn_backup/ when the newer folder has nothing for that file. Both stay git-ignored. Each COMP's legacy copies are retired automatically the first time Embody rotates a new backup for that COMP — which happens on the first save where its network actually changed (an unchanged COMP is skipped entirely and keeps its old copies, deliberately). Leave the old folder alone and it drains itself; there is no need to delete it by hand.
Backups are machine-local scratch and should not be committed. They are superseded on every write and carry no history git does not already have — the .tdxn files themselves are the versioned record. Envoy adds both folder names to your project's auto-managed .gitignore on startup. If your repo predates those entries and already tracks a backup folder, untrack it once:
# run for whichever folder your repo actually tracks -- git errors on a path it does not
git rm -r --cached .embody_backup
git rm -r --cached .tdn_backup
Deleting either folder at any time is safe — the current one is recreated on the next export.
Error Handling¶
TDXN import is best-effort — individual failures don't abort the entire operation.
| Situation | Behavior |
|---|---|
| Unknown field | Ignored (forward compatibility) |
Missing name or type |
Skip operator silently (no log) |
| Missing connection source | Skip connection, log warning |
| Unrecognized parameter style | Skip parameter, log warning |
| Unrecognized flag | Ignored |
| Invalid parameter value | Attempt type coercion; skip with warning if impossible |
| Version newer than build | Log warning, proceed (older/equal versions import silently) |
Unknown $t template reference |
Log warning, skip page |
Design principle
Log warnings for anything skipped so the developer can inspect the result. Never abort an entire import because a single operator, parameter, or connection failed — the partial result is more useful than no result.