Adom Desktop - Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Desktop: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
CLAUDE.md - adom-desktop-fusion-bridge (READ THIS, it is YOUR project)
This repo is the Adom Desktop Fusion 360 bridge. If you are an Adom AI touching anything Fusion, you own and maintain this code. You write it, version it, publish it, and reinstall it. Stop treating bridge problems as someone else's. (This file exists because an AI repeatedly forgot all of this and filed its own bridge bugs as "adom-desktop" requests. Don't.)
You vs adom-desktop (AD) core - the boundary
- AD core owns: the
adom-desktopCLI, the relay / command passthrough, bridge lifecycle (bridge_install/ streaming the bridge from the wiki into…\bridges-cache\fusion360), and the coredesktop_*verbs. That's it. AD just hosts and loads this bridge. - YOU (this bridge) own: every
fusion_*verb and its response_hints, the APS integration (auth/config/quota - see below), the Fusion add-in code AND deploying it into Fusion, theSKILL.md+ bundledskills/, and the edit → publish → reinstall loop. - So: a
fusion_*verb behaves wrong, a hint is missing, APS misbehaves, the add-in is stale → it's your code, fix it here and republish. It is NOT an adom-desktop request.
Map of what you manage
server.py- bridge server / verb router (talks to AD's relay, proxies to the add-in). On start it callsinstall_addin()to deploy the add-in (see deploy below).aps.py- the entire APS implementation: PKCE OAuth, token store, server-indexed cloud search, never-charge cap. All APS is yours (thefusion_aps_*verbs +fusion_search_cloud_files/fusion_walk_cloud_tree). NOTE: port 8910 is AD's vestigial "native" APS - NOT yours; this bridge uses 8917/8918/8920 and steers around it. The bareaps_status(8910) is that AD stub, not this bridge'sfusion_aps_status.addin/AdomBridge/- the add-in that runs inside Fusion:AdomBridge.py,http_server.py,commands/*.py(cloud_documents, open_electronics_file, electronics, export, modeling, manufacturing, screenshot, document_info, parameters, …).describe.py- thefusion_describeself-describe catalog (AD's Verbs tab).handlers/- close / recovery / dialog-classify / UI / open_design.skills/- bundled skills you ship:fusion-aps-search,fusion-aps-signin,fusion-bridge-dev,fusion-onboarding.resources/- JLCPCB CAM jobs, Adom DRU rules, the layer-detect ULP.bridge.json+BRIDGE_VERSION- the manifest/version (keep in lockstep).
How you DEPLOY when you change your code (VERIFIED by watching v1.5.1 ship, 2026-06-28)
Fusion loads the add-in from a PER-USER add-in dir that Autodesk has MOVED across versions:
**2025+ Fusion scans %APPDATA%\Roaming\Autodesk\FusionAddins\AdomBridge\**; older builds used
%APPDATA%\Roaming\Autodesk\Autodesk Fusion[ 360]\API\AddIns\AdomBridge\. An add-in in the
wrong dir is SILENTLY ignored (Fusion runs fine, port 8774 never comes up - issue #63, root-caused
live). install_addin.py therefore installs to ALL of them (TARGET_CANDIDATES); NEVER assume a
single path, and if a future Fusion stops loading the add-in, FIRST suspect the dir moved again
(hunt for new dirs under %APPDATA%\Autodesk yourself). NOT from this repo or the cache.
⛔ NEVER ask the user to restart Fusion or enable the add-in - YOU restart (fusion_stop +
fusion_start); runOnStartup does the rest. The exact, watched-it-happen release sequence (AFTER publishing to the wiki):
bridge_install {"manifestUrl": ".../adom-bridge-fusion-manifest.json"}- streams the new zip into the CACHE viamethod: in_place_merge. Updates the cache ONLY; it does NOT touch Fusion's AddIns dir. (Pass"force": trueto re-merge when the version already bumped.) Do NOT manuallybridge_kill-bridge_install's own output says "AD reaps + respawns it from the new cache on the next call (no manual kill needed)," so the bridge SERVER (new server.py/describe.py) reloads automatically on the next verb. (Runningbridge_killhere is pointless.)fusion_stop(graceful;fusion_killif it is wedged) - REQUIRED. Fusion holds the add-in files OPEN, so the sync canNOT overwrite them while Fusion runs. (This is why a bridge-server respawn alone does NOT update the add-in, and whyfusion_stop+fusion_startby itself deploys nothing.)- Deploy the add-in into Roaming. Two ways:
- (intended) Run the cache's
install_addin.py:shell_execute->cd <cache>\fusion360 && python install_addin.py. It printsUpdated: commands\cloud_documents.py .... - ⚠️ TRAP (hit 2026-06-29):
python/pyare NOT on the box's shell PATH. AD spawns the bridge with its OWN python, sopython install_addin.pyviashell_executereturns "Python was not found" / errors with empty output (same Windows-Store-alias stub that makes a bridge fail to respawn). (AD >=1.9.63 PROVISIONS Python itself - a system install if one is on PATH, else a portable no-UAC python-build-standalone copy under~/.adom/adom-runtimes/python-<ver>/, pinned to 3.12.13 - and spawnsserver.pyby ABSOLUTE PATH from it. So the bridge-SPAWN case is fully handled by AD; NEVER bootstrap/download your own Python. Check runtime state anytime with AD'sruntimesverb (state ∈ absent|installing|ready|failed). But a barepythonyou invoke yourself viashell_executestill won't resolve, so the file-copy deploy below remains the way.)cmd /c copyandrobocopyalso fight you on the spaces inAdom Desktop/Autodesk Fusion 360(robocopy exit 16). The reliable deploy that needs NO shell + NO python: copy the changed add-in file(s) with the BRIDGE's own file verbs -read_file {path:<cache>/addin/AdomBridge/ commands/<f>.py}thenwrite_file {path:<Roaming>/.../AddIns/AdomBridge/commands/<f>.py, content}(Fusion CLOSED so the file is not locked). Verify with anotherread_file(grep a marker from your change). The Roaming AddIns path is%APPDATA%/Roaming/Autodesk/Autodesk Fusion 360/API/AddIns/AdomBridge.
- (intended) Run the cache's
- Verify by SHA256, never findstr.
(Get-FileHash <Roaming>\commands\cloud_documents.py).Hashmust equalsha256sumof that same file unzipped from the publishedv<ver>.zip. findstr gives FALSE NEGATIVES on these files (UTF-8 box-drawing / dash chars break it) - it told me the reprimand was missing when the file was byte-identical to the release. Do not trust findstr; hash orGet-Content -Rawregex only. fusion_start- Fusion now loads the new add-in. Confirm afusion_*verb behaves new.
Memorize:
bridge_installbumpingBRIDGE_VERSIONto the new version does NOT mean the running add-in changed. The version marker moves independently of the Roaming sync. Always hash-verify (step 5).- The add-in sync only takes effect with Fusion closed (step 3). Skipping that is the #1 way a "successful" install leaves the OLD add-in running.
- Never hand-edit the cache or the Roaming copy. They get clobbered on the next stream/sync. Edit THIS source, publish, then run the sequence above.
The publish/reinstall loop
See skills/fusion-bridge-dev/SKILL.md for the full ritual. Short form: edit here → bump
BRIDGE_VERSION + bridge.json together → git commit → push GitHub backup + publish to the
wiki (wiki.adom.inc/adom/adom-desktop-fusion-bridge, the canonical repo) → bridge_install on the
box → reap the old bridge instance + restart so the new code (and synced add-in) loads → anon-verify.
Wiki = canonical, GitHub = backup, keep in lockstep.
Fusion electronics file hierarchy (the other thing you kept getting wrong)
A Fusion electronics design is a parent/child chain of separate files, each a distinct
productType. ALWAYS open the PROJECT; never the schematic/.brd/3D directly.
PROJECT EcadDesignProductType <- open THIS
|- schematic SchematicProductType
|- board/.brd BoardProductType
|- 3D PCB DesignProductType (generated FROM the .brd - the LEAF; no editable board)
Opening a child gives an isolated/empty view (you can't pull a board out of the 3D). Same-named
copies in OTHER projects are usually 3D-model derivatives (DesignProductType), not the project.
The open verbs (handle_open_cloud_file / handle_open_by_urn) reprimand via _hint when you open
a non-EcadDesignProductType file - read it.
# CLAUDE.md - adom-desktop-fusion-bridge (READ THIS, it is YOUR project)
This repo is **the Adom Desktop Fusion 360 bridge**. If you are an Adom AI touching anything
Fusion, **you own and maintain this code.** You write it, version it, publish it, and reinstall it.
Stop treating bridge problems as someone else's. (This file exists because an AI repeatedly forgot
all of this and filed its own bridge bugs as "adom-desktop" requests. Don't.)
## You vs adom-desktop (AD) core - the boundary
- **AD core owns:** the `adom-desktop` CLI, the relay / command passthrough, bridge lifecycle
(`bridge_install` / streaming the bridge from the wiki into `…\bridges-cache\fusion360`), and the
core `desktop_*` verbs. That's it. AD just **hosts and loads** this bridge.
- **YOU (this bridge) own:** every `fusion_*` verb and its response `_hint`s, the **APS** integration
(auth/config/quota - see below), the **Fusion add-in** code AND **deploying it into Fusion**, the
`SKILL.md` + bundled `skills/`, and the edit → publish → reinstall loop.
- So: a `fusion_*` verb behaves wrong, a hint is missing, APS misbehaves, the add-in is stale → it's
**your code**, fix it here and republish. It is NOT an adom-desktop request.
## Map of what you manage
- `server.py` - bridge server / verb router (talks to AD's relay, proxies to the add-in). **On start
it calls `install_addin()`** to deploy the add-in (see deploy below).
- `aps.py` - the **entire APS** implementation: PKCE OAuth, token store, server-indexed cloud search,
never-charge cap. **All APS is yours** (the `fusion_aps_*` verbs + `fusion_search_cloud_files` /
`fusion_walk_cloud_tree`). NOTE: port **8910 is AD's vestigial "native" APS** - NOT yours; this
bridge uses **8917/8918/8920** and steers around it. The bare `aps_status` (8910) is that AD stub,
not this bridge's `fusion_aps_status`.
- `addin/AdomBridge/` - the add-in that runs **inside Fusion**: `AdomBridge.py`, `http_server.py`,
`commands/*.py` (cloud_documents, open_electronics_file, electronics, export, modeling,
manufacturing, screenshot, document_info, parameters, …).
- `describe.py` - the `fusion_describe` self-describe catalog (AD's Verbs tab).
- `handlers/` - close / recovery / dialog-classify / UI / open_design.
- `skills/` - bundled skills you ship: `fusion-aps-search`, `fusion-aps-signin`, `fusion-bridge-dev`,
`fusion-onboarding`. `resources/` - JLCPCB CAM jobs, Adom DRU rules, the layer-detect ULP.
- `bridge.json` + `BRIDGE_VERSION` - the manifest/version (keep in lockstep).
## How you DEPLOY when you change your code (VERIFIED by watching v1.5.1 ship, 2026-06-28)
Fusion loads the add-in from a PER-USER add-in dir that Autodesk has MOVED across versions:
**2025+ Fusion scans `%APPDATA%\Roaming\Autodesk\FusionAddins\AdomBridge\`**; older builds used
`%APPDATA%\Roaming\Autodesk\Autodesk Fusion[ 360]\API\AddIns\AdomBridge\`. An add-in in the
wrong dir is SILENTLY ignored (Fusion runs fine, port 8774 never comes up - issue #63, root-caused
live). `install_addin.py` therefore installs to ALL of them (TARGET_CANDIDATES); NEVER assume a
single path, and if a future Fusion stops loading the add-in, FIRST suspect the dir moved again
(hunt for new dirs under %APPDATA%\Autodesk yourself). NOT from this repo or the cache.
⛔ NEVER ask the user to restart Fusion or enable the add-in - YOU restart (fusion_stop +
fusion_start); runOnStartup does the rest. The exact, watched-it-happen release sequence (AFTER publishing to the wiki):
1. **`bridge_install {"manifestUrl": ".../adom-bridge-fusion-manifest.json"}`** - streams the new zip
into the CACHE via `method: in_place_merge`. Updates the cache ONLY; it does NOT touch Fusion's
AddIns dir. (Pass `"force": true` to re-merge when the version already bumped.)
Do NOT manually `bridge_kill` - `bridge_install`'s own output says "AD reaps + respawns it from the
new cache on the next call (no manual kill needed)," so the bridge SERVER (new server.py/describe.py)
reloads automatically on the next verb. (Running `bridge_kill` here is pointless.)
2. **`fusion_stop`** (graceful; `fusion_kill` if it is wedged) - REQUIRED. Fusion holds the add-in files
OPEN, so the sync canNOT overwrite them while Fusion runs. (This is why a bridge-server respawn alone
does NOT update the add-in, and why `fusion_stop`+`fusion_start` by itself deploys nothing.)
3. Deploy the add-in into Roaming. Two ways:
- **(intended)** Run the cache's `install_addin.py`: `shell_execute` -> `cd <cache>\fusion360 &&
python install_addin.py`. It prints `Updated: commands\cloud_documents.py ...`.
- **⚠️ TRAP (hit 2026-06-29): `python`/`py` are NOT on the box's shell PATH.** AD spawns the bridge
with its OWN python, so `python install_addin.py` via `shell_execute` returns "Python was not
found" / errors with empty output (same Windows-Store-alias stub that makes a bridge fail to
respawn). (AD >=1.9.63 PROVISIONS Python itself - a system install if one is on PATH, else a
portable no-UAC python-build-standalone copy under `~/.adom/adom-runtimes/python-<ver>/`, pinned
to 3.12.13 - and spawns `server.py` by ABSOLUTE PATH from it. So the bridge-SPAWN case is fully
handled by AD; NEVER bootstrap/download your own Python. Check runtime state anytime with AD's
`runtimes` verb (`state ∈ absent|installing|ready|failed`). But a bare `python` you invoke
yourself via `shell_execute` still won't resolve, so the file-copy deploy below remains the way.)
`cmd /c copy` and `robocopy` also fight you on the spaces in `Adom Desktop` / `Autodesk
Fusion 360` (robocopy exit 16). **The reliable deploy that needs NO shell + NO python:** copy the
changed add-in file(s) with the BRIDGE's own file verbs - `read_file {path:<cache>/addin/AdomBridge/
commands/<f>.py}` then `write_file {path:<Roaming>/.../AddIns/AdomBridge/commands/<f>.py, content}`
(Fusion CLOSED so the file is not locked). Verify with another `read_file` (grep a marker from your
change). The Roaming AddIns path is `%APPDATA%/Roaming/Autodesk/Autodesk Fusion 360/API/AddIns/AdomBridge`.
4. **Verify by SHA256, never findstr.** `(Get-FileHash <Roaming>\commands\cloud_documents.py).Hash`
must equal `sha256sum` of that same file unzipped from the published `v<ver>.zip`. findstr gives
FALSE NEGATIVES on these files (UTF-8 box-drawing / dash chars break it) - it told me the reprimand
was missing when the file was byte-identical to the release. Do not trust findstr; hash or
`Get-Content -Raw` regex only.
5. **`fusion_start`** - Fusion now loads the new add-in. Confirm a `fusion_*` verb behaves new.
Memorize:
- `bridge_install` bumping `BRIDGE_VERSION` to the new version does NOT mean the running add-in changed.
The version marker moves independently of the Roaming sync. Always hash-verify (step 5).
- The add-in sync only takes effect with **Fusion closed** (step 3). Skipping that is the #1 way a
"successful" install leaves the OLD add-in running.
- **Never hand-edit the cache or the Roaming copy.** They get clobbered on the next stream/sync. Edit
THIS source, publish, then run the sequence above.
## The publish/reinstall loop
See `skills/fusion-bridge-dev/SKILL.md` for the full ritual. Short form: edit here → bump
`BRIDGE_VERSION` + `bridge.json` together → `git commit` → **push GitHub backup** + **publish to the
wiki** (`wiki.adom.inc/adom/adom-desktop-fusion-bridge`, the canonical repo) → `bridge_install` on the
box → reap the old bridge instance + restart so the new code (and synced add-in) loads → anon-verify.
Wiki = canonical, GitHub = backup, keep in lockstep.
## Fusion electronics file hierarchy (the other thing you kept getting wrong)
A Fusion electronics design is a **parent/child chain of separate files**, each a distinct
`productType`. **ALWAYS open the PROJECT; never the schematic/.brd/3D directly.**
```
PROJECT EcadDesignProductType <- open THIS
|- schematic SchematicProductType
|- board/.brd BoardProductType
|- 3D PCB DesignProductType (generated FROM the .brd - the LEAF; no editable board)
```
Opening a child gives an isolated/empty view (you can't pull a board out of the 3D). Same-named
copies in OTHER projects are usually 3D-model derivatives (`DesignProductType`), not the project.
The open verbs (`handle_open_cloud_file` / `handle_open_by_urn`) reprimand via `_hint` when you open
a non-`EcadDesignProductType` file - read it.