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.8: import_step in catalog + suite fixture staging
6 files changed
+113−14
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.7+1.9.8
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.9.7",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.7/adom-bridge-fusion-v1.9.7.zip",- "sha256": "966131c4d4fdf838be5a9055907f5cab846c4956b35e017d9235be2f5f2c15e3",- "size": 305121,+ "version": "1.9.8",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.8/adom-bridge-fusion-v1.9.8.zip",+ "sha256": "57a6ff28d7cbe7dbfe28b8fe4fbe995cc996e21fe93e46d2e6fba1cbefc9412f",+ "size": 305282, "verbPrefixes": [ "fusion_" ],
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.9.7",+ "version": "1.9.8", "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@@ -176,6 +176,7 @@ _T = [ # ── CAD & 3D export ──────────────────────────────────────────────────── ("fusion_export_step", "Export the active design to STEP (needs the 3D view).", {"outputPath": "required str"}, {"format": "str", "fileSizeKB": "int"}, 300, None, True, {"outputPath": "C:/out/board.step"}),+ ("fusion_import_step", "Import a STEP/.stp (or other native-importable) file into a NEW Fusion design via the native import API. The open-electronics hints redirect .step here. (Was implemented but missing from this catalog until the verb suite's reverse-drift check caught it, 2026-08-14.)", {"filePath": "required str (WINDOWS path C:/...; stage from container first)"}, {"success": "bool", "documentName": "str"}, 300, None, True, {"filePath": "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"}),
tests/run_verb_tests.py+55−8@@ -265,6 +265,44 @@ _SECTION_BY_VERB = { "fusion_uninstall": "bridge-lifecycle", "fusion_set_auto_update": "bridge-lifecycle", } +def stage_dynamic(target, kind):+ """Stage a dynamic fixture on the box at call time. Returns (args, None) or (None, why).++ Fixtures are real user files, staged the way a user's AI would stage them: text+ fixtures (STEP is ISO-10303 ASCII, .lbr is XML) travel via write_file to the+ scratch dir; the URN comes from a live aps_search. No test passes on canned data.+ """+ try:+ if kind == "staged_step":+ content = (REPO / "examples" / "ADS8588SIPM.step").read_text()+ dest = SCRATCH + "/ADS8588SIPM.step"+ r, _ = call(target, "write_file",+ {"path": dest, "content": content,+ "reason": "verb-test fixture: STEP sample for fusion_import_step"}, timeout=120)+ if not (r.get("success") or r.get("status") == "ok"):+ return None, "write_file: " + str(r.get("error"))[:80]+ return {"filePath": dest}, None+ if kind == "staged_lbr":+ content = (REPO / "examples" / "ADS8588SIPM.fusion.lbr").read_text()+ dest = SCRATCH + "/ADS8588SIPM.fusion.lbr"+ r, _ = call(target, "write_file",+ {"path": dest, "content": content,+ "reason": "verb-test fixture: .lbr sample for fusion_open_lbr"}, timeout=120)+ if not (r.get("success") or r.get("status") == "ok"):+ return None, "write_file: " + str(r.get("error"))[:80]+ return {"filePath": dest}, None+ if kind == "urn_from_search":+ r, _ = call(target, "fusion_aps_search", {"query": "BQ25792", "limit": 5}, timeout=90)+ for item in (r.get("results") or r.get("items") or []):+ urn = item.get("urn") or item.get("id")+ if urn and str(urn).startswith("urn:"):+ return {"urn": urn, "wait": False}, None+ return None, "no URN in aps_search results"+ return None, "unknown fixture kind " + kind+ except Exception as e:+ return None, str(e)[:100]++ def section_of(name): base = name.split("#")[0] if base in _SECTION_BY_VERB:@@ -294,25 +332,34 @@ def main(): fusion_was_running = pre.get("errorCode") != "fusion_not_running" described = {v[0] for v in describe._T}- untriaged = described - {k.split("#")[0] for k in PLAN}+ planned = {k.split("#")[0] for k in PLAN} - {"focus_invariance"} results, t0 = [], time.time()- for name in sorted(untriaged):+ # Catalog drift fails the run in BOTH directions: a described verb nobody triaged,+ # AND a planned verb missing from describe (fusion_import_step shipped undescribed+ # for weeks until this reverse check was added, 2026-08-14).+ for name in sorted(described - planned): results.append({"verb": name, "status": "FAIL", "why": "described but untriaged in tests/run_verb_tests.py"})+ for name in sorted(planned - described):+ results.append({"verb": name, "status": "FAIL", "why": "in the test plan but MISSING from describe.py - undescribed verbs are invisible to callers"}) for name, spec in PLAN.items(): if spec["tier"] == "manual" or spec["tier"] not in tiers or (only and name not in only): if spec["tier"] == "manual" and not only: results.append({"verb": name, "status": "MANUAL", "why": spec.get("note", "")}) continue- if "_dynamic" in spec["args"]:- results.append({"verb": name, "status": "SKIP", "why": "dynamic fixture not staged this run"})- continue+ run_args = spec["args"]+ if "_dynamic" in run_args:+ run_args, stage_err = stage_dynamic(a.target, run_args["_dynamic"])+ if run_args is None:+ results.append({"verb": name, "status": "FAIL", "why": "fixture staging failed: " + stage_err})+ print(" %-42s FAIL - fixture staging failed: %s" % (name, stage_err))+ continue verb = name.split("#")[0] vt = max(90, CATALOG_TIMEOUTS.get(verb, 60) + 30)- if "_pollDoc" in spec["args"]:- d, dt = call_polled(a.target, verb, spec["args"], spec["args"]["_pollDoc"], spec["args"]["_pollSec"])+ if "_pollDoc" in run_args:+ d, dt = call_polled(a.target, verb, run_args, run_args["_pollDoc"], run_args["_pollSec"]) else:- d, dt = call(a.target, verb, spec["args"], timeout=vt)+ d, dt = call(a.target, verb, run_args, timeout=vt) errs = expect(d, spec["exp"]) if name.startswith("fusion_search_cloud_files") or name.startswith("fusion_walk_cloud_tree"): # must refuse: success:true here is the FAILURE
tools/release-files.txtadded+51@@ -0,0 +1,51 @@+BRIDGE_VERSION+LICENSE+ad_client.py+addin/AdomBridge/AdomBridge.manifest+addin/AdomBridge/AdomBridge.py+addin/AdomBridge/commands/__init__.py+addin/AdomBridge/commands/app_state.py+addin/AdomBridge/commands/assembly_bom.py+addin/AdomBridge/commands/close_document.py+addin/AdomBridge/commands/cloud_documents.py+addin/AdomBridge/commands/design_info.py+addin/AdomBridge/commands/document_info.py+addin/AdomBridge/commands/electron_view.py+addin/AdomBridge/commands/electronics.py+addin/AdomBridge/commands/export.py+addin/AdomBridge/commands/import_file.py+addin/AdomBridge/commands/manufacturing.py+addin/AdomBridge/commands/modeling.py+addin/AdomBridge/commands/open_electronics_file.py+addin/AdomBridge/commands/open_lbr.py+addin/AdomBridge/commands/parameters.py+addin/AdomBridge/commands/physical_properties.py+addin/AdomBridge/commands/screenshot.py+addin/AdomBridge/commands/silkscreen_capture.py+addin/AdomBridge/http_server.py+addin/AdomBridge/resources/cam/jlcpcb-2layers.cam+addin/AdomBridge/resources/cam/jlcpcb-4layers.cam+addin/AdomBridge/resources/dru/Adom-InstaPCB-2-Layer.edru+addin/AdomBridge/resources/dru/Adom-InstaPCB-4-Layer.edru+addin/AdomBridge/resources/dru/AdomDefaults-jlcpcb-2layers-accurate.edru+addin/AdomBridge/resources/dru/AdomDefaults-jlcpcb-4layers-accurate.edru+addin/AdomBridge/resources/ulp/detect_layers.ulp+adom-bridge-fusion-manifest.json+aps.py+bridge.json+describe.py+eagle_lbr.py+fusion_detect.py+fusion_helper.py+handlers/__init__.py+handlers/close_fusion.py+handlers/dialog_classify.py+handlers/dismiss_recovery.py+handlers/fusion_ui.py+handlers/open_design.py+install.sh+install_addin.py+package.json+server.py+start.bat+uninstall.sh