| status | landed |
|---|---|
| summary | Highlight other creators' TouchDesigner tools on the website and in the picker, blog-style, with attribution. Builds on recommendations.json (link rows and pinned-tox rows already exist); adds post pages on the site, a Place button in the picker, and a third delivery kind for tdp Python packages. |
| since | 2026-09-25 |
The ask. Present tools by other creators on the website and in the installer, like a blog: each one gets a short write-up with clear credit. Three kinds:
| Kind | Example | What a user can do |
|---|---|---|
link |
a Patreon post, a Gumroad page | read our write-up, open the author's page |
tox |
a .tox attached to a GitHub release | the above, plus place the exact file we checked |
tdp |
a tox shipped as a Python package (tdp- convention) |
the above, plus install that exact version into the project's venv and place its tox |
packaging/recommendations.jsonis the list, validated bypackaging/recommendations.py. Rows needname,authorandurl. A row withtox_url+sha256+bytescan be placed; without them it is a link. Package-shaped fields (version,requires,access...) are refused, so the list can never be mistaken for our own packages. It publishes on its own (upload.py --recommendations), no release needed. Thetoolsarray is empty today.- The CMS edits the list and pins a
tox_urlby hash. - The picker shows the rows last, as "From other creators": a card with the author, the description and a link out. There is no Place button yet.
- The updater can already place a pinned row
(
CommunityList,RefreshCommunity,PlaceCommunityTool, files in<store>/community/). Nothing calls it from the picker. - The website shows none of it.
- Foreign packages (TDXMap, ParHoverMIDI_VSN1) are a different thing and stay different: we mirror and update those. Highlights are never updated by us.
- tdp is supported by the launcher (TDXLPP
tdp.rs, uv with pip fallback), not by FNSTools.
- The section is
/community/. - Write-ups are Markdown and may be long, articles rather than blurbs.
- Authors are told before a highlight goes up; their images are used only with their permission.
The new fields are optional and additive. No reader checks the schema number and every reader ignores fields it does not know, so the number stays 1 and installs already in the field keep working.
Each row becomes a post as well as a card. New optional fields:
slug: the page address,[a-z0-9-].date: when we highlighted it; the site lists newest first.image: a screenshot, stored in our repo and used only with the author's permission.platform:github,patreon,gumroad,itch,pypi, orother. It picks the link label ("Get it on Patreon").author_license: the author's terms, as they state them, shown as-is. (licensealone is refused: it is one of our package fields.)tdp:{ "package": "tdp-foo", "module": "tdpFoo", "tox": "Foo", "lock": ["tdp-foo==1.2.0 --hash=sha256:...", ...] }. The lock is every requirement the package resolves to, each at one version with its hash, written by the CMS pin. It plays the parttox_url+sha256plays for a tox: the exact set a curator checked. It is not update machinery.toxnames an entry in the package's_ToxFiles, or is left out for a package with a singleToxFile.
The write-up is Markdown in website/content/community/<slug>.md, with
name in its frontmatter to join it to the row. A row without a write-up
still gets a card, with no post page.
/community/lists every highlight, newest first: image, name, "by author", the kind (on their site, .tox file, Python package) and date, and the one-line description. A row with asluglinks to its post; a row without one links straight to the author./community/<slug>/is the post: title, byline with the author's link and the date, the image, then a credit box before the write-up (made by, their terms, the pinned file or package version, "Not part of FNSTools. made it and maintains it; questions and support go to them.", and the button to their page).- The landing page shows the three newest between the
<!-- COMMUNITY:START -->/<!-- COMMUNITY:END -->markers, and nothing at all while the list is empty. Both footers link/community/; the header nav does not yet. - The build refuses: a write-up with no row of that slug, a row with a
slug and no write-up, a row
imagethat is not inwebsite/content/community/images/, and an image in a write-up that is not/community/images/<file>(posts sit one folder down, so a relative path would break). - The pages sit outside
/docs/, so the docs search does not index them.
Drafts (2026-09-26). A row with draft: true is still being written:
recommendations.published() leaves it out of what installs download, the
site build leaves it out of /community/ and the landing strip, and its
write-up may exist without being an orphan. The CMS's Build site sets
FNS_SHOW_DRAFTS=1, so the local preview shows drafts marked "Draft"; the
public build never does, and the landing strip (committed in
website/index.html) never includes one. Untick Draft in the CMS to
publish. Frontmatter values containing a colon must be quoted; the build
names the file instead of crashing.
Adding a highlight.
- Ask the author. Get their OK for the write-up and for any image.
- Add the row to
packaging/recommendations.json(or in the CMS):name,author,author_url,url,description,slug,date,platform,author_license, andimageif there is one. For a tox, Pin it; for a Python package, filltdp(step 5 adds the Pin for it). - Write
website/content/community/<slug>.md. Frontmatter is optional:title(defaults to the row name) andsummary(defaults to the description). Put pictures inwebsite/content/community/images/and reference them as/community/images/<file>. python packaging/recommendations.py, thennpm run pagesinwebsite/to preview.- Publish the list to installs with
python packaging/upload.py --recommendations, and the site with the usual website mirror and promote.
- Community is its own place (owner, 2026-09-26: at the bottom of the
picker these read as more FNSTools tools). The console has a built-in
Community tab after Updates (
ConsoleRegistryExt.BUILTIN_TABS, order 30), listing the tools newest first. The picker lists none of them: one line, "N tools by other creators", switches the console to Community (postMessage('fns:community')), or opens the site's/community/when the picker is not inside the console (the installer's own window,/get/). - One copy of the card. The card and its actions live in
packaging/configurator/base.jsaswindow.fnsCommunity(card,load), inlined bysync_base.pyinto the picker and the console page like the rest of the base. A card is never selectable. Its name opens our post when the row has one; chips open "our write-up" and the author's page ("on GitHub", "on Patreon"...), through/openwhen served (a Web Render ignorestarget=_blank). - Served by the installer in TD, a pinned tox gets place in this
project. The page POSTs
/community/place; the installer resolves the working network (PanePlacement, the same ruleplacement: panepackages use, with its own rule,CommunityLock: /ui and /sys are refused, and so is the toolkit source, but an Embody-tracked network such as the project root is fine, because a placement only adds a component) and asks the updater, outside the request callback, toPlaceCommunityTool. The page pollsGET /community/place(the updater'scommunityPlaceResult()) until it reads placed or failed. - The updater checks size then hash before loading, as before, and now deletes a download that fails either check. One community download at a time; a second is refused with a message.
- The console forwards
/recommendations.jsonand/community/placeto the installer. Before this the console-hosted picker never showed the section at all. - New highlights are not announced by the new-tools window. That window is for our tools.
- Verified in TD 2026-09-25 with a temporary list: a pinned tox placed into a scratch COMP, a wrong hash refused with the author named and its file deleted, a link-only row and an unknown name refused. The card flow (placing, placed) was checked on a stubbed page. Not yet tried: the real served picker clicking Place against a published row.
What is on PyPI (checked 2026-09-25). Twelve projects start with
tdp-. Five are TouchDesigner tools, all by Wieland Hilker (plusplus.one):
tdp-TauCeti 5.1.16, tdp-QrCodeCOMP 1.0.0, tdp-QrCode 0.1.0,
tdp-markdownrender 0.0.1, tdp-touchutilcollection 0.8.0. The other
seven (datavisyn, Caleydo, Yitzchak Gale) are unrelated projects that
happen to share the prefix. So the prefix identifies nothing: every tdp
highlight is curated by name, and no list is built by searching.
The convention, read from the wheels.
- The importable module is
tdp<Name>(tdpTauCeti,tdpQrCodeCOMP), or something else entirely (touchutilcollection). The row names it. - A single-tox package exports
ToxFile, apathlib.Pathto the tox inside the installed package. - A package with several exports
_ToxFiles, a name-to-path map (TauCeti: PresetManager, Tweener, PresetDashboard, PresetCuelist, PresetChopMapper, TweenCHOP). __minimum_td_version__states the oldest TD build.tdp-touchutilcollectionis a library with no tox at all, so it is a dependency, never a highlight of its own.- Packages have dependencies.
tdp-QrCodeCOMPneedsqrcode[pil]andtdp-touchutilcollection. A hash on the top package alone cannot install it, because--require-hashesdemands a hash for every requirement. Hence the full lock in the row.
The install.
- The installer runs
uv pip install --require-hashes -r <lock>into the project's venv (pip if uv is missing), as a subprocess, with no TD access from the worker. It then imports the module, resolves the tox and places it. - A republished wheel no longer matches its hash and refuses to install.
That is the same promise the
toxrows make. - The CMS pin refuses a lock that contains a package TD bundles (numpy, opencv and friends). Installing one of those into the venv is a known crash class.
What the checks found (2026-09-25).
- TD's own manager.
app.pyEnvHelper(TDPyEnvManagerHelper, created at startup) links a venv before anything cooks when the .toe folder holds aTDPyEnvManagerContext.yamlor apyproject.toml[tool.touchdesigner.TDPyEnvManagerContext]. The venv isinstallPath/envName,./.venvby default.envPath,executablePathandsysPathsay what is linked. TD 2025.33070 runs Python 3.11.15. Docs: Palette:tdPyEnvManager, TDPyEnvManagerHelper. - The launcher (TDXLPP
tdp.rs). It reads the same context (pyproject, then yaml, then.venv-like folders). It never creates a venv itself: it drops the palettetdPyEnvManager.tox, whose activation shows Derivative's own disclaimer, which it deliberately does not bypass. It installs with uv (pip fallback) from outside TD, with no hashes and no check against TD's bundled packages. It finds the tox by importing the module'sToxFilein a venv subprocess, and places it withexternaltoxas the EXPRESSIONmod.<module>.ToxFile, so the binding follows the package on any machine. It does not read_ToxFiles. - Embody authors a minimal context yaml for its own uv venv, refuses
URL/VCS/path specs and TD's bundled packages, and strips
VIRTUAL_ENVandPYTHONPATHfrom child processes (tdPyEnvManager setsVIRTUAL_ENVprocess-wide). - TD's bundled packages are in
sys.base_prefix + '/Lib/site-packages'(numpy, requests, pyyaml, attrs, certifi, urllib3, packaging, ...). opencv and pyparsing ship without dist-info, so a dist-info scan misses them; Embody adds a seed list, and top-level folder names must be compared too.
What landed (2026-09-26). The proposal, as built:
- Environment.
FNS_Updater.PythonEnvStatus()readsapp.pyEnvHelper(envPath,executablePath). With none, Install answersnoenvand the picker offers "set up a Python environment first":SetUpPythonEnv()drops the palettetdPyEnvManager.toxat/(unwrapped like a palette drag), switches it Active a couple of frames later, and presses Create vEnv. Switching Active shows Derivative's dialog, read from the tox on 2026-09-26: title "DISCLAIMER", "Sideloading Python environments and third party packages is at your own risk and can cause instabilities", buttons Continue / Abort. Abort stops there. - Install.
InstallCommunityPackage(name, target)refuses a lock naming a package TD ships (read fromsys.base_prefixsite-packages plusTD_BUNDLED_SEED), writes the lock beside the store's community list, and startsuv pip install --python <venv> --require-hashes --only-binary :all: --no-deps -r <lock>(the venv's pip with the same flags when there is no uv) withVIRTUAL_ENV,PYTHONPATH,PYTHONHOMEandPYTHONUSERBASEremoved. It is polled every 15 frames and killed after 10 minutes; its output is kept in<name>.install.log. - Place. The venv's site-packages joins
sys.pathif it was not there, the module is imported, the tox is_ToxFiles[tox]orToxFile, and the loaded COMP getsexternaltox = mod.<module>.ToxFile(or the_ToxFilesentry) withenableexternaltoxon. A module that was already imported says "Restart TouchDesigner to use the new version". - Routes.
POST /community/install,GET/POST /community/pyenv; the answer is read fromGET /community/placelike a tox placement. The console forwards all of them. - Pin.
packaging/tdp_pin.py(the CMS "Pin" beside the package name) reads the newest release's wheel for the module and its toxes, compiles the universal hashed lock for Python 3.11, and refuses a bundled package.tdp.alsolists what a package imports without declaring it; it joins the lock and is kept so a re-pin remembers it.
Unpinned since 2026-09-27 (owner: no versions to keep updating). A
tdp row now names the package, its module, the tox and also; a lock
is refused. The installer runs a dry run first (uv pip install --dry-run,
or pip's --dry-run --report), refuses when the result would add a package
TouchDesigner ships, and then installs the latest release, wheels only.
tdp_pin.py became the curator's check (module, toxes, what it resolves
to today, the same refusal), the CMS button reads "Check", and the post
says "its latest release". Verified in TD against a scratch venv: also: opencv-python refused (numpy, opencv-python) with nothing installed;
TauCeti + touchutilcollection dry-run, installed, TweenCHOP placed with no
errors. What this gives up: a user gets whatever the author published last,
not the version the curator looked at.
Found on the way. tdp-TauCeti 5.1.16 imports touchutilcollection
in its TweenCHOP extension but does not declare tdp-touchutilcollection.
Pinned from its own metadata, it installs and places a TweenCHOP whose
extension fails to import. With also: ["tdp-touchutilcollection"] it
places cleanly. Worth telling the author.
Verified in TD 2025.33070 on 2026-09-26 against a scratch venv made
with TD's Python (never the project's own .venv): that install and
placement, the numpy refusal, and the noenv answer. The picker flow
(install, set up environment, install, placed) ran on a stubbed page.
Not yet run: SetUpPythonEnv() itself (it would drop the manager into
this project and raise the dialog), and a served picker against a
published row.
- The Community editor gives each row two folding sections. Website
post: slug (with "From name"), date, platform, the author's terms, an
image picked from
website/content/community/images/or uploaded there, and the write-up in Markdown with "Start one" and a preview. Python package: package, module, tox, and the lock, pasted asuv pip compile --generate-hashesprints it. - Save writes the list and the write-ups as one step, and checks
everything first: an invalid row, a slug with no write-up or a rename
onto an existing write-up leaves every file as it was. A changed slug
renames the write-up. A write-up whose row is gone moves to
website/content/community/archive/with a timestamp; nothing is deleted. - An upload is named after the row's slug, never overwrites a file, and is capped at 8 MB.
- The tdp Pin landed with step 5 (section 4).
A section of /community/, linkable as /community/#tdfam, listing
operator families made with TDFam. Its rows are families in
packaging/recommendations.json, edited in the CMS's Community editor
under "Built with TDFam", and validated by both validators like the
tools (name, author and url required; url https or a page on this site;
ops a positive whole number; tool must name a row in tools; ours
true or false). Installs never download it: published() leaves it out.
A card opens its tool's post when that post is published, else the
family's own url. ours marks the FNS family: its operator count is
counted at build from the manifest's family members, leaving previews
out, so it follows the releases. The first two: FNS (links
/docs/fns-opfamily/) and T3D (48 operators, opens the T3D post).
- The author's name and link on every card and post, above the fold.
- Their license text, unedited.
- Their images only with their permission; otherwise no image.
- Nothing suggests we made it, support it or update it.
- Tell the author before publishing a highlight.
- The new fields and both validators, with tests. (Landed 2026-09-25.)
- Website
/community/and post pages, landing strip. (Landed 2026-09-25.) - CMS fields and write-up editor. (Landed 2026-09-25.)
- Picker: Place for
toxrows, post links. (Landed 2026-09-25; ships with the next release.) - tdp install. (Landed 2026-09-26; ships with the next release.)