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)
15 files changed
+355−20
BRIDGE_VERSION+1−1adom-bridge-fusion-manifest.json+4−4bridge.json+1−1dashboard/Cargo.lock+1−1dashboard/Cargo.toml+1−1describe.py+4install.sh+1−1package.json+1−1page.json+11−8server.py+175−2skills/fusion-build-parts-library/SKILL.md+8skills/fusion-libraries/SKILL.md+8skills/fusion-multipart-libraries/SKILL.md+8skills/fusion-part-metadata/SKILL.md+123skills/fusion-web-control/SKILL.md+8BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.226+1.9.227
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.9.226",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.226/adom-bridge-fusion-v1.9.226.zip",- "sha256": "5677ad40bf39e25683ddce92903d084e1e60a67cf0cc9056b6ad50667a266ca9",- "size": 519012,+ "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, "verbPrefixes": [ "fusion_" ],
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.9.226",+ "version": "1.9.227", "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.226"+version = "1.9.227" dependencies = [ "serde_json", ]
dashboard/Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.9.226"+version = "1.9.227" edition = "2021" [dependencies]
describe.py+4@@ -268,6 +268,10 @@ _T = [ {"outputPath": "optional str"}, {"success": "bool", "path": "str"}, 60, None, True, {}), ("fusion_verb_times", "This machine MEASURED timings per verb/stage - the same store that paces every progress bar and ETA. Read it to see whether an estimate is measured (this box has run it) or typical (still the seeded literal), and to explain a slow run.", {}, {"stages": "{name: {count, p50, p90}}"}, 15, None, True, {}),+ ("fusion_library_parts", "Every ORDERABLE variant in a .lbr with its part numbers - offline, no Fusion needed. For an EE this is the point of the library: two variants can share ONE symbol, ONE footprint and ONE 3D body and still be different products (an AEC-Q100 automotive part vs the baseline one), and that difference lives ENTIRELY in the metadata. EAGLE spells the orderable identity as deviceset + device + technology: <device> carries the PACKAGE variant, <technology> carries the GRADE/temperature variant, and attributes hang off <technology>. Reports per-variant MPN / MANUFACTURER / MOUSER_PN / DIGIKEY_PN / LCSC_PN (the exact names this bridge's own BOM export reads), plus warnings when variants are indistinguishable, share an MPN, or a deviceset has no description. These warnings never block the library from opening - they say whether an EE could actually order from it.",+ {"libraryPath": "required str (also accepts lbrPath/filePath/path)"},+ {"success": "bool", "devicesets": "[{name, description, variants:[{fullName, device, technology, package, attributes, missing}]}]", "totals": "{devicesets, variants, withMpn, withDistributor}", "metadataOk": "bool", "warnings": "[]"},+ 15, None, True, {"libraryPath": "C:/tmp/TJA1050.lbr"}), ("fusion_plan_library_import", "PRE-FLIGHT for a library import: does NO work and changes nothing. Reports whether Fusion is installed, running, or needs its add-in loaded (with a plain-language message you can show the user), plus a per-stage plan with MEASURED per-machine time estimates scaled to the number of parts you are sending - a multi-part library does one STEP import per part, which is the slowest stage, so the estimate grows with the library. Call this FIRST so the wiki can render the summary and the progress bar's steps, then run the import and poll fusion_prove_result, which reports the SAME stage names.", {"parts": "optional [{package, lbrPath, modelPath}] - the parts you intend to import (drives the estimate)", "stepPath": "optional single .step when not passing parts[]", "libraryPath": "optional .lbr for context"}, {"success": "bool", "fusion": "{installed, running, addinReady, status, message}", "plan": "[{stage, label, estimatedSec, confidence, needsUserConsent?}]", "estimatedSec": "float", "estimatedSecIfAlreadyOpen": "float", "summary": "str"},
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.226"+DASH_VERSION="1.9.227" 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.226",+ "version": "1.9.227", "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.226",+ "version": "1.9.227", "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+175−2@@ -11140,8 +11140,180 @@ def _preflight_lbr(path: str) -> dict: "these: %s. Rename to e.g. 17, 17@2, 17@3..." % ", ".join(_dups[:6])) if _dups else "all pad names unique per package") ok = all(c["ok"] for c in checks)- return {"ok": ok, "checks": checks,- "errors": [c["check"] + ": " + c["detail"] for c in checks if not c["ok"]]}+ # PART METADATA IS REPORTED SEPARATELY, ON PURPOSE. `ok` must keep meaning "this file+ # will open in Fusion" - a geometry-only library opens perfectly well. Whether it is+ # fit to ORDER from is a different question and just as important to an EE, so it gets+ # its own key rather than being folded into a pass/fail that gates opening.+ res = {"ok": ok, "checks": checks,+ "errors": [c["check"] + ": " + c["detail"] for c in checks if not c["ok"]]}+ if root is not None:+ try:+ _lint = _lint_part_metadata(p)+ res["metadataOk"] = _lint["ok"]+ res["metadataWarnings"] = _lint["warnings"]+ res["partTotals"] = _lint.get("totals")+ if not _lint["ok"]:+ res["_metadataHint"] = (+ "The library will open, but %d part-metadata warning(s) mean an EE "+ "cannot order from it: MPN / MANUFACTURER / a distributor PN "+ "(MOUSER_PN, DIGIKEY_PN, LCSC_PN) are what this bridge's own BOM export "+ "reads, and variants that share geometry are told apart ONLY by these. "+ "See fusion_library_parts for the per-variant breakdown."+ % len(_lint["warnings"]))+ except Exception:+ pass+ return res+++# ── Part metadata + variants: the fields an EE actually orders from ──────────────+# John, 2026-08-28: "one chip could be the same symbol, footprint, and 3d chip, but there+# might be an automotive high temp version vs a cheaper baseline variant." That distinction+# is invisible in geometry - it lives ENTIRELY in the metadata - so a library that drops it+# is worse than useless: it looks complete and orders the wrong part.+#+# EAGLE expresses this with three nested names, and the full orderable part is all three:+# <deviceset name> + <device name> + <technology name>+# deviceset = the logical part, device = the PACKAGE variant, technology = the GRADE/temp+# variant. Attributes hang off <technology>, which is why two technologies can share one+# symbol, one footprint and one 3D body and still be different products.+#+# These names are not arbitrary - they are what this bridge's OWN BOM exporter reads+# (addin/AdomBridge/commands/manufacturing.py): MPN, MANUFACTURER, MOUSER_PN, DIGIKEY_PN,+# LCSC_PN. Emitting anything else means the BOM comes out blank.+PART_ATTRS = ("MPN", "MANUFACTURER", "MOUSER_PN", "DIGIKEY_PN", "LCSC_PN")+_DISTRIBUTOR_ATTRS = ("MOUSER_PN", "DIGIKEY_PN", "LCSC_PN")+++def _library_part_metadata(path: str) -> dict:+ """Every orderable variant in a .lbr, with its attributes. Offline, no Fusion.++ Returns {ok, devicesets:[{name, description, variants:[{device, technology, fullName,+ attributes{}, missing[]}]}], totals{...}}.+ """+ import xml.etree.ElementTree as _ET+ try:+ root = _ET.parse((path or "").replace("\\", "/")).getroot()+ except Exception as e:+ return {"ok": False, "error": str(e)[:160], "devicesets": []}+ out = []+ for ds in root.iter("deviceset"):+ ds_name = ds.get("name") or ""+ desc_el = ds.find("description")+ ds_desc = (desc_el.text or "").strip() if desc_el is not None else ""+ variants = []+ for dev in ds.iter("device"):+ dev_name = dev.get("name") or ""+ techs = list(dev.iter("technology")) or [None]+ for tech in techs:+ tname = (tech.get("name") or "") if tech is not None else ""+ attrs = {}+ if tech is not None:+ for a in tech.iter("attribute"):+ n = (a.get("name") or "").upper()+ v = (a.get("value") or "").strip()+ if n and v:+ attrs[n] = v+ # EAGLE's orderable identity is the three names concatenated.+ full = "%s%s%s" % (ds_name, dev_name, tname)+ missing = [k for k in ("MPN", "MANUFACTURER") if not attrs.get(k)]+ if not any(attrs.get(d) for d in _DISTRIBUTOR_ATTRS):+ missing.append("a distributor PN (MOUSER_PN/DIGIKEY_PN/LCSC_PN)")+ variants.append({"device": dev_name, "technology": tname,+ "fullName": full, "package": dev.get("package") or "",+ "attributes": attrs, "missing": missing})+ out.append({"name": ds_name, "description": ds_desc, "variants": variants})+ n_var = sum(len(d["variants"]) for d in out)+ return {"ok": True, "devicesets": out,+ "totals": {"devicesets": len(out), "variants": n_var,+ "withMpn": sum(1 for d in out for v in d["variants"]+ if v["attributes"].get("MPN")),+ "withDistributor": sum(1 for d in out for v in d["variants"]+ if any(v["attributes"].get(x)+ for x in _DISTRIBUTOR_ATTRS))}}+++def _lint_part_metadata(path: str) -> dict:+ """EE-grade warnings about a library's part metadata. Never blocks opening.++ Deliberately SEPARATE from _preflight_lbr's hard checks: a geometry-only library opens+ in Fusion perfectly well, so `ok` there must keep meaning "will open". These warnings+ mean something different and just as important - "is this fit to ORDER from".+ """+ md = _library_part_metadata(path)+ if not md.get("ok"):+ return {"ok": False, "warnings": [], "error": md.get("error")}+ warns = []+ for ds in md["devicesets"]:+ vs = ds["variants"]+ if not ds["description"]:+ warns.append({"code": "deviceset_no_description", "deviceset": ds["name"],+ "detail": "No <description>. Fusion's Content Manager shows this "+ "to whoever places the part; without it they are picking "+ "on a symbol outline alone."})+ for v in vs:+ if v["missing"]:+ warns.append({"code": "variant_missing_part_numbers",+ "deviceset": ds["name"], "variant": v["fullName"],+ "missing": v["missing"],+ "detail": "Missing %s. This bridge's own BOM export reads "+ "these exact attribute names, so the BOM comes out "+ "blank for this part." % ", ".join(v["missing"])})+ # ⚠️ THE VARIANT TRAP (John's automotive-vs-baseline case). Two variants that share+ # geometry are distinguishable ONLY by their metadata. If their MPNs match, or are+ # both absent, nobody downstream can tell an AEC-Q100 part from the cheap one.+ if len(vs) > 1:+ mpns = [v["attributes"].get("MPN") for v in vs]+ named = [m for m in mpns if m]+ if len(named) < len(vs):+ warns.append({"code": "variants_indistinguishable", "deviceset": ds["name"],+ "detail": "%d variants but only %d carry an MPN. Variants that "+ "share a symbol, footprint and 3D body are "+ "distinguishable ONLY by metadata - an automotive "+ "grade part and a baseline one look identical here."+ % (len(vs), len(named))})+ elif len(set(named)) < len(named):+ warns.append({"code": "duplicate_variant_mpn", "deviceset": ds["name"],+ "detail": "Two or more variants share the same MPN (%s), so "+ "they are not actually different parts - or one MPN "+ "is wrong." % ", ".join(sorted(set(+ m for m in named if named.count(m) > 1))[:3])})+ return {"ok": not warns, "warnings": warns, "totals": md["totals"]}+++def _handle_library_parts(fusion_info: dict, args: dict) -> dict:+ """Every ORDERABLE variant in a .lbr with its part numbers. Offline, no Fusion needed.++ For an EE this is the point of the library: two variants can share one symbol, one+ footprint and one 3D body and still be different products (an AEC-Q100 automotive part+ versus the baseline one). That difference lives entirely in the metadata.+ """+ path = (args.get("libraryPath") or args.get("lbrPath") or args.get("filePath")+ or args.get("path") or "").replace("\\", "/")+ if not path:+ return {"success": False, "errorCode": "no_path_argument",+ "error": "No library path given; the file was never examined.",+ "acceptedArgs": ["libraryPath", "lbrPath", "filePath", "path"]}+ md = _library_part_metadata(path)+ if not md.get("ok"):+ return {"success": False, "errorCode": "library_unreadable",+ "error": md.get("error"), "libraryPath": path}+ lint = _lint_part_metadata(path)+ t = md["totals"]+ return {+ "success": True, "libraryPath": path,+ "devicesets": md["devicesets"], "totals": t,+ "metadataOk": lint["ok"], "warnings": lint["warnings"],+ "attributeNames": list(PART_ATTRS),+ "_hint": ("%d deviceset(s), %d orderable variant(s); %d carry an MPN and %d a "+ "distributor PN. %s The full orderable identity is deviceset + device + "+ "technology (e.g. TJA1050 + T + -AUTO), because EAGLE puts package "+ "variants on <device> and grade/temperature variants on <technology> - "+ "which is how two variants share geometry and remain different parts."+ % (t["devicesets"], t["variants"], t["withMpn"], t["withDistributor"],+ ("No metadata warnings." if lint["ok"] else+ "%d warning(s) in `warnings` - these do NOT stop the library opening, "+ "but an EE cannot order from it as-is." % len(lint["warnings"])))),+ } def _handle_preflight_lbr(fusion_info: dict, args: dict) -> dict:@@ -13002,6 +13174,7 @@ def _handle_inspect_bodies(fusion_info: dict, args: dict) -> dict: COMMAND_HANDLERS["inspect_bodies"] = _handle_inspect_bodies COMMAND_HANDLERS["restart_for_addin"] = _handle_restart_for_addin COMMAND_HANDLERS["plan_library_import"] = _handle_plan_library_import+COMMAND_HANDLERS["library_parts"] = _handle_library_parts
skills/fusion-build-parts-library/SKILL.md+8@@ -103,3 +103,11 @@ with a newline ("LM358\nADOM-A" = MPN + variant, per-line auto-fit). With `outpu `<step>.manifest.json` records every setting + why + the calculated bboxes; to REFRESH a marking (e.g. add a variant line) re-call the verb with the manifest's `type` + `paramsCm` (`unitsCm:true`) + the new etch text - generation is deterministic, so placement reproduces exactly.++## Part numbers and variants (an EE cannot order without these)++Geometry is not the part. MPN, MANUFACTURER, MOUSER_PN, DIGIKEY_PN and LCSC_PN, and the+variant model that lets an automotive-grade part and a baseline one share one symbol, one+footprint and one 3D body, are covered in **`fusion-part-metadata`**. Check+`fusion_library_parts {libraryPath}` before calling any library complete - `ok: true` from+preflight only means the file OPENS.
skills/fusion-libraries/SKILL.md+8@@ -119,3 +119,11 @@ The full lane - pre-flight with measured ETAs, the four-artifact evidence bundle a false pass - is in **`fusion-web-control`**. Read it before importing a library or rendering a progress bar for one. Note that the wiki's web control and you share ONE verb surface: there is no verb only one side can call.++## Part numbers and variants (an EE cannot order without these)++Geometry is not the part. MPN, MANUFACTURER, MOUSER_PN, DIGIKEY_PN and LCSC_PN, and the+variant model that lets an automotive-grade part and a baseline one share one symbol, one+footprint and one 3D body, are covered in **`fusion-part-metadata`**. Check+`fusion_library_parts {libraryPath}` before calling any library complete - `ok: true` from+preflight only means the file OPENS.
skills/fusion-multipart-libraries/SKILL.md+8@@ -229,3 +229,11 @@ The full lane - pre-flight with measured ETAs, the four-artifact evidence bundle a false pass - is in **`fusion-web-control`**. Read it before importing a library or rendering a progress bar for one. Note that the wiki's web control and you share ONE verb surface: there is no verb only one side can call.++## Part numbers and variants (an EE cannot order without these)++Geometry is not the part. MPN, MANUFACTURER, MOUSER_PN, DIGIKEY_PN and LCSC_PN, and the+variant model that lets an automotive-grade part and a baseline one share one symbol, one+footprint and one 3D body, are covered in **`fusion-part-metadata`**. Check+`fusion_library_parts {libraryPath}` before calling any library complete - `ok: true` from+preflight only means the file OPENS.
skills/fusion-part-metadata/SKILL.mdadded+123@@ -0,0 +1,123 @@+---+name: fusion-part-metadata+description: >+ Part numbers and VARIANTS in a Fusion/EAGLE library - the metadata an EE actually orders+ from. MPN, MANUFACTURER, MOUSER_PN, DIGIKEY_PN, LCSC_PN; how EAGLE expresses variants+ (deviceset + device + technology) so two parts can share one symbol, one footprint and+ one 3D body and still be different products; per-variant descriptions; and the lint that+ catches an unorderable library. Read before building, importing, reviewing or linting any+ .lbr, and before telling anyone a library is complete.+ Trigger words - mpn, manufacturer part number, mouser part number, digikey, lcsc, part+ number, variant, variants, automotive grade, AEC-Q100, high temp, technology, deviceset,+ device, orderable, BOM blank, library metadata, fusion_library_parts.+discovery_triggers:+ - "mpn"+ - "part number"+ - "library variant"+ - "automotive grade part"+ - "fusion_library_parts"+ - "bom is blank"+---++# Part numbers and variants: what makes a library orderable++**The rule:** geometry is not the part. A library that carries a symbol, a footprint and a+3D body but no part numbers *looks* finished and is useless to an EE, because nothing in it+says what to buy.++> "one chip could be the same symbol, footprint, and 3d chip, but there might be an+> automotive high temp version vs a cheaper baseline variant" — John, 2026-08-28++That difference is invisible in the geometry. It lives **entirely** in the metadata. Ship a+library that drops it and you have not shipped an incomplete library, you have shipped a+misleading one.++## How EAGLE expresses a variant (three names, not one)++The orderable identity is **deviceset + device + technology**:++| Element | Carries | Example |+|---|---|---|+| `<deviceset>` | the logical part + its description | `TJA1050` |+| `<device>` | the **package** variant | `T` (SO08) |+| `<technology>` | the **grade / temperature** variant | `-AUTO` |++Attributes hang off `<technology>`, which is exactly *why* two variants can share one+symbol, one footprint and one 3D body and remain different products.++```xml+<deviceset name="TJA1050" prefix="U">+ <description>High-speed CAN transceiver, 1 Mbit/s</description>+ <devices><device name="T" package="SO08">+ <technologies>+ <technology name="">+ <attribute name="MPN" value="TJA1050T/CM,118"/>+ <attribute name="MANUFACTURER" value="NXP"/>+ <attribute name="DIGIKEY_PN" value="568-4885-1-ND"/>+ <attribute name="DESCRIPTION" value="Baseline, -40 to +125C"/>+ </technology>+ <technology name="-AUTO">+ <attribute name="MPN" value="TJA1050T/VM,118"/>+ <attribute name="MANUFACTURER" value="NXP"/>+ <attribute name="MOUSER_PN" value="771-TJA1050TVM118"/>+ <attribute name="DESCRIPTION" value="AEC-Q100 automotive, high temp"/>+ </technology>+ </technologies>+ </device></devices>+</deviceset>+```++Full names: `TJA1050T` and `TJA1050T-AUTO`.++## The attribute names are NOT arbitrary++Use exactly these. They are what this bridge's own BOM exporter reads+(`addin/AdomBridge/commands/manufacturing.py`); any other spelling produces a **blank BOM**:++`MPN` · `MANUFACTURER` · `MOUSER_PN` · `DIGIKEY_PN` · `LCSC_PN`++Add `DESCRIPTION` per technology when a deviceset has more than one variant. The deviceset+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.++## Check before you claim a library is done++```+fusion_library_parts {libraryPath}+```++Offline, no Fusion. Returns every variant with its attributes, plus `metadataOk` and+`warnings`. `fusion_preflight_lbr` reports the same under `metadataOk` /+`metadataWarnings` / `partTotals`.++### The four warnings, and why each one matters++| code | Means | Why it is dangerous |+|---|---|---|+| `variant_missing_part_numbers` | No MPN / MANUFACTURER / distributor PN | The BOM comes out blank for this part |+| `variants_indistinguishable` | Several variants, not all with an MPN | An automotive part and a baseline one look identical |+| `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 |++## These warnings do NOT block opening, deliberately++`fusion_preflight_lbr`'s `ok` keeps meaning **"this file will open in Fusion"**, because a+geometry-only library opens perfectly well. Whether it is fit to **order from** is a+different question, so it has its own key. Do not conflate them, and do not report a+library as complete on `ok: true` alone — check `metadataOk` too.++## For web control and the wiki++The bridge is the **end** of the chain: it can only carry through what it is handed. If a+wiki component's metadata lacks part numbers, this bridge cannot invent them, and the+right response is to report the gap upstream rather than emit a library that looks whole.+Surface `warnings` to the user with the variant names, so the fix happens where the data+lives.++## Verified behaviour++- Attributes **survive the 3D bind** — `<package3dinstances>` is inserted *before*+ `<technologies>`, so per-variant metadata is preserved through `fusion_build_library_3d`.+- 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.
skills/fusion-web-control/SKILL.md+8@@ -126,3 +126,11 @@ the user's machine, and a container path means nothing to it. A modal freezes the add-in's single main thread, so the bridge stops answering and every later verb reads as a crash. See the **fusion-no-modal-dialogs** skill. Short version: prove a command unnecessary offline, or pre-answer it — never issue one and plan to sweep up.++## Part numbers and variants (an EE cannot order without these)++Geometry is not the part. MPN, MANUFACTURER, MOUSER_PN, DIGIKEY_PN and LCSC_PN, and the+variant model that lets an automotive-grade part and a baseline one share one symbol, one+footprint and one 3D body, are covered in **`fusion-part-metadata`**. Check+`fusion_library_parts {libraryPath}` before calling any library complete - `ok: true` from+preflight only means the file OPENS.