app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
← Commit history
Publish 1.9.213
5 files changed
+199−10
CLAUDE.md+10@@ -16,6 +16,16 @@ all of this and filed its own bridge bugs as "adom-desktop" requests. Don't.) - 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-bridge request. +## ⛔ NEVER LET FUSION DRAW A DIALOG (John, 3 reports; read the skill)++A modal freezes the add-in's single main thread, so the bridge stops answering and every+later verb reads as a crash - and it dings the user's speaker. **Prevention, never cleanup:**+prove the command unnecessary offline (`_resolve_artifact_name` against the `.lbr`), or+pre-answer it (`electron_run {confirm: "yes"|"no"}` -> `SET CONFIRM`, so the dialog is never+drawn). A dismissal sweep is a BACKSTOP whose every firing is a defect report. If you are+writing a hint telling the caller to dismiss a dialog afterwards, you have the bug.+Full rules + the command table: `skills/fusion-no-modal-dialogs/SKILL.md`.+ ## THE AUTOMATION DOCTRINE (John, 2026-08-18, verbatim - this outranks tidy error messages) > "your whole goal is to automate the life of the adom user and never slow them down or stop
addin/AdomBridge/commands/electronics.py+29@@ -327,6 +327,9 @@ def handle_electron_run(app: adsk.core.Application, args: dict) -> dict: args: Dict with: - command: The EAGLE command to execute (required). Examples: "EDIT resistor.sym", "USE mylib.lbr", "WINDOW FIT"+ - confirm: "yes" | "no" (optional). Pre-answers any confirmation the command+ would raise, via SET CONFIRM, so the modal is never drawn. "yes" = we mean to+ create; "no" = inspect only. EDIT with no `confirm` defaults to "no". """ eagle_cmd = args.get("command", "") if not eagle_cmd:@@ -394,6 +397,32 @@ def handle_electron_run(app: adsk.core.Application, args: dict) -> dict: }, } + # ⛔ DECLARE CONFIRM INTENT BEFORE ANY COMMAND THAT CAN PROMPT.+ # John, 2026-08-26 (3rd report of the same modal): "why do you still allow these dialogs+ # to pop up ... dialogs are dangerous cuz they're usually modals and kill programmatic+ # code from running ... and it dings the computer's speaker."+ # EAGLE/Electron has a deterministic answer for this and we were only using it for+ # EXPORT: SET CONFIRM pre-answers the confirmation so the modal is NEVER DRAWN. There is+ # no third state - a command that can prompt must say which answer it wants:+ # confirm="yes" -> we INTEND to create (EDIT <new>.pac to author a footprint)+ # confirm="no" -> we are only INSPECTING; a prompt means a bad name, decline it+ # The caller declaring intent is what makes this deterministic instead of a race with a+ # dismissal sweep, which loses whenever Fusion is slow.+ _confirm = str(args.get("confirm") or "").strip().lower()+ if _confirm in ("yes", "no"):+ try:+ app.executeTextCommand("Electron.run SET CONFIRM %s" % _confirm.upper())+ except Exception:+ pass # best effort; the server-side name pre-check is the primary guard+ elif cmd_verb == "EDIT":+ # An EDIT with no declared intent is the exact call that raised John's dialog. We+ # cannot know whether a create is wanted, so refuse to gamble the main thread: the+ # safe answer is NO (decline the create), which also leaves no stray artifact.+ try:+ app.executeTextCommand("Electron.run SET CONFIRM NO")+ except Exception:+ pass+ # Auto-suppress overwrite/confirmation dialogs for EXPORT commands. # Without this, EAGLE shows a Qt "Overwrite?" dialog that blocks the # add-in thread and is invisible to the CLI/AI — freezing everything.
addin/AdomBridge/commands/open_lbr.py+3@@ -78,6 +78,9 @@ def handle_open_lbr(app: adsk.core.Application, args: dict) -> dict: sym_ref = symbol_name if symbol_name.endswith(".sym") else f"{symbol_name}.sym" try:+ # inspect-only navigation: pre-answer NO so a stale/renamed sym_ref can never+ # raise a blocking "Create new ...?" modal on the add-in main thread.+ app.executeTextCommand("Electron.run SET CONFIRM NO") app.executeTextCommand(f"Electron.run EDIT {sym_ref}") results.append(f"EDIT {sym_ref}: ok") except Exception as e:
page.json+7−10@@ -11,10 +11,6 @@ "uninstall": "./uninstall.sh" }, "tags": [- "ab",- "bridge",- "fusion",- "desktop-app", "fusion360", "autodesk", "cad",@@ -79,10 +75,7 @@ "build a basic parts library", "add a 3d model to a component", "make a fusion 360 library with 3d",- "set up the fusion bridge",- "fusion dashboard",- "fusion status page",- "open the fusion launch page"+ "set up the fusion bridge" ], "discovery_pitch": "Use the Adom Bridge Fusion 360 bridge when the user wants to DRIVE Autodesk Fusion 360 from the cloud: open/search their team's Fusion cloud designs, lay out a PCB or do electronics, export STEP/IGES/STL/Gerbers/BOM, or build a component library with real rendering 3D. It runs Fusion on the user's own machine via Adom Bridge. NOT for web/browser/login/form-filling (that's the native-browser extension) and NOT for KiCad (that's the kicad bridge).", "brief": "Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.",@@ -111,5 +104,9 @@ "label": "Part volume", "prompt": "How much material does each printed part in this assembly use?" }- ]-}+ ],+ "author": {+ "name": "John Lauer",+ "email": "[email protected]"+ }+}
skills/fusion-no-modal-dialogs/SKILL.mdadded+150@@ -0,0 +1,150 @@+---+name: fusion-no-modal-dialogs+description: >+ NEVER let Fusion raise a modal dialog from automation. A modal freezes the add-in's main+ thread (so every later verb reads as a crash), dings the user's speaker with an alert+ sound, and cannot be reliably cleaned up after the fact. Every command that CAN prompt+ must pre-answer it (SET CONFIRM) or be proven unnecessary offline first. Read before+ writing any code that issues EDIT, EXPORT, WRITE, ADD, SHOW, GRID, OPEN, or CLOSE against+ Fusion Electronics, and before "fixing" a dialog by dismissing it.+ Trigger words - create new footprint, create new symbol, dialog popped, modal, blocking+ dialog, dismiss dialog, SET CONFIRM, EDIT .pac, EDIT .sym, add-in not responding, Fusion+ hung, unsaved changes prompt, save changes dialog, alert sound, speaker ding.+discovery_triggers:+ - "create new footprint"+ - "create new symbol"+ - "fusion dialog"+ - "blocking modal"+ - "SET CONFIRM"+ - "add-in not responding"+---++# Never let Fusion draw a dialog++**The doctrine (John, verbatim, after the THIRD report of the same modal):**++> "why do you still allow these dialogs to pop up? i've asked you 100's of times to never+> allow that. you can pre-empt it by always deterministically closing out items from code+> and telling the api you call to 'not save changes' or you could literally save it+> somewhere programatically so 'there are no changes' and then you wouldn't get prompts like+> that. dialogs are dangerous cuz they're usually modals and kill programmatic code from+> running, especially async tasks you're calling into fusion. and it dings the computer's+> speaker with an error alert audio sound which is annoying to the user."++## Why a dialog is a SEVERITY-1 bug, not an inconvenience++1. **It freezes the add-in's main thread.** Fusion's API is single-threaded. A modal sits on+ that thread, so the add-in's HTTP server stops answering. Every subsequent verb times out+ and reads as "add-in not responding" or "Fusion crashed". One un-answered prompt looks+ exactly like a dead bridge, and the debugging goes somewhere else entirely.+2. **It kills in-flight async work.** Any chain already running (a library import, a matrix+ run, a capture sweep) stalls mid-way and leaves half-built state behind.+3. **It dings the user's speaker.** An alert sound at a human who did not touch anything, on+ a machine they are using for something else.+4. **Dismissal is a race you lose.** Sweeping the dialog after it appears means it was+ already drawn, already sounded, and already blocked the thread. When Fusion is slow the+ sweep arrives late and the modal just sits there.++## The rule++> **A command that can prompt must either be proven unnecessary offline, or carry its answer+> with it. Never issue one and plan to clean up.**++Three techniques, in priority order. Use the highest one that applies.++### 1. Prove it offline. Do not ask Fusion a question you can answer yourself.++The `.lbr` on disk lists every symbol, package, and deviceset. `EDIT <name>.pac` on a name+that is not in the library is what raises **"Create new footprint 'X'?"**. That is knowable+in milliseconds without Fusion.++```python+resolved, err = _resolve_artifact_name(name, "pac", lbr_path)+if err:+ return err # EDIT is NEVER issued. No dialog is possible.+```++**The trap that made this fail for months:** the guard needs the `.lbr` path, and+`fusion_capture_library_views` never took one, so the single verb that EDITs the most names+was the one verb structurally unable to run the guard. It "handled" the prompt by raising it+and dismissing it. If a guard cannot reach the data it needs, **give it the data** (here:+`_remember_open_library()` persists the open `.lbr` so any later verb can pre-check). Do not+downgrade to sweeping.++### 2. Pre-answer it. Declare intent so the modal is never drawn.++EAGLE/Electron has a deterministic answer built in, and it is not a dismissal - the dialog+is never rendered at all:++```+SET CONFIRM YES # we INTEND to create this artifact+SET CONFIRM NO # inspect only; a prompt means a bad name, decline it+```++Every `electron_run` that can prompt takes `confirm: "yes" | "no"`. **`EDIT` with no declared+intent defaults to `"no"`**, because an undeclared EDIT is precisely the call that raised+John's dialog, and declining leaves no stray artifact.++This answers John's "why can't creating a footprint be triggered so the dialog doesn't show+up?" - it can: `confirm: "yes"` **is** the create, performed without a question.++### 3. Remove the reason to ask.++For save/close prompts, do not answer "don't save" - arrange for there to be nothing to+save. Save programmatically first, or close with the explicit no-save flag, so Fusion has no+question. See `_close_docs_opened_since` and `handle_close_all_documents({force, closeUntitled})`.++## Dismissal is a BACKSTOP, and it must shout++Keep a sweep as a last resort, but treat every firing as a **defect report**: it means a+prevention hole exists. Log it loudly and record it, so it cannot rot into the normal path.++```python+dismissed = _dismiss_dialogs_bg()+if dismissed:+ print("[Fusion Bridge] ⚠ PRE-CHECK HOLE: EDIT %s.%s still raised a dialog" % (name, ext))+ _bridge_action_ledger("dialog_precheck_hole", {...})+```++If you find yourself writing a hint that tells the CALLER to run+`fusion_dismiss_blocking_dialogs` afterwards, stop. You are telling a user to sweep up a+mess you chose to make.++## Never swallow a guard's failure++The first version of `_remember_open_library` ended in `except Exception: pass`. It raised+`NameError` on every call (this module imports `time` inside functions, not at module level),+so the guard was **silently disarmed while the code read as correct**. The offline test+caught it only because it asserted on the EDITs actually sent, not on the guard's presence.++- A prevention guard that fails quietly is worse than none: it removes the pressure to notice.+- `except Exception: pass` around a safety mechanism is a bug, always. Log it.+- **Test what reached Fusion, not what the code says.** Stub the add-in proxy, record every+ command, and assert the dangerous one is absent:++```python+bad = [e for e in edits if "TJA1042T.pac" in e]+assert not bad, "EDIT was issued -> dialog would pop"+```++## Commands that prompt (assume prompting unless proven otherwise)++| Command | Prompt | Prevention |+|---|---|---|+| `EDIT <new>.sym/.pac/.dev` | "Create new ...?" | Resolve name offline; else `confirm` |+| `EXPORT` onto an existing file | "Overwrite?" | Pre-delete the target, `SET CONFIRM YES` |+| `WRITE` | Save As | Never call from automation; pass an explicit path |+| `ADD` | component picker | Never from automation |+| `SHOW <name>` | interactive highlight | Avoid; use a capture instead |+| `GRID` (no params) | grid settings | Always pass params |+| closing a dirty document | "Save changes?" | Save first, or close with `force` |+| opening a malformed `.lbr` | "has errors and cannot be opened" | `_preflight_lbr` refuses it offline |++## The check before you ship any Fusion-touching code++1. List every command this path can send.+2. For each, ask: **can it prompt?** If unsure, assume yes.+3. For each that can: is it proven unnecessary offline, or does it carry `confirm`?+4. Is there a dismissal sweep doing load-bearing work? If yes, that is the bug.+5. Does any guard swallow its own failure? Make it shout.+6. Test by recording the commands that actually reach Fusion.