TDN Specification¶
Version 1.3
TDN is the substrate that makes "create at the speed of thought" possible. It's the format your AI agent reads to understand what's on the screen, the format that lets you compare two attempts side by side, and the format a network rebuilds itself from on the next project open. Without it, AI-driven TouchDesigner work is one-directional — you generate, and you're stuck with what you got. With it, every step of the loop — generate, compare, revert, branch — runs at the speed of typing.
TDN (TouchDesigner Network) is a JSON-based file format for representing TouchDesigner operator networks as human-readable, diffable text. It stores only non-default properties, keeping files minimal.
- File extension:
.tdn - MIME type:
application/json - Encoding: UTF-8
- JSON Schema:
tdn.schema.json
Document Structure¶
A .tdn file is a JSON object with the following top-level fields:
{
"format": "tdn",
"version": "1.3",
"build": 1,
"generator": "Embody/5.0.237",
"td_build": "2025.32050",
"exported_at": "2025-02-19T12:34:56Z",
"network_path": "/",
"type": "containerCOMP",
"options": {
"include_dat_content": true,
"include_storage": true
},
"type_defaults": { ... },
"par_templates": { ... },
"custom_pars": { ... },
"parameters": { ... },
"flags": [ ... ],
"color": [0.3, 0.5, 0.9],
"tags": ["tdn"],
"comment": "Main UI container",
"storage": { ... },
"operators": [ ... ],
"annotations": [ ... ]
}
| Field | Type | Required | Description |
|---|---|---|---|
format |
string | Yes | Always "tdn". Identifies the file format. |
version |
string | Yes | Format version. Currently "1.3". |
build |
integer | No | Embody build number for the exported COMP. Incremented each time the network is saved via Embody. Useful for version tracking and git diffs. null if the COMP has no build tracking. |
generator |
string | Yes | Tool that produced the file (e.g., "Embody/5.0.237"). |
td_build |
string | Yes | TouchDesigner version and build number (e.g., "2025.32050"). |
exported_at |
string | Yes | ISO 8601 UTC timestamp of export (e.g., "2025-02-19T12:34:56Z"). |
network_path |
string | Yes | The COMP path represented by this file (e.g., "/" for the entire project). |
type |
string | No | TouchDesigner operator type of the target COMP (e.g., "baseCOMP", "containerCOMP", "geometryCOMP"). Added in v1.1. Makes the file self-describing for portable import into other projects. On import, a mismatch between this field and the destination COMP's type triggers a warning. |
options |
object | Yes | Export settings used when generating this file. |
options.include_dat_content |
boolean | Yes | Whether DAT text/table content was included in the export. |
options.include_storage |
boolean | No | Whether operator storage entries were included in the export. Absent means true (included). Added in v1.2. Can be toggled per-COMP via the embed_storage_in_tdn storage key. |
type_defaults |
object | No | Per-type shared properties (parameters, flags, size, color, tags). See Type Defaults. |
par_templates |
object | No | Reusable custom parameter page definitions. See Parameter Templates. |
custom_pars |
object | No | Target COMP's own custom parameter definitions and values. Same format as operator-level custom_pars. Only present if the target COMP has custom parameters. |
parameters |
object | No | Target COMP's own non-default built-in parameter values. Same format as operator-level parameters. Only present if the target COMP has non-default built-in parameters. |
flags |
array | No | Target COMP's own non-default flags. Same format as operator-level flags. Added in v1.1. |
color |
[r, g, b] |
No | Target COMP's node color, if different from default gray. RGB floats 0.0–1.0, rounded to 4 decimal places. Added in v1.1. |
tags |
array of strings | No | Target COMP's tags, if any. Added in v1.1. |
comment |
string | No | Target COMP's node comment, if non-empty. Added in v1.1. |
storage |
object | No | Target COMP's persistent storage entries. Same format as operator-level storage. Added in v1.1. |
operators |
array | Yes | Array of operator objects. |
annotations |
array | No | Array of annotation objects. Only present if the root COMP contains annotations. |
Operator Object¶
Each entry in the operators array (and in nested children arrays) is an operator object:
{
"name": "noise1",
"type": "noiseTOP",
"position": [200, -100],
"size": [300, 150],
"color": [0.2, 0.6, 0.9],
"comment": "Primary noise source",
"tags": ["audio", "generator"],
"parameters": { ... },
"custom_pars": { ... },
"flags": [ ... ],
"storage": { ... },
"startup_storage": { ... },
"inputs": [ ... ],
"comp_inputs": [ ... ],
"dat_content": "...",
"dat_content_format": "text",
"children": [ ... ],
"palette_clone": true
}
Field Reference¶
| Field | Type | Required | Condition for inclusion |
|---|---|---|---|
name |
string | Yes | Always included. The operator's name. |
type |
string | Yes | Always included. TouchDesigner operator type (e.g., "baseCOMP", "noiseTOP", "textDAT", "waveCHOP"). |
position |
[x, y] |
No | Omitted when [0, 0] (default). Included only for operators not at the origin. |
size |
[width, height] |
No | Only if different from the default [200, 100]. |
color |
[r, g, b] |
No | Only if different from the default gray [0.545, 0.545, 0.545] (tolerance: 0.01 per channel). RGB values are floats from 0.0 to 1.0, rounded to 4 decimal places. |
comment |
string | No | Only if non-empty. Annotation text on the node. |
tags |
array of strings | No | Only if the operator has tags. |
dock |
string | No | Only if the operator is docked to another operator. Sibling name or full path. |
parameters |
object | No | Only if there are non-default built-in parameters (after type_defaults are factored out). |
custom_pars |
object | No | Only if the operator has custom parameters. Dict keyed by page name. |
flags |
array | No | Only if any flags differ from their defaults. |
storage |
object | No | Only if the operator has non-transient storage entries. Dict of key-value pairs. |
startup_storage |
object | No | Only if the operator has startup storage entries. Values restored via storeStartupValue() on import. |
inputs |
array | No | Only if the operator has operator-level connections. |
comp_inputs |
array | No | Only if the operator has COMP-level connections. COMPs only. |
dat_content |
string or array | No | Only for DAT-family operators when include_dat_content is true. See DAT Content. |
dat_content_format |
string | No | "text" or "table". Present whenever dat_content is present. |
children |
array | No | Only for COMPs with child operators (excluding palette clones). Contains nested operator objects. See Children and Hierarchy. |
annotations |
array | No | Only for COMPs with annotations. Contains annotation objects. |
palette_clone |
boolean | No | true if this COMP is cloned from the TouchDesigner palette (/sys/). When set, children are not exported (TD recreates them from the clone source). |
sequences |
object | No | Only if the operator has built-in parameter sequences with non-default block counts or values. See Built-in Parameter Sequences. Added in v1.3. |
tdn_ref |
string | No | Only for COMPs with their own TDN externalization. Relative file path to the child's .tdn file. Mutually exclusive with children. See COMP References. Added in v1.2. |
Compact Formatting¶
Short arrays and objects (≤80 characters when inlined) are written on a single line:
"position": [200, -100],
"size": [300, 150],
"color": [0.2, 0.6, 0.9],
"tags": ["audio", "generator"],
"inputs": ["noise1"]
Longer arrays remain multi-line. This dramatically reduces file size while maintaining readability.
Built-in Parameters¶
The parameters object maps parameter names to their values. Only built-in (non-custom) parameters whose current value differs from their default are included. Parameters shared unanimously across all operators of a type are factored into type_defaults instead.
Parameter Modes¶
Parameters can be in one of three exportable modes:
Constant — the value is stored directly:
Expression — prefixed with =. A Python expression that TouchDesigner evaluates each frame:
Bind — prefixed with ~. A reference expression that binds this parameter to another:
Note
A fourth mode, Export, exists in TouchDesigner but is not stored in TDN. Export mode is set by the exporting operator, not the parameter itself, and cannot be meaningfully imported.
Escaping¶
Constant string values that literally start with = or ~ are escaped by doubling the prefix:
| Stored value | Meaning |
|---|---|
"=foo" |
Expression: foo |
"==foo" |
Constant string: "=foo" |
"~bar" |
Bind expression: bar |
"~~bar" |
Constant string: "~bar" |
Skipped Parameters¶
The following built-in parameters are never exported, as they are internal actions or not meaningful outside a live project:
By name:
externaltox,enableexternaltox,reloadtoxreinitextensions,savebackupsavecustom,reloadcustompageindex
Info
file and syncfile parameters ARE exported when non-default, so that TDN files are self-contained for externalized DATs. This ensures file references survive import into a different project.
By style:
Pulse— action buttons (fire-once, no persistent state)Momentary— momentary buttons (no persistent state)Header— visual section dividers (no value)
Other exclusions:
- Read-only parameters
- Custom parameters (handled separately in
custom_pars)
Non-Default Comparison¶
A constant parameter is included only if its current value differs from its default:
- Floats: considered different if
abs(current - default) > 1e-9 - OP-reference parameters:
Noneand""are treated as equivalent (both mean "no operator connected") - All other types: standard equality comparison (
!=)
Divergent Defaults and the Creation-Defaults Catalog¶
Some TouchDesigner operators reset certain parameters during initialization, meaning the value reported by p.default differs from the value TouchDesigner actually assigns when the operator is created. For example:
cameraCOMPtz:p.defaultreports0, but TD creates cameras withtz = 5lightCOMPtz:p.defaultreports0, but TD creates lights withtz = 5renderTOPresolution parameters: reported defaults differ from creation values
If TDN used p.default directly for non-default comparison, a user-set value that happens to match p.default but differs from the actual creation value would be silently omitted from export. On import, TD would create the operator with the (different) creation value, and the user's intended value would be lost. Conversely, a parameter at the true creation value might be incorrectly included in the export, bloating the file with false positives.
The Catalog System¶
Embody solves this with a three-tier system that discovers and caches the true creation values for every operator type:
1. Background scan at startup
On project open, the CatalogManager extension checks whether a creation-values catalog exists for the current TD build (stored as a JSON file in the .embody/ directory at the project root). If no catalog exists, it runs a background scan:
- Iterates every creatable operator type in TouchDesigner (TOPs, CHOPs, SOPs, DATs, MATs, COMPs, POPs)
- Creates a temporary instance of each type
- Records
p.val(the actual value TD assigned) for every non-custom, non-read-only parameter
The catalog stores all creation values, not just divergent ones. This means TDN always has the ground-truth creation value for every parameter — _getCreationDefault() returns the catalog value directly, falling back to p.default only for parameters the catalog doesn't cover. The divergent-default problem (where p.val differs from p.default) is solved implicitly: by recording what TD actually assigns, the catalog captures both correct and divergent defaults without needing to distinguish between them.
The scan processes 1–2 operator types per frame to avoid dropping frames. The resulting catalog is written to .embody/catalog_{build}.json (e.g., .embody/catalog_099.2025.32280.json) and cached for future sessions on the same TD build.
2. Export/import correction
During TDN export, the _getCreationDefault() method checks the catalog before falling back to p.default:
def _getCreationDefault(self, op_type, par_name, par):
divergent = self._getDivergentDefaults(op_type)
if par_name in divergent:
return divergent[par_name]
return par.default
This means TDN compares parameter values against the actual creation value, not the reported default. Parameters are included in the export if and only if the user's value truly differs from what TD would assign to a freshly created operator.
3. Cross-build patching
When a .tdn file is opened on a different TD build than the one that exported it, creation defaults may have shifted between builds. The CatalogManager handles this automatically:
- Reads the
td_buildfield from each.tdnfile header - Loads the catalog for the source build (if available)
- Loads the catalog for the current build
- Finds parameters whose creation defaults changed between the two builds
- For each affected operator, checks if the current value matches the new default — if so, the parameter was omitted during export (because it matched the old default), and TD has now assigned the wrong new default
- Patches those parameters back to the old creation value
A summary dialog is shown to the user listing every corrected parameter.
Fallback chain¶
The catalog is loaded in priority order:
.embody/catalog file — per-build JSON written by the background scan- Embedded
divergent_defaultstable — a DAT inside the Embody COMP with bootstrap data for known TD builds - On-the-fly probing — if neither source has data for the current build, a temporary operator is created at export time to discover the true creation value
This ensures correct non-default comparison regardless of TD version, even on builds that Embody has never seen before.
Built-in Parameter Sequences¶
Added in v1.3.
Many TouchDesigner operators have resizable parameter blocks — mathmixPOP Combine blocks, glslPOP/glslTOP uniform sequences, attributePOP attribute blocks, constantCHOP channel blocks, etc. These are called parameter sequences in TD's API.
The sequences object stores per-operator sequence data. It is keyed by sequence name, where each value is an array of block objects containing only non-default parameter values using base names (without the sequence prefix or block index):
{
"name": "mathmix1",
"type": "mathmixPOP",
"sequences": {
"comb": [
{"oper": "A", "scopea": "P", "result": "startPos"},
{"oper": "A + B", "scopea": "vel", "scopeb": "direction", "result": "vel"},
{}
]
}
}
Design¶
- Array length =
numBlocks: The importer setsseq.numBlocks = len(blocks)to create the right number of parameter slots before setting values. - Base names:
"oper"rather than"comb0oper". The full parameter name is{seqName}{blockIndex}{baseName}(e.g.,comb2oper), but only the base name is stored. This makes the format portable and readable. - Empty objects
{}: Represent blocks where all parameters are at their default values. Included to preserve correct block count and ordering. - Omission: The
sequenceskey is omitted entirely when all sequences on the operator have their default block count and all block values are defaults. - Value shorthand: Same as built-in parameters —
=prefix for expressions,~prefix for binds, literal values for constants.
Import Phase¶
Sequences are expanded in Phase 2.5 (between custom parameter creation and parameter value setting). This ensures the dynamically-created sequence parameters exist before Phase 3 attempts to set values on them.
Exclusion from type_defaults¶
Sequence data is never included in type_defaults. Sequences are inherently per-instance (different operators have different block counts), so they cannot be compressed into per-type defaults.
Custom Parameters¶
The custom_pars object maps page names to arrays of parameter definitions. Unlike built-in parameters, custom parameters are always fully exported (including their definitions, ranges, and current values) because the importer must recreate them from scratch.
Note
Only COMPs can have custom parameters in TouchDesigner.
Page-Grouped Format¶
Custom parameters are grouped by page name. Each page contains an array of parameter definitions:
"custom_pars": {
"Controls": [
{
"name": "Speed",
"style": "Float",
"default": 1,
"max": 10,
"clampMin": true,
"normMax": 5,
"value": 2.5
},
{
"name": "Mode",
"style": "Menu",
"menuNames": ["linear", "ease", "bounce"],
"menuLabels": ["Linear", "Ease In/Out", "Bounce"],
"value": 1
}
],
"About": [
{
"name": "Build",
"style": "Int",
"label": "Build Number",
"readOnly": true,
"value": 14
}
]
}
The page name is the dict key — individual parameter definitions do not include a "page" field.
Template References¶
When a page's parameter definitions match a parameter template, the page is stored as a template reference with value overrides:
"custom_pars": {
"About": {
"$t": "about",
"Build": 14,
"Date": "2026-02-19 16:09:43 UTC",
"Touchbuild": "2025.32050"
}
}
The $t field names the template. Other keys are parameter value overrides (parameter name → current value). See Parameter Templates.
Custom Parameter Definition¶
| Field | Type | Condition | Description |
|---|---|---|---|
name |
string | Always | Base name of the parameter. For multi-component parameters, this is the group name without any suffix (e.g., "Pos" for a group of Posx, Posy, Posz). |
label |
string | If different from name |
Display label shown in the parameter dialog. Omitted when the label matches the parameter name. |
style |
string | Always | Parameter style. See Supported Styles. |
size |
integer | Multi-component Float/Int only |
Number of components when > 1 (e.g., 3 for a 3-component float). |
default |
any | If non-standard | Default value. Omitted when the default is a standard value (0, 0.0, "", or false). |
min |
number | If != 0 |
Minimum value. |
max |
number | If != 1 |
Maximum value. |
clampMin |
boolean | If true |
Whether the value is clamped to min. |
clampMax |
boolean | If true |
Whether the value is clamped to max. |
normMin |
number | If != 0 |
Normalized range minimum (for slider UI). |
normMax |
number | If != 1 |
Normalized range maximum (for slider UI). |
menuNames |
array of strings | Manually defined menus | Internal names for each menu option. |
menuLabels |
array of strings | If different from menuNames |
Display labels for each menu option. Omitted when labels match names. |
menuSource |
string | Dynamically populated menus | DAT path or expression that populates the menu. When present, menuNames/menuLabels are omitted. |
startSection |
boolean | If true |
Whether this parameter starts a new visual section. |
readOnly |
boolean | If true |
Whether the parameter is read-only. |
help |
string | If non-empty | Tooltip help text shown when hovering the parameter in the dialog. Omitted when empty. |
value |
any | Single-component, if non-default | Current value. Can be a constant, "=expr" string, or "~bind" string. Omitted when the value equals the default. |
values |
array | Multi-component, if any non-default | Current values for each component. Same format as value per element. Omitted when all values equal their defaults. |
Supported Styles¶
All 32 parameter styles recognized by TDN:
| Style | Category | Description |
|---|---|---|
Float |
Numeric | Floating-point number. Supports size > 1 for multi-component (suffixed 1, 2, 3...). |
Int |
Numeric | Integer number. Supports size > 1 for multi-component (suffixed 1, 2, 3...). |
XY |
Numeric compound | Two-component float (x, y). |
XYZ |
Numeric compound | Three-component float (x, y, z). |
XYZW |
Numeric compound | Four-component float (x, y, z, w). |
WH |
Numeric compound | Two-component float (w, h). |
UV |
Numeric compound | Two-component float (u, v). |
UVW |
Numeric compound | Three-component float (u, v, w). |
RGB |
Numeric compound | Three-component float (r, g, b). |
RGBA |
Numeric compound | Four-component float (r, g, b, a). |
Str |
String | Text string. |
Menu |
Menu | Dropdown menu. Uses menuNames/menuLabels for static menus, or menuSource for dynamic menus. |
StrMenu |
Menu | Editable string with dropdown suggestions. Uses menuNames/menuLabels or menuSource. |
Toggle |
Boolean | On/off checkbox. |
Pulse |
Action | Fire-once button (no persistent value). |
Momentary |
Action | Button that is active while held. |
Header |
Visual | Section header label (no value). |
File |
Path | File path selector (open). |
FileSave |
Path | File path selector (save). |
Folder |
Path | Folder path selector. |
Python |
Code | Python expression field. |
OP |
Reference | Operator path reference (any type). |
COMP |
Reference | COMP operator reference. |
TOP |
Reference | TOP operator reference. |
CHOP |
Reference | CHOP operator reference. |
SOP |
Reference | SOP operator reference. |
DAT |
Reference | DAT operator reference. |
MAT |
Reference | MAT operator reference. |
POP |
Reference | POP operator reference. |
Object |
Reference | Object COMP reference. |
PanelCOMP |
Reference | Panel COMP reference. |
Sequence |
Sequence | Sequence block parameter. |
Multi-Component Parameters¶
Some parameters consist of multiple related components grouped together (called "tuplets" in TouchDesigner).
Compound styles (XY, XYZ, XYZW, WH, UV, UVW, RGB, RGBA) have named suffixes:
This creates three parameters: Posx, Posy, Posz. The suffix mappings are:
| Style | Suffixes |
|---|---|
XY |
x, y |
XYZ |
x, y, z |
XYZW |
x, y, z, w |
WH |
w, h |
UV |
u, v |
UVW |
u, v, w |
RGB |
r, g, b |
RGBA |
r, g, b, a |
Numeric multi-component (Float or Int with size > 1) use numeric suffixes:
This creates three parameters: Weight1, Weight2, Weight3.
Type Defaults¶
The type_defaults section hoists properties that are shared unanimously across all operators of a given type into a single location, removing them from individual operators. Supported properties: parameters, flags, size, color, and tags.
"type_defaults": {
"containerCOMP": {
"parameters": {
"borderover": false,
"reloadbuiltin": false,
"resizecomp": "=me",
"repocomp": "=me"
},
"flags": ["viewer"],
"size": [300, 150]
},
"textDAT": {
"parameters": {
"language": "text"
},
"flags": ["viewer"],
"size": [130, 90],
"color": [0.67, 0.67, 0.67],
"tags": ["source"]
}
}
Unanimity Rule¶
A property enters type_defaults only if:
- The operator type appears 2+ times in the export
- The property is present on every operator of that type
- The property has the same value across all operators of that type
This eliminates the need for a "reset to default" marker — if a property is in type_defaults, every operator of that type has it.
Import Behavior¶
On import, type_defaults are merged into each operator before the relevant import phase. parameters use dict-level merge (operator-specific keys override individual defaults). flags, size, color, and tags use whole-value replacement (the operator either has its own value or inherits entirely from type_defaults):
effective_params = type_defaults[op_type].parameters | operator.parameters
effective_flags = operator.flags ?? type_defaults[op_type].flags
effective_size = operator.size ?? type_defaults[op_type].size
effective_color = operator.color ?? type_defaults[op_type].color
effective_tags = operator.tags ?? type_defaults[op_type].tags
When Type Defaults are Omitted¶
- If no types have 2+ operators with shared properties, the
type_defaultskey is absent - Single-instance operator types never contribute to type_defaults
Parameter Templates¶
The par_templates section extracts custom parameter page definitions that repeat across 2+ operators into named, reusable templates.
"par_templates": {
"about": [
{"name": "Build", "style": "Int", "label": "Build Number", "readOnly": true},
{"name": "Date", "style": "Str", "label": "Build Date", "readOnly": true},
{"name": "Touchbuild", "style": "Str", "label": "Touch Build", "readOnly": true}
]
}
Templates contain parameter definitions without values — they define the structure (name, style, label, ranges, etc.) of a page's parameters.
Template References¶
Operators reference templates via $t in their custom_pars:
"custom_pars": {
"About": {
"$t": "about",
"Build": 14,
"Date": "2026-02-19 16:09:43 UTC",
"Touchbuild": "2025.32050"
}
}
| Field | Description |
|---|---|
$t |
Template name (matches a key in par_templates) |
| Other keys | Value overrides: parameter name → current value |
Import Behavior¶
On import, $t references are resolved before Phase 2 (create custom parameters). Each template reference is expanded back into a full array of parameter definitions, with value overrides applied:
// Resolved from template + overrides:
"About": [
{"name": "Build", "style": "Int", "label": "Build Number", "readOnly": true, "value": 14},
{"name": "Date", "style": "Str", "label": "Build Date", "readOnly": true, "value": "2026-02-19 16:09:43 UTC"},
{"name": "Touchbuild", "style": "Str", "label": "Touch Build", "readOnly": true, "value": "2025.32050"}
]
Template Naming¶
Template names are derived from the page name (lowercased, spaces replaced with underscores). Collision suffixes (_2, _3) are added if multiple distinct page definitions share the same page name.
When Templates are Omitted¶
- If no page definition appears on 2+ operators, the
par_templateskey is absent - Pages unique to a single operator are always stored inline
Flags¶
The flags array contains string names of flags whose values differ from their defaults.
| Flag | Default | Description |
|---|---|---|
bypass |
false |
Operator is skipped in the processing chain. |
lock |
false |
Operator output is locked (frozen). See Lock Flag Limitation. |
display |
false |
Marks this operator as the display output (blue flag). |
render |
false |
Marks this operator for rendering (purple flag). |
viewer |
false |
Shows the operator's viewer on its node tile. |
expose |
true |
Whether the node is visible in the network editor. |
allowCooking |
true |
Whether the COMP is allowed to cook. COMPs only. |
Format¶
Flags that default to false are listed by name when set to true:
Flags that default to true use a - prefix when set to false:
Combined example — viewer on, cooking disabled:
Lock Flag Limitation¶
Locked content is NOT preserved for TOPs, CHOPs, or SOPs
TDN preserves the lock flag for all operator families, but it cannot store frozen pixel, channel, or geometry data. After a TDN round-trip (export + import), locked non-DAT operators will be locked but empty — no texture, no samples, no mesh.
This is by design, not a bug. Storing binary data would defeat TDN's purpose as a diffable, version-control-friendly format. A single locked 4K TOP could add over 100 MB to a .tdn file.
Embody warns you at save time if your network contains locked non-DAT operators.
The lock flag applies to all operator families — DATs, TOPs, CHOPs, and SOPs — freezing their cooked output so it no longer updates from inputs or parameters. However, TDN only persists the frozen data for DATs.
| Family | Flag persisted? | Frozen data persisted? | Notes |
|---|---|---|---|
| DAT | Yes | Yes (via dat_content) |
Full round-trip: both the lock state and text/table content are preserved. |
| TOP | Yes | No | Pixel data is not stored. On import, the lock flag is set but no texture data exists. The operator will appear black. |
| CHOP | Yes | No | Channel data is not stored. On import, the lock flag is set but no sample data exists. |
| SOP | Yes | No | Geometry data is not stored. On import, the lock flag is set but no mesh data exists. |
Workarounds:
- Unlock before saving — the operator will re-cook from its inputs on reload.
- Use TOX strategy instead of TDN for COMPs containing locked non-DAT operators. TOX files are binary and preserve all locked content.
- Store data externally — write pixel data to image files, channel data to CSV, etc., and reference them from your network.
Connections¶
TouchDesigner operators have two kinds of connections. TDN stores both as string arrays where array position equals the input index.
Operator Connections¶
Standard wiring between operators (left/right connectors). Stored in the inputs array:
Multi-input example — noise1 at index 0, nothing at index 1, level1 at index 2:
COMP Connections¶
COMP-level wiring (top/bottom connectors). Only applicable to COMPs. Stored in the comp_inputs array:
Source Resolution¶
Each string element references the source operator:
- If the source operator is a sibling (same parent), only the operator name is stored (e.g.,
"noise1"). - If the source is in a different parent, the full path is stored (e.g.,
"/project/other/transform1"). nullmeans no connection at that index.
On import, the source is resolved by first looking for a sibling with that name, then falling back to interpreting it as a full path.
Docking¶
Operators in TouchDesigner can be visually docked to other operators. A docked operator moves with its host in the network editor and can be collapsed into the host's tile.
When an operator is docked, TDN records a "dock" field on it:
| Field | Type | Condition |
|---|---|---|
dock |
string | Only if the operator is docked to another operator. |
The value is the sibling name of the dock host when they share a parent COMP, or the full operator path for cross-hierarchy docking. This follows the same reference convention as operator connections.
Docking is a purely visual/organizational relationship — it has no effect on operator behavior, data flow, or cooking. It is omitted from type_defaults because docking is always instance-specific.
Example:
During import, the dock target is resolved by sibling name first, then full path fallback. If the target cannot be found, a warning is logged and docking is skipped gracefully.
DAT Content¶
DAT-family operators can optionally include their text or table data. This is controlled by the include_dat_content option.
Text Format¶
For text-based DATs (textDAT, etc.):
{
"name": "script1",
"type": "textDAT",
"dat_content": "print('hello world')\nprint('goodbye')",
"dat_content_format": "text"
}
dat_content: raw text string with newlinesdat_content_format:"text"
Table Format¶
For table-based DATs (tableDAT, etc.):
{
"name": "lookup1",
"type": "tableDAT",
"dat_content": [
["name", "value", "type"],
["speed", "1.5", "float"],
["active", "1", "int"]
],
"dat_content_format": "table"
}
dat_content: array of row arrays (each row is an array of cell value strings)dat_content_format:"table"
Inclusion Conditions¶
DAT content is only included when:
- The operator belongs to the DAT family
- The
include_dat_contentoption istrue - The DAT has content (non-empty text or at least one row)
Operator Storage¶
Every TouchDesigner operator has a .storage dictionary for persistent Python data. TDN exports all serializable storage entries except known transient/internal keys used by Embody's runtime.
Per-COMP Storage Toggle¶
Storage export can be disabled per-COMP by setting the embed_storage_in_tdn storage key to false on the target COMP, or globally via Embody's Embedstorageintdns parameter. When disabled, the options.include_storage field is false and operator storage entries are omitted — except for Embody control keys (embed_dats_in_tdn, embed_storage_in_tdn) which are always preserved to maintain round-trip fidelity of export preferences.
Format¶
{
"name": "my_comp",
"type": "baseCOMP",
"storage": {
"count": 42,
"label": "hello",
"items": [1, 2, 3],
"config": {"key": "value"},
"coords": {"$type": "tuple", "$value": [10, 20]},
"tags": {"$type": "set", "$value": ["a", "b", "c"]},
"raw": {"$type": "bytes", "$value": "AAEC/w=="}
}
}
Value Serialization¶
Python types that map directly to JSON are stored as-is. Non-JSON types use a $type/$value wrapper:
| Python Type | JSON Representation |
|---|---|
str, int, float, bool |
Direct JSON value |
None |
null |
list |
JSON array (recursive) |
dict |
JSON object (recursive, string keys only) |
tuple |
{"$type": "tuple", "$value": [...]} |
set |
{"$type": "set", "$value": [...]} (sorted for determinism) |
bytes |
{"$type": "bytes", "$value": "<base64>"} |
Values that cannot be serialized to JSON (threading objects, operator references, custom class instances) are silently skipped during export.
Skipped Keys¶
The following storage keys are never exported (runtime/transient state managed by Embody):
_tdn_stripped_paths, _git_root, envoy_running, envoy_shutdown_event, expanded_paths, manage_file_path, visible_count, hover
Startup Storage¶
TouchDesigner supports storeStartupValue(key, value) — values that reset to their initial state on every project open, regardless of what they were when the file was saved. TDN supports this via an optional startup_storage field:
{
"name": "my_comp",
"type": "baseCOMP",
"storage": {"runtime_count": 0},
"startup_storage": {"version": 1, "default_mode": "auto"}
}
On import, keys in startup_storage are restored via storeStartupValue(), while keys in storage use store().
Export limitation: TouchDesigner provides no API to introspect which storage keys were set with storeStartupValue() vs store(). During automatic export, all entries go into storage. The startup_storage field must be populated manually or by tools that know the intent (e.g., code generators, StorageManager-aware exporters).
Import Behavior¶
Storage is restored during Phase 6a (after DAT content, before positions). Keys in storage are restored via op.store(key, value). Keys in startup_storage are restored via op.storeStartupValue(key, value). $type wrappers are deserialized back to their Python types. Unknown $type values are treated as plain dicts with a warning logged.
Children and Hierarchy¶
COMPs can contain child operators. These are stored in the children array, which contains nested operator objects following the exact same schema:
{
"name": "container1",
"type": "baseCOMP",
"children": [
{
"name": "noise1",
"type": "noiseTOP"
},
{
"name": "null1",
"type": "nullTOP",
"position": [300, 0],
"inputs": ["noise1"]
}
]
}
Note that container1 omits position (defaults to [0, 0]) and noise1 also omits position. Only null1 at [300, 0] includes its position.
Nesting is recursive — COMPs inside COMPs can have their own children. The optional max_depth export parameter limits recursion depth (null means unlimited). COMPs may also contain an annotations array alongside children — see Annotations.
Nested TDN-Externalized COMPs¶
When a parent TDN file contains children for a child COMP that has its own TDN externalization entry in the externalizations table, the child's children array is skipped during import. The child COMP shell is still created (its operator definition — name, type, position, parameters — is applied), but its internal network is not populated from the parent's snapshot. The child's own .tdn file is the source of truth for its contents.
This prevents a common problem: if a child COMP is updated and re-exported to its own .tdn file, but the parent is not re-exported, importing the parent would silently overwrite the child with stale data. By skipping nested TDN children, each .tdn file owns exactly one level of the hierarchy.
What this means in practice:
- Export is unchanged — parent TDN files still include the full recursive hierarchy in their
childrenarrays. This keeps the file self-contained and useful as a portable snapshot. - Import detects child COMPs with their own TDN entries and skips their children. A log message is emitted for each skipped child (e.g.,
Skipping children of /project/parent/child — has its own TDN externalization). - Reconstruction on project open imports parents before children (sorted by path depth). Combined with the skip logic, this means each COMP's network is populated exactly once, from its own authoritative
.tdnfile.
If a child COMP is removed from the externalizations table (no longer tagged for TDN), its children array in the parent TDN will be imported normally — no special handling needed.
COMP References (tdn_ref)¶
Added in TDN v1.2.
When a parent COMP is exported and a child COMP has its own TDN externalization, the parent's operator definition for that child includes a tdn_ref field instead of a children array:
{
"name": "audio_mixer",
"type": "baseCOMP",
"tdn_ref": "Embody/project1/audio_mixer.tdn",
"position": [600, 0]
}
| Field | Type | Description |
|---|---|---|
tdn_ref |
string |
Relative file path from the externalization folder to the child's .tdn file. Includes the COMP name in the path for cross-validation. |
Mutually exclusive with children: When tdn_ref is present, the operator definition does not contain a children array. The COMP's internal network is defined entirely in the referenced file.
Resolution: On import, the importer creates the COMP shell (name, type, position, parameters, flags) but does not populate its children. The referenced .tdn file is imported separately during reconstruction (sorted by path depth — parents before children).
Cross-validation: The tdn_ref value is checked against two independent sources:
- Externalizations table: The child COMP's path must have an entry with
strategy='tdn'and a matchingrel_file_path. - Disk: The referenced
.tdnfile must exist at the resolved absolute path.
Mismatches produce warnings, not errors — the COMP shell is always created regardless. This ensures graceful degradation when files are moved or the table is out of sync.
Backward compatibility:
- Files without
tdn_ref(TDN v1.1 and earlier) continue to work. The existing_stripNestedTDNChildrenmechanism handles them via the externalizations table. - Files with
tdn_refimported by an older Embody that doesn't recognize the field will silently ignore it. The_stripNestedTDNChildrenpath handles the nested COMP correctly as a fallback. - The
embed_all=Trueexport option suppressestdn_refand inlines all children, producing a fully self-contained file regardless of child externalization status.
Palette Clones¶
COMPs that originate from the TouchDesigner palette (e.g. abletonLink, Widget components, anything under Samples/Palette/) are detected and marked with "palette_clone": true. Their children are not exported because TouchDesigner automatically recreates them from the palette source when the project loads.
Parameter handling for palette clones: During export, parameters are compared against two baselines — the built-in default (p.default) and the clone source's actual value. If a parameter matches p.default but differs from the clone source, it is still exported. This prevents user-set values from being silently dropped when they happen to match the built-in default but not the clone source (e.g., a buttontype whose p.default is "momentary" but whose clone source is "toggledown"). The clone and enablecloning parameters are always excluded — TD auto-sets these during rebuild.
Palette Detection¶
Detection uses two strategies:
- Palette catalog (primary): Embody ships a catalog at
embody/Embody/palette_catalog.tsvbuilt by scanning every.toxin TD's installed palette directory. The catalog records each component'sname,OPType, andmin_childrencount (264 entries for TD 099.2025.32280). A COMP is detected as a palette if its name matches a catalog entry, itsOPTypematches, and it has at leastmin_children // 2children (a floor that tolerates user modifications while rejecting empty user COMPs that happen to share a palette name). - Clone expression heuristic (fallback): if the
cloneparameter points to/sys/or referencesTDBasicWidgets,TDResources, orTDTox, the COMP is detected as a palette. Catches cases where the catalog doesn't cover the current TD build. Exception: paths and expressions under/sys/TDTox/defaultCOMPs/are explicitly excluded. That directory holds TD's native-operator templates — every freshly-createdbuttonCOMP,panelCOMP, etc. clones from there by default, and those are stock types, not palette components. Export them as regular COMPs.
The catalog is loaded into memory by CatalogManagerExt.EnsureCatalogs() at startup from the shipped TSV (skipping a runtime scan) or from .embody/catalog_<build>.json if already cached locally.
Palette Handling¶
When the export path encounters a detected palette COMP, the Tdnpalettehandling parameter on Embody's TDN page decides what to do:
| Value | Behavior |
|---|---|
Ask (default) |
On first encounter of each palette COMP, prompts with four buttons: Black Box (this COMP), Full Export (this COMP), Black Box for All (flips the project-wide par), Full Export for All (flips the project-wide par). The per-COMP decision is persisted via comp.store('_tdn_palette_handling', 'blackbox'|'fullexport') so subsequent exports don't re-prompt. |
Black Box |
Always emit "palette_clone": true with parameter overrides only. Children are re-dropped from the palette on import. Correct for stock palette COMPs; lets upstream palette updates from Derivative flow through on round-trip. |
Full Export |
Always export all internal children as if the COMP were a regular user COMP. Use when you've heavily customized the palette internals and need that state preserved across round-trip. |
Per-COMP stored decisions take precedence over the project-wide par. To reset a COMP's stored decision, call comp.unstore('_tdn_palette_handling').
Annotations¶
Annotations are visual documentation elements in TouchDesigner networks (comments, network boxes, and annotate panels). They are stored in an annotations array at the top level (for root-level annotations) and optionally on each COMP operator (for nested annotations).
{
"operators": [ ... ],
"annotations": [
{
"name": "annot_core",
"mode": "annotate",
"title": "Core Tests",
"text": "Unit tests for core functionality",
"position": [-70, -300],
"size": [1070, 660],
"color": [0.5, 0.5, 0.5],
"opacity": 0.8
}
]
}
For nested COMPs, annotations appears alongside children:
{
"name": "container1",
"type": "baseCOMP",
"children": [ ... ],
"annotations": [
{
"name": "annot1",
"mode": "comment",
"text": "Signal processing chain"
}
]
}
Annotation Object¶
| Field | Type | Required | Condition for inclusion |
|---|---|---|---|
name |
string | Yes | Always included. The annotation operator's name. |
mode |
string | Yes | Always included. One of "annotate", "comment", or "networkbox". |
title |
string | No | Only if non-empty. Title bar text (for annotate and networkbox modes). |
text |
string | No | Only if non-empty. Body text content. |
position |
[x, y] |
No | Omitted when [0, 0] (default). |
size |
[width, height] |
Yes | Always included — annotations have no standard default size. |
color |
[r, g, b] |
No | Only if different from the default gray [0.545, 0.545, 0.545]. Background color. |
opacity |
number | No | Only if different from 1.0. Background opacity (0.0 to 1.0). |
Import Behavior¶
Annotations are created during Phase 7a (after operator positions, before file link restoration). Each annotation is created as an annotateCOMP with utility=True (matching TD's native behavior — annotations are utility operators hidden from .children).
Value Serialization¶
All parameter and content values are converted to JSON-safe types using these rules, applied in order:
| Python Type | JSON Output | Rule |
|---|---|---|
None |
string | Converted to empty string "". |
bool |
boolean | Stored as-is (true/false). |
int |
number | Stored as-is. |
float |
number (int) | If the value is a whole number (and fits in 53-bit integer range), it is converted to an integer. E.g., 1.0 becomes 1. |
float |
number (float) | Rounded to 10 decimal places to eliminate floating-point noise. |
str |
string | Stored as-is (with =/~ escaping applied for parameter values). |
list / tuple |
array | Each element is recursively serialized. |
| Any other type | string | Converted via str(). |
Color values (color field on operators) are rounded to 4 decimal places.
System Exclusions¶
The following top-level paths and all their descendants are always excluded from export. These contain TouchDesigner system internals that should not be version-controlled:
| Path | Contents |
|---|---|
/local |
Local parameters |
/sys |
System operators (Thread Manager, TDJSON, etc.) |
/perform |
Performance monitoring |
/ui |
UI framework operators |
An operator is excluded if its path equals one of these or starts with one followed by / (e.g., /sys/TDResources is excluded).
Import Process¶
Importing a .tdn file reconstructs the network in a pre-phase plus eight sequential phases. This ordering ensures that dependencies are satisfied — for example, operators must exist before they can be connected, and positions are set last because creating operators may shift existing nodes.
| Phase | Action | Details |
|---|---|---|
| Pre | Resolve templates and defaults | If par_templates is present, $t references in custom_pars are expanded to full definitions with value overrides. If type_defaults is present, shared properties are merged into each operator (parameters via dict merge, flags/size/color/tags via whole-value injection; operator-specific values take precedence). |
| 1 | Create operators | All operators are created depth-first. COMPs are created first so their children can be placed inside them. |
| 2 | Create custom parameters | Custom parameter definitions are created on COMPs (pages, types, ranges, menu entries, defaults). |
| 3 | Set parameter values | Both built-in and custom parameter values are applied. = prefix sets expression mode, ~ prefix sets bind mode, all other values set constant mode. |
| 4 | Set flags | Operator flags are applied. Array entries without - prefix set the flag to true; entries with - prefix set to false. |
| 5 | Wire connections | Operator and COMP connections are established. Source references are resolved (sibling name first, then full path). Array position equals input index. |
| 6 | Set DAT content | Text or table data is loaded into DAT operators. |
| 6a | Restore storage | Storage key-value pairs are restored via op.store(). $type wrappers are deserialized to Python types (tuple, set, bytes). |
| 7 | Set positions | Node positions, sizes, colors, and comments are applied last. Missing position defaults to [0, 0]. |
| 7a | Create annotations | Annotations are created from the annotations array (top-level and per-COMP). Each annotation is created as an annotateCOMP with utility=True, then its mode, title, body text, position, size, color, and opacity are set. |
The importer accepts either a full .tdn document (with metadata) or just the operators array directly.
Extension Initialization Timing¶
Extensions initialize BEFORE TDN import
When a TDN COMP is reconstructed (on project open or after save), the COMP shell is created first and any extensions on it initialize immediately. The TDN import runs after extension initialization, calling ImportNetwork with clear_first=True — which deletes all children and recreates them from the .tdn file. This means any state set up by onInitTD inside the COMP is overwritten.
Timeline on project open:
| Step | Frame | What happens |
|---|---|---|
| 1 | Early | COMP shell created (exists but empty) |
| 2 | Early | Extension __init__ runs |
| 3 | End of frame | onInitTD fires — network may not exist yet |
| 4 | Frame 60 | ReconstructTDNComps runs ImportNetwork(clear_first=True) |
| 5 | Frame 60+ | All children deleted and recreated from .tdn |
Timeline on save (strip/restore cycle):
| Step | What happens |
|---|---|
| 1 | Pre-save: children stripped from TDN COMPs |
| 2 | .toe saved without TDN children |
| 3 | Post-save: ImportNetwork re-imports children from .tdn |
| 4 | Extensions may reinitialize during restore |
Impact: If an extension's onInitTD creates operators, sets parameter values, writes to storage, or builds any state inside the COMP, that work is destroyed by the import. This affects extensions that live inside TDN COMPs and extensions whose ownerComp is a TDN-strategy COMP.
Solution: Defer initialization using run() with delayFrames:
def onInitTD(self):
run('args[0].postInit()', self, delayFrames=5)
def postInit(self):
"""Runs after TDN import completes. Safe to set up state."""
pass
The deferred method must be idempotent — it will run on every project open, after every save, and on manual reimport. Use a delay of at least 5 frames to ensure all import phases have completed.
For full guidance on writing extensions that coexist with TDN, see the Extensions documentation.
Version Compatibility¶
When importing a full .tdn document, the importer checks the metadata fields for compatibility:
version: Compared against the current TDN format version. A warning is logged if they differ, indicating the file may use a newer or older schema.td_build: Compared against the running TouchDesigner version. An informational message is logged if they differ, since operator types and parameter defaults may vary between TD builds.build: Logged for informational purposes, identifying which save iteration is being imported.
These checks are non-blocking — the import always proceeds regardless of mismatches.
Round-Trip Guarantees¶
For most networks, export → import → re-export produces identical .tdn output. The format is designed to be stable across round-trips, with a few documented exceptions.
Preserved¶
- Target COMP metadata (v1.1+): type, flags, color, tags, comment, storage
- Operator names, types, and hierarchy
- Non-default parameter values (constant, expression, and bind modes)
- Custom parameter definitions (all fields, all styles)
- Flags, connections, positions, sizes, colors, comments, tags
- Operator storage (serializable entries only — see Operator Storage)
- Annotations (mode, title, body text, position, size, color, opacity)
- DAT text and table content (byte-for-byte when
include_dat_contentistrue) - Float values (stable after the first export — see below)
- Type defaults and parameter templates (re-computed on each export)
Known Exceptions¶
Palette clones — On first export, a palette-cloned COMP is marked "palette_clone": true and its children are skipped. After import, TouchDesigner materializes the children from the clone source. A subsequent re-export will include those children as regular operators. This means the second export is larger than the first. Parameters that match p.default but differ from the clone source are preserved (see Palette Clones).
Color tolerance — Colors within 0.01 per channel of the default gray [0.545, 0.545, 0.545] are treated as default and not exported. A color of [0.55, 0.55, 0.55] survives; [0.546, 0.546, 0.546] is dropped.
Float precision — Values are rounded to 10 decimal places on first export. This can change the last digits of very precise values (e.g., 3.14159265358979 → 3.1415926536). After that first rounding, subsequent exports are stable.
Type defaults recomputation — Type defaults and parameter templates are recomputed from scratch on each export. If operator populations change between exports (operators added/removed), different properties may qualify as "unanimous" for type_defaults, and different pages may qualify as templates. The final network state is always identical, but the JSON structure may differ.
Locked non-DAT data — When a TOP, CHOP, or SOP is locked, TDN preserves the lock flag but not the frozen pixel, channel, or geometry data. After import, the operator is locked but empty. See Lock Flag Limitation.
Intentionally Excluded¶
The following are never exported and are not considered a loss:
- Export-mode parameters — set by the exporting operator, not the parameter itself
- Pulse / Momentary / Header styles — no persistent state
- Read-only parameters — cannot be set on import
- COMP externalization parameters (
externaltox,enableexternaltox,reloadtox) — COMP.toxexternalization is managed separately by Embody - Transient storage keys — runtime state used by Embody (
envoy_running,_git_root, etc.) - Non-serializable storage values — threading objects, operator references, custom class instances
Error Handling¶
TDN import is best-effort — individual failures should not abort the entire operation. This section describes the expected behavior for developers working with TDN files.
Unknown Fields¶
Developers should ignore unknown fields when parsing TDN documents. This ensures forward compatibility — a file exported by a newer version of Embody can still be imported by an older version, with unrecognized fields silently skipped.
Failure Modes¶
| Situation | Expected behavior |
|---|---|
| Unknown field in any object | Ignore it. |
Missing required field (name, type) on an operator |
Skip that operator, log an error. |
| Missing connection source (operator not found) | Skip that connection, log a warning. |
Unrecognized custom parameter style |
Skip that parameter definition, log a warning. |
| Unrecognized flag name | Ignore it. |
| Invalid parameter value type | Attempt type coercion; if impossible, skip with a warning. |
Version mismatch (version, td_build) |
Log a warning, proceed with import. |
Target COMP type mismatch (type vs destination) |
Log a warning, proceed with import. The file's type field is informational — import does not change the destination COMP's type. |
Unknown $t template reference |
Log a warning, skip that page. |
Missing type_defaults entry for a type |
No-op (operator uses its own properties). |
| Non-serializable storage value on export | Skip that value, log at DEBUG level. |
Unknown $type in storage on import |
Treat as plain dict, log a warning. |
Failed store() call on import |
Skip that key, log a warning. |
General 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.
Complete Example¶
A realistic .tdn file demonstrating all major features:
{
"format": "tdn",
"version": "1.3",
"build": 3,
"generator": "Embody/5.0.237",
"td_build": "2025.32050",
"exported_at": "2026-02-19T14:30:00Z",
"network_path": "/",
"type": "baseCOMP",
"options": {
"include_dat_content": true,
"include_storage": true
},
"type_defaults": {
"baseCOMP": {
"parameters": {
"resizecomp": "=me",
"repocomp": "=me"
}
}
},
"par_templates": {
"about": [
{"name": "Build", "style": "Int", "label": "Build Number", "readOnly": true},
{"name": "Version", "style": "Str", "label": "Version", "readOnly": true}
]
},
"operators": [
{
"name": "controller",
"type": "baseCOMP",
"color": [0.2, 0.4, 0.8],
"comment": "Main controller",
"tags": ["core"],
"custom_pars": {
"Controls": [
{
"name": "Speed",
"style": "Float",
"default": 1,
"max": 10,
"clampMin": true,
"normMax": 5,
"value": 2.5
},
{
"name": "Mode",
"style": "Menu",
"menuNames": ["linear", "ease", "bounce"],
"menuLabels": ["Linear", "Ease In/Out", "Bounce"],
"value": 1
},
{
"name": "Color",
"style": "RGB",
"clampMin": true,
"clampMax": true,
"values": [1, 0.5, 0]
}
],
"About": {
"$t": "about",
"Build": 3,
"Version": "1.0.0"
}
},
"flags": ["viewer"],
"comp_inputs": ["renderer"],
"children": [
{
"name": "noise1",
"type": "noiseTOP",
"parameters": {
"type": "sparse",
"amp": 0.8,
"period": 2,
"monochrome": true,
"resolutionw": 1920,
"resolutionh": 1080
}
},
{
"name": "level1",
"type": "levelTOP",
"position": [300, 0],
"parameters": {
"opacity": "=parent().par.Speed / 10"
},
"inputs": ["noise1"],
"flags": ["display"]
},
{
"name": "config",
"type": "tableDAT",
"position": [0, -200],
"dat_content": [
["key", "value"],
["resolution", "1920x1080"],
["fps", "60"]
],
"dat_content_format": "table",
"flags": ["lock"]
},
{
"name": "script1",
"type": "textDAT",
"position": [300, -200],
"dat_content": "# Initialize\nprint('Controller ready')",
"dat_content_format": "text"
}
]
},
{
"name": "renderer",
"type": "baseCOMP",
"position": [500, 0],
"size": [300, 150],
"custom_pars": {
"About": {
"$t": "about",
"Build": 1,
"Version": "0.9.0"
}
}
}
]
}
Key observations:
type_defaults: BothbaseCOMPs shareresizecompandrepocompexpressions, so those are hoisted out of individual operatorspar_templates: The "About" page definition is shared betweencontrollerandrenderer, with different values- Expression shorthand:
"=parent().par.Speed / 10"instead of{"expr": "..."} - Flags as arrays:
["viewer"],["display"],["lock"] - Simplified connections:
["noise1"]instead of[{"index": 0, "source": "noise1"}] - Optional position:
noise1at[0, 0]omitsposition;controllerat[0, 0]also omits it - Compact formatting: Arrays like
[300, 0],[0.2, 0.4, 0.8],["core"]are inline
Changelog¶
| Version | Date | Changes |
|---|---|---|
| 1.0 | 2026-02-19 | Initial release with 8 format optimizations: expression shorthand (=/~ prefixes), flags as arrays, page-grouped custom parameters, type defaults, parameter templates, optional position, simplified connections, compact JSON formatting. |
| 1.0 | 2026-02-22 | Extended type_defaults to support flags, size, color, and tags in addition to parameters. Backward-compatible: old importers ignore unknown keys, new importers handle files without the new keys. |
| 1.0 | 2026-03-01 | Added annotation support (annotations array at top level and per-COMP). Added Phase 7a to import process. Removed file/syncfile from SKIP_PARAMS so DAT file references are preserved in TDN exports. Pre-save now auto-exports current state before stripping TDN COMPs. |
| 1.3 | 2026-04-07 | Added built-in parameter sequence support (sequences key on operator objects). Operators with resizable parameter blocks (mathmixPOP, glslPOP, attributePOP, constantCHOP, etc.) now round-trip correctly. Added Phase 2.5 to import process. Sequence parameters excluded from type_defaults compression and _buildParCache. |