← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.309\ No newline at end of file+1.9.310\ No newline at end of file
adom-bridge-fusion-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "fusion360",-  "version": "1.9.309",-  "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.309/adom-bridge-fusion-v1.9.309.zip",-  "sha256": "ff9559d26ca94e102e970038e0f036c6fdcf0379bc3e08c328868abdcc10790b",-  "size": 589869,+  "version": "1.9.310",+  "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.310/adom-bridge-fusion-v1.9.310.zip",+  "sha256": "f0db7d034a3ec2baded372676a18384682d585e5d314fb59c050998ebe28e662",+  "size": 592356,   "verbPrefixes": [     "fusion_"   ],
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.9.309",+  "version": "1.9.310",   "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.309"+version = "1.9.310" dependencies = [  "serde_json", ]
dashboard/Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.9.309"+version = "1.9.310" 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.309"+DASH_VERSION="1.9.310" 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.309",+  "version": "1.9.310",   "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.309",+  "version": "1.9.310",   "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+145
@@ -2825,6 +2825,7 @@ COMMAND_HANDLERS = {         or {"success": True, "dialogsDetected": 0, "dialogs": []},         float(args.get("alertsSinceSec", 300))),     # Fusion's own error/warning toasts (the CEF overlays no window enumeration can see).+    "managed_library_create": lambda fi, args: _handle_managed_library_create(fi, args),     "alerts": lambda fi, args: {         "success": True,         "alerts": _fusion_alerts(float(args.get("sinceSec", 300))),@@ -15619,6 +15620,150 @@ def _handle_live_feed(fusion_info: dict, args: dict) -> dict:                       "'[main #n] ...'). tools/live_feed_drain.py does exactly that.")}  +# ── library.io managed libraries (John, 2026-09-01) ──────────────────────────+# Fusion's LIBRARY.IO commands live in the EAGLE-hosted library editor: they are NOT Fusion+# CommandDefinitions (all 3100 were enumerated; none match) and the ribbon is invisible to UIA,+# so the only drive is coordinates. Everything here is therefore SELF-VERIFYING: each step+# confirms the real dialog window it expects (those ARE top-level and UIA-reachable) and fails+# with the step that broke rather than clicking on blindly. See dev-skills/fusion-cloud-libraries.+_RIBBON_DIP = {           # measured on ConfRoomROG, Fusion 2026; DIP so it ports across DPI+    "manage_tab": (296, 64),+    "libraryio_group": (455, 126),+    "push_item": (466, 148),+}+++def _dip_to_screen(dip_xy, info):+    """DIP (device-independent px) -> physical screen px for the Fusion window."""+    scale = float(info.get("dpiScale") or 1.0) or 1.0+    rect = info.get("rect") or {}+    return (int(rect.get("left", 0) + dip_xy[0] * scale),+            int(rect.get("top", 0) + dip_xy[1] * scale))+++def _fusion_main_window():+    """{hwnd, rect, dpiScale} for Fusion's main window, or None."""+    try:+        r = _ad_call("desktop_list_windows", {}, timeout=20) or {}+        out = r.get("output")+        data = json.loads(out) if isinstance(out, str) else (r.get("data") or {})+        for w in ((data.get("data") or data).get("windows") or []):+            t = str(w.get("title") or "")+            if "Autodesk Fusion" in t and w.get("kind") == "normal":+                rect = w.get("rect") or {}+                dpi = 1.0+                try:+                    sc = _ad_call("desktop_screenshot_screen", {}, timeout=30) or {}+                    dpi = float(((sc.get("coordMap") or {}).get("dpiScale")) or 1.0)+                except Exception:+                    pass+                return {"hwnd": w.get("hwnd"), "rect": rect, "dpiScale": dpi, "title": t}+    except Exception:+        pass+    return None+++def _wait_for_dialog(title_substr: str, timeout_s: float = 20.0):+    """Wait for a REAL top-level Fusion dialog whose title contains title_substr."""+    import time as _t+    deadline = _t.time() + timeout_s+    while _t.time() < deadline:+        try:+            d = _capture_dialog_array(settle=0.2) or {}+            for x in d.get("dialogs") or []:+                if title_substr.lower() in str(x.get("title") or "").lower():+                    return x+        except Exception:+            pass+        _t.sleep(1.0)+    return None+++def _handle_managed_library_create(fusion_info: dict, args: dict) -> dict:+    """Publish the library that is IN FRONT as a library.io managed library.++    Preconditions Fusion enforces (both refused here rather than prompting the user):+      1. a LIBRARY document is in front (not a design, not a board)+      2. that document is saved in a Fusion HUB - a library opened from a local .lbr is not,+         and Fusion answers "Cannot create a managed library from a document not saved in a+         Fusion Hub" as an in-canvas CEF error no window enumeration can see (fusion_alerts).+    """+    import time as _t+    st = _proxy_to_addin("get_app_state", {}, timeout=10) or {}+    data = st.get("data") or {}+    doc = str(data.get("activeDocument") or "")+    ws = str(data.get("activeWorkspace") or "")+    if "librar" not in ws.lower():+        return {"success": False, "errorCode": "not_a_library",+                "error": "The document in front is %r in the %s workspace, not a library."+                         % (doc, ws or "unknown"),+                "_hint": "fusion_open_lbr {lbrPath} first - it lands the library editor in front "+                         "and verifies it, which activate_document alone does NOT."}+    hub = _proxy_to_addin("run_modeling_script", {"script": (+        "import adsk.core\n"+        "app=adsk.core.Application.get(); d=app.activeDocument\n"+        "df=None\n"+        "try: df=d.dataFile\n"+        "except Exception: df=None\n"+        "result={'name': d.name, 'inHub': df is not None,"+        " 'id': (df.id if df else None)}\n")}, timeout=30) or {}+    hub_res = ((hub.get("data") or {}).get("result")) or {}+    if not hub_res.get("inHub"):+        return {"success": False, "errorCode": "not_in_hub",+                "error": "Library %r is not saved in a Fusion Hub, so Fusion cannot make it a "+                         "managed library." % (hub_res.get("name") or doc),+                "remedyVerb": "fusion_save_to_cloud",+                "_hint": "fusion_save_to_cloud {name, projectName, folderPath} on this library "+                         "first; a real library save returns a fileId ending in .flbr (a .f3d "+                         "means the WRONG document was in front)."}+    win = _fusion_main_window()+    if not win:+        return {"success": False, "errorCode": "no_fusion_window",+                "error": "Could not locate Fusion's main window to drive the ribbon."}+    steps = []+    def _click_dip(key, why):+        x, y = _dip_to_screen(_RIBBON_DIP[key], win)+        _ad_call("desktop_click", {"x": x, "y": y, "reason": why}, timeout=25)+        steps.append({"step": key, "at": [x, y]})+    _click_dip("manage_tab", "open the library editor's MANAGE tab")+    _t.sleep(1.5)+    _click_dip("libraryio_group", "open the LIBRARY.IO command group")+    _t.sleep(1.5)+    # The popup is its own window: a foregrounding click would dismiss it, so use the raw+    # mouse verb (measured 2026-09-01).+    px, py = _dip_to_screen(_RIBBON_DIP["push_item"], win)+    _ad_call("desktop_mouse_click", {"x": px, "y": py,+                                     "reason": "Push changes to library.io"}, timeout=25)+    steps.append({"step": "push_item", "at": [px, py]})+    dlg = _wait_for_dialog("Create Managed Library", 25)+    if not dlg:+        return {"success": False, "errorCode": "create_dialog_missing", "steps": steps,+                "alerts": _fusion_alerts(120),+                "error": "The Create Managed Library dialog never appeared - the ribbon click "+                         "probably missed (the ribbon is invisible to UIA, so this verb drives "+                         "measured DIP offsets).",+                "_hint": "Re-run with the Fusion window maximized, or recalibrate _RIBBON_DIP "+                         "from a desktop_screenshot_screen coordMap."}+    ok = _ad_call("desktop_ui_click", {"hwnd": dlg.get("hwnd"), "name": "Create",+                                       "reason": "create the managed library on library.io"},+                  timeout=30) or {}+    steps.append({"step": "create", "ok": bool(ok.get("success"))})+    conf = _wait_for_dialog("Create Managed Library", 40)   # the confirmation reuses the title+    if conf:+        _ad_call("desktop_ui_click", {"hwnd": conf.get("hwnd"), "name": "OK",+                                      "reason": "acknowledge the managed-library confirmation"},+                 timeout=30)+        steps.append({"step": "ok"})+    alerts = _fusion_alerts(120)+    return {"success": not alerts, "library": hub_res.get("name") or doc, "steps": steps,+            "alerts": alerts,+            "_hint": ("Managed library created; it lives under the account's managed FOLDER on "+                      "library.io (not public - a public search will not find it). Share it by "+                      "managing that folder on library.io." if not alerts else+                      "Fusion raised an alert during the push - read alerts[] and treat this as "+                      "failed.")}++ def _handle_restart_for_addin(fusion_info: dict, args: dict) -> dict:     """Save open documents, restart Fusion, wait for the add-in, report back.