# 160.0010 - TouchDesigner-Python
Created: 2026-09-06
Completed:
Type: #software
Tags: #touchdesigner #python
[Github Repository](https://github.com/wtkns/touchdesigner-python)
## Introduction
**Description:** Command-line bootstrapping framework that generates a git repository holding a minimal TouchDesigner file wired to load a Python startup DAT, which then builds and configures the node network from version-controlled code.
TouchDesigner projects normally live inside a `.toe` — a binary that can't be meaningfully diffed, reviewed, or merged, and that quietly becomes the only record of how a patch was built. This project inverts that: the `.toe` becomes a thin loader, and the project itself is Python.
The framework is a command-line utility — a `.bat` wrapper or a Python entry point run from a terminal — that scaffolds a new project in one step. Given a project name it generates a git repository containing the Python package, ignore rules, and README, plus a `.toe` file holding only the minimum internal components needed to reach Python: a startup DAT wired to run on open and pointed at the repository's modules. From there the Python code does the work, creating the operators and configuring the node network when the file launches.
The result is a project whose authored structure lives in text — editable in an IDE, diffable, and version-controlled like any other source — with a near-disposable `.toe` that can be rebuilt by reopening it. Scope is deliberately minimal: enough to get a fresh project to a reproducible development starting point, not a general application framework or a full abstraction layer over the node graph.
The working code lives in the [touchdesigner-python](https://github.com/wtkns/touchdesigner-python) repository, cloned locally to `300-Code/330 - TouchDesigner/0010 - TouchDesigner-Python/`.
## Repository
- **Remote:** https://github.com/wtkns/touchdesigner-python
- **Local:** `300-Code/330 - TouchDesigner/0010 - TouchDesigner-Python/` (excluded from Obsidian sync and from this vault's git tracking)
- **Code-side page:** [[300-Code/330 - TouchDesigner/0010 - TouchDesigner-Python|330.0010 - TouchDesigner-Python]]
- **State:** Phases 0–2 complete; latest commit `52f843e` (2026-09-08). Phase 3 still deliberately unwritten — see Tasks.
## Development Plan
Three decisions shape everything else. Two were taken up front:
**Two Pythons, kept strictly apart.** The command-line scaffolder runs in system Python — stdlib only, no dependencies. The generated project's code runs inside TouchDesigner's bundled Python. They never share an interpreter or a requirements file; if the generator imports something the generated project needs, the boundary has leaked. Held in practice: Python 3.11.9 installed 2026-09-07 for the scaffolder, invoked as `py -3.11`, with its own `scaffold_vEnv` for pytest.
**The `.toe` is a build artifact, not source.** Everything the loader needs is a handful of lines: an Execute DAT whose `onStart` resolves the repository root from `project.folder`, puts it on `sys.path`, imports the project package, and calls its build entry point. Logic that creeps into the DAT is logic that can't be diffed.
A third decision arrived from outside, during Phase 0. **Startup ordering is a constraint, not an afterthought.** Derivative's guidance for tdPyEnvManager is that it must initialize before anything imports from the side-loaded environment, and that components should therefore not be forced to initialize early by an Execute DAT. Since building at startup is precisely what this project does, it complies two ways instead of abandoning the premise: the bootstrap imports only the standard library, and the build is deferred a frame so it lands after components have come up.
## Tasks
**Phase 0 — build the golden output by hand. Done 2026-09-07.** The working `.toe` holds no project code: an Execute DAT reads its source from `DAT/StartupExec.py`, puts the repository on `sys.path`, and defers to Python a frame later, which builds the network. Reasoning held up — every hard problem was in this phase, and none of them were the ones the plan predicted.
**Phase 1 — the generator. Done 2026-09-07.** `new-project.bat <name> [path]` emits a working project — `.toe`, `tdpy` package, Execute DAT source, launcher, README, and a git repository with one commit. The plan called for a checked-in template `.toe` and a separate template tree; the tree was dropped, because a second copy of `tdpy/` would be a second source of truth free to drift. The repository is the template, and the generator copies from its own root. 16 tests, all running outside TouchDesigner as the plan required.
**Phase 2 — the dev loop. Done 2026-09-07.** `reload()` and error surfacing landed in Phase 0 because nothing was debuggable without them; a rebuild button followed. The question of how much of a hook belongs in the `.toe` answered itself — the button and its callback DAT are created at every launch by code on disk and never saved, so the binary gains nothing at all.
**Environment creation, added out of sequence 2026-09-07.** Not a numbered phase — it came from TDPyMovieplayer hitting it. tdPyEnvManager's palette component names a project's virtual environment after the folder containing it, with no override, so a project in a vault-numbered folder gets a vault-numbered environment. Generated projects now ship a `create-venv.bat` that calls Derivative's standalone helper with the name passed explicitly. Worth noting as the first thing the framework learned from a real project rather than from planning.
**Logging, added out of sequence 2026-09-08** (commit `6f15550`). Also not a numbered phase, and also prompted by TDPyMovieplayer: `report()` now stamps each line with a short `MM-DD HH:MM:SS`. Only the first line of a message, so a traceback stays pasteable, and the textport copy as well as the file — a line pasted out of a live session stops being self-dating the moment it leaves. The log is append-only across every launch, so without a stamp nothing marked where one session ended and the next began.
**Naming, added out of sequence 2026-09-08** (commits `05ac8c8`, `afa81a7`, `52f843e`). Three helpers, all prompted by TDPyMovieplayer's Phase 2, all about not guessing at names. `set_menu()` sets a menu parameter by internal name or UI label, resolving against the live parameter and reporting what it landed on — menus are the one case `set_par()` cannot catch, because a wrong item name leaves the parameter existing and the network quietly built on the default. `scaffold/params.py` reads `Config/TDParameterHelp.json`, which ships inside every install and lists every operator's parameters, so parameter names are looked up rather than remembered. And `create()` takes the name a node was created with, because `COMP.create()` appends a digit rather than fail — even when the name is free, which is how the rebuild button lived at `rebuild1` from the day it was written until someone read the log line it had been printing all along.
**Phase 3 — the build layer. Still not yet, and now for a better reason than before.** It was held open so a real project could say which helpers it needed. One has now been through two phases and asked for four things: a timestamped logger, `set_menu`, a parameter lookup, and `create`. Three are about *knowing and reporting*; the fourth is a construction primitive, but a corrective one — it makes `create` do what it already claimed to. None of them is the network-description layer this phase imagines, and none of them wanted one.
That is worth treating as evidence rather than coincidence. What a project building a network in code actually struggles with is not describing the network — `create`, `_place` and `set_par` have covered every node so far — but finding out what TouchDesigner calls things, and seeing what happened when it goes wrong. The original sketch stands below, unchanged, but the case for building it is weaker than it was three helpers ago. Thin, and only as far as it stays useful: operator creation, connection, and layout helpers. Split the *description* of a network (pure data — types, names, parameters, connections) from the *application* of it to TouchDesigner (a thin adapter). The description half is unit-testable on a normal machine; the adapter half isn't, so keep it small. Resist the pull toward a general node-graph abstraction — and wait for a real project to say which helpers it needs, rather than designing them in the abstract.
TDPyMovieplayer's Phase 1 was the first real test of that "wait and see" policy, and the answer it gave was *not yet*. Building a Table DAT and filling it needed `create`, `_place` and `set_par` and nothing more, so no helper was promoted. The one thing that did go upstream was the logger above — which is telling: what a real project wanted from the framework first was better reporting, not better network construction.
**Phase 4 — idempotent rebuild. Decided early.** Clear-and-rebuild, implemented in Phase 0: each run destroys the `generated` container and recreates it, leaving anything outside it alone. Revisit only if destroying and rebuilding proves too blunt.
**Phase 5 (optional) — headless template regeneration.** A script that launches TouchDesigner from the command line to rebuild the template `.toe` from code, so the binary stops being the source of truth. More pointed now than when written: `template.toe` is simultaneously the working file and the template, so anything that accumulates in it is inherited by every generated project.
## Notes
### Risks
- **`.toe` version compatibility.** TouchDesigner files are forward-only — a template saved in a newer build won't open in an older one. The seed `EmptyProject.toe` was saved from **2025.32050**, so that is the minimum build; document it in the generated README, and re-saving the template from a newer build silently raises the floor.
- **Load on Start ordering is undocumented.** The Execute DAT reads its code from a file rather than carrying a copy, which depends on *Load on Start* completing before `onStart` fires. Verified empirically on 2025.32050, but Derivative does not promise it. If a future build changes the order, the symptom is a project that opens and silently does nothing — a missing `logs/` folder is the tell.
- **Phase 2 is the real project.** Scaffolding a repository is a day's work; making the edit-reload-rebuild cycle pleasant is what determines whether this replaces UI-building in practice. Borne out immediately: `reload()` and error surfacing had to be built during Phase 0 because nothing was debuggable without them.
- **`project.folder` assumptions** break as soon as the `.toe` moves out of the repository root. Decide early whether that is supported or explicitly not.
### Precedent
[[300-Code/330 - TouchDesigner/0060 - PORTALS|330.0060 - PORTALS]] already externalizes its Python into a `DAT/` folder of extension modules (`StartupExt.py`, `ControlPanelExt.py`, and so on). This project is the generalization of a pattern already arrived at by hand there.