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
v1.9.30: eight-surfaces parity + tour cold-start
6 files changed
+128−11
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.29+1.9.30
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"})