app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
← Commit history
Update 72 file(s)
10 files changed
+163−18
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.227+1.9.228
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.9.227",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.227/adom-bridge-fusion-v1.9.227.zip",- "sha256": "fdee5acb7457e5244f0b785de68f8b60ae4b263be7f30c6d29c6d15444ccc96c",- "size": 522566,+ "version": "1.9.228",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.228/adom-bridge-fusion-v1.9.228.zip",+ "sha256": "bf1ab587b0cf1ebcf176ffa60e8464cb9fac0c8f4dbf74782b7226d3cb090393",+ "size": 524308, "verbPrefixes": [ "fusion_" ],
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.9.227",+ "version": "1.9.228", "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.227"+version = "1.9.228" dependencies = [ "serde_json", ]
dashboard/Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.9.227"+version = "1.9.228" edition = "2021" [dependencies]
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.227"+DASH_VERSION="1.9.228" 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.227",+ "version": "1.9.228", "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.227",+ "version": "1.9.228", "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]"- }-}+ ]+}
server.py+96@@ -3405,9 +3405,19 @@ def _orchestrate_open_lbr(args: dict) -> dict: # Remember it so a later capture/EDIT can pre-check names offline instead of letting # Fusion raise "Create new footprint 'X'?" and dismissing it after the fact. _remember_open_library(file_path)+ # BUBBLE THE PART-NUMBER GAP UP WITH THE IMPORT (John, 2026-08-28). The import is NOT+ # blocked by missing part numbers - a library without them is still a real library and+ # the user asked for it - but the gap has to reach the top (the wiki's quick actions+ # UI, or web control) so they see "2 of my 5 components had no Mouser part number" and+ # can go fix it where the data lives. We are the only thing that has read the file.+ try:+ _pm = _part_metadata_report(file_path)+ except Exception:+ _pm = None response = { "success": True, "output": f"Opened library: {os.path.basename(file_path)}",+ "partMetadata": _pm, "data": {"results": results, "filePath": file_path}, "_hint": ( "Library opened in the Electronics Library editor (Content Manager). "@@ -11280,6 +11290,82 @@ def _lint_part_metadata(path: str) -> dict: return {"ok": not warns, "warnings": warns, "totals": md["totals"]} +def _part_metadata_report(path: str) -> dict:+ """A SHORT, user-facing metadata report an import can carry all the way up the chain.++ John, 2026-08-28: importing must NOT be blocked by missing part numbers, but the gap has+ to "bubble up all the way to the top, like in the wiki quick actions UI or via web+ control so the user sees 'oh, 2 of my 5 components had no mouser part number' so they+ can go fix it". The bridge is the end of the chain and cannot invent the data - but it+ is the only thing that has actually read the file, so it is the only thing that can say+ what is missing. Silence here means the user finds out at ordering time.++ Counts are per COMPONENT (deviceset), because that is the unit a user thinks in, with+ per-variant detail underneath for the automotive-vs-baseline case.+ """+ md = _library_part_metadata(path)+ if not md.get("ok"):+ return {"ok": None, "unavailable": md.get("error", "could not read the library")}+ lint = _lint_part_metadata(path)+ # TWO SEVERITIES, and conflating them made a GOOD library look broken (caught by my own+ # test): a part needs an MPN and a manufacturer, and it needs AT LEAST ONE distributor -+ # not all three. A library with DigiKey on one variant and Mouser on another is+ # perfectly orderable, and reporting "no LCSC part number" as a deficiency there is+ # noise that trains people to ignore the whole report.+ # missing -> actionable: cannot order this component+ # absentDistributors -> informational: which catalogues lack a number, John's+ # "2 of my 5 components had no mouser part number"+ dist = ("MOUSER_PN", "DIGIKEY_PN", "LCSC_PN")+ fields = ("MPN", "MANUFACTURER") + dist+ comps, missing_counts = [], {f: 0 for f in fields}+ n_no_distributor = 0+ for ds in md["devicesets"]:+ vs = ds["variants"] or []+ # A component is missing a field when NO variant of it carries that field.+ absent = [f for f in fields if not any(v["attributes"].get(f) for v in vs)]+ for f in absent:+ missing_counts[f] += 1+ blocking = [f for f in ("MPN", "MANUFACTURER") if f in absent]+ if all(d in absent for d in dist):+ blocking.append("a distributor part number")+ n_no_distributor += 1+ if blocking or [d for d in dist if d in absent]:+ comps.append({"component": ds["name"], "variants": len(vs),+ "missing": blocking,+ "absentDistributors": [d for d in dist if d in absent],+ "variantNames": [v["fullName"] for v in vs]})+ total = len(md["devicesets"])+ label = {"MPN": "no manufacturer part number", "MANUFACTURER": "no manufacturer",+ "MOUSER_PN": "no Mouser part number", "DIGIKEY_PN": "no DigiKey part number",+ "LCSC_PN": "no LCSC part number"}++ def _n(n, what):+ return "%d of %d component%s ha%s %s" % (n, total, "" if total == 1 else "s",+ "s" if n == 1 else "ve", what)+ bits = [_n(missing_counts[f], label[f]) for f in ("MPN", "MANUFACTURER")+ if missing_counts[f]]+ if n_no_distributor:+ bits.append(_n(n_no_distributor, "no distributor part number at all"))+ return {+ "ok": lint["ok"],+ "componentsTotal": total,+ "variantsTotal": md["totals"]["variants"],+ "missingCounts": missing_counts,+ "componentsWithoutAnyDistributor": n_no_distributor,+ "distributorCoverage": {d: total - missing_counts[d] for d in dist},+ "components": comps[:40],+ "warnings": lint["warnings"],+ "summary": ("; ".join(bits) + ". The library imported fine - this is about ordering, "+ "not geometry." ) if bits else+ "All %d component(s) carry a manufacturer part number and at least one "+ "distributor part number." % total,+ "distributorNote": ("; ".join(_n(missing_counts[d], label[d]) for d in dist+ if missing_counts[d]) or None),+ "userAction": ("Add the missing part numbers where the component metadata lives "+ "(upstream of this bridge), then re-export." if bits else None),+ }++ def _handle_library_parts(fusion_info: dict, args: dict) -> dict: """Every ORDERABLE variant in a .lbr with its part numbers. Offline, no Fusion needed. @@ -11887,6 +11973,16 @@ def _handle_create_library(fusion_info: dict, args: dict) -> dict: state.update(stage="done", success=bool(prove.get("proven")), verdict=("proven" if prove.get("proven") else "not_proven"), evidenceCount=_frames, prove=prove)+ # The part-number gap rides the RUN STATE, so web control sees it on the same+ # poll it already makes rather than needing a second call (John, 2026-08-28).+ # Attached on the failure path too: a chain that could not prove the geometry+ # still read the file, and the user still wants to know what is missing.+ try:+ _lbr_for_meta = state.get("libraryPath") or target_lbr+ if _lbr_for_meta:+ state["partMetadata"] = _part_metadata_report(_lbr_for_meta)+ except Exception:+ pass _save() except Exception as e: state.update(stage="failed", success=False, verdict="not_proven",
skills/fusion-part-metadata/SKILL.md+46@@ -121,3 +121,49 @@ lives. - Libraries this bridge *generates* from bare geometry emit `<technology name=""/>` with no attributes. That is correct for a geometry-only wrapper and **wrong** to ship as a real part: add the attributes before it reaches a user.++## Missing part numbers NEVER block the import — they bubble up++John, 2026-08-28: *"i think its ok to continue to import a library if it doesn't have this+data, but you should report it upstream so it bubbles up all the way to the top."*++So the import succeeds and the gap travels with it. `fusion_open_lbr` returns+`partMetadata`, and `fusion_create_library` puts the same block in its run state, so+`fusion_prove_result` carries it on the poll web control already makes — no second call:++```json+"partMetadata": {+ "ok": false,+ "componentsTotal": 5,+ "summary": "2 of 5 components have no distributor part number at all.+ The library imported fine - this is about ordering, not geometry.",+ "distributorNote": "2 of 5 components have no Mouser part number; ...",+ "distributorCoverage": { "MOUSER_PN": 3, "DIGIKEY_PN": 0, "LCSC_PN": 0 },+ "components": [ { "component": "U1", "missing": ["a distributor part number"],+ "absentDistributors": ["MOUSER_PN","DIGIKEY_PN","LCSC_PN"] } ],+ "userAction": "Add the missing part numbers where the component metadata lives+ (upstream of this bridge), then re-export."+}+```++### Two severities, and do not conflate them++| Field | Severity | Meaning |+|---|---|---|+| `missing` / `summary` / `ok` | **Actionable** | No MPN, no manufacturer, or **no distributor at all** — this component cannot be ordered |+| `absentDistributors` / `distributorNote` / `distributorCoverage` | **Informational** | *Which* catalogues lack a number |++A part needs an MPN, a manufacturer, and **at least one** distributor — not all three. A+library with DigiKey on one variant and Mouser on another is perfectly orderable; calling+that a deficiency is noise that trains people to ignore the whole report. (I shipped exactly+that bug for one commit and my own test caught it: a complete library reported `ok: true`+while the summary nagged about LCSC.)++Render `summary` as the headline and `distributorNote` as the detail line. `userAction` is+`null` when there is nothing to fix, so a clean library stays quiet.++### The bridge cannot fix this, only report it++We are the end of the chain. If a component's metadata lacks part numbers, they are missing+upstream where the component is defined — surface the names and let the fix happen where the+data lives. Never invent a part number.