← Commit history

v1.9.28: bind_library_3d one-call

John Lauer ·dae8161fd9 ·2mo ago ·parent 9970399
6 files changed +68−8
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.27\ No newline at end of file+1.9.28\ 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.27",-    "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.27/adom-bridge-fusion-v1.9.27.zip",-    "sha256": "ba577894f317fce8b6da1c2bb58ef8fa09be6f9f39608377f7e9ddff70af231a",-    "size": 378383,+    "version": "1.9.28",+    "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.28/adom-bridge-fusion-v1.9.28.zip",+    "sha256": "87e49c4489c299f4dc478daf78f33554f1418a02a73775d70238e1555501179c",+    "size": 379445,     "verbPrefixes": [         "fusion_"     ],
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.9.27",+  "version": "1.9.28",   "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+1
@@ -204,6 +204,7 @@ _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_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+60−2
@@ -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,+    "show_symbol": 180, "show_footprint": 180, "show_3d_package": 300, "make_test_project": 300, "bind_library_3d": 480,     "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.@@ -6167,6 +6167,61 @@ def _orchestrate_make_test_project(args: dict) -> dict:     return out  +def _orchestrate_bind_library_3d(args: dict) -> dict:+    """ONE CALL for the correct library (John's bar: the 3D renders in the Content+    Manager): mint the part's 3D as a cloud f3d, inject the wip_urn binding into the+    .lbr ON DISK, and reopen the library. Needs a signed-in Fusion session (the one+    cloud-dependent step in the circle).++    args: {libraryPath: Windows .lbr (required), stepPath: Windows .step (required),+           deviceset: optional (defaults to the library's first)}+    """+    import time as _t+    lbr_path = (args.get("libraryPath") or "").replace("\\", "/")+    step_path = (args.get("stepPath") or "").replace("\\", "/")+    if not (lbr_path and step_path):+        return {"success": False, "error": "libraryPath (.lbr) and stepPath (.step) required (Windows paths)."}+    try:+        txt = open(lbr_path, encoding="utf-8", errors="replace").read()+    except Exception as e:+        return {"success": False, "error": "cannot read %s: %s" % (lbr_path, e)}+    deviceset = args.get("deviceset")+    if not deviceset:+        m = re.search(r'<deviceset name="([^"]+)"', txt)+        if not m:+            return {"success": False, "error": "no deviceset in the library"}+        deviceset = m.group(1)+    pkg_m = re.search(r'<package name="([^"]+)"', txt)+    pkg_name = pkg_m.group(1) if pkg_m else deviceset+    mint = _apply_failure_dialogs(_orchestrate_make_3d_package(+        {"lbrPath": lbr_path, "modelPath": step_path,+         "fileName": "ADOM-%s-3D" % re.sub(r"[^A-Za-z0-9_-]", "_", deviceset)[:40]}))+    urn = mint.get("wip_urn") or ""+    if not urn:+        return {"success": False, "error": "mint failed: " + str(mint.get("error"))[:150],+                "_hint": str(mint.get("_hint") or "")[:150]}+    txt = re.sub(r'<packages3d>.*?</packages3d>\n?', '', txt, flags=re.S)+    binding = ('<packages3d><package3d name="%s" urn="" wip_urn="%s" locally_modified="yes" type="model">'+               '<packageinstances><packageinstance name="%s"/></packageinstances>'+               '</package3d></packages3d>' % (deviceset, urn, pkg_name))+    txt = txt.replace('</packages>', '</packages>\n' + binding, 1)+    txt = re.sub(r'<package3dinstances>.*?</package3dinstances>', '', txt, flags=re.S)+    txt = txt.replace('<technologies>',+                      '<package3dinstances><package3dinstance package3d_urn="%s"/></package3dinstances><technologies>' % urn, 1)+    try:+        open(lbr_path, "w", encoding="utf-8").write(txt)+    except Exception as e:+        return {"success": False, "error": "binding write failed: %s" % e, "wip_urn": urn}+    _t.sleep(1)+    reopened = _orchestrate_open_lbr({"filePath": lbr_path})+    return {"success": bool(reopened.get("success")), "wip_urn": urn,+            "libraryPath": lbr_path, "deviceset": deviceset,+            "bound": True, "reopened": bool(reopened.get("success")),+            "_hint": ("Library re-opened WITH the 3D binding. The Content Manager's 3D preview "+                      "resolves after Autodesk's thumbnail service catches up (~2-3 min for a "+                      "fresh mint) - EDIT %s.dev then screenshot to verify the render." % deviceset)}++ def _orchestrate_demo(args: dict) -> dict:     """FIRST-TIME-USER DEMO: take a brand-new user from nothing to "wow" in one verb. @@ -6610,6 +6665,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",     }     if command in _WINDOW_RAISING:         if (args or {}).get("foreground"):@@ -6630,7 +6686,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") 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") 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.@@ -6762,6 +6818,8 @@ def dispatch_command(command: str, args: dict, caller_identity: dict = None) ->         return _merge_dialog_array(_orchestrate_show_3d_package(args))     if command == "make_test_project":         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 == "save_lbr":         return _merge_dialog_array(_orchestrate_save_lbr(args))     if command == "attach_3d_package":
tests/run_verb_tests.py+1
@@ -217,6 +217,7 @@ t("fusion_show_symbol#wrap", "weekly",   {"ok": True}, "bare-symbol wrapping: the bridge generates the library around the fragment (John, 2026-08-14)") t("fusion_generate_package", "weekly", {"type": "chip", "params": {"D": 1.6, "E": 0.8, "A": 0.45}}, {"ok": True}) t("fusion_show_3d_package", "manual", note="enters Package3DEnvironment (exit needs Package3DStop + dialog); tour covers it; attach path in release rehearsals")+t("fusion_bind_library_3d", "manual", note="cloud f3d mint per run; release rehearsals (needs signed-in session)") t("fusion_attach_3d_package", "manual", note="modal Save dialog needs a human eye") t("fusion_make_3d_package", "manual", note="cloud upload; run in release rehearsals") t("fusion_build_library_3d", "manual", note="long multi-part build; release rehearsals")