← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.113+1.9.114
adom-bridge-fusion-manifest.json+4−4
@@ -1,10 +1,10 @@ {     "manifest_version": 1,     "name": "fusion360",-    "version": "1.9.113",-    "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.128/adom-bridge-fusion-v1.9.113.zip",-    "sha256": "2fb4e6334c4fa174d69eb987f513e6dc7a7ee6fe8d4d009a55e2cc7adbca0eac",-    "size": 449019,+    "version": "1.9.114",+    "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.129/adom-bridge-fusion-v1.9.114.zip",+    "sha256": "21824c866d5dfcef3afa70dcb34b6640f56cda1dee1ca9626b42a5537b7f952e",+    "size": 450971,     "verbPrefixes": [         "fusion_"     ],
aps.py+57
@@ -1422,3 +1422,60 @@ HANDLERS = {     "aps_upload": handle_upload,     "aps_get": handle_get, }+++# ── 3D thumbnail state: deterministic, free-endpoint, no pixels ─────────────────────────+# John, 2026-08-21: "you must ensure [the 3D thumbnail] is there ... is there a programmatic+# way to test for a thumbnail working correctly so you don't have to do a screenshot+# analysis?" There is: the Library Manager thumbnail is rendered by Autodesk's CLOUD against+# the bound f3d, and Data Management exposes it as a RELATIONSHIP on the file's version -+# present once rendered, absent while Fusion "takes its good old time". Reading that+# relationship is a free /data/v1 call: an HTTP/JSON state check, never image analysis.+++def thumbnail_state(project_id: str, folder_id: str, wip_urn: str = "", file_name: str = "") -> dict:+    """State of the cloud thumbnail for a file in a folder we uploaded to.++    Match by wip_urn (the .lbr package3d urn, minus ?version=) or by displayName.+    Returns {state: ready|pending|not_found|not_signed_in|error, item, thumbnailId}.+    """+    if not project_id or not folder_id:+        return {"state": "error", "why": "projectId and folderId required"}+    want_urn = (wip_urn or "").split("?", 1)[0].strip()+    status, data = _aps_get(+        "/data/v1/projects/%s/folders/%s/contents" % (project_id, folder_id),+        {"include": "versions"})+    if status is None:+        return {"state": "not_signed_in"}+    if status != 200 or not isinstance(data, dict):+        return {"state": "error", "http": status,+                "why": (data or {}).get("error") if isinstance(data, dict) else None}+    items = data.get("data") or []+    included = data.get("included") or []+    # join tip versions to items; match by version urn or display name+    hit_item = None+    hit_version = None+    for v in included:+        vid = str(v.get("id") or "").split("?", 1)[0]+        vname = str((v.get("attributes") or {}).get("displayName") or "")+        if (want_urn and vid == want_urn) or (file_name and vname.lower().startswith(file_name.lower())):+            hit_version = v+            break+    if hit_version is None and file_name:+        for it in items:+            nm = str((it.get("attributes") or {}).get("displayName") or "")+            if nm.lower().startswith(file_name.lower()):+                hit_item = it+                break+    src = hit_version or hit_item+    if src is None:+        return {"state": "not_found", "searched": {"urn": want_urn, "name": file_name},+                "itemCount": len(items)}+    rel = ((src.get("relationships") or {}).get("thumbnails") or {})+    tid = ((rel.get("data") or {}) or {}).get("id")+    if tid:+        return {"state": "ready", "thumbnailId": tid,+                "item": str((src.get("attributes") or {}).get("displayName") or "")}+    return {"state": "pending",+            "item": str((src.get("attributes") or {}).get("displayName") or ""),+            "why": "version exists but Fusion's cloud has not rendered a thumbnail yet"}
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.9.113",+  "version": "1.9.114",   "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.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.9.128"+version = "1.9.129" 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.128"+DASH_VERSION="1.9.129" 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.128",+  "version": "1.9.129",   "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+10−7
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.9.128",+  "version": "1.9.129",   "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",@@ -70,7 +74,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.",@@ -99,9 +106,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+67
@@ -9182,6 +9182,21 @@ def _handle_prove_library(fusion_info: dict, args: dict) -> dict:         evidence.append({"step": "ganged", "view": "library-manager", "ok": False,                          "error": str(r.get("error"))[:160]}) +    # THE THUMBNAIL IS PART OF THE VERDICT (John: "you must ensure its there"). A bound urn+    # whose cloud thumbnail never rendered shows the user a grey slab in the Library Manager+    # while every local check passes. Cloud state, not pixels; waits up to 3 min because+    # Fusion renders it asynchronously after a bind.+    thumb = None+    if artifacts["package3d"]["present"]:+        try:+            thumb = _handle_check_3d_thumbnail(fusion_info,+                                               {"libraryPath": lbr, "waitSeconds": 180})+            artifacts["package3d"]["thumbnail"] = (thumb.get("thumbnail") or {}).get("state")+            if not thumb.get("success"):+                missing.append("thumbnail(%s)" % ((thumb.get("thumbnail") or {}).get("state")+                                                  or thumb.get("errorCode") or "unknown"))+        except Exception as _e:+            missing.append("thumbnail(check-failed)")     captured = [e for e in evidence if e.get("ok") and e.get("screenshot")]     proven = (not missing) and len(captured) == 4     out = {"success": proven, "proven": proven, "libraryPath": lbr, "deviceset": want,@@ -9525,6 +9540,58 @@ def _handle_create_library(fusion_info: dict, args: dict) -> dict: COMMAND_HANDLERS["create_library"] = _handle_create_library  ++def _handle_check_3d_thumbnail(fusion_info: dict, args: dict) -> dict:+    """Is the Library Manager's 3D THUMBNAIL actually rendered? Deterministic, no pixels.++    The thumbnail is rendered by Autodesk's cloud against the bound f3d and exposed as a+    relationship on the file's version - a free /data/v1 read. Fusion takes its time after a+    bind, so `waitSeconds` polls (default 0 = single check). This is the check John named as+    "an area you always screw up": a bound urn with NO rendered thumbnail shows the user a+    grey slab in the Library Manager while everything else reports success.++    args: {libraryPath | urn, fileName?, projectId?, folderId?, waitSeconds?}+    """+    import time as _t+    import aps as _aps+    urn = (args.get("urn") or "").strip()+    lbr = (args.get("libraryPath") or "").replace("\\", "/")+    file_name = args.get("fileName") or ""+    if not urn and lbr:+        try:+            import xml.etree.ElementTree as _ET+            root = _ET.parse(lbr).getroot()+            for p3 in root.iter("package3d"):+                urn = (p3.get("urn") or "").strip() or urn+                file_name = file_name or (p3.get("name") or "")+        except Exception:+            pass+    if not urn and not file_name:+        return {"success": False, "errorCode": "no_3d_reference",+                "error": "No urn/fileName given and the .lbr carries no package3d urn.",+                "_hint": "Bind first (fusion_bind_library_3d), then check the thumbnail."}+    project_id, folder_id = _discover_upload_target(args)+    if not folder_id:+        return {"success": False, "errorCode": "no_work_folder",+                "error": "Cannot resolve the upload folder to look in (APS signed in?)."}+    deadline = _t.time() + float(args.get("waitSeconds") or 0)+    while True:+        st = _aps.thumbnail_state(project_id, folder_id, wip_urn=urn, file_name=file_name)+        if st.get("state") == "ready" or _t.time() >= deadline:+            break+        _t.sleep(10)+    ready = st.get("state") == "ready"+    return {"success": ready, "thumbnail": st, "urn": urn or None,+            "_hint": ("Thumbnail is RENDERED in the cloud - the Library Manager will show the "+                      "real chip." if ready else+                      "Not rendered yet (state=%s). Fusion uploads + renders asynchronously "+                      "after a bind - re-call with waitSeconds:180 to poll, and treat a "+                      "still-pending thumbnail as NOT proven." % st.get("state"))}+++COMMAND_HANDLERS["check_3d_thumbnail"] = _handle_check_3d_thumbnail++ def dispatch_command(command: str, args: dict, caller_identity: dict = None) -> dict:     """Thin wrapper: run the verb, then guarantee the windowEtiquette annotation lands on     EVERY backgrounded window-raising result - the inner dispatcher's closure only covered