← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.263\ No newline at end of file+1.9.264\ No newline at end of file
README.md+1
@@ -23,6 +23,7 @@ Short version below, full detail in each guide. | [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. |+| [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. | | [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. |
adom-bridge-fusion-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "fusion360",-  "version": "1.9.263",-  "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.263/adom-bridge-fusion-v1.9.263.zip",-  "sha256": "67b364b0418e02b31bf1fb282b3363f5fd351b0c0a27592f074d2ebd6f69cf07",-  "size": 556122,+  "version": "1.9.264",+  "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.264/adom-bridge-fusion-v1.9.264.zip",+  "sha256": "bcd6148e63c31ec87e58fc1da5fe2d525efda3113347bfaa27aa282686c9f6f9",+  "size": 556647,   "verbPrefixes": [     "fusion_"   ],
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.9.263",+  "version": "1.9.264",   "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.",
dashboard/Cargo.lock+1−1
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "fusion-dashboard"-version = "1.9.263"+version = "1.9.264" dependencies = [  "serde_json", ]
dashboard/Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.9.263"+version = "1.9.264" edition = "2021"  [dependencies]
docs/MULTIPART_LIBRARY_IMPORT.mdadded+147
@@ -0,0 +1,147 @@+# Multi-part library import: how a Fusion library gets real, rendering 3D on every part++This is the technical account of what `fusion_install_library_bundle` does between "a zip of+`.lbr` + `.step` files" and "a library in your Fusion Library Manager where every package+renders its chip on its own footprint". It exists because the middle of that sentence is+where Fusion makes things hard, and because two of the ways to get it subtly wrong were+found on real runs and are pictured below.++If you only want to use it: press **Create Fusion library** on a wiki part page (or several+parts on the Quick Actions page), and the bridge does everything here. If you are debugging+it, extending it, or wondering why your chip sits on the wrong pads, read on.++## The problem Fusion sets++An EAGLE/Fusion library (`.lbr`) describes symbols, footprints (packages) and devicesets in+XML. A footprint can carry a **3D package**, but the 3D package is not in the XML. It is a+reference to a **Fusion cloud design**, addressed by a **urn**:++```xml+<packages3d>+  <package3d name="TPS54427DDAR" urn="" wip_urn="urn:adsk.wipprod:fs.file:vf.AbCd...?version=1"+             locally_modified="yes" type="model">+    <packageinstances><packageinstance name="TPS54427DDAR"/></packageinstances>+  </package3d>+</packages3d>+...+<device name="" package="TPS54427DDAR">+  <connects>...</connects>+  <package3dinstances><package3dinstance package3d_urn="urn:adsk.wipprod:fs.file:vf.AbCd...?version=1"/></package3dinstances>+  <technologies>...</technologies>+</device>+```++Three facts drive the whole design:++1. **Only a Fusion design renders.** Upload a raw STEP to the cloud and bind its urn: the+   reference resolves, the library opens, and the 3D preview says "Thumbnail download+   failed". A `.f3d` (a real Fusion design with the STEP imported into it) binds and renders+   exactly like a package minted by hand in the GUI.+2. **The GUI way is dialog-trapped.** Fusion's Package3D generator ends with FINISH, which+   pops a native Save dialog and two Chromium modals that no automation can read or dismiss+   in the background. So the bridge never presses FINISH.+3. **The generator loads the first package of the open library**, whatever path you hand+   the command. This one is the trap pictured below.++## What one part goes through (the "mint")++```+per-part .lbr  --open-->  library active+                --Electron.Create3DPackage-->  generator document, footprint (pads + courtyard) loaded+                --importToTarget(STEP)-->      chip placed exactly as the STEP was published+                --doc.saveAs(<pkg>_3d)-->      cloud design in  Adom AI Workspace/<library>/   (no FINISH)+                --wait for lineage id-->       urn:adsk.wipprod:fs.file:vf.<id>?version=1+```++- 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`).+- 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.+- The lineage id doubles as the version id for these single-version designs, which is why+  the urn can be constructed rather than looked up.++## What a multi-part library goes through++```+bundle.zip (one merged .lbr, N STEPs)+  -> unpack (zip-slip guarded, size capped), place under Documents/AdomLibraries/<name>/+  -> pair every deviceset with a STEP by name (deviceset, package, normalised, prefix)+  -> SPLIT the merged library into one temporary library per deviceset+  -> mint each part on ITS OWN library          (fact 3 above)+  -> bind all N urns into the merged .lbr in one pass (merge-aware, idempotent)+  -> open the merged library, prove every deviceset (4 frames each, measured)+  -> save the library to <project>/Adom Libraries/<name>  (Library Manager lists it)+  -> close the scratch (generators, <pkg>_3d tabs, temp libraries), leave the library open+```++### Why the split matters: the wrong-footprint run++The first multi-part run minted all three parts against the merged library. Because the+generator loads the **first** package of whatever library is open, chips two and three were+imported onto part one's footprint, saved, uploaded, bound and "proven". Here is+TPS54427DDAR on XL7026E1's footprint; the pads run out past the left edge of the body:++![TPS54427DDAR on the wrong footprint](img/multipart/wrong-footprint-tps54427ddar.png)++After the split, the same chip on its own SOP-8 footprint, a pad under every lead:++![TPS54427DDAR on its own footprint](img/multipart/own-footprint-tps54427ddar.png)++The mint now reads the generator document's name back (Fusion names it after the package it+loaded) and refuses with `wrong_footprint_loaded` if it is not the package being minted, so+this class of error fails before anything is uploaded.++### The finished library++Three devicesets in one library, each with its symbol, footprint and rendering 3D package:++![adom-multi-3 in the Content Manager](img/multipart/library-three-parts-ganged.png)++## What the user sees while it runs++The bridge shows an Adom-branded palette inside Fusion for the whole run, including while+the Package3D generator is active (this is a capture of that palette during the generator+stage):++![The Adom banner inside the Package3D generator](img/multipart/banner-in-package3d-generator.png)++The Quick Actions page (and any web-control caller) gets the same run as a 1 fps live view+of the Fusion window with its owned popups composited at their screen positions, the+bridge's measured percent, the stage text, and the proof frames as they land:++![Quick Actions live view during an import](img/multipart/quick-actions-live-view.png)++Several wiki parts can be picked into one multi-part library from the page:++![Picking three parts for one library](img/multipart/quick-actions-multi-pick.png)++## Proof, not "done"++`fusion_prove_result` is the single poll surface. `stage: done` only means the chain+finished; the score is `verdict`. Every deviceset is proved with four frames (symbol,+footprint, package3d, ganged), each frame is measured from the file (a 237x39 title-bar+sliver once passed as a "symbol" until this check existed), and `provedParts[]` is per part.+`partMetadata` reports missing MPN / manufacturer / distributor numbers; it never blocks the+import, it bubbles up so someone can fix the data upstream.++## Contracts worth knowing++| Situation | What you get |+|---|---|+| 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` |+| 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` |++## Skills++- `fusion-multipart-libraries`: the manual recipe (per-part f3d, urn construction, binding+  markup, merge rules) and the dead ends.+- `fusion-web-control`: the import lane, pre-flight, progress, evidence, and every failure+  contract, for web callers and AI threads alike.+- `fusion-part-metadata`: MPN, manufacturer and distributor numbers, and variants.+- `fusion-ralph-test-via-wiki`: how the bridge tests this lane the way a user drives it.
docs/img/multipart/banner-in-package3d-generator.pngadded
⋯ 1 unchanged line ⋯
docs/img/multipart/library-three-parts-ganged.pngadded
⋯ 1 unchanged line ⋯
docs/img/multipart/own-footprint-tps54427ddar.pngadded
⋯ 1 unchanged line ⋯
docs/img/multipart/quick-actions-live-view.pngadded
⋯ 1 unchanged line ⋯
docs/img/multipart/quick-actions-multi-pick.pngadded
⋯ 1 unchanged line ⋯
docs/img/multipart/wrong-footprint-tps54427ddar.pngadded
⋯ 1 unchanged line ⋯
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.9.263"+DASH_VERSION="1.9.264" 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.9.263",+  "version": "1.9.264",   "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−8
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.9.263",+  "version": "1.9.264",   "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,9 +111,5 @@       "label": "Part volume",       "prompt": "How much material does each printed part in this assembly use?"     }-  ],-  "author": {-    "name": "John Lauer",-    "email": "[email protected]"-  }-}\ No newline at end of file+  ]+}
server.py+31
@@ -4519,6 +4519,14 @@ def _build_library_3d_core(args: dict) -> dict:     for p in parts:         pkg = p.get("package")         per_lbr = (p.get("lbrPath") or "").replace("\\", "/")+        # CLEAN UP PER PART, NOT AT THE END (John, 2026-08-29: "be better about clean up as you+        # progress"). Snapshot the open documents now; everything this part opens (its+        # generator Untitled, its <pkg>_3d design, its temporary library) is closed the moment+        # its urn is in hand, so a 20-part library never has 40 tabs waiting for a final sweep.+        try:+            _docs_before_part = _open_doc_names()+        except Exception:+            _docs_before_part = set()         step = (p.get("modelPath") or "").replace("\\", "/")         if not pkg or not per_lbr or not step:             results.append({"package": pkg, "success": False,@@ -4553,6 +4561,10 @@ def _build_library_3d_core(args: dict) -> dict:         if mk.get("after"):             shots.append({"package": pkg, "stage": "after", "path": mk["after"]})         results.append(entry)+        try:+            entry["scratchClosed"] = _close_docs_opened_since(_docs_before_part)+        except Exception:+            entry["scratchClosed"] = []         _emit_part(entry, len(results), len(parts))     # inject every binding in ONE pass, write the bound library. Read the ALREADY-BOUND output if it     # exists so a re-run ACCUMULATES (merge-aware) onto prior successes instead of starting clean.@@ -12115,6 +12127,17 @@ def _publish_library_to_cloud(lbr_path: str, name: str, folder: str = "Adom Libr     # Make the library the active document ourselves (the generic helper also demands a     # library workspace and gave up after the prove left a 3D document in front, 2026-08-29).     import time as _tpa+    # The prove stage closes the library behind itself, so it is usually NOT open here; open it+    # from disk (the bound file is the truth) before trying to activate it.+    try:+        if stem.lower() not in {n.rstrip("*").strip().lower() for n in _open_doc_names()}:+            _op = _orchestrate_open_lbr({"filePath": lbr_path})+            out["reopened"] = bool(_op.get("success"))+            if not _op.get("success"):+                out["error"] = "could not reopen the library to save it: " + str(_op.get("error"))[:160]+                return out+    except Exception as e:+        out["reopenNote"] = str(e)[:120]     _deadline = _tpa.time() + 120     _seen = None     _active_ok = False@@ -12556,6 +12579,14 @@ def _handle_create_library(fusion_info: dict, args: dict) -> dict:                                        "error": _r.get("error")})                 state["provedParts"] = _prove_results                 _all_ok = _all_ok and bool(_r.get("proven"))+                # Close what THIS deviceset's frames opened (the 3D frame's design, any+                # Untitled) before moving to the next one; the library itself stays.+                try:+                    _lib_stem = (target_lbr or "").replace("\\", "/").rsplit("/", 1)[-1].rsplit(".", 1)[0].lower()+                    _prove_results[-1]["scratchClosed"] = _close_docs_opened_since(+                        _docs_before, keep={_lib_stem})+                except Exception:+                    pass                 _save()             # The library is proven only if EVERY part is. One good part does not vouch             # for nine unchecked ones.
skills/fusion-multipart-libraries/SKILL.md+5−1
@@ -21,7 +21,11 @@ real chip, flat on its footprint, built entirely this way. > the generator loads the merged file's first package, so three chips landed on one footprint, > John 2026-08-29), trusts the STEP's published origin and orientation (`orient:false`, no > bounding-box guesses), binds every urn into the merged library, proves it, and then saves-> it into the user's cloud project under `Adom Libraries` so Fusion's Library Manager lists it.+> it into the user's cloud project under `Adom Libraries` so Fusion's Library Manager lists it,+> and leaves that library OPEN in front of the user (only the scratch is closed). The mint+> refuses with `wrong_footprint_loaded` if the generator loaded a different package than the+> one being minted. The full technical account, with the wrong-footprint and corrected frames,+> is `docs/MULTIPART_LIBRARY_IMPORT.md` on the fusion-bridge wiki page.  For a whole many-part library, you no longer loop `make_3d_package` + hand-write bindings. ONE verb does the entire pipeline - per-part footprint+chip f3d packages, **all** bindings injected in one
tools/release.sh+3−1
@@ -301,7 +301,9 @@ PUSH_FILES="server.py fusion_detect.py describe.py timings.py aps.py install_add   dashboard/src/main.rs dashboard/Cargo.toml dashboard/Cargo.lock dashboard/ui/index.html \   dockbar.json install.sh" for f in handlers/*.py addin/AdomBridge/*.py addin/AdomBridge/commands/*.py \-         addin/AdomBridge/*.manifest skills/*/SKILL.md tools/release.sh CLAUDE.md; do+         addin/AdomBridge/*.manifest skills/*/SKILL.md tools/release.sh CLAUDE.md \+         library_bundle.py README.md docs/*.md docs/img/*.svg docs/img/*.png \+         docs/img/*/*.png dashboard/ui/*.html; do   [ -f "$f" ] && PUSH_FILES="$PUSH_FILES $f" done echo "  pushing $(echo $PUSH_FILES | wc -w) files"