← Commit history

v1.9.8: import_step in catalog + suite fixture staging

John Lauer ·5033497410 ·1mo ago ·parent 79f3069
6 files changed +113−14
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.7\ No newline at end of file+1.9.8\ 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.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