← Commit history

v1.9.30: eight-surfaces parity + tour cold-start

John Lauer ·07d197f331 ·2mo ago ·parent 93eb19d
6 files changed +128−11
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.29\ No newline at end of file+1.9.30\ 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.29",-    "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.29/adom-bridge-fusion-v1.9.29.zip",-    "sha256": "7267697dff96a35ee9529206ddb42924e1cdf59ef936b5070ca4d23a9be5f2e0",-    "size": 379606,+    "version": "1.9.30",+    "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.30/adom-bridge-fusion-v1.9.30.zip",+    "sha256": "7ab9fde1f6ca13dbb6c792460d5e5346b4c54bbe5dd54532936bc7078e52feb4",+    "size": 380901,     "verbPrefixes": [         "fusion_"     ],
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.9.29",+  "version": "1.9.30",   "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.",
describe.py+2
@@ -205,6 +205,8 @@ _T = [     ("fusion_make_test_project", "Create a NEW electronics test project FROM SCRATCH around a library part - the Web Control demo's PROJECT button, and the answer to 'a customer only has a component page'. Generates a minimal test circuit server-side (the part + a generated 330R series resistor with VCC/GND stubs on a small board) as real EAGLE .sch/.brd, then imports the pair as a new electronics design. NO cloud, NO sign-in, no pre-existing content. After it, fusion_show_schematic / fusion_show_2d_board / fusion_show_3d_board all work on the result. Wiring uses the deviceset's own connects (first two pins), so 2-terminal parts get a real circuit and big ICs get a structurally-valid integration proof.", {"libraryPath": "required str (WINDOWS path .lbr; stage from container first)", "deviceset": "optional str (defaults to the library's first deviceset)"}, {"success": "bool", "schPath": "str", "brdPath": "str", "circuit": "str"}, 300, None, True, {"libraryPath": "C:/Temp/IN-S42ATR.lbr"}),     ("fusion_screenshot_fusion", "Background PrintWindow capture of Fusion's MAIN window (largest same-title window; works occluded/bottom-of-z-order, never raises anything) or a specific dialog by hwnd. Returns savedTo (PNG/WebP on the box; pull with pull_file). (Live since early versions but undescribed until 2026-08-15 - it was invisible to callers AND to the drift check.)", {"hwnd": "optional int (a dialog; default the main window)"}, {"success": "bool", "savedTo": "str", "sizeKB": "int"}, 60, None, False, {}),     ("fusion_bind_library_3d", "The CORRECT library in ONE call (the Library button's real bar: the 3D chip renders in the Content Manager preview): mints the part's 3D as a cloud f3d (make_3d_package), injects the wip_urn binding into the .lbr ON DISK (packages3d + package3dinstances), and reopens the library. Needs a signed-in Fusion session - the one cloud-dependent step in the wiki-to-EE circle. Autodesk's thumbnail preview lags ~2-3 min behind a fresh mint. NOTE: distinct from the older fusion_build_library_3d (multi-part batch pipeline).", {"libraryPath": "required str (WINDOWS .lbr path)", "stepPath": "required str (WINDOWS .step path)", "deviceset": "optional str (defaults to the library's first)"}, {"success": "bool", "wip_urn": "str", "bound": "bool", "reopened": "bool"}, 480, None, True, {"libraryPath": "C:/Temp/PART.lbr", "stepPath": "C:/Temp/PART.step"}),+    ("fusion_show_library", "Eight-surfaces contract: the LIBRARY surface. libraryPath (WINDOWS .lbr) opens it; name alone handles the already-open case (symmetric with kicad_show_library). Background; returns a screenshot path.", {"libraryPath": "optional str (WINDOWS .lbr)", "name": "optional str (already-open library name)"}, {"success": "bool", "view": "str", "screenshot": "str"}, 240, None, True, {"libraryPath": "C:/Temp/PART.lbr"}),+    ("fusion_show_project", "Eight-surfaces contract: bring the PROJECT/design to its top level. No args = activate this session's generated test project (fusion_make_test_project). query = cloud design name (already-open match wins, else fire-and-poll open). urn = open_by_urn passthrough. Background; returns a screenshot path.", {"query": "optional str (cloud design name)", "urn": "optional str", "fileName": "alias of query"}, {"success": "bool", "view": "str", "document": "str", "screenshot": "str"}, 240, "fusion_get_app_state", True, {"query": "BQ25792"}),     ("fusion_export_iges", "Export to IGES.", {"outputPath": "required str"}, {"format": "str"}, 300, None, True, {"outputPath": "C:/out/board.iges"}),     ("fusion_export_stl", "Export to STL mesh.", {"outputPath": "required str"}, {"format": "str"}, 300, None, True, {"outputPath": "C:/out/part.stl"}),     ("fusion_export_3mf", "Export to 3MF mesh.", {"outputPath": "required str"}, {"format": "str"}, 300, None, True, {"outputPath": "C:/out/part.3mf"}),
server.py+117−5
@@ -2071,7 +2071,7 @@ ADDIN_COMMAND_MAP = { # timeout in http_server.py should be the source of truth; these are # matched to that so urllib doesn't cut off before the add-in does. ADDIN_COMMAND_TIMEOUTS = {-    "show_symbol": 180, "show_footprint": 180, "show_3d_package": 300, "make_test_project": 300, "bind_library_3d": 480,+    "show_symbol": 180, "show_footprint": 180, "show_3d_package": 300, "make_test_project": 300, "bind_library_3d": 480, "show_library": 240, "show_project": 240,     "demo_progress": 15,     # getPhysicalProperties runs on Fusion's MAIN thread per component, so a big assembly     # (or assembly_bom with includePhysicalProperties) is slow. Give both real headroom.@@ -5628,7 +5628,16 @@ def _orchestrate_demo_tour(args: dict) -> dict:     demo_id = "tour-" + uuid.uuid4().hex[:8]     include_design = bool(args.get("includeDesign", True))     query = args.get("query") or ""-    steps = [+    # Cold-start (issue #30, verified live: a cold box gave a 100s bar ending in+    # nothing): when Fusion/add-in is not responding, the tour launches it itself,+    # as a REPORTED step so the front-end's bar covers the wait.+    cold = True+    try:+        probe = _proxy_to_addin("get_app_state", {}, timeout=8)+        cold = not probe.get("success")+    except Exception:+        pass+    steps = ([("launch", "Starting Fusion (cold start)", 120)] if cold else []) + [         ("stage", "Staging the sample library", 3),         ("library", "Opening the electronics library", 35),         ("symbol", "Symbol: full pinout in the symbol editor", 8),@@ -5676,7 +5685,20 @@ def _orchestrate_demo_tour(args: dict) -> dict:                 state.update(step=key, stepLabel=label,                              percent=int(100 * done_est / est),                              elapsedSec=int(time.time() - state["started"]))-                if key == "stage":+                if key == "launch":+                    dispatch_command("start", {})+                    ready = False+                    for _ in range(30):+                        time.sleep(5)+                        try:+                            if _proxy_to_addin("get_app_state", {}, timeout=8).get("success"):+                                ready = True+                                break+                        except Exception:+                            pass+                    if not ready:+                        raise RuntimeError("Fusion launched but its add-in never became ready (150s)")+                elif key == "stage":                     lbr = _demo_tour_sample_lbr()                     if not lbr:                         raise RuntimeError("bundled sample library missing from the bridge install")@@ -6243,6 +6265,92 @@ def _orchestrate_bind_library_3d(args: dict) -> dict:                       "fresh mint) - EDIT %s.dev then screenshot to verify the render." % deviceset)}  +def _orchestrate_show_library(args: dict) -> dict:+    """Eight-surfaces contract (ab SDK, issue #30): the LIBRARY surface by its smallest+    name. libraryPath opens it (open_lbr); name alone handles the already-open case."""+    import time as _t+    lbr = (args.get("libraryPath") or "").replace("\\", "/")+    name = (args.get("name") or "").strip()+    if lbr:+        opened = _orchestrate_open_lbr({"filePath": lbr})+        if not opened.get("success"):+            return {"success": False, "error": "open_lbr failed: " + str(opened.get("error"))[:150]}+        _t.sleep(2)+    elif name:+        st = _proxy_to_addin("get_app_state", {}, timeout=10)+        active = str((st.get("data") or {}).get("activeDocument", "")).lower()+        if name.lower() not in active:+            return {"success": False,+                    "error": "library %r is not the active document and no libraryPath was given" % name,+                    "_hint": "Pass libraryPath (WINDOWS .lbr path) to open it; name alone only handles the already-open case."}+    else:+        return {"success": False, "error": "libraryPath or name required."}+    out = {"success": True, "view": "library"}+    try:+        shot = _handle_screenshot_fusion({}, {})+        p = shot.get("savedTo") or shot.get("localSafePath")+        if shot.get("success") and p:+            out["screenshot"] = p+    except Exception:+        pass+    return out+++def _orchestrate_show_project(args: dict) -> dict:+    """Eight-surfaces contract: bring the PROJECT/design to its top level.+    - No args + a generated test project in this session: activates its board doc.+    - query: already-active match wins; else fire open_cloud_file + poll.+    - urn: open_by_urn passthrough."""+    import time as _t+    query = (args.get("query") or args.get("fileName") or "").strip()+    urn = (args.get("urn") or "").strip()+    if not query and not urn:+        if _TEST_PROJECT:+            ok = _test_project_activate("brd")+            out = {"success": bool(ok), "view": "project", "document": _TEST_PROJECT["docBase"],+                   "source": "generated test project"}+            if not ok:+                out["error"] = "could not activate the test project's board document"+            else:+                try:+                    shot = _handle_screenshot_fusion({}, {})+                    p = shot.get("savedTo") or shot.get("localSafePath")+                    if shot.get("success") and p:+                        out["screenshot"] = p+                except Exception:+                    pass+            return out+        return {"success": False, "error": "query (cloud design name) or urn required - or run fusion_make_test_project first."}+    if urn:+        return dispatch_command("open_by_urn", {"urn": urn})+    st = _proxy_to_addin("get_app_state", {}, timeout=10)+    active = str((st.get("data") or {}).get("activeDocument", "")).lower()+    if query.lower() in active:+        out = {"success": True, "view": "project", "document": active, "source": "already open"}+    else:+        dispatch_command("open_cloud_file", {"fileName": query, "wait": False})+        deadline = _t.time() + 170+        opened = False+        while _t.time() < deadline:+            st = _proxy_to_addin("get_app_state", {}, timeout=10)+            if query.lower() in str((st.get("data") or {}).get("activeDocument", "")).lower():+                opened = True+                break+            _t.sleep(5)+        if not opened:+            return {"success": False, "error": "%r never became the active document within 170s" % query,+                    "_hint": "Check the name with fusion_aps_search; cloud opens on cold sessions take minutes."}+        out = {"success": True, "view": "project", "document": query, "source": "cloud open"}+    try:+        shot = _handle_screenshot_fusion({}, {})+        p = shot.get("savedTo") or shot.get("localSafePath")+        if shot.get("success") and p:+            out["screenshot"] = p+    except Exception:+        pass+    return out++ def _orchestrate_demo(args: dict) -> dict:     """FIRST-TIME-USER DEMO: take a brand-new user from nothing to "wow" in one verb. @@ -6686,7 +6794,7 @@ def dispatch_command(command: str, args: dict, caller_identity: dict = None) ->         "capture_library_views", "generate_package", "export_optimized_glb",         "board_stackup", "save_to_cloud", "start",         "show_symbol", "show_footprint", "show_3d_package", "make_test_project",-        "bind_library_3d",+        "bind_library_3d", "show_library", "show_project",     }     if command in _WINDOW_RAISING:         if (args or {}).get("foreground"):@@ -6707,7 +6815,7 @@ def dispatch_command(command: str, args: dict, caller_identity: dict = None) ->     # The user should launch Fusion themselves; we just report the status.     # Commands that need Fusion running (add-in commands + orchestrated commands)     _OPEN_WITH_SCREENSHOT = {"open_schematic", "open_board", "show_3d_board", "show_2d_board", "show_schematic", "import_electronics"}-    if command in ADDIN_COMMANDS or command in ("open_lbr", "save_lbr", "attach_3d_package", "make_3d_package", "build_library_3d", "capture_library_views", "cleanup_cloud_files", "generate_package", "export_optimized_glb", "board_stackup", "show_symbol", "show_footprint", "show_3d_package", "make_test_project", "bind_library_3d") or command in _OPEN_WITH_SCREENSHOT:+    if command in ADDIN_COMMANDS or command in ("open_lbr", "save_lbr", "attach_3d_package", "make_3d_package", "build_library_3d", "capture_library_views", "cleanup_cloud_files", "generate_package", "export_optimized_glb", "board_stackup", "show_symbol", "show_footprint", "show_3d_package", "make_test_project", "bind_library_3d", "show_library", "show_project") or command in _OPEN_WITH_SCREENSHOT:         # Issue #326: Fusion may be installed under a different Windows user profile than the current         # one, so file-path detection fails even though Fusion is running. Check if it's actually         # running (via tasklist) as a reliable fallback. If running, it's installed somewhere.@@ -6841,6 +6949,10 @@ def dispatch_command(command: str, args: dict, caller_identity: dict = None) ->         return _merge_dialog_array(_orchestrate_make_test_project(args))     if command == "bind_library_3d":         return _merge_dialog_array(_orchestrate_bind_library_3d(args))+    if command == "show_library":+        return _merge_dialog_array(_orchestrate_show_library(args))+    if command == "show_project":+        return _merge_dialog_array(_orchestrate_show_project(args))     if command == "save_lbr":         return _merge_dialog_array(_orchestrate_save_lbr(args))     if command == "attach_3d_package":
tests/run_verb_tests.py+3
@@ -204,6 +204,7 @@ t("fusion_set_design_rules", "manual", note="mutates the board's DRC rules") # ── stage 5: libraries (weekly) + the show verbs (issue #29) ──────────────── t("fusion_open_lbr", "weekly", {"_dynamic": "staged_lbr"}, {"ok": True}) t("fusion_capture_library_views", "weekly", {"packages": ["ADS8588SIPM"]}, {"ok": True}, "after open_lbr; packages arg required")+t("fusion_show_library", "weekly", {"name": "ADS8588SIPM"}, {"ok": True}, "already-open case right after open_lbr") t("fusion_show_symbol", "weekly", {"name": "ADS8588SIPM"}, {"ok": True}, "library already open from open_lbr") t("fusion_show_footprint", "weekly", {"name": "ADS8588SIPM"}, {"ok": True}) t("fusion_show_symbol#wrap", "weekly",@@ -232,6 +233,8 @@ t("fusion_import_step", "weekly", {"_dynamic": "staged_step"}, {"ok": True}, t("fusion_make_test_project", "weekly", {"_dynamic": "staged_lbr_mtp"}, {"ok": True, "fields": ["schPath", "brdPath"]},   "from-scratch project generation around the staged sample part (John's no-BQ25792 rule)") t("fusion_screenshot_fusion", "daily", {}, {"ok": True, "fields": ["savedTo"]}, "background main-window capture")+t("fusion_show_project", "weekly", {}, {"ok": True, "fields": ["document"]},+  "no-args form: activates the generated test project from make_test_project just above") t("fusion_demo#tour", "weekly", {"mode": "tour", "includeDesign": False, "_pollTour": 300}, {"ok": True},   "the six-content-type showcase (issue #29): async start + progress polls until done") t("fusion_demo_progress#unknown", "negative", {"demoId": "tour-nope"}, {"pred": "must_refuse"})