← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.10.24\ No newline at end of file+1.10.25\ No newline at end of file
README.md+319−87
@@ -32,16 +32,101 @@ Short version below, full detail in each guide.  | guide | what it covers | |---|---|-| [APS cloud search](docs/aps-cloud-search.md) | Find any team design in ~2s instead of a 30-minute folder walk. Setup, the never-charge guarantee, and why the old search is hard-blocked. |-| [Schematics](docs/schematics.md) | Open the project (not the schematic), drive the schematic editor, and import legacy EAGLE so parts instantiate. |-| [2D board layout](docs/board-layout-2d.md) | PCB editor, design rules, layer + stackup discovery, routing, and gerbers/BOM/CPL for the fab. |-| [3D view, export and STEP colorization](docs/3d-viewer-and-export.md) | The 3D PCB, every export format, web-grade GLB (16 MB to ~465 KB), and recoloring a grey imported STEP. |-| [Electronics library manager](docs/electronics-libraries.md) | Symbols, IPC footprints and real 3D packages, generated rather than hand-drawn, plus how to verify a part before shipping it. |+| [APS cloud search](docs/aps-cloud-search.md) | Find any team design in ~2s instead of a 30-minute folder walk. Setup, the never-charge guarantee, why the old search is hard-blocked, and the zero-result fallback that lists sibling folders through the Data API. |+| [Schematics](docs/schematics.md) | The front door `fusion_schematic {action: open, create, show}`, a library's parts on a new schematic and board with `fusion_make_showcase_project`, driving the schematic editor, and the async EAGLE import so parts instantiate. |+| [2D board layout](docs/board-layout-2d.md) | PCB editor, design rules by fab with `ruleWarnings`, layer + stackup discovery, live per-net routing with native DRC, and gerbers/BOM/CPL for the fab. |+| [3D view, export and STEP colorization](docs/3d-viewer-and-export.md) | The 3D PCB, the native viewport frame in `fusion_state`, camera motion with `fusion_view`, every export format, web-grade GLB (16 MB to ~465 KB), and recoloring a grey imported STEP. |+| [Electronics library manager](docs/electronics-libraries.md) | Symbols, IPC footprints and real 3D packages, generated rather than hand-drawn, explicit 3D `placement`, plus how to verify a part before shipping it. | | [Multi-part library import](docs/MULTIPART_LIBRARY_IMPORT.md) | How a zip of `.lbr` + STEPs becomes a Library Manager library with rendering 3D on every part: cloud `.f3d` urns, per-part minting (and the wrong-footprint trap it avoids), proof, and what the user sees. With screenshots. | | [Mechanical BOM and physical properties](docs/mechanical-bom.md) | Kit-aware assembly BOM that matches Fusion's Manage to BOM, and per-part volume/mass for costing. |-| [Sign-in, sessions and updates](docs/sign-in-and-updates.md) | Multi-profile Autodesk sign-in, SSO without passwords, one login covering APS, and silent auto-updates. |+| [Sign-in, sessions and updates](docs/sign-in-and-updates.md) | Multi-profile Autodesk sign-in, SSO without passwords, one login covering APS, the `needs_signin` short-circuit, `restartPolicy`, and silent auto-updates. | | [Autodesk's Fusion MCP server](docs/fusion-mcp-server.md) | Proxy Autodesk + Anthropic's local MCP server (text-to-CAD, Electronics object model) so a cloud AI can reach it, and turn it on for the user. | | [Driving Fusion's preferences](docs/fusion-preferences.md) | Reach ANY Fusion preference, not just the thin API slice, by driving the Preferences dialog. |+| [Contributing](CONTRIBUTING.md) | The wiki page is the repo: file an issue with evidence, clone, build the exe, try it behind a dev pin, and send a pull request with `adom-wiki`. |+++## New since 1.10++Short entries, newest behavior first in each guide. The [changelog](CHANGELOG.md) has every release.++| what | verbs and fields | in short |+|---|---|---|+| Schematic front door | `fusion_schematic {action: open, create, show}` | One verb for every way into a schematic. `open` takes a cloud `fileName`, `urn` or `query`, a local EAGLE `schPath`, or a Fusion-native `filePath`. `create` writes a blank EAGLE schematic on the box and opens it with no dialog. `show` switches the open design to its schematic view. Every reply names the verb that did the work in `via`. |+| Showcase project | `fusion_make_showcase_project`, `fusion_eagle_import_status` | Puts every deviceset of a library (or the ones in `devicesets`) on a new schematic and a new board, with no nets and with the library's bound 3D carried across. It is generated as EAGLE source and opened dialog-free. It is async: it returns a `runId`, and you poll `fusion_eagle_import_status`. |+| Live per-net routing | `fusion_routing_state`, `fusion_plan_route`, `fusion_route_net`, `fusion_routing_drc` | Read the live board, plan a bounded two-pad path, create copper trace by trace with board and revision guards, then run native Autodesk DRC. Each DRC error carries `layer`, `x` and `y` in mm. |+| Design rules by fab | `fusion_set_design_rules` | `{action:"list"}` lists every fab, process and layer count from the rule packs on [adom/pcb-design-rules](https://wiki.adom.inc/adom/pcb-design-rules). Applying a profile returns `ruleWarnings` when the profile carries a rule that native DRC will report as noise. |+| Explicit 3D placement | `fusion_make_3d_package`, `fusion_build_library_3d` | `placement: {translation, rotation, scale:1}` applies the manufacturer's authored transform and bypasses every heuristic. `orient:false` imports the model exactly as authored. |+| Native viewport frame | `fusion_state` | The reply carries `nativeViewport`, `geometry` and `windowPixels`, so a blank window capture is reported as blank and the frame Fusion itself renders is there to use. |+| APS zero-result fallback | `fusion_aps_search` | A zero from the server index never stands alone. The reply carries `data.fallback` and `data.completeness`, and sibling folders are listed through the Data API. |+| Caller-aware prove | `fusion_create_library`, `fusion_install_library_bundle`, `fusion_prove_result` | An AI thread binds and places only (`verdict: bound_not_proven`). Web control proves. `prove:true` or `prove:false` always wins. |+| Restart policy | `restartPolicy`, `allowRestart`, `fusion_restart_for_addin` | Under the default `auto` the bridge restarts Fusion itself when the add-in needs it. Chains never restart Fusion unless the caller passes `allowRestart:true`. |+| Sign-in short-circuit | `needs_signin` | Every add-in verb answers `errorCode: needs_signin` in under a second while Fusion is signing in. |++### The schematic front door: `fusion_schematic`++A schematic is a view of an electronics design, not a file of its own, so there is one verb that+gets you there from whatever you have. `fusion_schematic {action:"open"}` lands in the schematic+editor from a cloud `fileName`, `urn` or `query`, from a local EAGLE `.sch` (its sibling `.brd` is+imported with it so parts instantiate, or pass `withBoard:false` for a dialog-free schematic alone),+or from a Fusion-native `.fsch`. `{action:"create"}` writes a blank EAGLE schematic on the box and+opens it as a new local electronics design with no dialog. `{action:"show"}` switches the design+that is already open. When a name search opens the 3D PCB or a mechanical file instead, the verb+retries the exact cloud name once and otherwise answers `not_an_electronics_design` with the+candidate documents.++```bash+adom-bridge fusion_schematic '{"action":"open","query":"BQ25792"}'+adom-bridge fusion_schematic '{"action":"create","name":"scratch"}'+```++### Show off a library: `fusion_make_showcase_project`++"Make a schematic and board with these three sensors on it" is one call:+`fusion_make_showcase_project {libraryPath, devicesets?, name?}`. Every deviceset in the library (or+the ones you name) is placed in a row on a fresh sheet and a fresh board, with no nets, and the+library's bound 3D urns are carried across so the 3D PCB shows the real bodies. The two documents+are named `<name>-sch` and `<name>-brd`, the board opens last so it is active for the 2D and 3D+views, and each view is fitted. The verb is async: it returns a `runId` and you poll+`fusion_eagle_import_status {runId}`. The library block is embedded verbatim, because Fusion+refuses a schematic whose library was re-assembled piecewise from a Fusion-exported `.lbr`.++### Live per-net routing++Four verbs route a live two-layer board one net at a time. `fusion_routing_state` reads the board's+identity, geometry revision, pads, copper and per-net airwires. `fusion_plan_route` calculates a+read-only, bounded orthogonal path between two pads. `fusion_route_net` creates the copper with+board and revision guards and reads the exact copper back. `fusion_routing_drc` runs native+Autodesk DRC, and each error carries `layer`, `x` and `y` in mm, plus a `summary` by description+and a note when Overlap warnings are same-signal joints. The geometric preflight is conservative.+Native DRC is the final word. Detail in [2D board layout](docs/board-layout-2d.md).++### The dialog policy++A dialog in front of Fusion is never a reason to restart it. The bridge reads every dialog through+UIA (title, text, button names) and expires it safely without a person: the known ones through+their rule, the unknown ones with WM_CLOSE, which is Cancel or the X and never saves or discards.+When that clears it, the verb runs (`dialog_expired_retry` after a timeout). Only a dialog that+survives comes back, as `blocked_by_dialog` with `dialogs[] {hwnd, title, category, text, buttons,+resolution}`, so the AI answers it by button name. Native Windows Open and Save dialogs are read+and expired like any other, and Fusion's own tool windows do not count as dialogs.++### Restarts, prove and sign-in++- `restartPolicy` (in `~/.adom/fusion-bridge/prefs.json`, or the env var+  `ADOM_FUSION_RESTART_POLICY`) defaults to `auto`: the bridge restarts Fusion in the background+  when the add-in needs it (`addin_restarting`), saves what Fusion can save, and records the+  restart in the action ledger (`fusion_action_ledger`). `ask` keeps the confirmation toast and+  the wait, for a machine a person shares with the AI.+- Chains (`fusion_create_library`, `fusion_install_library_bundle`) never restart Fusion on their+  own. `allowRestart:true` with `restartReason` is the only way, and the restart is recorded in+  `restarts[]` on `fusion_prove_result`. A long operation in flight answers `fusion_addin_busy`+  instead of being mistaken for a dead add-in.+- Prove is decided by the caller. An AI thread binds and places only (`verdict:+  bound_not_proven`, `partsBound`, the library path). Web control proves. `prove:true` or+  `prove:false` always wins, and the reply says what prove costs before it starts.+- While Fusion's main window says it is signing in, every add-in verb answers `errorCode:+  needs_signin` in well under a second, with `needsSignin`, `mainTitle` and a hint that points at+  the sign-in skill. It is the same signal as `fusion_readiness`, never a reason to restart.   ## The demo: one verb, a whole guided tour (`fusion_demo`)@@ -232,9 +317,9 @@ views on command - each `show_*` verb returns a screenshot array so the AI SEES | View | Verb | |---|---| | Project (parent: schematic + 2D + 3D previews) | `fusion_open_cloud_file` / `fusion_aps_open` (the project file) |-| Schematic | `fusion_show_schematic` |+| Schematic | `fusion_schematic {action:"open"}` from anywhere, or `fusion_show_schematic` on the open design | | 2D board layout | `fusion_show_2d_board` |-| 3D board | `fusion_show_3d_board` (orbitable via `fusion_run_modeling_script`) |+| 3D board | `fusion_show_3d_board` (orbit it with `fusion_view {action:"orbit"}`) |  Real Adom boards driven through it - the DRV8411A copper layout in the 2D editor, and the TI DRV8323SR + ST VL53L8 boards rendered as populated 3D PCBs:@@ -261,18 +346,25 @@ adom-bridge fusion_export_cpl     '{"outputPath": "C:/tmp/cpl.csv"}'   # pick-an Plus mechanical exports: STEP / IGES / STL / 3MF / USDZ / OBJ / DXF / DWG. Long exports return promptly with `statusVerb: fusion_get_app_state` - the AI polls instead of blocking. -## Design rules per manufacturer (JLCPCB 2-layer, 4-layer, ...)+## Design rules per manufacturer -The bridge ships manufacturer rule files + CAM jobs in `resources/` and loads them into the open-board, so DRC matches what the fab checks:+`fusion_set_design_rules` loads a fab's design rules into the open board, so DRC matches what the+fab checks. The rule packs live on the wiki page+[adom/pcb-design-rules](https://wiki.adom.inc/adom/pcb-design-rules) (JLCPCB, PCBWay, OSH Park,+Seeed, Eurocircuits, Aisler, Elecrow, Advanced Circuits, Sierra, MacroFab, NextPCB, ALLPCB and+Adom InstaPCB), one `.edru` per vendor, process and layer count.  ```bash-adom-bridge fusion_load_design_rules '{"ruleFile": "JLCPCB-2layer.dru"}'-adom-bridge fusion_set_design_rules  '{"rules": {"minClearance": 0.127}}'   # ad-hoc tweaks+adom-bridge fusion_set_design_rules '{"action":"list"}'                 # every fab, process, layers row (no Fusion needed)+adom-bridge fusion_set_design_rules '{"fab":"jlcpcb","layers":"2"}'     # fetch, stage and load that profile+adom-bridge fusion_set_design_rules '{"druPath":"C:/tmp/house.edru"}'   # a local rule file as-is ``` -Included: JLCPCB 2-layer + 4-layer DRU rules, the matching Gerber CAM jobs (used by-`fusion_export_gerbers`), and Adom's house rules.+With no `fab` the bundled Adom InstaPCB rules load, and `layers` auto-detects from the open board.+The reply carries `ruleWarnings` when the profile has a same-signal Copper Clearance rule over an+all/all or trace scope, which native DRC reports as Overlap warnings at every pad entry and trace+joint. Confirm the load with `fusion_alerts`: a refused file is an in-canvas toast, never a dialog.+The matching JLCPCB CAM jobs used by `fusion_export_gerbers` ship in `resources/`.  ## How the bridge talks to Fusion (architecture) @@ -280,7 +372,7 @@ Included: JLCPCB 2-layer + 4-layer DRU rules, the matching Gerber CAM jobs (used  The bridge picks the right channel per task: -1. **Python bridge server** (`server.py`) - spawned by Adom Bridge, receives every `fusion_*` verb.+1. **The bridge exe** (`adom-fusion-bridge.exe`, Rust, source under `rust/`): spawned by Adom Bridge, receives every `fusion_*` verb. The Python server left the package with the first exe-only release, 1.9.357 (2026-09-04); since 1.10.0 no sidecar exists at all. 2. **AdomBridge add-in** - installed BY the bridge into every Fusion add-in location    (`%APPDATA%\Autodesk\FusionAddins` on 2025+ Fusion, plus the legacy `API\AddIns` dirs - Fusion    moved this across versions and silently ignores the others). It serves HTTP :8774 inside Fusion@@ -292,7 +384,7 @@ The bridge picks the right channel per task: 4. **Win32 / UIA side channel** - when the API can't reach it: background hwnd screenshots, WM_CLOSE    dialog dismissal (no focus steal), SendInput keys/clicks, and ab core verbs    (`desktop_find_control` / `desktop_ui_click`).-5. **APS cloud** (`aps.py`) - fast server-indexed search + file ops at scale (below).+5. **APS cloud** (inside the exe): fast server-indexed search + file ops at scale (below).  ## Dialog awareness: parent/child screenshots (the automation breakthrough) @@ -309,8 +401,10 @@ array + screenshots** and the AI can never fly blind past one. - DECISION dialogs - like the seat conflict above, whose options suspend Fusion on the user's OTHER   machine - are **never auto-picked**: the bridge returns `needsUserDecision:true` and the AI   notifies + asks the user.-- `fusion_check_dialogs` polls the dialog array on demand; `fusion_dismiss_blocking_dialogs` clears-  the known-safe ones.+- `fusion_state` carries the classified dialog list (`blockingDialogs`) on demand, and+  `fusion_dismiss_blocking_dialogs` answers the known-safe ones.+- A dialog is never a reason to restart Fusion. See "The dialog policy" above: the bridge expires+  what it safely can and returns the rest as `blocked_by_dialog` with its text and buttons.  ## Fast cloud search + file management (APS) - the ONLY cloud-search path @@ -388,80 +482,203 @@ The AI does the whole setup - the user never gets handed a download link: ## Full verb catalog  All verbs return a rich `_hint`. `fusion_describe '{}'` is the machine-readable source of truth; this table is generated from it.-_80 verbs._+_160 verbs._ Regenerate with `python3 tools/gen-readme-verbs.py`. -**Lifecycle**+**Lifecycle and readiness** +- `fusion_addin_status` - Is the AdomBridge add-in loaded and answering inside Fusion?+- `fusion_apply_fusion_update` - Readiness EXECUTOR (Rust port, Phase 3): click Fusion's update offer IN THE BACKGROUND when auto-update is on (sticky pref; fusion_set_auto_update).+- `fusion_describe` - Self-describe every verb the bridge exposes (this list) for ab's Verbs tab + runner.+- `fusion_get_app_state` - Current app state: running, active document, workspace, isElectronics.+- `fusion_install_fusion` - Install Fusion 360 FOR the user - downloads Autodesk's official Client Downloader and streams the free trial silently, with NO shell_execute and NO ab approval...+- `fusion_kill` - Force-KILL Fusion 360 (taskkill /F) - the desperate path for when fusion_stop cannot close it (stuck modal / wedged process).+- `fusion_readiness` - FAST readiness check (does NOT launch Fusion): is the Fusion host app installed + running + the bridge ready to drive?+- `fusion_set_auto_update` - Turn AUTOMATIC Fusion updating on/off for this user. - `fusion_start` - Launch Fusion 360 (if needed) and wait for the AdomBridge add-in to be ready.-- `fusion_stop` - Gracefully STOP Fusion 360: close docs cleanly + WM_CLOSE, then wait-- `fusion_kill` - Force-KILL Fusion 360 (taskkill /F) - the desperate path for when fusion_stop cannot close it (stuck modal / wedged proc-- `fusion_close_window` - Close the active Fusion document/window.-- `fusion_get_app_state` - Current app state: running, active document, workspace, isElectronics-- `fusion_readiness` - FAST readiness check (does NOT launch Fusion): is the Fusion host app installed + running + the bridge ready to drive? A-- `fusion_new_electronics_from_eagle` - Import a legacy EAGLE .sch (+ paired .brd) into a NEW Fusion electronics design so the parts INSTANTIATE (schematic + po-- `fusion_notify_owner` - LAST-RESORT human escalation: toast the user's MAIN computer (fans out cross-ab to every peer on the relay, so it reache+- `fusion_stop` - Gracefully STOP Fusion 360: close docs cleanly + WM_CLOSE, then wait.+- `fusion_uninstall` - CASCADE CLEANUP, called by ab's bridge_uninstall BEFORE this bridge's cache is deleted (ab 2.0.35+).+- `fusion_verb_times` - This machine MEASURED timings per verb/stage - the same store that paces every progress bar and ETA. -**Cloud search & files (APS)**+**Sign-in and seat** -- `fusion_aps_status` - APS state: configured, signed in, token live, never-charge meter.-- `fusion_aps_set_client_id` - Set the org's APS (PKCE) client id used for cloud search.-- `fusion_aps_signin` - Open the Autodesk sign-in in the REMEMBERED browser+profile (not the OS default); a background listener captures the tok-- `fusion_aps_set_browser` - Remember the browser+profile that authed to Autodesk so every later sign-in reuses it (power users have many browsers/pr-- `fusion_aps_get_browser` - Show the remembered Autodesk sign-in browser (None until set). - `fusion_aps_forget_browser` - Forget the remembered Autodesk sign-in browser (re-detect next time).-- `fusion_aps_search` - Server-indexed search across the whole team hub (seconds, not a 30-min folder walk).-- `fusion_aps_open` - Find a cloud design by name and open it in Fusion (any folder depth)-- `fusion_open_by_urn` - Open a cloud ELECTRONICS DESIGN by URN+- `fusion_aps_get_browser` - Show the remembered Autodesk sign-in browser (None until set).+- `fusion_aps_set_browser` - Remember the browser+profile that authed to Autodesk so every later sign-in reuses it (power users have many browsers/profiles; the OS default is usually the...+- `fusion_aps_set_client_id` - Set the org's APS (PKCE) client id used for cloud search.+- `fusion_aps_signin` - Open the Autodesk sign-in in the REMEMBERED browser+profile (not the OS default); a background listener captures the token.+- `fusion_resolve_seat_dialog` - Readiness EXECUTOR (Rust port, Phase 3): detect + resolve the Autodesk seat/licensing modal ('Active Sessions Exceeded' / 'Suspend Remote Session') in the...+- `fusion_set_seat_takeover` - Read/toggle the mid-session Autodesk seat-takeover sweep (STICKY per user, default ON).+- `fusion_signin` - SELF-DRIVING as of 1.9.32: launches Fusion if needed, clicks Sign In, opens the OAuth in the right profile, TYPES THE EMAIL and advances to the exact remaining...+- `fusion_signin_2fa` - Submit Autodesk's emailed 6-DIGIT verification code to finish a Fusion sign-in, typed IN THE BACKGROUND (UIA SetValue + Invoke - never steals the user's...+- `fusion_web_signin` - Sign a pup browser window into an Autodesk WEB surface (library.io, the hub portal at <hub>.autodesk360.com, Autodesk Account) via Google SSO.++**Cloud files and APS**+ - `fusion_aps_browse` - Browse hubs/projects/folders without launching Fusion.-- `fusion_aps_recent` - Most-recently-modified designs across the team hub, newest first.-- `fusion_aps_file_info` - Version history + who/when/size for a file (tip + recent versions).-- `fusion_aps_versions` - Full (uncapped) version history of a file.+- `fusion_aps_create_folder` - Create a subfolder in a project (free; needs data:create scope). - `fusion_aps_download` - Download a cloud file to the local machine (no Fusion needed).-- `fusion_aps_upload` - Upload a local file as a NEW cloud file (free Data Management; needs data:write scope)-- `fusion_aps_create_folder` - Create a subfolder in a project (free; needs data:create scope)+- `fusion_aps_file_info` - Version history + who/when/size for a file (tip + recent versions). - `fusion_aps_get` - Raw authenticated GET against a free APS path (dev/verify helper).-- ~~`fusion_walk_cloud_tree`~~  -  DISABLED (returns an error): the in-app add-in walk took 30+ min and CRASHED Fusion (WinError 10054)-- ~~`fusion_search_cloud_files`~~  -  DISABLED (returns an error): the in-app add-in search took 30+ min and CRASHED Fusion (WinError 10054)--**Electronics**+- `fusion_aps_open` - Find a cloud design by name and open it in Fusion (any folder depth).+- `fusion_aps_recent` - Most-recently-modified designs across the team hub, newest first.+- `fusion_aps_search` - Server-indexed search across the whole team hub (seconds, not a 30-min folder walk).+- `fusion_aps_status` - APS state: configured, signed in, token live, never-charge meter.+- `fusion_aps_upload` - Upload a local file as a NEW cloud file (free Data Management; needs data:write scope).+- `fusion_aps_versions` - Full (uncapped) version history of a file.+- `fusion_cleanup_cloud_files` - PRECISELY delete a list of cloud files by lineage urn - SAFE cleanup of AI-created clutter.+- `fusion_create_cloud_folder` - Create a subfolder in a cloud project through the signed-in Fusion session's Data API (no APS needed).+- `fusion_delete_cloud_file` - DESTRUCTIVE: delete ONE cloud file through the signed-in Fusion session's Data API.+- `fusion_list_cloud_files` - List files/folders in a cloud project (optionally a subfolder).+- `fusion_list_cloud_projects` - List the cloud PROJECTS visible to the signed-in Fusion session (hub walk - slow on big teams; prefer fusion_aps_search for finding files).+- `fusion_open_by_urn` - Open a cloud ELECTRONICS DESIGN by URN. PCB/ELECTRONICS NAV RULE: a board is NOT a standalone file - the schematic, 2D board and 3D board are VIEWS inside ONE...+- `fusion_open_by_urn_note` - (see fusion_open_by_urn)+- `fusion_open_cloud_file` - Open a CLOUD file by name (the front door to every design flow).+- `fusion_save_to_cloud` - Save the ACTIVE document to Autodesk cloud storage with an explicit name and destination.+- `fusion_search_cloud_files` - DISABLED (returns an error): the in-app add-in search took 30+ min and CRASHED Fusion (WinError 10054).+- `fusion_walk_cloud_tree` - DISABLED (returns an error): the in-app add-in walk took 30+ min and CRASHED Fusion (WinError 10054).++**Libraries and 3D packages**++- `fusion_attach_3d_package` - LOW-LEVEL primitive: attaches ONE model to ONE package.+- `fusion_bind_library_3d` - LOW-LEVEL primitive: binds ONE STEP onto one deviceset.+- `fusion_build_library_3d` - Build a RENDERING multi-part 3D library in ONE call - the whole programmatic pipeline.+- `fusion_capture_library_views` - Capture the LIBRARY-EDITOR views EEs trust: the schematic SYMBOL (full pinout), the FOOTPRINT (pads + layer stack), and the COMPONENT/device view (Content...+- `fusion_create_library` - ONE-CALL library creation for web-control (one call = one consent).+- `fusion_generate_package` - Generate an IPC-7351-compliant parametric 3D package via Fusion's BUILT-IN ElectronicsPackageGenerator (EPG) - ~50 package families...+- `fusion_install_library_bundle` - COMPOSITE, the Fusion twin of kicad_install_library_bundle (#68): ONE call takes a staged Cloud Library bundle (zip of <Lib>.lbr + <Part>.step models, or a...+- `fusion_library_manager` - Open Fusion's Library Manager and switch to one of its three tabs.+- `fusion_library_parts` - Every ORDERABLE variant in a .lbr with its part numbers - offline, no Fusion needed.+- `fusion_make_3d_package` - Create a RENDERING component 3D package (FOOTPRINT + chip, aligned) for ONE part, fully programmatically with ZERO GUI dialogs.+- `fusion_managed_library_create` - Create a NEW managed library on library.io FROM the library in front (which must already be saved in a Fusion Hub).+- `fusion_managed_library_link` - Link the library IN FRONT to an existing managed library on library.io.+- `fusion_managed_library_pull` - Pull the newest version FROM library.io into the linked library in front.+- `fusion_managed_library_push` - Push a NEW VERSION of the library in front up to its linked managed library on library.io.+- `fusion_managed_library_unlink` - Unlink the library in front from its managed library on library.io.+- `fusion_open_lbr` - Import an EAGLE .lbr library into Fusion's Electronics LIBRARY editor (Content Manager / Electronics Library workspace), not a PCB design.+- `fusion_plan_library_import` - PRE-FLIGHT for a library import: does NO work and changes nothing.+- `fusion_preflight_lbr` - PRE-ANALYZE a .lbr: will Fusion open it?+- `fusion_prove_result` - POLL a background run started by fusion_create_library or the detached fusion_build_library_3d.+- `fusion_show_3d_package` - Open the 3D CHIP view (Package3DEnvironment) for a library package - and the RELIABLE way to get a 3D model into a library: pass stepPath and it is attached...+- `fusion_show_attributes` - Show a component's METADATA: returns the attributes of a deviceset (MPN, MANUFACTURER, MOUSER_PN, DIGIKEY_PN, LCSC_PN, DATASHEET, ADOM_WIKI, per variant) read...+- `fusion_show_footprint` - Open a FOOTPRINT (package) in Fusion's library editor.+- `fusion_show_library` - Eight-surfaces contract: the LIBRARY surface.+- `fusion_show_symbol` - Open a schematic SYMBOL in Fusion's library editor - first-class, no hand-built EDIT strings.++**Schematic, board and routing** -- `fusion_show_schematic` - Switch the OPEN electronics design to its SCHEMATIC view (the symmetric partner of fusion_show_2d_board / fusion_show_3d-- `fusion_show_2d_board` - Switch the OPEN electronics design to its 2D PCB Editor (board layout) view-- `fusion_show_3d_board` - Switch the OPEN electronics design to its 3D PCB view (populated board) - `fusion_board_info` - Raw board geometry XML (copper/arcs/lines) for the open board.-- `fusion_open_lbr` - Import an EAGLE .lbr library into Fusion's Electronics LIBRARY editor (Content Manager / Electronics Library workspace),-- `fusion_attach_3d_package` - Attach a real 3D chip model to a library package, end to end: opens the .lbr (library active), runs Electron.Create3DPac-- `fusion_install_fusion` - Install Fusion 360 FOR the user - downloads Autodesk's official Client Downloader and streams the free trial silently, w-- `fusion_generate_package` - Generate an IPC-7351-compliant parametric 3D package via Fusion's BUILT-IN ElectronicsPackageGenerator (EPG) - ~50 packa-- `fusion_make_3d_package` - Create a RENDERING component 3D package (FOOTPRINT + chip, aligned) for ONE part, fully programmatically with ZERO GUI d-- `fusion_build_library_3d` - Build a RENDERING multi-part 3D library in ONE call - the whole programmatic pipeline-- `fusion_capture_library_views` - Capture the LIBRARY-EDITOR views EEs trust: the schematic SYMBOL (full pinout), the FOOTPRINT (pads + layer stack), and -- `fusion_cleanup_cloud_files` - PRECISELY delete a list of cloud files by lineage urn - SAFE cleanup of AI-created clutter-- `fusion_run_modeling_script` - Run an adsk.fusion modeling script in the live session (sketches/extrudes/params)-- `fusion_set_preference` - Set Fusion appearance/navigation preferences in the LIVE session - applies immediately, NO restart-- `fusion_get_preferences` - Read current Fusion appearance/navigation preferences (theme, activeTheme, invertScrollZoom, orbitScheme, modelingOrient--**CAD & 3D export**--- `fusion_export_step` - Export the active design to STEP (needs the 3D view).--**Manufacturing**--- `fusion_export_gerbers` - Run the bundled JLCPCB CAM job (auto 2/4-layer) to produce the gerber + drill ZIP.+- `fusion_eagle_import_status` - The run record of a background fusion_new_electronics_from_eagle import: stage (started | done | failed), documentName, partsExpected (elements in the .brd)...+- `fusion_electron_pan` - Smoothly PAN + zoom the view to frame a board-coordinate box (mm; get part positions from fusion_board_info).+- `fusion_electron_run` - Run an EAGLE/Electron command in the open board (the generic extension point, e.g. RUN <ulp>).+- `fusion_electron_select` - SELECT a part (by reference designator, or x/y board mm) and surface its properties - the way clicking a part reveals the properties panel.+- `fusion_electron_zoom` - Smoothly ZOOM the schematic/2D board for a recording (the whole motion is one call).+- `fusion_electronics_query` - TYPED electronics read model: rows for one of Autodesk's 49 electronics entity classes (Element, Signal, Net, Layer, Via, Smd, Wire, Pad, Part, Instance, Pin...+- `fusion_import_electronics` - Import a Fusion-NATIVE electronics file (.fsch schematic, .fbrd board, .flbr library; NOT legacy EAGLE .sch/.brd/.lbr) from the Fusion machine as a NEW local...+- `fusion_make_showcase_project` - Put a library's parts on a NEW schematic and board to SHOW THEM OFF: every deviceset in the .lbr (or the ones in devicesets[]) placed in a row on a fresh sheet...+- `fusion_make_test_project` - Create a NEW electronics test project FROM SCRATCH around a library part - the Web Control demo's PROJECT button, and the answer to 'a customer only has a...+- `fusion_new_electronics_from_eagle` - Import a legacy EAGLE .sch (+ paired .brd) into a NEW Fusion electronics design so the parts INSTANTIATE (schematic + populated board, lands in the PCB editor).+- `fusion_open_board` - Open a LOCAL legacy EAGLE .brd in Fusion's 2D board editor (dialog-free Document.newDesignFromLocal; a background open).+- `fusion_open_electronics` - Read whether an ELECTRONICS editor (schematic, board or library) is the active workspace, so you know if Electron.* text commands and fusion_electron_run will...+- `fusion_open_schematic` - Open a LOCAL legacy EAGLE .sch in Fusion's Schematic Editor (dialog-free Document.newDesignFromLocal; a background open).+- `fusion_plan_route` - Calculate a read-only, bounded orthogonal A* path between two pads on a live two-layer board.+- `fusion_route_net` - Create explicit net copper in the live Fusion canvas.+- `fusion_routing_drc` - Run fresh native Autodesk DRC and read UL_BOARD.errors, including approved errors, warnings and airwires.+- `fusion_routing_state` - Read the live board's identity, geometry revision, pads, copper and per-net airwires from a fresh Fusion export.+- `fusion_schematic` - START HERE for a schematic. ONE verb, three actions.+- `fusion_show_2d_board` - Switch the OPEN electronics design to its 2D PCB Editor (board layout) view.+- `fusion_show_3d_board` - Switch the OPEN electronics design to its 3D PCB view (populated board).+- `fusion_show_project` - Eight-surfaces contract: bring the PROJECT/design to its top level.+- `fusion_show_schematic` - Switch the OPEN electronics design to its SCHEMATIC view (the symmetric partner of fusion_show_2d_board / fusion_show_3d_board, via Fusion's SwitchSchDocCmd).++**Design rules and manufacturing**++- `fusion_board_stackup` - Read a board's PHYSICAL fabrication stackup (see the pcb-stackup skill): copper layer count + copper/dielectric thicknesses from the EAGLE design rules...+- `fusion_detect_layers` - Detect 2-layer vs 4-layer board (ULP + CAM comparison). - `fusion_export_bom` - Export a Bill of Materials CSV (grouped by value+package) from the open board. - `fusion_export_cpl` - Export the Component Placement List (pick-and-place) CSV.-- `fusion_set_design_rules` - Load manufacturing-grade DRC into the open board (JLCPCB 2/4-layer profiles).-- `fusion_load_design_rules` - Load a bundled .edru design-rule set into the board.-- `fusion_detect_layers` - Detect 2-layer vs 4-layer board (ULP + CAM comparison).+- `fusion_export_gerbers` - Run the bundled JLCPCB CAM job (auto 2/4-layer) to produce the gerber + drill ZIP.+- `fusion_set_design_rules` - Load a FAB's design rules (DRC) into the open board.++**Exports**++- `fusion_export_3mf` - Export to 3MF mesh.+- `fusion_export_board_image` - Render the OPEN board (or schematic) to a PNG via EAGLE's EXPORT IMAGE with a layer preset: all, top_copper, bottom_copper, top_silkscreen, bottom_silkscreen...+- `fusion_export_cloud_file` - Export the ACTIVE Fusion Design document to a local file on the Fusion machine in one of step, stl, f3d, iges, sat or smt (despite the name it does not fetch a...+- `fusion_export_dwg` - Export 2D DWG.+- `fusion_export_dxf` - Export 2D DXF.+- `fusion_export_eagle_source` - Export the ACTIVE electronics document as plain EAGLE XML (.sch or .brd; the outputPath extension picks the format).+- `fusion_export_f3d` - Export the active design as a Fusion archive (.f3d).+- `fusion_export_fbx` - Export to FBX mesh.+- `fusion_export_iges` - Export to IGES.+- `fusion_export_lbr` - Export the electronics LIBRARY in front as an EAGLE .scr script via Electron.run EXPORT SCRIPT: a text dump of every symbol, package and device, the round-trip...+- `fusion_export_obj` - Export to OBJ mesh.+- `fusion_export_optimized_glb` - Fusion board -> wiki-grade optimized GLB.+- `fusion_export_sat` - Export to SAT (ACIS solid).+- `fusion_export_skp` - Export to SketchUp (.skp).+- `fusion_export_source` - Export the ACTIVE electronics document in Fusion's native source format via Document.CopyToDesktop: .fbrd (board), .fsch (schematic) or .flbr (library); the...+- `fusion_export_step` - Export the active design to STEP (needs the 3D view).+- `fusion_export_stl` - Export to STL mesh.+- `fusion_export_usdz` - Export to USDZ (AR / Hydrogen 3D).+- `fusion_fetch_optimized_glb` - Finish a fusion_export_optimized_glb job: bounded-poll the optimizer by jobId and write the GLB to outputPath when complete.++**View, state and capture**++- `fusion_activate_document` - Make a named open document the ACTIVE one before driving it.+- `fusion_document_info` - Metadata for the ACTIVE document: name, product type, save state, version, parent project.+- `fusion_interactive` - Is Fusion taking input RIGHT NOW? Reads the add-in's busy flag (a long bridge command in flight), Fusion's own hasActiveJobs, and the main thread's latency on...+- `fusion_live_feed` - A ~1 fps look INTO Fusion while the bridge drives it, kept as a BOX-SIDE ring buffer (~10 min).+- `fusion_screenshot_fusion` - Background PrintWindow capture of Fusion's MAIN window (largest same-title window; works occluded/bottom-of-z-order, never raises anything) or a specific...+- `fusion_set_driving_badge` - Read/toggle the 'Adom is driving Fusion' TASKBAR OVERLAY BADGE (STICKY per user, default ON).+- `fusion_set_window_sizing` - Read/toggle auto-sizing Fusion's WINDOW after launch (STICKY, default ON).+- `fusion_state` - The RENDER-ME verb (kicad_state parity, issues #28/#30): running + window + dialog + busy state PLUS a fresh background screenshot of whatever Fusion is...+- `fusion_take_screenshot` - Screenshot the Fusion window in the BACKGROUND (no focus steal, reads through occlusion).+- `fusion_view` - FIRST-CLASS camera motion. 3D (the active Design viewport, through Fusion's own camera): fit | home | iso | top | front | right | back | left | bottom | orbit...++**Dialogs and recovery**++- `fusion_action_ledger` - Read the append-only record of what the bridge did in the BACKGROUND on this machine - dialogs it answered, Untitled scratch docs it swept, Fusion auto-starts...+- `fusion_alerts` - Fusion's OWN alert toasts, read from its log.+- `fusion_cancel_run` - STOP a library import that is in flight (fusion_install_library_bundle / fusion_create_library) - the user's escape hatch from a long run, wired to a Cancel...+- `fusion_check_recovery` - Ask whether a cloud file has a crash-recovery document waiting, which would raise Fusion's recovery modal on open.+- `fusion_close_all_documents` - Close ALL open documents without save dialogs.+- `fusion_close_document` - Close ONE document WITHOUT the modal save dialog (save:false discards silently - the automation-safe close).+- `fusion_close_window` - Close a DIALOG window by hwnd (WM_CLOSE, like clicking X) - for dialogs without Cancel/Escape handling (e.g. Recovered Documents). - `fusion_dismiss_blocking_dialogs` - Detect + classify (and optionally dismiss) blocking Fusion dialogs.-- `fusion_electron_run` - Run an EAGLE/Electron command in the open board (the generic extension point, e.g-- `fusion_electron_zoom` - Smoothly ZOOM the schematic/2D board for a recording (the whole motion is one call)-- `fusion_electron_pan` - Smoothly PAN + zoom the view to frame a board-coordinate box (mm; get part positions from fusion_board_info)-- `fusion_electron_select` - SELECT a part (by reference designator, or x/y board mm) and surface its properties - the way clicking a part reveals th--**Self**--- `fusion_describe` - Self-describe every verb the bridge exposes (this list) for ab's Verbs tab + runner.+- `fusion_notify_owner` - LAST-RESORT human escalation: toast the user's MAIN computer (fans out cross-ab to every peer on the relay, so it reaches them even when this bridge runs on an...+- `fusion_reset_workspace` - Bring Fusion back to a clean slate between runs: closes named documents AND the Untitled scratch the library/bind flow leaves behind (page issue #59).+- `fusion_restart_for_addin` - ASYNC: returns immediately (started:true) and restarts Fusion in the background, SAVING the user's open documents first; poll fusion_readiness until ready with...++**Modeling and parameters**++- `fusion_assembly_bom` - Structured, kit-aware MECHANICAL bill of materials for the active Design assembly (the counterpart to the electronics-only fusion_export_bom).+- `fusion_get_design_info` - Design-level summary of the active design: components, bodies, sketches, units, root name.+- `fusion_get_parameters` - All user + model parameters of the active design (name, expression, value, unit).+- `fusion_import_step` - Import a STEP/.stp (or other native-importable) file into a NEW Fusion design via the native import API.+- `fusion_inspect_bodies` - GEOMETRY READ-BACK: what actually got built, in MILLIMETRES.+- `fusion_open_design` - Open a LOCAL design file (routes through the add-in import).+- `fusion_physical_properties` - Per-component physical properties (volume cm3, mass kg, density, area, center of mass) for the active design.+- `fusion_run_modeling_script` - Run an adsk.fusion modeling script in the live session (sketches/extrudes/params).+- `fusion_set_parameter` - Set one parameter's expression by name in the ACTIVE 3D Design document (user parameters first, then model parameters).++**Demo and narration**++- `fusion_demo` - FIRST-TIME-USER DEMO in one verb - the thing to run when someone new wants to SEE what this bridge does (HD's installer calls this).+- `fusion_demo_progress` - Poll a running fusion_demo tour: {percent, elapsedSec, estimatedSec, step, stepLabel, done, failed, shots, shotCount, stepShot}.+- `fusion_narrate` - Play one bundled narration clip on the box now (the guided tour's voice): dock, dashboard, or any <beat>[-a|-b] clip in resources/demo-audio.++**Other**++- `fusion_execute_text_command` - Run a raw Fusion TEXT COMMAND (app.executeTextCommand) - the escape hatch to Fusion internals (Electron.* electronics commands included).+- `fusion_get_preferences` - Read current Fusion appearance/navigation preferences (theme, activeTheme, invertScrollZoom, orbitScheme, modelingOrientation, gestureNav, cameraPivot...+- `fusion_list_text_commands` - List Fusion's internal TEXT COMMANDS (TextCommands.List) whose name contains filter, case-insensitive; the discovery tool for fusion_execute_text_command.+- `fusion_mcp_call` - Call any tool on Autodesk's Fusion MCP server (fusion_mcp_read, fusion_mcp_update, fusion_mcp_execute, fusion_mcp_electronics_read).+- `fusion_mcp_enable` - Turn Autodesk's Fusion MCP server ON by driving Fusion's Preferences dialog for the user.+- `fusion_mcp_resources` - List Autodesk MCP resources, or read one by uri.+- `fusion_mcp_status` - Is Autodesk's Fusion MCP server up, and what does it expose?+- `fusion_mcp_tools` - Full tool list from Autodesk's Fusion MCP server INCLUDING input schemas, so you can construct a valid fusion_mcp_call.+- `fusion_prefs_close` - Apply+OK (save:true, the default) or Cancel Fusion's Preferences dialog.+- `fusion_prefs_open` - Open Fusion's Preferences dialog and optionally jump to a section, then hand back a SCREENSHOT + shotId you can click in.+- `fusion_set_preference` - Set Fusion appearance/navigation preferences in the LIVE session - applies immediately, NO restart.  ## Two install layers (they are NOT the same thing) @@ -470,7 +687,7 @@ This page ships **two separate artifacts**, and it matters which one you want: | Layer | What it is | Who installs it | Contents | Size | |---|---|---|---|---| | **Wiki package** (`adom-wiki pkg install adom/fusion-bridge`) | **SKILLS ONLY** for a container's AI | your cloud/ah container | `SKILL.md` + `skills/**` (the AI playbooks: APS, driving, onboarding, cloud hygiene, molecule export, PCB stackup) | tiny, text |-| **Release zip** (`adom-bridge-fusion-v<ver>.zip`, pointed at by `adom-bridge-fusion-manifest.json`) | the **bridge RUNTIME** | Adom Bridge, via `bridge_install` | `server.py`, `aps.py`, `describe.py`, `handlers/`, the `AdomBridge` Fusion add-in, `resources/` | ~456 KB |+| **Release zip** (`adom-bridge-fusion-v<ver>.zip`, pointed at by `adom-bridge-fusion-manifest.json`) | the **bridge RUNTIME** | Adom Bridge, via `bridge_install` | `adom-fusion-bridge.exe`, `describe.py`, the `AdomBridge` Fusion add-in, `resources/` | about 6 MB |  The package teaches your AI **how** to drive Fusion; the release zip is the code Adom Bridge runs on the machine where Fusion lives. Installing the package alone gives you skills with no bridge; `bridge_install` alone gives you a working bridge whose calling AI has no skills. Most setups want both.@@ -485,15 +702,18 @@ adom-bridge bridge_install '{"manifestUrl": "https://wiki.adom.inc/api/v1/pages/ adom-wiki pkg install adom/fusion-bridge   # the skills pack for your AI ``` -Auto-updates via `updateManifestUrl` (ab polls on launch + every 4h). 80 verbs -+Auto-updates via `updateManifestUrl` (ab polls on launch + every 4h). Every release lands on the+insiders manifest first, and the public manifest moves only after the pre-production gate, so the+two tiers can serve different versions. 160 verbs - `fusion_describe '{}'` returns the full machine-readable catalog. More demos:  <video controls width="100%" src="https://wiki.adom.inc/api/v1/pages/fusion-bridge/files/library2-3d-montage.mp4"></video>  ## Source code -The full source lives on this page (Files tab): `server.py`, `aps.py`, the AdomBridge add-in,-`describe.py`, handlers, skills, and manufacturer resources. GitHub mirror:+The full source lives on this page (Files tab): the Rust exe (`rust/`), the AdomBridge add-in+(`addin/`), the verb contract (`describe.py`, `contracts/describe.json`), skills, tests and+manufacturer resources. GitHub mirror: `adom-inc/fusion-bridge` (the wiki is canonical). MIT licensed.  ## Platforms@@ -503,7 +723,19 @@ supported (Autodesk ships no Fusion host app for Linux).  ## Contributing -Open a discussion on this page - bug reports with a repro (like-[#55](https://wiki.adom.inc/adom/fusion-bridge/discussions/55) and-[#63](https://wiki.adom.inc/adom/fusion-bridge/discussions/63), both root-caused and-fixed) directly shape releases.+The wiki page is the repo. Every release pushes the whole source tree (the Rust exe, the Fusion+add-in, the verb contract, the skills, the tests) to+[adom/fusion-bridge](https://wiki.adom.inc/adom/fusion-bridge), and pull requests are reviewed+there. A GitHub mirror exists, and the wiki repo is canonical.++If your AI had to work around the bridge, that workaround is a contribution waiting to happen.+Every failing reply from the exe carries a `_contribute` field that says where to file it or fix+it, and `fusion_describe` carries a `contribute` block with the same pointer. The+`fusion-contributing` skill (installed with the package) walks an AI through the whole loop: file+the issue with the reply JSON, clone with `adom-wiki repo clone adom/fusion-bridge`, make the+change, build the exe, try it on your own box behind a dev pin, and send the pull request with+`adom-wiki pr create`. [CONTRIBUTING.md](CONTRIBUTING.md) has the same commands for people.++Bug reports with a repro (like [#55](https://wiki.adom.inc/adom/fusion-bridge/discussions/55) and+[#63](https://wiki.adom.inc/adom/fusion-bridge/discussions/63), both root-caused and fixed)+directly shape releases.
bridge.json+3−2
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.10.24",+  "version": "1.10.25",   "description": "Drive Autodesk Fusion 360 from the cloud: launch Fusion, electronics board layout, design rules, exports (STEP/IGES/STL/3MF/USDZ/OBJ/DXF/DWG/Gerbers/BOM/CPL), fast APS server-indexed cloud search plus browse/recent/file-info/versions, cloud file download/upload and folder creation, and in-app parametric modeling (fusion_run_modeling_script). Never-charge: APS calls are capped to the free tier.",   "homepage": "https://wiki.adom.inc/adom/fusion-bridge",   "author": "Adom Inc.",@@ -84,7 +84,8 @@     "fusion_plan_route",     "fusion_schematic",     "fusion_make_showcase_project",-    "fusion_eagle_import_status"+    "fusion_eagle_import_status",+    "fusion_show_attributes"   ],   "dependencies": {},   "category": "CAD",
contracts/describe.json+25−2
@@ -1779,7 +1779,8 @@       "input": {         "devicesets": "optional [str] (default: every deviceset in the library)",         "libraryPath": "required str (.lbr on the Fusion machine; a component page's .lbr or a merged bundle library)",-        "name": "optional str document name (default <library>-showcase)"+        "name": "optional str document name (default <library>-showcase)",+        "silkLabels": "optional bool (default true): the part name on layer 21 under each footprint"       },       "longRunning": true,       "name": "fusion_make_showcase_project",@@ -1792,7 +1793,7 @@         "success": "bool"       },       "statusVerb": "fusion_get_app_state",-      "summary": "Put a library's parts on a NEW schematic and board to SHOW THEM OFF: every deviceset in the .lbr (or the ones in devicesets[]) placed in a row on a fresh sheet and a fresh board, no nets, with the library's bound 3D urns carried across so the 3D PCB shows the real bodies. Generated as EAGLE source on the box and opened dialog-free (Document.newDesignFromLocal), so nothing touches the user's screen. This is THE verb for 'make a board with these new parts on it'; fusion_make_test_project is the one-part circuit variant. After it: fusion_show_2d_board / fusion_show_3d_board {capture:true}, fusion_schematic {action:\"show\"}, fusion_save_to_cloud.",+      "summary": "Put a library's parts on a NEW schematic and board to SHOW THEM OFF: every deviceset in the .lbr (or the ones in devicesets[]) placed in a row on a fresh sheet and a fresh board, no nets, with the library's bound 3D urns carried across so the 3D PCB shows the real bodies. Generated as EAGLE source on the box and opened dialog-free (Document.newDesignFromLocal), so nothing touches the user's screen. This is THE verb for 'make a board with these new parts on it'; fusion_make_test_project is the one-part circuit variant. After it: fusion_show_2d_board / fusion_show_3d_board {capture:true}, fusion_schematic {action:\"show\"}, fusion_save_to_cloud. The name of each part is written on the top silkscreen under its footprint (silkLabels:false turns it off); fusion_make_test_project does the same.",       "timeoutSeconds": 300     },     {@@ -2769,6 +2770,28 @@       "summary": "Open the 3D CHIP view (Package3DEnvironment) for a library package - and the RELIABLE way to get a 3D model into a library: pass stepPath and it is attached AFTER the library opens (embedded <packages3d> in hand-built .lbr files is the Fusion quirk that fails; attach-after-open works). stepPath with no library/packageXml auto-generates a minimal 2-pad carrier footprint. Without stepPath it just enters the 3D view of the named package.",       "timeoutSeconds": 300     },+    {+      "example": {+        "deviceset": "VL53L8CX",+        "libraryPath": "C:/Users/me/Downloads/Adom_ToF.lbr"+      },+      "input": {+        "capture": "optional bool (default true)",+        "deviceset": "required str",+        "libraryPath": "optional str (.lbr on the Fusion machine; opened first and read for the attributes; omit when the library is already the active document)"+      },+      "longRunning": false,+      "name": "fusion_show_attributes",+      "output": {+        "attributes": "obj (the component's metadata)",+        "screenshot": "str|null",+        "success": "bool",+        "variantAttributesDialog": "obj (recipe)"+      },+      "statusVerb": "fusion_get_app_state",+      "summary": "Show a component's METADATA: returns the attributes of a deviceset (MPN, MANUFACTURER, MOUSER_PN, DIGIKEY_PN, LCSC_PN, DATASHEET, ADOM_WIKI, per variant) read from the library file with their full values, and opens the DEVICE editor for it (EDIT <name>.dev, no dialog: symbol, the Packages table with the Mapped check, footprint and 3D previews in one frame) with a capture. Fusion's own Variant Attributes table sits behind a button in an opaque Qt panel; the reply carries the positioned-click recipe for it and notes that a main-window recording does not include that owned dialog.",+      "timeoutSeconds": 120+    },     {       "example": {         "libraryPath": "C:/Temp/lib.lbr",
dashboard/Cargo.lock+1−1
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "fusion-dashboard"-version = "1.10.24"+version = "1.10.25" dependencies = [  "serde_json", ]
dashboard/Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.10.24"+version = "1.10.25" edition = "2021"  [dependencies]
describe.py+5−2
@@ -251,8 +251,11 @@ _T = [     ("fusion_show_3d_package", "Open the 3D CHIP view (Package3DEnvironment) for a library package - and the RELIABLE way to get a 3D model into a library: pass stepPath and it is attached AFTER the library opens (embedded <packages3d> in hand-built .lbr files is the Fusion quirk that fails; attach-after-open works). stepPath with no library/packageXml auto-generates a minimal 2-pad carrier footprint. Without stepPath it just enters the 3D view of the named package.", {"capture": "optional bool - ONE call = ONE consent: the bridge settles the surface (waits for the add-in main thread + a paint beat) and returns proof inline as shot:{title,image,thumbnailDataUrl,maxWidth} (base64 frame + a ready-to-use data: URL for BROWSER callers who cannot pull a file; set captureMaxWidth to shrink it - 320 keeps a /command body small, default 640. The bridge encodes PNG; the relay may transcode to WebP, so sniff rather than assume). Use this instead of polling fusion_state after a show - page-side settle polling costs one human consent per poll (issue #30).", "name": "str", "libraryPath": "optional str (.lbr)", "packageXml": "optional str (bare <package> fragment)", "stepPath": "optional str (WINDOWS path .step to attach)", "foreground": "optional bool - DEFAULT IS BACKGROUND (no focus steal; response carries windowEtiquette:background). true REQUIRES foregroundReason and shows it to the user as a 3s ab caption before any focus change - only when the USER asked to watch", "foregroundReason": "required WITH foreground:true - a short user-facing sentence shown as the 3s caption (e.g. 'You asked to watch the 3D board build')"}, {"success": "bool", "view": "str", "screenshot": "str path", "wrappedLibrary": "str"}, 300, None, True, {"name": "MYCHIP", "stepPath": "C:/Temp/chip.step"}),     ("fusion_demo_progress", "Poll a running fusion_demo tour: {percent, elapsedSec, estimatedSec, step, stepLabel, done, failed, shots, shotCount, stepShot}. Poll ~1-2.5s and render a REAL progress bar. LIVE EVIDENCE (issue #30): each frame MAY carry stepShot {step, title, image} - a ~640px base64 capture the BRIDGE took at that step's completion, one per step, riding the first frame after it completes (render these during the run; the page should not guess-snap). shots[] streams too: mid-run it carries paths for every completed step; pass imagesAs:base64 + sinceShot:<count you have> to receive only NEW shots fully encoded. done:true = the full gallery (base64 by default). Finished tours are DISK-SNAPSHOTTED, so the final frame stays pollable even across a bridge respawn.", {"demoId": "required str (from fusion_demo mode:tour; omitted = the single running tour)", "imagesAs": "optional str paths|base64|both", "sinceShot": "optional int - shots you already received; response windows to the new ones"}, {"percent": "int", "step": "str", "stepLabel": "str", "done": "bool", "shots": "list", "shotCount": "int", "stepShot": "dict|absent"}, 15, None, False, {"demoId": "tour-abc123", "imagesAs": "base64", "sinceShot": 3}), -    ("fusion_make_showcase_project", "Put a library's parts on a NEW schematic and board to SHOW THEM OFF: every deviceset in the .lbr (or the ones in devicesets[]) placed in a row on a fresh sheet and a fresh board, no nets, with the library's bound 3D urns carried across so the 3D PCB shows the real bodies. Generated as EAGLE source on the box and opened dialog-free (Document.newDesignFromLocal), so nothing touches the user's screen. This is THE verb for 'make a board with these new parts on it'; fusion_make_test_project is the one-part circuit variant. After it: fusion_show_2d_board / fusion_show_3d_board {capture:true}, fusion_schematic {action:\"show\"}, fusion_save_to_cloud.",-     {"libraryPath": "required str (.lbr on the Fusion machine; a component page's .lbr or a merged bundle library)", "devicesets": "optional [str] (default: every deviceset in the library)", "name": "optional str document name (default <library>-showcase)"},+    ("fusion_show_attributes", "Show a component's METADATA: returns the attributes of a deviceset (MPN, MANUFACTURER, MOUSER_PN, DIGIKEY_PN, LCSC_PN, DATASHEET, ADOM_WIKI, per variant) read from the library file with their full values, and opens the DEVICE editor for it (EDIT <name>.dev, no dialog: symbol, the Packages table with the Mapped check, footprint and 3D previews in one frame) with a capture. Fusion's own Variant Attributes table sits behind a button in an opaque Qt panel; the reply carries the positioned-click recipe for it and notes that a main-window recording does not include that owned dialog.",+     {"deviceset": "required str", "libraryPath": "optional str (.lbr on the Fusion machine; opened first and read for the attributes; omit when the library is already the active document)", "capture": "optional bool (default true)"},+     {"success": "bool", "attributes": "obj (the component's metadata)", "screenshot": "str|null", "variantAttributesDialog": "obj (recipe)"}, 120, _STATUS, False, {"libraryPath": "C:/Users/me/Downloads/Adom_ToF.lbr", "deviceset": "VL53L8CX"}),+    ("fusion_make_showcase_project", "Put a library's parts on a NEW schematic and board to SHOW THEM OFF: every deviceset in the .lbr (or the ones in devicesets[]) placed in a row on a fresh sheet and a fresh board, no nets, with the library's bound 3D urns carried across so the 3D PCB shows the real bodies. Generated as EAGLE source on the box and opened dialog-free (Document.newDesignFromLocal), so nothing touches the user's screen. This is THE verb for 'make a board with these new parts on it'; fusion_make_test_project is the one-part circuit variant. After it: fusion_show_2d_board / fusion_show_3d_board {capture:true}, fusion_schematic {action:\"show\"}, fusion_save_to_cloud. The name of each part is written on the top silkscreen under its footprint (silkLabels:false turns it off); fusion_make_test_project does the same.",+     {"silkLabels": "optional bool (default true): the part name on layer 21 under each footprint", "libraryPath": "required str (.lbr on the Fusion machine; a component page's .lbr or a merged bundle library)", "devicesets": "optional [str] (default: every deviceset in the library)", "name": "optional str document name (default <library>-showcase)"},      {"success": "bool", "documentName": "str", "parts": "[{ref, deviceset, package, symbol}]", "partsPlaced": "int (read back from the board)", "schPath": "str", "brdPath": "str"}, 300, _STATUS, True, {"libraryPath": "C:/Users/me/Downloads/Adom_ToF.lbr", "devicesets": ["VL53L5CX", "VL53L7CX", "VL53L8CX"]}),     ("fusion_eagle_import_status", "The run record of a background fusion_new_electronics_from_eagle import: stage (started | done | failed), documentName, partsExpected (elements in the .brd), partsPlaced (read back from the board), placed[], and the final result. Poll it every few seconds after the import returned its runId.",      {"runId": "required str (from fusion_new_electronics_from_eagle)"},
docs/3d-viewer-and-export.md+46
@@ -18,6 +18,48 @@ component sits at its footprint with its 3D package, which is what feeds the GLB  ![TI DRV8323SR gate-driver board rendered in 3D](fz-3d.png) +## Seeing the 3D view: `fusion_state`++`fusion_state` is the verb that shows you what Fusion is showing. It reports geometry separately+from window pixels, because a window capture of a Design viewport can come back blank under+Windows Graphics Capture while the model is perfectly fine.++- `nativeViewport {path, ok, bodies, productType}`: the frame Fusion itself renders, exported+  through `activeViewport.saveAsImageFile`. It needs no foreground and does not depend on the+  window capture.+- `geometry`: the body count, read through the API. Bodies present with a blank capture means the+  capture failed, not the model.+- `windowPixels`: a measurement of the window capture. `dominantColorFraction` is the share of+  the most common color, and `blank` is true above 0.97. A blank capture says so in the reply and+  points at the native frame.++```bash+adom-bridge fusion_state '{}'                        # window capture plus the native frame+adom-bridge fusion_state '{"viewport":"native"}'     # skip the window capture entirely+adom-bridge fusion_state '{"screenshot":false}'      # state only: running, window, dialogs, busy+```++`fusion_state {screenshot:false}` is also the answer wherever an old hint said+`fusion_window_info`, which is not a verb in the contract.++## Moving the camera: `fusion_view`++First-class camera motion, through Fusion's own camera, with smooth transitions by default:++```bash+adom-bridge fusion_view '{"action":"iso"}'+adom-bridge fusion_view '{"action":"orbit","degrees":40}'+adom-bridge fusion_view '{"action":"zoom","factor":1.5}'+adom-bridge fusion_view '{"action":"style","style":"shaded-edges"}'+adom-bridge fusion_view '{"action":"fit","space":"2d"}'      # the schematic, board or library editor+```++In 3D the actions are `fit`, `home`, `iso`, `top`, `front`, `right`, `back`, `left`, `bottom`,+`orbit {degrees}` (default 40), `zoom {factor}`, `pan {dx, dy}` and `style`. With `space:"2d"` it+drives the active schematic, board or library editor: `fit`, `zoom {factor, steps}` and+`pan {x1, y1, x2, y2}` in board mm. Orbit a little and capture again with `fusion_state` to show a+board from a second angle. This replaces hand-written camera code in `fusion_run_modeling_script`.+ ## Export formats  | verb | format |@@ -115,6 +157,10 @@ Separate from board export, the bridge builds 3D packages for component librarie `fusion_make_3d_package`, `fusion_attach_3d_package`, `fusion_build_library_3d`, `fusion_capture_library_views`. See [electronics libraries](electronics-libraries.md). +When the manufacturer publishes an authored transform, pass it as `placement` on+`fusion_make_3d_package` and no heuristic runs at all. `orient:false` imports the model exactly as+authored. See [electronics libraries](electronics-libraries.md).+ One relevant fix: auto-orient used to rotate the **smallest** bbox dimension to Z, which is right for a flat SMD chip and wrong for a tall through-hole pin, so tall parts were laid on their side. Tall parts now stay vertical.
docs/MULTIPART_LIBRARY_IMPORT.md+22−1
@@ -56,6 +56,14 @@ per-part .lbr  --open-->  library active - The bridge trusts the STEP's origin and orientation as published on the wiki. It does   **not** rotate or translate by bounding box: some chips are lopsided and no bounding-box   rule is ever right for all of them (`orient:false`).+- `orient:false` means exactly that since 1.10.8: no bounding-box reorientation, and the pin-1+  mark routine does not rotate the model either. When a manufacturer publishes an authored+  transform, `fusion_make_3d_package` and each `parts[]` entry of `fusion_build_library_3d` take+  an explicit `placement: {translation:[x,y,z] mm, rotation:[rx,ry,rz] deg, scale:1}`, which+  bypasses every heuristic. The reply carries `placement.appliedMatrix`,+  `placement.verification` (`verified` or `mismatch`) and `source {path, sha256, bytes,+  preserved}`. A bad shape or a scale other than 1 is refused before Fusion is touched+  (`invalid_placement`, `unsupported_scale`). - The saved design lives in the user's project under `Adom AI Workspace/<library name>/`,   one per package. That folder is the 3D package store; Fusion's own GUI keeps the same kind   of design, it just hides it. Do not delete it: the library's urns point at it.@@ -136,6 +144,18 @@ sliver once passed as a "symbol" until this check existed), and `provedParts[]` `partMetadata` reports missing MPN / manufacturer / distributor numbers; it never blocks the import, it bubbles up so someone can fix the data upstream. +**Prove is decided by the caller.** Proof frames are for web control, so web control and anonymous+callers prove. An AI thread binds and places only: the run ends with `verdict: bound_not_proven`,+`partsBound` and the library path, `prove.planned` and `prove.skipped` say what happened and why,+and the banner's final line says "bound; proof skipped" instead of a proof count. `prove:true` or+`prove:false` on `fusion_create_library` or `fusion_install_library_bundle` always wins, and the+reply says what prove costs before it starts. For one part's frames afterwards, use+`fusion_capture_library_views`.++The offline lint in `fusion_library_parts` reports pads that share a base name (`B4` and `B4@2`,+heatsink vias on the exposed pad's own net) as an info line, `heatsink_vias`. They are one net, not+a short. `pad_overlap` stays for pads with genuinely different names.+ ## Contracts worth knowing  | Situation | What you get |@@ -143,7 +163,8 @@ import, it bubbles up so someone can fix the data upstream. | A deviceset has no STEP in the bundle | a named skip in `unbound[]`; the rest still bind; `partsBound < partsTotal` | | No STEP at all | `installed:true`, `nothing_to_bind`, nothing opened in Fusion | | Fusion closed | the chain launches it (about a minute) and carries on |-| Fusion open, add-in silent | the chain **asks** (toast + `addin_restart_required` in the run) and waits; "Save and restart" continues the same run, "I'll handle it" stops it with `user_owns_restart` |+| Fusion open, add-in silent | the chain never restarts Fusion on its own. A long operation in flight reads as `fusion_addin_busy` and the chain waits. When the add-in really is not answering, the run says so and stops short of a restart unless the caller passed `allowRestart:true` with a `restartReason`; a restart the chain performs is listed in `restarts[] {at, why, kind}` on `fusion_prove_result`. The "Save and restart" toast exists only under `restartPolicy: ask`. |+| A dialog in front of Fusion | read through UIA and expired safely, then the chain carries on. A dialog that survives ends the run as `blocked_by_dialog` with the dialog's text and buttons. Fusion is never restarted over a dialog | | The bridge restarts mid-run | `bridge_restarted_mid_run`, honest and terminal; scratch documents are swept before the next run | | Same library name already in `Adom Libraries` | reported in `cloudLibrary.olderSameName`; retired only with `replaceExisting:true` | | Several devicesets on one package (symbol variants, value clones) | one mint per package; every device on it gets the urn; listed in `shared[]`, not `unbound[]` |
docs/aps-cloud-search.md+28−2
@@ -97,6 +97,29 @@ failed with `APS token_expired`. If you see `refreshFailed: true`, run `fusion_a  ## When a search returns nothing +A zero from the server index is not absence. The index lags behind new files and it tokenises+names, so a file that exists can miss. `fusion_aps_search` never lets an index zero stand alone:++- Shortened queries run speculatively in parallel with the real one ("Pincer Moving" for "Pincer+  Moving Large"). When the full query comes back empty, the hits of the shortened queries are+  resolved from versions to their items, and the parent folders of those related hits (the sibling+  folders) are listed through the Data API, not the index. Names are matched directly against the+  folder contents.+- A two-level folder walk runs only when `projectId` scopes the search, so there is never an+  unbounded cloud walk.+- The reply carries `data.fallback` (what the fallback scanned and found) and `data.completeness`+  (a plain statement that the index lags and tokenises). Fallback results carry `projectId`,+  `folderId` and lineage, so `fusion_aps_open` or `fusion_open_by_urn` can open them directly.+- The hint also points at `fusion_aps_browse` on a known sibling's folder, scoped, as the manual+  next step.+- The shortened queries run in parallel with the main scan, so the whole answer fits one relay+  call.++This was verified on the Depth Lab file from page issue #85, which the index missed and the folder+listing found.++If the fallback is empty too:+ - Confirm `fusion_aps_status` shows `signedIn: true` and `refreshFailed: false`. - APS indexes the **cloud**. Unsaved local documents are not searchable. - The design may live in a hub the signed-in account cannot see. `fusion_aps_browse` shows which@@ -113,6 +136,9 @@ PROJECT        EcadDesignProductType   <- open this   |- 3D PCB      DesignProductType     (generated from the .brd; a leaf) ``` -Opening a child gives an isolated or empty view. The open verbs warn in their `_hint` when you open-a non-`EcadDesignProductType` file. See [schematics](schematics.md) and+Opening a child gives an isolated or empty view. The open verbs warn in their `_hint` when the file+you opened looks like the 3D leaf of an electronics chain, or when you passed+`intent:"electronics"`. Opening a mechanical design is not an error: `intent:"mechanical"`+confirms the open. For a schematic, `fusion_schematic {action:"open", query}` does the search,+the open and the view switch in one call. See [schematics](schematics.md) and [2D board layout](board-layout-2d.md).
docs/board-layout-2d.md+79−7
@@ -16,12 +16,32 @@ Opening the `.brd` child directly gives an isolated view. See [schematics](schem ## Design rules  ```bash-adom-bridge fusion_load_design_rules '{}'                       # what is loaded now-adom-bridge fusion_set_design_rules  '{"preset":"adom"}'        # apply Adom's DRU+adom-bridge fusion_set_design_rules '{"action":"list"}'                              # every fab, process, layers row+adom-bridge fusion_set_design_rules '{"fab":"jlcpcb","process":"standard","layers":"2"}'+adom-bridge fusion_set_design_rules '{}'                                             # the bundled Adom InstaPCB rules+adom-bridge fusion_set_design_rules '{"druPath":"C:/tmp/house.edru"}'                # a local rule file, as-is+adom-bridge fusion_alerts '{}'                                                       # confirm the load ``` -Adom's DRU rules ship with the bridge under `resources/`. Applying a house ruleset before routing is-what keeps a board manufacturable at the fab you use.+`fusion_set_design_rules` loads a fab's rules into the open board. The rule packs live on the wiki+page [adom/pcb-design-rules](https://wiki.adom.inc/adom/pcb-design-rules): JLCPCB, PCBWay, OSH+Park, Seeed, Eurocircuits, Aisler, Elecrow, Advanced Circuits, Sierra, MacroFab, NextPCB, ALLPCB+and Adom InstaPCB, one `.edru` per vendor, process and layer count, every number citing the+vendor's own capabilities page. The bridge fetches the pack, stages it on the box, and the add-in+runs `drc load`. `layers` auto-detects from the open board. `{action:"list"}` answers on a machine+without Fusion, so you can offer the user a real choice of fab before a board is open. `apply` and+`export` need a board open and say so themselves.++Read `ruleWarnings` in the reply. The bridge scans the downloaded profile and warns when it carries+a same-signal Copper Clearance rule over an all/all or trace scope. The InstaPCB 2L profile, like+the other generated profiles, carries "Same-net track spacing" (0.25 mm, all/all, samesignal=yes).+On the six-net test fixture native DRC reported that one rule as 31 Overlap warnings, at the 13 pad+entries and 18 trace and via joints. With the rule disabled the identical board is clean. Use+`{action:"export"}` to read the loaded values back.++Always confirm with `fusion_alerts` afterwards. A refused rule file is an in-canvas toast, never a+dialog. Applying a fab's ruleset before routing is what keeps a board manufacturable at the fab you+use.  ## Layers @@ -41,7 +61,45 @@ which is where `fusion_electron_run` commands act.  ## Routing -Fusion Electronics is EAGLE underneath, so routing is driven with EAGLE commands:+### Live per-net routing++Four verbs route a live two-layer board one net at a time, with explicit copper and no general+autorouting. All four take `expectDocument` (the exact active board name), and the three that+follow `fusion_routing_state` take the `boardId` and `expectedRevision` it returned, so a call made+against a board that changed underneath it refuses instead of mutating the wrong geometry.++```bash+adom-bridge fusion_routing_state '{"expectDocument":"six-net-brd"}'+adom-bridge fusion_plan_route    '{"expectDocument":"six-net-brd","boardId":"...","expectedRevision":"...",+                                   "net":"SDA","fromPad":"U1.3","toPad":"R2.1"}'+adom-bridge fusion_route_net     '{"expectDocument":"six-net-brd","boardId":"...","expectedRevision":"...",+                                   "net":"SDA","layer":1,"width":0.25,"points":[{"pad":"U1.3"},[12.5,8.0],{"pad":"R2.1"}]}'+adom-bridge fusion_routing_drc   '{"expectDocument":"six-net-brd","boardId":"...","expectedRevision":"..."}'+```++| verb | what it does |+|---|---|+| `fusion_routing_state` | Reads the live board from a fresh Fusion export: `boardId`, geometry `revision`, `pads`, `segments`, `vias`, `nets` and `remainingNets` (per-net airwires). |+| `fusion_plan_route` | Read-only. Calculates a bounded orthogonal A* path between `fromPad` and `toPad`, using existing copper and the shared conservative clearance model. Returns explicit arguments for `fusion_route_net`. Tunable with `grid`, `allowVias`, `viaCost`, `maxNodes` and `timeoutMs`. |+| `fusion_route_net` | Creates the copper. One or several trace segments per call (`points`, or `paths`), on layer 1 or 16, with `width`, `clearance`, `viaDiameter` and `viaDrill`. It runs a conservative clearance preflight, checks the board and revision guards, and reads the exact copper back (`verified`, the new `revision`, `remainingNets`). `dryRun:true` plans without mutating. |+| `fusion_routing_drc` | Runs fresh native Autodesk DRC and reads `UL_BOARD.errors`, including approved errors, warnings and airwires. It does not accept or clear any violation. |++`fusion_routing_drc` in detail: the reply carries `drc {checked, clean, errorCount, warningCount,+airwireCount, errors}`. Each error reports `layer`, `x` and `y` in mm, so you can pan straight to+it with `fusion_electron_pan`. A `summary` groups the errors by description. When Overlap warnings+are same-signal joints (a trace meeting its own pad or via), a note says so, and the usual cause is+the same-signal clearance rule that `ruleWarnings` names above. The geometric preflight in+`fusion_route_net` covers two layers and is deliberately conservative. Native DRC is the final+word.++The `fusion-routing` skill is the board-engineering playbook for these verbs. It asks which+manufacturer the board is for, loads and reads back that house's rules through+`fusion_set_design_rules`, and keeps the stricter of the rules and the user's electrical+constraints.++### EAGLE commands++Fusion Electronics is EAGLE underneath, so the editor also takes EAGLE commands:  ```bash adom-bridge fusion_electron_run '{"command":"RATSNEST;"}'@@ -50,6 +108,13 @@ adom-bridge fusion_electron_run '{"command":"AUTO ;"}'      # autoroute  `fusion_execute_text_command` is **space-delimited**. Note the space in `AUTO ;`. +An explicit `SHOW <name>` is allowed through `fusion_electron_run`: it highlights the net or part+and returns. Only a bare `SHOW`, which waits for a click and would park the add-in, stays blocked.++```bash+adom-bridge fusion_electron_run '{"command":"SHOW SDA;"}'+```+ ## Manufacturing output  | verb | produces |@@ -84,7 +149,14 @@ page exists so someone else can build the design, and a 3D model alone does not  - Wrong workspace is the usual cause of a refused verb. Check `isElectronics` in   `fusion_get_app_state`.-- A blocking dialog (recovery prompt, update nag) will swallow commands silently. Clear it with-  `fusion_dismiss_blocking_dialogs`.+- A blocking dialog (recovery prompt, update nag) no longer swallows commands silently. The bridge+  reads it, expires it safely and runs the verb. A dialog that survives comes back as+  `blocked_by_dialog` with its text and button names. Answer it by name with+  `fusion_dismiss_blocking_dialogs`. The bridge never restarts Fusion over a dialog.+- The `expectDocument` guard: every add-in verb accepts `expectDocument`, and refuses with+  `wrong_document` when the named document is not the active one. For example+  `fusion_run_modeling_script {expectDocument:"X"}` will not run against whatever happens to be in+  front. The guard fails open when the active document cannot be read. The routing verbs require+  it. - Gerber export runs a CAM job and is genuinely slow on a dense board. A relay timeout at ~60s does   not mean it failed; the work continues on the bridge, so poll.
docs/electronics-libraries.md+46
@@ -64,6 +64,33 @@ adom-bridge fusion_capture_library_views '{...}'  # render views for a wiki page `fusion_build_library_3d` is the batch path: hand it a list of parts with their STEP models and it binds 3D to each package in one pass, returning per-part success plus screenshots. +### Explicit `placement`: the manufacturer's transform, no heuristics++`fusion_make_3d_package`, and each `parts[]` entry of `fusion_build_library_3d`, takes+`placement`:++```bash+adom-bridge fusion_make_3d_package '{+  "lbrPath":"C:/Users/me/adom-lib/TMF8829.lbr",+  "modelPath":"C:/Users/me/adom-lib/TMF8829.step",+  "placement":{"translation":[0,0,0.05],"rotation":[0,0,90],"scale":1}+}'+```++- `translation` is `[x, y, z]` in mm. `rotation` is `[rx, ry, rz]` in degrees, applied about X,+  then Y, then Z (extrinsic, about the origin), then the translation. `scale` must be 1.+- It bypasses every heuristic: no bounding-box reorientation, no centering, no pin-1 rotation.+- The reply returns `placement.appliedMatrix` (a row-major 4x4 read back from the occurrence, with+  the translation column in mm), `placement.verification` (`verified` or `mismatch`),+  `placement.heuristics`, and `source {path, sha256, bytes, preserved}` for the STEP Fusion read.+- Non-finite numbers, wrong shapes, units other than mm and degrees, and any scale other than 1 are+  refused before Fusion is touched (`invalid_placement`, `unsupported_scale`).++`orient:false` now means what it says. The model is imported exactly as authored, and the pin-1+mark routine no longer rotates it (it used to survive `orient:false`). The pin-1 part of the reply+says `skipped_orient_false` or `skipped_explicit_placement`. A default mint still reports+`rotated` or `aligned`, plus a warning that heuristics were used.+ ### The auto-orient rule  Auto-orient used to rotate the **smallest** bounding-box dimension to Z. That is correct for a flat@@ -73,6 +100,25 @@ Tall parts now stay vertical: if `dz` is already the largest dimension, the part only flat SMD parts get tipped. If you are adding a part type with unusual proportions, check the 3D orientation before committing it to a library. +## Whole libraries: prove, restarts and the pad lint++`fusion_create_library` and `fusion_install_library_bundle` run the bind and prove chain in the+background, and `fusion_prove_result` is the poll surface. Three behaviors are worth knowing.+[Multi-part library import](MULTIPART_LIBRARY_IMPORT.md) has the full account.++- **Prove is decided by the caller.** An AI thread binds and places only: the run ends with+  `verdict: bound_not_proven`, `partsBound` and the library path, and `prove.skipped` says why. Web+  control proves (four evidence frames per part). `prove:true` or `prove:false` always wins. The+  reply says what prove costs before it starts. For one part's frames afterwards, use+  `fusion_capture_library_views`.+- **Chains never restart Fusion on their own.** `allowRestart:true` with a `restartReason` is the+  only way, and every restart a chain performs is listed in `restarts[] {at, why, kind}` on+  `fusion_prove_result`. A long STEP bind that starves the add-in answers `fusion_addin_busy`, not+  a restart.+- **The `heatsink_vias` lint.** `fusion_library_parts` no longer calls same-net pads a short. Pads+  that share a base name (`B4` and `B4@2`, heatsink vias on the exposed pad's own net) are reported+  as an info line, `heatsink_vias`. `pad_overlap` stays for pads with genuinely different names.+ ## Verifying a part before you commit it  A part that is subtly wrong is worse than a missing one, because it silently produces bad boards.
docs/fusion-preferences.md+1−1
@@ -102,7 +102,7 @@ The pattern generalizes: 5. **Verify the effect**, do not trust the click. For MCP that means probing the port; for a    rendering setting it means observing the change. -If you automate a section often, add its fractional coordinates to `_PREF_SECTIONS` in `server.py`+If you automate a section often, add its fractional coordinates to `PREF_SECTIONS` in `rust/bridge/src/maintenance.rs` so `fusion_prefs_open` can jump straight there.  ## What is NOT in Preferences: the 3D canvas background
docs/schematics.md+100−9
@@ -3,6 +3,35 @@ Opening, viewing and authoring Fusion Electronics schematics, including bringing legacy EAGLE designs in so their parts instantiate. +## Start here: `fusion_schematic`++One verb, three actions, and it is the one to reach for first. A schematic is a view of an+electronics design, not a file of its own, so the verb takes whatever names the design and lands+you in the schematic editor.++```bash+adom-bridge fusion_schematic '{"action":"open","query":"BQ25792"}'                  # cloud design, by search+adom-bridge fusion_schematic '{"action":"open","fileName":"BME690","projectName":"Molecules"}'+adom-bridge fusion_schematic '{"action":"open","schPath":"C:/Users/me/adom-lib/MyBoard.sch"}'+adom-bridge fusion_schematic '{"action":"create","name":"scratch"}'                 # a new blank schematic+adom-bridge fusion_schematic '{"action":"show"}'                                    # the design already open+```++- `open` accepts a cloud `fileName` (with `projectName`), a `urn` or a `query`. It accepts a local+  EAGLE `schPath`: when a sibling `.brd` exists it is imported with the schematic so the parts+  instantiate, and `withBoard:false` opens the `.sch` alone, dialog-free. It accepts `filePath`+  for a Fusion-native `.fsch`.+- `create` writes a blank EAGLE schematic on the box and opens it as a new local electronics+  design with the schematic editor active. No dialog is drawn anywhere. The reply carries+  `documentName` and `schPath`.+- `show` switches the open design to its schematic view.+- Every reply says which verb did the work in `via`.++`open` lands on the electronics design. When a name search opens the design's 3D PCB or a+mechanical file instead (a `DesignProductType` with no schematic), the verb retries the exact+cloud name once. If that also misses, it answers `not_an_electronics_design` with the candidate+documents, instead of Fusion's bare "Please open or create an electronics design".+ ## Open the PROJECT, never the schematic  This is the mistake that costs the most time, so it leads:@@ -16,14 +45,18 @@ PROJECT        EcadDesignProductType   <- open THIS  Opening the schematic child directly gives you an isolated view with no board relationship, and you cannot pull the board back out of it. Same-named copies in *other* projects are usually 3D-model-derivatives (`DesignProductType`), not the project. The open verbs reprimand you through their-`_hint` when the file you opened is not an `EcadDesignProductType`.+derivatives (`DesignProductType`), not the project. The open verbs warn you through their+`_hint` when the file you opened looks like the 3D leaf of an electronics chain, or when you passed+`intent:"electronics"`. Opening a mechanical design on purpose is fine: `intent:"mechanical"`+confirms it.  ```bash adom-bridge fusion_aps_open '{"query":"BQ25792"}'    # resolves + opens the project adom-bridge fusion_show_schematic '{}'               # switch to the schematic editor ``` +`fusion_schematic {action:"open", query:"BQ25792"}` does both steps in one call.+ Below is a real Adom board's schematic, the BME690 environmental-sensor Molecule, open in the Fusion Electronics schematic editor after exactly those two calls. The parts are live and net-connected: the BME690 sensor, its decoupling caps, the I2C pull-ups, and the JP1 breakout header, with the layer set@@ -41,10 +74,41 @@ the other two views. Use `fusion_get_app_state` to confirm where you landed: it reports `activeDocument`, `activeWorkspace` and `isElectronics`. +## A library's parts on a new schematic and board: `fusion_make_showcase_project`++"Make a schematic and board to show off these three sensors" is one call:++```bash+adom-bridge fusion_make_showcase_project '{+  "libraryPath":"C:/Users/me/adom-lib/Adom_ToF.lbr",+  "devicesets":["TMF8829","VL53L8"],+  "name":"tof-showcase"+}'+adom-bridge fusion_eagle_import_status '{"runId":"..."}'+```++Every deviceset of the library (or the ones in `devicesets`) is placed in a row on a fresh sheet+and a fresh board, with no nets, and the library's bound 3D urns are carried across so the 3D PCB+shows the real bodies. The design is generated as EAGLE source on the box and opened dialog-free.+The verb is async: it returns a `runId` at once and you poll `fusion_eagle_import_status`. The two+documents are named `<name>-sch` and `<name>-brd`, the board opens last so it is active for the+2D and 3D views, and each view is fitted. Symbols are pitched by their own measured width, so wide+parts do not overlap. `fusion_make_test_project` is the one-part circuit variant.++### The verbatim-library finding++Fusion refuses a schematic whose library block was re-assembled piecewise from a Fusion-exported+`.lbr`. It says the file "has errors and cannot be opened" and instantiates nothing. The same+library block embedded verbatim, whole, opens with every part. So `fusion_make_showcase_project`+embeds the library verbatim in both the generated schematic and the board.+`fusion_make_test_project` still rebuilds the block from pieces and fails the same way on+Fusion-exported libraries. That is a known follow-up.+ ## Importing a legacy EAGLE schematic -`fusion_new_electronics_from_eagle` is the only reliable way to author a Fusion board from EAGLE-source, and the reason is worth understanding:+`fusion_new_electronics_from_eagle` is the one dialog-driven verb in the bridge, because Autodesk+gives no API for a paired `.sch` plus `.brd` import. It is the only reliable way to author a Fusion+board from EAGLE source, and the reason is worth understanding:  - `Document.newDesignFromLocal` opens the schematic editor but does **not instantiate the parts**.   You get an empty-looking design.@@ -61,10 +125,31 @@ adom-bridge fusion_new_electronics_from_eagle '{ ```  Stage both files onto Windows first with `send_files`. `brdPath` defaults to the sibling `.brd`.+Most callers should use `fusion_schematic {action:"open", schPath}`, which picks this route only+when a `.brd` exists next to the `.sch`. -**Paths with spaces work.** They used to fail silently; the open verbs now quote the path, fall back-to the Windows 8.3 short name, and verify `activeDocument` changed rather than reporting a-no-op as success.+**It is async by contract.** The call returns `{started:true, runId, statusVerb}` at once, and+`fusion_get_app_state` keeps answering while the import runs. Poll the run record:++```bash+adom-bridge fusion_eagle_import_status '{"runId":"..."}'+```++The record carries `stage` (started, done or failed), `documentName`, `partsExpected` (elements in+the `.brd`) and `partsPlaced` (read back from the board). Success needs parts on the board. A run+that placed nothing answers `nothing_instantiated` with the likely causes.++**It preflights, and it never leaves a dialog behind.** The verb refuses with+`eagle_files_missing` before it touches any dialog when either file is not on the Fusion machine.+It cancels a stale Open dialog left by an earlier attempt. When the Open dialog does not accept+the file, the bridge cancels the dialog itself and answers `open_dialog_stuck`. The reply says the+dialogs were driven.++**Paths with spaces work, and are never shortened.** Fusion's text command line is space-delimited+and rejects a quoted path, so a file under a path with a space is staged: it is copied with its+same-stem siblings (`.sch` and `.brd`) into a space-free folder, and Fusion is handed the copy. The+document keeps its real name. The bridge does not use Windows 8.3 short names, which made Fusion+title documents things like "0603WA~1". Paths without a space are untouched.  ## Driving the schematic editor @@ -77,6 +162,9 @@ adom-bridge fusion_execute_text_command '{"command":"Commands.Start ..."}'  `fusion_execute_text_command` is **space-delimited**, which trips people up. +An explicit `SHOW <name>` is allowed through `fusion_electron_run`: it highlights and returns.+Only a bare `SHOW`, which waits for a click, stays blocked.+ For clean captures there are view controls that do not disturb the user: `fusion_electron_zoom`, `fusion_electron_pan`, `fusion_electron_select`. @@ -89,7 +177,10 @@ switch to it with `fusion_show_schematic` first, then capture.  - `isElectronics: false` in `fusion_get_app_state` means you are in the mechanical Design workspace.   Electronics verbs will refuse; open the project first.-- If an open appears to do nothing, check `fusion_dismiss_blocking_dialogs`. A recovery prompt or an-  update nag can sit in front of the document and swallow the action.+- If an open appears to do nothing, read the reply. The bridge reads any dialog in front of Fusion,+  expires it safely and runs the verb. A dialog that survives comes back as `blocked_by_dialog`+  with its text and button names, so you answer it by name (`fusion_dismiss_blocking_dialogs`).+- Every add-in verb accepts `expectDocument`. When the named document is not the active one the+  verb refuses with `wrong_document` instead of running against whatever is in front. - The schematic and board are **separate documents in one project**. Saving one does not save the   other.
docs/sign-in-and-updates.md+46
@@ -19,6 +19,41 @@ where Fusion had been removed.  **Never ask the user to restart Fusion or enable the add-in.** That is the bridge's job. +### `restartPolicy`: auto or ask++Fusion loads add-ins only at launch, so an add-in that was installed or updated while Fusion was+open needs a restart. Who decides is a pref: `restartPolicy` in `~/.adom/fusion-bridge/prefs.json`,+or the env var `ADOM_FUSION_RESTART_POLICY`. The default is `auto`.++- **`auto`**: the AI is the only thing driving Fusion on that box, so the open documents are the+  AI's documents. The bridge restarts Fusion in the background and answers `addin_restarting`.+  No confirmation toast is raised. `fusion_restart_for_addin` never refuses under auto: documents+  Fusion can save are saved, never-saved scratch is discarded (named in `discarded` and in the+  action ledger), and when the add-in cannot save at all the restart is a kill plus a start. The+  restart is recorded in the action ledger (`fusion_action_ledger`) with its summary.+- **`ask`**: for a machine a person shares with the AI. A verb that needs the restart answers+  `addin_restart_required` with `userMessage`, `openDocuments` and `unsavedDocuments`, the+  confirmation toast is raised, and the bridge waits for the person.++**The bridge never restarts Fusion over a dialog.** A dialog in front of Fusion is read through+UIA (title, text, button names) and expired safely: the known ones through their rule, the unknown+ones with WM_CLOSE, which is Cancel or the X and never saves or discards. After expiring a dialog+the gate re-probes the add-in for up to 20 s and then runs the verb or answers+`dialog_expired_retry`. A dialog that survives comes back as `blocked_by_dialog` with+`dialogs[] {hwnd, title, category, text, buttons, resolution}`, so the AI answers it by button+name. A long operation in flight (a STEP bind can starve the add-in for 20 to 60 s) answers+`fusion_addin_busy`, not a restart. Library chains restart Fusion only when the caller passes+`allowRestart:true` with a `restartReason`.++## `needs_signin`: the fast answer while Fusion is signing in++While Fusion's main window says "Signing in" or "Welcome to Fusion", every add-in verb+short-circuits with `errorCode: needs_signin` in well under a second. The reply carries+`needsSignin`, `mainTitle` and a hint that points at the sign-in skill. It is the same signal+`fusion_readiness` gives, and it is never a reason to restart Fusion. Before 1.10.1 the same state+took over a minute to come back, as `addin_restart_required`, which sent callers down the wrong+path. When you see `needs_signin`, finish the sign-in below and re-run the verb.+ ## Signing in through the right browser profile  Fusion fires its OAuth at the **OS-default browser**. If your Autodesk seat lives in a different@@ -102,6 +137,17 @@ adom-bridge fusion_set_auto_update '{"enabled":false}'   # opt out, STICKY On by default for everyone. Opting out persists to `~/.adom/fusion-bridge/prefs.json` and is honoured permanently; once off, the bridge never touches your update prompts again. +### Bridge updates: insiders and public manifests++The bridge itself updates through Adom Bridge, which polls the manifest on launch and every 4+hours. There are two manifests. Every release is an insiders release first: it lands on the+insiders manifest (`insidersManifestUrl` in `bridge.json`, on the private adom/insiders page). The+public manifest (`adom-bridge-fusion-manifest.json` on the bridge's wiki page, the+`updateManifestUrl`) moves only when a maintainer promotes a version that passed the+pre-production gate. So a box on the insiders tier can run a newer version than a public box, and+`fusion_readiness` tells you which version a box actually runs (`bridgeVersion`). A feature in the+changelog that a public box does not have yet is usually this, not a failed update.+ Honest limit: the nag only enters the UIA tree while its "View Job Status" panel is **open**, and there is no API to enumerate a pending update while it is closed. So the bridge sweeps on a schedule and catches the nag whenever Fusion shows it, rather than pre-empting it.
install.sh+1−1
@@ -21,7 +21,7 @@ echo "Installed fusion-bridge skills: $(ls "$PKG_DIR/skills" 2>/dev/null | tr '\ # binary (the three-artifact pattern: bridge zip -> ab, THIS pkg -> container skills + # this download, page media -> wiki). Per the wiki's PKG_VS_RELEASE policy, binaries are # platform-tagged RELEASE assets, never tarball content - the installer fetches them.-DASH_VERSION="1.10.24"+DASH_VERSION="1.10.25" DASH_URL="https://wiki.adom.inc/download/adom/fusion-bridge/${DASH_VERSION}/fusion-dashboard" DEST="" if [ -w /usr/local/bin ]; then DEST=/usr/local/bin
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.10.24",+  "version": "1.10.25",   "type": "app",   "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.",   "dependencies": {
page.json+11−27
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.10.24",+  "version": "1.10.25",   "type": "app",   "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.",   "dependencies": {@@ -11,6 +11,10 @@     "uninstall": "./uninstall.sh"   },   "tags": [+    "ab",+    "bridge",+    "fusion",+    "desktop-app",     "fusion360",     "autodesk",     "cad",@@ -75,7 +79,10 @@     "build a basic parts library",     "add a 3d model to a component",     "make a fusion 360 library with 3d",-    "set up the fusion bridge"+    "set up the fusion bridge",+    "fusion dashboard",+    "fusion status page",+    "open the fusion launch page"   ],   "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.",@@ -104,28 +111,5 @@       "label": "Part volume",       "prompt": "How much material does each printed part in this assembly use?"     }-  ],-  "agent_permissions": {-    "allow_bash": [],-    "allow": [-      "adom-desktop-fusion-bridge read-only verbs (via the adom-bridge CLI): fusion_readiness, fusion_get_app_state, fusion_addin_status, fusion_check_dialogs - state queries; they change nothing.",-      "adom-desktop-fusion-bridge read-only verbs: fusion_alerts (reads Fusion's own log for toasts), fusion_document_info, fusion_window_info, fusion_board_info, fusion_library_parts - inspect only.",-      "adom-desktop-fusion-bridge read-only verbs: fusion_prove_result (a persisted run report), fusion_plan_library_import (report-only preflight), fusion_list_cloud_projects, fusion_describe.",-      "adom-desktop-fusion-bridge evidence captures fusion_screenshot_fusion and fusion_capture_library_views read pixels and write local image files only."-    ],-    "soft_deny": [-      "adom-desktop-fusion-bridge lifecycle verbs fusion_stop, fusion_kill, fusion_restart_for_addin close or kill the Fusion application - keep the human prompt.",-      "adom-desktop-fusion-bridge fusion_install_fusion silently installs Autodesk Fusion on the user's machine.",-      "adom-desktop-fusion-bridge fusion_cleanup_cloud_files deletes files from the user's Autodesk cloud hub.",-      "adom-desktop-fusion-bridge managed-library verbs (fusion_managed_library_push/pull/create/link/unlink) publish to or overwrite the user's library.io libraries."-    ],-    "environment": [-      "adom-desktop-fusion-bridge is the Fusion 360 bridge for Adom Bridge; its fusion_* verbs are invoked through the adom-bridge CLI and drive Autodesk Fusion on the user's own desktop.",-      "adom-desktop-fusion-bridge desktop actions also pass Adom Bridge's own human-approval gate (caller identity, per-call reason, Activity Log) - a container approval is the first of two gates."-    ]-  },-  "author": {-    "name": "John Lauer",-    "email": "[email protected]"-  }-}\ No newline at end of file+  ]+}
rust/Cargo.toml+1−1
@@ -4,7 +4,7 @@ members = ["core", "lbr", "os-win", "os-mac", "bridge"]  # One version for the whole bridge, in lockstep with BRIDGE_VERSION (tools/release.sh bumps both). [workspace.package]-version = "1.10.24"+version = "1.10.25" edition = "2021" license = "MIT" 
rust/bridge/src/main.rs+1−1
@@ -408,7 +408,7 @@ fn main() {                         r["shell"] = json!("rust"); r },                     "fusion_start" | "start" | "launch" if addin_owner_is_exe(&**h) => { let (sc, ca) = (sidecar.clone(), caller.clone()); let side = move |verb: &str, a: Value, t: u64| proxy_with_timeout(&sc, &json!({"command": verb, "args": a}).to_string(), &ca, t); let sd = h.bridge_state_dir(); let dlg = |r: Value| r; let ctx = addin::Ctx { host: &**h, fusion_exe: h.fusion_executable(), short_path: |p| host().short_path(p), state_dir: sd.clone(), caller: caller.clone(), pids: h.fusion_processes().iter().map(|p| p.pid).collect(), sidecar: &side, dialogs: &dlg }; let t0 = Instant::now(); let ind = timings::Indicator::start(&sd, "start", &caller); let mut r = launch::start(&ctx, &parsed["args"]); ind.stop(r["success"].as_bool() == Some(false)); timings::log_run(&sd, "start", t0.elapsed().as_secs_f64(), r["success"].as_bool() != Some(false)); r["shell"] = json!("rust"); r },                     "fusion_open_lbr" | "open_lbr" if addin_owner_is_exe(&**h) => { let (sc, ca, ca2) = (sidecar.clone(), caller.clone(), caller.clone()); let side = move |verb: &str, a: Value, t: u64| proxy_with_timeout(&sc, &json!({"command": verb, "args": a}).to_string(), &ca, t); let sd = h.bridge_state_dir(); let pids: Vec<u32> = h.fusion_processes().iter().map(|p| p.pid).collect(); let dlg = |r: Value| small_verbs::merge_dialog_array(r, &ca2, &pids); let ctx = addin::Ctx { host: &**h, fusion_exe: h.fusion_executable(), short_path: |p| host().short_path(p), state_dir: sd.clone(), caller: caller.clone(), pids: pids.clone(), sidecar: &side, dialogs: &dlg }; let t0 = Instant::now(); let ind = timings::Indicator::start(&sd, "open_lbr", &caller); let mut r = orchestrations::open_lbr(&ctx, &parsed["args"], &|v, a| addin::pass_through(v, &a, &ctx)); ind.stop(r["success"].as_bool() == Some(false)); timings::log_run(&sd, "open_lbr", t0.elapsed().as_secs_f64(), r["success"].as_bool() != Some(false)); r["shell"] = json!("rust"); r },-                    v @ ("fusion_show_symbol" | "show_symbol" | "fusion_show_footprint" | "show_footprint" | "fusion_show_library" | "show_library" | "fusion_show_project" | "show_project" | "fusion_show_schematic" | "show_schematic" | "fusion_show_2d_board" | "show_2d_board" | "fusion_show_3d_board" | "show_3d_board" | "fusion_capture_library_views" | "capture_library_views" | "fusion_open_cloud_file" | "open_cloud_file" | "fusion_save_lbr" | "save_lbr" | "fusion_cleanup_cloud_files" | "cleanup_cloud_files" | "fusion_inspect_bodies" | "inspect_bodies" | "fusion_board_stackup" | "board_stackup" | "fusion_export_optimized_glb" | "export_optimized_glb" | "fusion_fetch_optimized_glb" | "fetch_optimized_glb" | "fusion_set_design_rules" | "set_design_rules" | "fusion_make_test_project" | "make_test_project" | "fusion_make_showcase_project" | "make_showcase_project" | "fusion_open_design" | "open_design" | "fusion_new_electronics_from_eagle" | "new_electronics_from_eagle" | "fusion_aps_open" | "aps_open" | "fusion_schematic" | "schematic" | "fusion_eagle_import_status" | "eagle_import_status") if addin_owner_is_exe(&**h) => { let (sc, ca, ca2) = (sidecar.clone(), caller.clone(), caller.clone()); let side = move |verb: &str, a: Value, t: u64| proxy_with_timeout(&sc, &json!({"command": verb, "args": a}).to_string(), &ca, t); let sd = h.bridge_state_dir(); let pids: Vec<u32> = h.fusion_processes().iter().map(|p| p.pid).collect(); let dlg = |r: Value| small_verbs::merge_dialog_array(r, &ca2, &pids); let ctx = addin::Ctx { host: &**h, fusion_exe: h.fusion_executable(), short_path: |p| host().short_path(p), state_dir: sd.clone(), caller: caller.clone(), pids: pids.clone(), sidecar: &side, dialogs: &dlg }; let cmd = v.strip_prefix("fusion_").unwrap_or(v); let t0 = Instant::now(); let ind = timings::Indicator::start(&sd, cmd, &caller); let pass = |verb: &str, a: Value| addin::pass_through(verb, &a, &ctx); let r0 = match cmd { "show_symbol" => orchestrations::show_symbol(&ctx, &parsed["args"], &pass), "show_footprint" => orchestrations::show_footprint(&ctx, &parsed["args"], &pass), "show_library" => orchestrations::show_library(&ctx, &parsed["args"], &pass), "show_project" => match orchestrations::show_project(&ctx, &parsed["args"], &pass) { Some(r) => r, None => side("show_project", parsed["args"].clone(), 240) }, "capture_library_views" => small_verbs::apply_failure_dialogs(orchestrations::capture_library_views(&ctx, &parsed["args"], &pass), &caller, &pids), "open_cloud_file" => orchestrations::open_cloud_file(&ctx, &parsed["args"], &pass), "save_lbr" => small_verbs::merge_dialog_array(orchestrations::save_lbr(&ctx, &parsed["args"], &pass), &caller, &pids), "cleanup_cloud_files" => orchestrations::cleanup_cloud_files(&parsed["args"], &pass), "inspect_bodies" => orchestrations::inspect_bodies(&parsed["args"], &pass), "board_stackup" => services::board_stackup(&parsed["args"], &pass), "export_optimized_glb" => if parsed["args"]["async"].as_bool() == Some(true) { services::start_glb_export_async(&ctx, &parsed["args"]) } else { services::export_optimized_glb(&parsed["args"], &pass) }, "fetch_optimized_glb" => if parsed["args"]["exportId"].is_string() { services::glb_export_async_status(&parsed["args"]) } else { services::fetch_optimized_glb(&parsed["args"]) }, "set_design_rules" => services::set_design_rules(&ctx, &parsed["args"], &pass), "make_test_project" => small_verbs::merge_dialog_array(testproject::make_test_project(&ctx, &parsed["args"], &pass), &caller, &pids), "make_showcase_project" => small_verbs::merge_dialog_array(testproject::make_showcase_project(&ctx, &parsed["args"], &pass), &caller, &pids), "open_design" => orchestrations::open_design(&ctx, &parsed["args"], &pass), "new_electronics_from_eagle" => orchestrations::new_electronics_from_eagle(&ctx, &parsed["args"], &pass), "schematic" => orchestrations::schematic(&ctx, &parsed["args"], &pass), "eagle_import_status" => orchestrations::eagle_import_status(&ctx, &parsed["args"]), "aps_open" => orchestrations::aps_open(&ctx, &parsed["args"], &pass), other => orchestrations::open_with_screenshot(&ctx, other, &parsed["args"], &pass) }; let mut r = small_verbs::merge_dialog_array(r0, &caller, &pids); ind.stop(r["success"].as_bool() == Some(false)); timings::log_run(&sd, cmd, t0.elapsed().as_secs_f64(), r["success"].as_bool() != Some(false)); r["shell"] = json!("rust"); r },+                    v @ ("fusion_show_symbol" | "show_symbol" | "fusion_show_footprint" | "show_footprint" | "fusion_show_library" | "show_library" | "fusion_show_project" | "show_project" | "fusion_show_schematic" | "show_schematic" | "fusion_show_2d_board" | "show_2d_board" | "fusion_show_3d_board" | "show_3d_board" | "fusion_capture_library_views" | "capture_library_views" | "fusion_open_cloud_file" | "open_cloud_file" | "fusion_save_lbr" | "save_lbr" | "fusion_cleanup_cloud_files" | "cleanup_cloud_files" | "fusion_inspect_bodies" | "inspect_bodies" | "fusion_board_stackup" | "board_stackup" | "fusion_export_optimized_glb" | "export_optimized_glb" | "fusion_fetch_optimized_glb" | "fetch_optimized_glb" | "fusion_set_design_rules" | "set_design_rules" | "fusion_make_test_project" | "make_test_project" | "fusion_make_showcase_project" | "make_showcase_project" | "fusion_open_design" | "open_design" | "fusion_new_electronics_from_eagle" | "new_electronics_from_eagle" | "fusion_aps_open" | "aps_open" | "fusion_schematic" | "schematic" | "fusion_eagle_import_status" | "eagle_import_status" | "fusion_show_attributes" | "show_attributes") if addin_owner_is_exe(&**h) => { let (sc, ca, ca2) = (sidecar.clone(), caller.clone(), caller.clone()); let side = move |verb: &str, a: Value, t: u64| proxy_with_timeout(&sc, &json!({"command": verb, "args": a}).to_string(), &ca, t); let sd = h.bridge_state_dir(); let pids: Vec<u32> = h.fusion_processes().iter().map(|p| p.pid).collect(); let dlg = |r: Value| small_verbs::merge_dialog_array(r, &ca2, &pids); let ctx = addin::Ctx { host: &**h, fusion_exe: h.fusion_executable(), short_path: |p| host().short_path(p), state_dir: sd.clone(), caller: caller.clone(), pids: pids.clone(), sidecar: &side, dialogs: &dlg }; let cmd = v.strip_prefix("fusion_").unwrap_or(v); let t0 = Instant::now(); let ind = timings::Indicator::start(&sd, cmd, &caller); let pass = |verb: &str, a: Value| addin::pass_through(verb, &a, &ctx); let r0 = match cmd { "show_symbol" => orchestrations::show_symbol(&ctx, &parsed["args"], &pass), "show_footprint" => orchestrations::show_footprint(&ctx, &parsed["args"], &pass), "show_library" => orchestrations::show_library(&ctx, &parsed["args"], &pass), "show_project" => match orchestrations::show_project(&ctx, &parsed["args"], &pass) { Some(r) => r, None => side("show_project", parsed["args"].clone(), 240) }, "capture_library_views" => small_verbs::apply_failure_dialogs(orchestrations::capture_library_views(&ctx, &parsed["args"], &pass), &caller, &pids), "open_cloud_file" => orchestrations::open_cloud_file(&ctx, &parsed["args"], &pass), "save_lbr" => small_verbs::merge_dialog_array(orchestrations::save_lbr(&ctx, &parsed["args"], &pass), &caller, &pids), "cleanup_cloud_files" => orchestrations::cleanup_cloud_files(&parsed["args"], &pass), "inspect_bodies" => orchestrations::inspect_bodies(&parsed["args"], &pass), "board_stackup" => services::board_stackup(&parsed["args"], &pass), "export_optimized_glb" => if parsed["args"]["async"].as_bool() == Some(true) { services::start_glb_export_async(&ctx, &parsed["args"]) } else { services::export_optimized_glb(&parsed["args"], &pass) }, "fetch_optimized_glb" => if parsed["args"]["exportId"].is_string() { services::glb_export_async_status(&parsed["args"]) } else { services::fetch_optimized_glb(&parsed["args"]) }, "set_design_rules" => services::set_design_rules(&ctx, &parsed["args"], &pass), "make_test_project" => small_verbs::merge_dialog_array(testproject::make_test_project(&ctx, &parsed["args"], &pass), &caller, &pids), "make_showcase_project" => small_verbs::merge_dialog_array(testproject::make_showcase_project(&ctx, &parsed["args"], &pass), &caller, &pids), "open_design" => orchestrations::open_design(&ctx, &parsed["args"], &pass), "new_electronics_from_eagle" => orchestrations::new_electronics_from_eagle(&ctx, &parsed["args"], &pass), "schematic" => orchestrations::schematic(&ctx, &parsed["args"], &pass), "eagle_import_status" => orchestrations::eagle_import_status(&ctx, &parsed["args"]), "show_attributes" => orchestrations::show_attributes(&ctx, &parsed["args"], &pass), "aps_open" => orchestrations::aps_open(&ctx, &parsed["args"], &pass), other => orchestrations::open_with_screenshot(&ctx, other, &parsed["args"], &pass) }; let mut r = small_verbs::merge_dialog_array(r0, &caller, &pids); ind.stop(r["success"].as_bool() == Some(false)); timings::log_run(&sd, cmd, t0.elapsed().as_secs_f64(), r["success"].as_bool() != Some(false)); r["shell"] = json!("rust"); r },                     "fusion_state" | "state" if addin_owner_is_exe(&**h) => { let (sc, ca) = (sidecar.clone(), caller.clone()); let side = move |verb: &str, a: Value, t: u64| proxy_with_timeout(&sc, &json!({"command": verb, "args": a}).to_string(), &ca, t); let dlg = |r: Value| r; let ctx = addin::Ctx { host: &**h, fusion_exe: h.fusion_executable(), short_path: |p| host().short_path(p), state_dir: h.bridge_state_dir(), caller: caller.clone(), pids: h.fusion_processes().iter().map(|p| p.pid).collect(), sidecar: &side, dialogs: &dlg }; let mut r = orchestrations::fusion_state(&ctx, &parsed["args"]); r["shell"] = json!("rust"); r },                     v @ ("fusion_make_3d_package" | "make_3d_package" | "fusion_build_library_3d" | "build_library_3d" | "fusion_bind_library_3d" | "bind_library_3d" | "fusion_prove_library" | "prove_library" | "fusion_attach_3d_package" | "attach_3d_package" | "fusion_show_3d_package" | "show_3d_package") if addin_owner_is_exe(&**h) => { let (sc, ca, ca2) = (sidecar.clone(), caller.clone(), caller.clone()); let side = move |verb: &str, a: Value, t: u64| proxy_with_timeout(&sc, &json!({"command": verb, "args": a}).to_string(), &ca, t); let sd = h.bridge_state_dir(); let pids: Vec<u32> = h.fusion_processes().iter().map(|p| p.pid).collect(); let dlg = |r: Value| small_verbs::merge_dialog_array(r, &ca2, &pids); let ctx = addin::Ctx { host: &**h, fusion_exe: h.fusion_executable(), short_path: |p| host().short_path(p), state_dir: sd.clone(), caller: caller.clone(), pids: pids.clone(), sidecar: &side, dialogs: &dlg }; let cmd = v.strip_prefix("fusion_").unwrap_or(v); let t0 = Instant::now(); *LONG_OP.lock().unwrap() = Some(cmd.to_string()); *LONG_OP_SINCE.lock().unwrap() = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs_f64()).unwrap_or(0.0); let pass = |verb: &str, a: Value| addin::pass_through(verb, &a, &ctx); let r0 = match cmd { "make_3d_package" => mint::make_3d_package(&ctx, &parsed["args"], &pass), "bind_library_3d" => mint::bind_library_3d(&ctx, &parsed["args"], &pass), "prove_library" => prove::prove_library(&ctx, &parsed["args"], &pass), "attach_3d_package" => mint::attach_3d_package(&ctx, &parsed["args"], &pass), "show_3d_package" => mint::show_3d_package(&ctx, &parsed["args"], &pass), _ => mint::build_library_3d(&ctx, &parsed["args"], &pass) }; *LONG_OP.lock().unwrap() = None; let mut r = small_verbs::apply_failure_dialogs(r0, &caller, &pids); timings::log_run(&sd, cmd, t0.elapsed().as_secs_f64(), r["success"].as_bool() != Some(false)); r["shell"] = json!("rust"); r },                     v @ ("fusion_apply_fusion_update" | "apply_fusion_update" | "fusion_install_fusion" | "install_fusion" | "fusion_uninstall" | "uninstall" | "fusion_prefs_open" | "prefs_open" | "fusion_prefs_close" | "prefs_close" | "fusion_generate_package" | "generate_package" | "fusion_mcp_status" | "mcp_status" | "fusion_mcp_tools" | "mcp_tools" | "fusion_mcp_call" | "mcp_call" | "fusion_mcp_resources" | "mcp_resources" | "fusion_mcp_enable" | "mcp_enable" | "fusion_library_manager" | "library_manager" | "fusion_managed_library_create" | "managed_library_create" | "fusion_managed_library_link" | "managed_library_link" | "fusion_managed_library_pull" | "managed_library_pull" | "fusion_managed_library_push" | "managed_library_push" | "fusion_managed_library_unlink" | "managed_library_unlink" | "fusion_signin" | "signin" | "fusion_signin_2fa" | "signin_2fa" | "fusion_web_signin" | "web_signin" | "fusion_aps_signin" | "aps_signin" | "fusion_demo" | "demo" | "fusion_demo_progress" | "demo_progress" | "fusion_demo_control" | "demo_control" | "fusion_electronics_query" | "electronics_query" | "fusion_view" | "view" | "fusion_interactive" | "interactive" | "fusion_narrate" | "narrate") if addin_owner_is_exe(&**h) => { let (sc, ca) = (sidecar.clone(), caller.clone()); let side = move |verb: &str, a: Value, t: u64| proxy_with_timeout(&sc, &json!({"command": verb, "args": a}).to_string(), &ca, t); let dlg = |r: Value| r; let ctx = addin::Ctx { host: &**h, fusion_exe: h.fusion_executable(), short_path: |p| host().short_path(p), state_dir: h.bridge_state_dir(), caller: caller.clone(), pids: h.fusion_processes().iter().map(|p| p.pid).collect(), sidecar: &side, dialogs: &dlg }; let pass = |verb: &str, a: Value| addin::pass_through(verb, &a, &ctx); let cmd = v.strip_prefix("fusion_").unwrap_or(v); let mut r = match cmd { "apply_fusion_update" => maintenance::apply_fusion_update(&ctx, &pass), "install_fusion" => maintenance::install_fusion(&ctx), "prefs_open" => maintenance::prefs_open(&ctx, &parsed["args"], &pass), "prefs_close" => maintenance::prefs_close(&ctx, &parsed["args"]), "generate_package" => epg::generate_package(&parsed["args"], VERSION, &pass),
rust/bridge/src/orchestrations.rs+33
@@ -954,6 +954,39 @@ pub fn schematic(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value) -> Value) -     } } +/// `fusion_show_attributes`: a component's metadata, as data AND on screen. The attributes+/// (MPN, manufacturer, distributor part numbers, datasheet, wiki link) are read from the+/// library file; the DEVICE editor is opened for the deviceset (EDIT <name>.dev, no dialog: the+/// symbol, the package table with the Mapped check, the footprint and 3D previews in one+/// frame) and captured. Fusion's own "Variant Attributes" table lives behind a "..." button in+/// an opaque Qt panel with no UIA children, so it is reachable only by positioned clicks; the+/// reply carries that recipe instead of pretending (page issue #93).+pub fn show_attributes(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value) -> Value) -> Value {+    let ds = args["deviceset"].as_str().unwrap_or("").trim().to_string();+    let lbr = args["libraryPath"].as_str().unwrap_or("").replace('\\', "/");+    if ds.is_empty() { return json!({"success": false, "errorCode": "deviceset_required", "error": "deviceset is required (the component's name in the library).", "_hint": "fusion_library_parts {libraryPath} lists the devicesets of a library."}); }+    let mut attributes = Value::Null;+    if !lbr.is_empty() {+        let rep = part_metadata_report(&lbr);+        attributes = rep["components"].as_array().and_then(|a| a.iter().find(|c| c["deviceset"].as_str().map(|n| n.eq_ignore_ascii_case(&ds)).unwrap_or(false)).cloned()).unwrap_or(Value::Null);+        if attributes.is_null() { return json!({"success": false, "errorCode": "deviceset_not_in_library", "deviceset": ds, "available": rep["components"].as_array().map(|a| a.iter().map(|c| c["deviceset"].clone()).collect::<Vec<_>>()), "error": format!("{ds} is not in {lbr}")}); }+        let op = open_lbr(ctx, &json!({"filePath": lbr}), pass);+        if op["success"].as_bool() != Some(true) { return json!({"success": false, "errorCode": "open_lbr_failed", "error": op["error"], "attributes": attributes}); }+        std::thread::sleep(Duration::from_secs(1));+    }+    let ed = pass("electron_run", json!({"command": format!("EDIT {ds}.dev"), "confirm": "no"}));+    if ed["success"].as_bool() != Some(true) { return json!({"success": false, "errorCode": "device_editor_failed", "attributes": attributes, "error": format!("EDIT {ds}.dev failed: {}", ed["error"].as_str().unwrap_or("unknown")), "_hint": "A library must be the active document (pass libraryPath, or fusion_open_lbr first)."}); }+    std::thread::sleep(Duration::from_millis(1200));+    let _ = pass("execute_text_command", json!({"command": "Electron.run WINDOW FIT;"}));+    let shot = if args["capture"].as_bool() != Some(false) { capture_labeled(ctx, &format!("attributes_{ds}")) } else { None };+    json!({"success": true, "deviceset": ds, "attributes": attributes, "deviceEditor": true, "screenshot": shot,+        "onScreen": "the DEVICE editor for the deviceset: symbol, the Packages table with the Mapped check, footprint and 3D package previews (Fusion's ganged view)",+        "variantAttributesDialog": {"how": "In the Packages table the variant row has a ... button at its right edge; click it, then Attribute Sets. Both live in an opaque Qt panel with no UIA children, so use positioned clicks: desktop_screenshot_window {hwnd}, then desktop_click {hwnd, space:image, shotId, x, y}. The modal is titled Variant Attributes and appears in fusion_state.dialogs[] with its own hwnd; close it with desktop_ui_click {hwnd, name:Cancel}.",+            "recording": "desktop_record_window_start on Fusion's main window does NOT include that owned dialog: record the dialog's own hwnd (from fusion_state.dialogs[]) for that shot.",+            "columns": "the DEFAULT column truncates URLs and part numbers at its default width; drag its edge wider before capturing, or rely on attributes in this reply for the full values"},+        "_hint": "attributes carries the component's metadata read from the library file (the full values); screenshot shows it on screen in the device editor. For Fusion's own Attribute Sets table follow variantAttributesDialog.how."})+}+ /// Where an EAGLE import run keeps its state (`fusion_eagle_import_status {runId}`). fn eagle_run_path(state_dir: &std::path::Path, run_id: &str) -> std::path::PathBuf { state_dir.join("eagle-imports").join(format!("{run_id}.json")) } pub fn now_pub() -> f64 { now_f() }
rust/bridge/src/testproject.rs+15−2
@@ -143,7 +143,13 @@ pub fn make_test_project(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value) ->     let solo = args["withResistor"].as_bool() != Some(true);     let rex = if solo { None } else { resistor_extract() };     let r_urn = if rex.is_some() { std::fs::read_to_string(ctx.state_dir.join("r3d-urn.json")).ok().and_then(|t| serde_json::from_str::<Value>(&t).ok()).and_then(|v| v["0603WAF3300T5E"].as_str().map(str::to_string)).unwrap_or_default() } else { String::new() };-    let (sch, brd) = mtp_generate(&lib_name, &ex, &deviceset, &urn, rex.as_ref(), &r_urn, solo);+    let (sch, mut brd) = mtp_generate(&lib_name, &ex, &deviceset, &urn, rex.as_ref(), &r_urn, solo);+    if args["silkLabels"].as_bool() != Some(false) {+        let geo = board_geometry(&ex.package, 3.0, !solo);+        let (x0, y0, x1, _) = pkg_extent_mm(&ex.package);+        let label = format!(r#"<text x="{}" y="{}" size="1" layer="21" ratio="12" align="center">{}</text>"#, py_float(round_to(geo.px + (x0 + x1) / 2.0, 3)), py_float(round_to((geo.py + y0 - 1.6).max(0.8), 3)), deviceset);+        brd = brd.replacen("</plain>", &format!("{label}</plain>"), 1);+    }     let d = std::env::temp_dir().join("adom-fusion-wrap"); let _ = std::fs::create_dir_all(&d);     let base: String = format!("ADOM-TEST-{}", regex::Regex::new(r"[^A-Za-z0-9_-]").unwrap().replace_all(&deviceset, "_").chars().take(40).collect::<String>());     let sch_path = d.join(format!("{base}.sch")).to_string_lossy().replace('\\', "/");@@ -222,6 +228,7 @@ pub fn make_showcase_project(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value)     let mut parts = String::new(); let mut instances = String::new(); let mut elements = String::new();     let (mut bx, margin) = (4.0f64, 4.0f64); let mut bh = 10.0f64;     let mut sx = 20.0f64; // sheet x cursor: each symbol is pitched by its own measured width+    let mut labels = String::new();     let mut refs: Vec<Value> = Vec::new();     for (i, e) in extracts.iter().enumerate() {         let pfx = { let p = e.prefix.trim(); if p.is_empty() { "U" } else { p } };@@ -236,13 +243,19 @@ pub fn make_showcase_project(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value)         let (pw, ph) = (x1 - x0, y1 - y0);         let ex_ = round_to(bx - x0, 3);         elements.push_str(&format!(r#"<element name="{rname}" library="{lib_name}" package="{}" value="{}" x="{}" y="{}"{}/>"#, e.pkg_name, wanted[i], py_float(ex_), py_float(round_to(margin + ph.max(6.0) / 2.0 - (y0 + y1) / 2.0 + 6.0, 3)), { let pk = regex::escape(&e.pkg_name); let u = regex::Regex::new(r#"(?s)<package3d [^>]*wip_urn="([^"]+)"[^>]*>(.*?)</package3d>"#).unwrap().captures_iter(&p3ds).find(|c| regex::Regex::new(&format!(r#"<packageinstance name="{pk}"/>"#)).unwrap().is_match(&c[2])).map(|c| c[1].to_string()).unwrap_or_default(); if u.is_empty() { String::new() } else { format!(r#" package3d_urn="{u}""#) } }));+        // the part name on the top silkscreen under its footprint (John's rule for every board+        // the tools make, page issue #91); silkLabels:false turns it off+        if args["silkLabels"].as_bool() != Some(false) {+            let ey = round_to(margin + ph.max(6.0) / 2.0 - (y0 + y1) / 2.0 + 6.0, 3);+            labels.push_str(&format!(r#"<text x="{}" y="{}" size="1" layer="21" ratio="12" align="center">{}</text>"#, py_float(round_to(ex_ + (x0 + x1) / 2.0, 3)), py_float(round_to(ey + y0 - 1.6, 3)), wanted[i]));+        }         refs.push(json!({"ref": rname, "deviceset": wanted[i], "package": e.pkg_name, "symbol": e.sym_name}));         bx = round_to(bx + pw.max(6.0) + 6.0, 2);         bh = bh.max(ph + 2.0 * margin + 12.0);     }     let (bw, bh) = (round_to(bx + margin, 2), round_to(bh, 2));     let sch = format!("{HEAD}{}<schematic xreflabel=\"%F%N/%S.%C%R\" xrefpart=\"/%S.%C%R\"><libraries>{lib_block}</libraries><attributes/><variantdefs/><classes><class number=\"0\" name=\"default\" width=\"0\" drill=\"0\"/></classes><parts>{parts}</parts><sheets><sheet><plain/><instances>{instances}</instances><busses/><nets/></sheet></sheets></schematic></drawing></eagle>\n", extracts[0].layers);-    let brd = format!("{HEAD}{}<board><plain>{}</plain><libraries>{lib_block_brd}</libraries><attributes/><variantdefs/><classes><class number=\"0\" name=\"default\" width=\"0\" drill=\"0\"/></classes><designrules name=\"default\"></designrules><autorouter></autorouter><elements>{elements}</elements><signals/></board></drawing></eagle>\n", extracts[0].layers, outline(bw, bh));+    let brd = format!("{HEAD}{}<board><plain>{}{labels}</plain><libraries>{lib_block_brd}</libraries><attributes/><variantdefs/><classes><class number=\"0\" name=\"default\" width=\"0\" drill=\"0\"/></classes><designrules name=\"default\"></designrules><autorouter></autorouter><elements>{elements}</elements><signals/></board></drawing></eagle>\n", extracts[0].layers, outline(bw, bh));     let d = std::env::temp_dir().join("adom-fusion-wrap"); let _ = std::fs::create_dir_all(&d);     // two documents with DISTINCT names: a local .sch and a local .brd open as separate designs,     // so the caller must be able to activate the board one by name for the 2D/3D views
skills/fusion-driving/SKILL.md+21−4
@@ -1,6 +1,6 @@ --- name: fusion-driving-description: How to SAFELY drive Fusion 360 through the Adom bridge across many steps without losing work - the discipline of reading the screenshot/owned-popup ARRAY after every mutating op and ANALYZING any dialog BEFORE the next step, never blind-dismissing, and never fullscreen-capturing (it forces foreground and disrupts the user). Includes the blocking-dialog catalog with the CORRECT response for each, and how the bridge now returns the dialog array + an analyze-this hint automatically. Read this BEFORE running any multi-step Fusion automation (attaching 3D, saving libraries, closing docs, modeling scripts). Trigger words - drive fusion, fusion dialog, blocking dialog, are you sure you want to close, packages are being uploaded, save was cancelled, ownedPopupCount, screenshot array, owned popups, fusion notification, lower right error, dismiss dialog, fusion modal, fusion_screenshot_all, fusion_window_info, fusion_check_dialogs, fullscreen capture fusion, analyze before proceeding, foreground vs background, does driving fusion foreground it, run fusion in the background, background screenshot, foreground fusion, focus steal, capture fusion without raising it, drive fusion while user works.+description: How to SAFELY drive Fusion 360 through the Adom bridge across many steps without losing work - the discipline of reading the screenshot/owned-popup ARRAY after every mutating op and ANALYZING any dialog BEFORE the next step, never blind-dismissing, and never fullscreen-capturing (it forces foreground and disrupts the user). Includes the blocking-dialog catalog with the CORRECT response for each, and how the bridge now returns the dialog array + an analyze-this hint automatically. Read this BEFORE running any multi-step Fusion automation (attaching 3D, saving libraries, closing docs, modeling scripts). Trigger words - drive fusion, fusion dialog, blocking dialog, are you sure you want to close, packages are being uploaded, save was cancelled, ownedPopupCount, screenshot array, owned popups, fusion notification, lower right error, dismiss dialog, fusion modal, fusion_screenshot_all, fusion_state, fusion_check_dialogs, fullscreen capture fusion, analyze before proceeding, foreground vs background, does driving fusion foreground it, run fusion in the background, background screenshot, foreground fusion, focus steal, capture fusion without raising it, drive fusion while user works. ---  # Driving Fusion 360 safely (read the popup array, never fly blind)@@ -97,7 +97,7 @@ ways to get the array, in order of preference:    shots, and returns the list of saved paths. Read each. 3. **`desktop_screenshot_window {"hwnd": <fusion-hwnd>}`** (ab core) - returns `screenshots[]` and    **`ownedPopupCount`**. `ownedPopupCount > 0` means dialogs are stacked on the window. Read the-   shots. Get the hwnd from `fusion_window_info` (its `hwnd` field) or `desktop_find_window+   shots. Get the hwnd from `fusion_state {screenshot:false}` (its `hwnd` field) or `desktop_find_window    {"titleContains":"Autodesk Fusion"}`.  **To actually SEE a shot - mind WHERE it lands:**@@ -191,7 +191,7 @@ mid-run, that was almost certainly YOU, not the user - own it, don't blame them. commands and told the AI to read them. That same capture now fires after **all mutating verbs** via `_capture_dialog_array()`, which: -- cheaply enumerates dialog windows (`get_fusion_window_info`, a Win32 `EnumWindows` - no screenshot+- cheaply enumerates dialog windows (the same read `fusion_state {screenshot:false}` gives you: title, text and buttons through UIA - no screenshot   when nothing is up), - suppresses false positives: a docked panel (Browser/Timeline) shares the "Fusion360" title and can   slip past the size filter, but it does NOT disable the main window. So a generic-titled candidate is@@ -251,7 +251,7 @@ the whole time. The evidence was in `screenshots[]` (ownedPopupCount > 0) all al  Rule: to verify anything a palette shows, call `desktop_screenshot_window {hwnd}` and read `screenshots[]`. Also: the hwnd CHANGES on every Fusion restart - re-read it from-`fusion_window_info` or the capture returns "Window is not visible".+`fusion_state` or the capture returns "Window is not visible".  ## A probe that rides a gated verb reports a default, not the truth @@ -262,3 +262,20 @@ the busy gate during bind/prove and my parser turned the refusal into a default. instruments disagree, check which one can even reach the thing before believing either. Anything that must read state DURING a long stage needs to be in BUSY_EXEMPT_COMMANDS, or it is measuring the gate, not Fusion.++## When the window capture is blank or stale (Design documents)++A background capture of a GPU Design viewport can come back blank or a stage behind while the+model is fine. `fusion_state {}` therefore reports three separate things: `windowPixels`+(`blank:true` above 97 percent one colour), `geometry.bodies` (the API body count, independent+of any capture) and `nativeViewport.path` (the frame Fusion itself renders through+`activeViewport.saveAsImageFile`, no foreground). Trust the native frame for geometry;+`{viewport:"native"}` skips the window capture. Never foreground Fusion to fix a capture.++## When a dialog is in the way++The bridge reads every dialog through UIA, answers the ones it has a rule for, sends WM_CLOSE+(Cancel or the X, never a save or a discard) to the rest, and runs your verb. A dialog that+survives comes back as `blocked_by_dialog` with `dialogs[]` (text and button names): click the+right button with `desktop_ui_click {hwnd, name}` and re-run. `dialog_expired_retry` means the+box is gone, call again. The bridge never restarts Fusion over a dialog, and neither should you.
skills/fusion-eagle-commands/SKILL.md+1
@@ -86,6 +86,7 @@ These commands open modal prompts EAGLE waits on forever. Your call will hang un  | Command | Effect | |---|---|+| `GRID MM 1; LAYER 21; CHANGE SIZE 1; CHANGE RATIO 12; CHANGE ALIGN CENTER; TEXT VL53L5CX (7.44 9.6); GRID INCH 0.1` | Silkscreen text at a coordinate: non-interactive (inline string + coordinates, no dialog). ALWAYS prefix `GRID MM`: a generated board's grid is inches, so a bare `CHANGE SIZE 1` makes a 1 inch label. `fusion_make_showcase_project` and `fusion_make_test_project` write the part name under each footprint themselves (`silkLabels`). | | `NAME U1 MCU` | Rename part U1 to MCU | | `VALUE R1 10k` | Set R1's value to 10k | | `ROTATE =R90 U1` | Rotate U1 by 90° absolute |
skills/fusion-electronics/SKILL.md+8−3
@@ -12,7 +12,10 @@ description: How to drive Fusion 360 Electronics (the EAGLE-based schematic + PC > design to the schematic view. The reply's `via` names the verb it used. To put a LIBRARY'S PARTS on > a new schematic and board (a demo board, "show these sensors"), `fusion_make_showcase_project > {libraryPath, devicesets}` places them all, with their bound 3D bodies, dialog-free. Everything-> below is the detail behind that front door.+> below is the detail behind that front door. `fusion_new_electronics_from_eagle` and the showcase are+> async: they return a `runId`; poll `fusion_eagle_import_status {runId}` for stage, document names,+> `partsExpected` and `partsPlaced`. `fusion_show_attributes {libraryPath, deviceset}` returns a+> component's metadata and shows its device editor.  ## 1. ALWAYS open the PROJECT, never a child file @@ -105,8 +108,10 @@ Fixed in `_open_local_document()` (`commands/open_electronics_file.py`), now use `fusion_open_board`, `fusion_open_schematic`, `fusion_import_electronics` and `fusion_open_lbr`:  1. **Quote the path**, then-2. if that fails and the path has spaces, retry with the Windows **8.3 short path**-   (`GetShortPathNameW`) - same file, no spaces, nothing copied.+2. if that fails and the path has spaces, open a **staged copy**: the file and its same-stem+   siblings (.sch with .brd, .lbr with .step) are copied into a space-free folder and Fusion is+   handed the copy. Never the 8.3 short path: Fusion titles the document after the path it is+   given, and a name like `0603WA~1` broke every document-name check (bridge 1.10.11).  **If you are writing a new verb that hands a path to `executeTextCommand`, use that helper.** Never interpolate a bare path into a text command; assume every user path has spaces in it, because
skills/fusion-multipart-libraries/SKILL.md+8
@@ -77,6 +77,14 @@ without a placement imports the model exactly as authored, no reorientation and says so in `warnings`, so read `pin1.status` (`rotated` means the mark heuristic moved the chip). Each `parts[]` entry of `fusion_build_library_3d` accepts the same `placement` and `orient`. +### Prove is for web control (1.10.17)++Called from an AI thread, `fusion_install_library_bundle` and `fusion_create_library` bind and place+only (`verdict: bound_not_proven`, `partsBound`, the library path); web control proves. `prove:true`+asks for the four frames per part; `fusion_capture_library_views {packages}` shows one part on demand.+A chain never restarts Fusion on its own: `allowRestart:true` with `restartReason`, recorded in+`restarts[]`.+ ### ⚠️ Relay timeout - a many-part run "times out" but KEEPS GOING (don't panic)  The `adom-bridge` relay times out the *request* at ~60s, but `fusion_build_library_3d` for a dozen
skills/fusion-part-metadata/SKILL.md+12
@@ -80,6 +80,15 @@ Add `DESCRIPTION` per technology when a deviceset has more than one variant. The description says what the part *is*; the technology description says **which variant this is**, and it is the only thing distinguishing them in Fusion's Content Manager. +## Show it on screen: `fusion_show_attributes`++`fusion_show_attributes {libraryPath, deviceset}` returns the component's attributes with their+full values (read from the library file) and opens the DEVICE editor for it with a capture:+symbol, the Packages table with the Mapped check, footprint and 3D previews in one frame.+Fusion's own Attribute Sets table sits behind a "..." button in an opaque Qt panel; the reply+carries the positioned-click recipe and the note that a main-window recording does not include+that owned dialog.+ ## Check before you claim a library is done  ```@@ -99,6 +108,9 @@ Offline, no Fusion. Returns every variant with its attributes, plus `metadataOk` | `duplicate_variant_mpn` | Two variants share one MPN | They are not different parts, or one MPN is wrong | | `deviceset_no_description` | No `<description>` | Whoever places it is choosing on a symbol outline alone | +Same-net pads are not shorts: `pad_overlap` skips pads that share a base name (`B4` and `B4@2`,+heatsink vias on the exposed pad's own net) and reports them as an info line, `heatsink_vias`.+ ## These warnings do NOT block opening, deliberately  `fusion_preflight_lbr`'s `ok` keeps meaning **"this file will open in Fusion"**, because a
skills/fusion-web-control/SKILL.md+3−3
@@ -121,7 +121,7 @@ now stops at "asking you whether to save and restart it" with `errorCode: addin_restart_required`, `userMessage`, `documentsKnown`, `remedyVerb` and `notificationId` in the run state, fires the two-button toast, and waits (30 min). Fusion's process is untouched while it waits (same pid before and during). Answers:-- `fusion_restart_for_addin {save:true}` (the toast's "Save and restart" or the page button):+- `fusion_restart_for_addin {save:true}` (the page button; under `restartPolicy: ask` also the toast's "Save and restart"):   Fusion restarts, the add-in answers, the SAME run continues to `proven` with   `restartChoice: "restart"`. - `fusion_restart_for_addin {decline:true}` (the page's "I'll handle it"; the toast button@@ -182,7 +182,7 @@ the user's machine, and a container path means nothing to it. bridge used to build `Document.newDesignFromLocal C:/Users/john/Documents/Adom Libraries/...` raw, so Fusion opened `.../Adom` and answered `3 : Filename not found`; the add-in's own quote-then-8.3 fallback never ran because the bridge bypasses it. Measured inside Fusion's-Python: the quoted form ALSO fails with error 3; the Windows 8.3 short path with forward+Python: the quoted form ALSO fails with error 3. (History: until 1.10.10 the bridge then used the 8.3 short path; since 1.10.11 it STAGES the file into a space-free folder instead, because Fusion titled documents after the alias.) The Windows 8.3 short path with forward slashes (`C:/Users/john/DOCUME~1/ADOMLI~1/RAY-DA~1/part.lbr`) opens first try. Every bridge-side text command that embeds a path now goes through `_fusion_text_path()`. A user whose profile folder has a space (`C:/Users/John Doe`) hits this on every import otherwise.@@ -262,7 +262,7 @@ promised "we will save your documents first" while its only button was an acknow the text made an offer the UI could not keep. That is lazy, and it leaves the user exactly where Adonis was. -The restart notice now offers **"Save and restart"** and **"I'll handle it"**, and the+Under `restartPolicy: ask` (NOT the default since 1.10.6: the default `auto` raises no toast and the bridge restarts Fusion itself when the add-in is truly gone, never over a dialog) the restart notice offers **"Save and restart"** and **"I'll handle it"**, and the bridge honours whichever is pressed (`_watch_restart_choice` polls `notify_response` and either runs `fusion_restart_for_addin {save:true}` or records the decline). Both land in the action ledger, so "why did my Fusion restart" has a timestamped answer.
tools/check-naming.sh

  
tools/check-portable.sh

  
tools/promote.sh

  
tools/release.sh