← Commit history

v1.5.0: ship fusion_describe (self-describe 41 verbs for AD Verbs tab)

John Lauer ·b5739b1c65 ·4mo ago ·parent 028ac16
6 files changed +150−8
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.4.9\ No newline at end of file+1.5.0
adom-bridge-fusion-manifest.json+6−6
@@ -1,9 +1,9 @@ {   "manifest_version": 1,   "name": "fusion360",-  "version": "1.4.9",-  "url": "https://wiki.adom.inc/api/v1/pages/adom-desktop-fusion-bridge/files/adom-bridge-fusion-v1.4.9.zip",-  "sha256": "201fd9afecc8d1f9afb10f4d017004c57896959db23dc61d85b5d65a1f55c6e9",-  "size": 187168,-  "released_at": "2026-06-26T00:00:00Z"-}\ No newline at end of file+  "version": "1.5.0",+  "url": "https://wiki.adom.inc/api/v1/pages/adom-desktop-fusion-bridge/files/adom-bridge-fusion-v1.5.0.zip",+  "sha256": "77e8bd788215264c30ad346a2011640b7f6597a623669a743ede8b58853d8f84",+  "size": 191002,+  "released_at": "2026-06-27T14:56:41Z"+}
adom-bridge-fusion-v1.5.0.zipadded
⋯ 1 unchanged line ⋯
bridge.json+2−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.4.9",+  "version": "1.5.0",   "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://github.com/adom-inc/adom-desktop-fusion-bridge",   "author": "Adom Inc.",@@ -19,6 +19,7 @@     "fusion_"   ],   "verbs": [+    "fusion_describe",     "fusion_start",     "fusion_close_window",     "fusion_apply_instapcb_rules",
describe.pyadded+139
@@ -0,0 +1,139 @@+"""fusion_describe — self-describe the bridge's verb surface for Adom Desktop.++AD v1.9.23+ added a Verbs tab to each bridge's detail pop-out (lists verbs,+expands each for its I/O + timeout + status schema, with an inline runner). AD+does NOT hardcode a cloud-owned bridge's verbs, so the bridge must self-describe.+AD POSTs {command:"describe", args:{}} and caches the result at ~/.adom/bridge-verbs/.++Each entry: name, summary, input, output, timeoutSeconds, statusVerb, longRunning,+example. Keep this in step with the actual handlers as verbs are added.+"""++_STATUS = "fusion_get_app_state"++# Compact source table: name -> (summary, input, output, timeout, statusVerb, longRunning, example)+_T = [+    # ── Lifecycle ──────────────────────────────────────────────────────────+    ("fusion_start", "Launch Fusion 360 (if needed) and wait for the AdomBridge add-in to be ready.",+     {}, {"success": "bool", "addinReady": "bool", "status": "str"}, 150, None, True, {}),+    ("fusion_close_window", "Close the active Fusion document/window.",+     {"name": "optional str (default: active)"}, {"success": "bool"}, 30, None, False, {}),+    ("fusion_get_app_state", "Current app state: running, active document, workspace, isElectronics. The poll/status verb for long-running calls.",+     {}, {"activeDocument": "str", "activeWorkspace": "str", "isElectronics": "bool"}, 20, None, False, {}),++    # ── Cloud search & files (APS) ─────────────────────────────────────────+    ("fusion_aps_status", "APS state: configured, signed in, token live, never-charge meter.",+     {}, {"configured": "bool", "signedIn": "bool", "neverCharge": "bool"}, 20, None, False, {}),+    ("fusion_aps_set_client_id", "Set the org's APS (PKCE) client id used for cloud search.",+     {"clientId": "required str"}, {"success": "bool"}, 20, None, False, {"clientId": "D5nn...EPAq"}),+    ("fusion_aps_signin", "Open the Autodesk sign-in in the browser; a background listener captures the token.",+     {}, {"success": "bool", "authUrl": "str"}, 30, "fusion_aps_status", False, {}),+    ("fusion_aps_search", "Server-indexed search across the whole team hub (seconds, not a 30-min folder walk).",+     {"query": "required str", "limit": "optional int (default 50)", "hubId": "optional str", "projectId": "optional str"},+     {"count": "int", "results": "[{name,id,projectName,lastModified}]"}, 60, None, False, {"query": "Molecule", "limit": 10}),+    ("fusion_aps_open", "Find a cloud design by name and open it in Fusion (any folder depth). Fire-and-poll.",+     {"query": "required str", "wait": "optional bool"}, {"opening": "bool", "match": "{name}", "statusVerb": "str"}, 60, _STATUS, True, {"query": "BME690 Molecule"}),+    ("fusion_open_by_urn", "Open a cloud design directly by its APS/Fusion URN.",+     {"urn": "required str"}, {"success": "bool", "output": "str"}, 240, _STATUS, True, {"urn": "urn:adsk.wipprod:fs.file:vf.xxxx?version=1"}),+    ("fusion_aps_browse", "Browse hubs/projects/folders without launching Fusion.",+     {"hubId": "optional str", "projectId": "optional str", "folderId": "optional str"}, {"items": "[{name,type,id}]"}, 60, None, False, {}),+    ("fusion_aps_recent", "Most-recently-modified designs across the team hub, newest first.",+     {"limit": "optional int (default 25)"}, {"count": "int", "results": "[...]"}, 60, None, False, {"limit": 25}),+    ("fusion_aps_file_info", "Version history + who/when/size for a file (tip + recent versions).",+     {"query": "str  OR  projectId+urn"}, {"versionCount": "int", "tip": "{...}", "versions": "[...]"}, 40, None, False, {"query": "BME690 Molecule"}),+    ("fusion_aps_versions", "Full (uncapped) version history of a file.",+     {"query": "str  OR  projectId+urn"}, {"versionCount": "int", "versions": "[...]"}, 40, None, False, {"query": "BME690 Molecule"}),+    ("fusion_aps_download", "Download a cloud file to the local machine (no Fusion needed).",+     {"query": "str OR projectId+urn", "saveDir": "optional str", "fileName": "optional str"}, {"path": "str", "bytes": "int"}, 90, None, False, {"query": "BME690 Molecule"}),+    ("fusion_aps_upload", "Upload a local file as a NEW cloud file (free Data Management; needs data:write scope).",+     {"projectId": "required str", "folderId": "required str", "localPath": "required str", "fileName": "optional str"},+     {"itemUrn": "str", "bytes": "int"}, 90, None, False, {"projectId": "a.YnVz...", "folderId": "urn:...", "localPath": "C:/x.f3d"}),+    ("fusion_aps_create_folder", "Create a subfolder in a project (free; needs data:create scope).",+     {"projectId": "required str", "parentFolderId": "required str", "name": "required str"}, {"folderId": "str"}, 50, None, False, {"projectId": "a.YnVz...", "parentFolderId": "urn:...", "name": "new-folder"}),+    ("fusion_aps_get", "Raw authenticated GET against a free APS path (dev/verify helper).",+     {"path": "required str"}, {"status": "int", "body": "obj"}, 40, None, False, {"path": "/project/v1/hubs"}),+    ("fusion_walk_cloud_tree", "Slow in-app folder-by-folder cloud walk (legacy; prefer fusion_aps_search).",+     {}, {"tree": "obj"}, 600, _STATUS, True, {}),+    ("fusion_search_cloud_files", "Slow in-app cloud search (legacy; prefer fusion_aps_search).",+     {"query": "required str"}, {"results": "[...]"}, 180, _STATUS, True, {"query": "Molecule"}),++    # ── Electronics ────────────────────────────────────────────────────────+    ("fusion_show_2d_board", "Switch to the 2D PCB Editor (board layout) view.",+     {}, {"activeWorkspace": "str"}, 70, None, True, {}),+    ("fusion_show_3d_board", "Switch to the 3D PCB view (populated board).",+     {}, {"activeWorkspace": "str", "screenshots": "[...]"}, 70, None, True, {}),+    ("fusion_open_by_urn_note", "(see fusion_open_by_urn)", {}, {}, 30, None, False, {}),+    ("fusion_board_info", "Raw board geometry XML (copper/arcs/lines) for the open board.",+     {}, {"rawXml": "str"}, 60, None, False, {}),+    ("fusion_open_lbr", "Import an EAGLE .lbr library into Fusion's Electronics library editor.",+     {"filePath": "required str (.lbr on the machine)", "symbolName": "optional str", "verify": "optional bool"},+     {"success": "bool", "postOpenScreenshot": "{savedTo}"}, 50, None, False, {"filePath": "C:/libs/nRF52840-CKAA.lbr"}),+    ("fusion_run_modeling_script", "Run an adsk.fusion modeling script in the live session (sketches/extrudes/params). Free programmatic CAD.",+     {"script": "required str (Python using adsk; globals: adsk, app, ui, design)"},+     {"result": "obj", "documentName": "str"}, 180, None, True, {"script": "import adsk.fusion\n# ...build geometry..."}),++    # ── 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_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"}),+    ("fusion_export_usdz", "Export to USDZ (AR / Hydrogen 3D).", {"outputPath": "required str"}, {"format": "str"}, 300, None, True, {"outputPath": "C:/out/part.usdz"}),+    ("fusion_export_obj", "Export to OBJ mesh.", {"outputPath": "required str"}, {"format": "str"}, 300, None, True, {"outputPath": "C:/out/part.obj"}),+    ("fusion_export_dxf", "Export 2D DXF.", {"outputPath": "required str"}, {"format": "str"}, 120, None, True, {"outputPath": "C:/out/sketch.dxf"}),+    ("fusion_export_dwg", "Export 2D DWG.", {"outputPath": "required str"}, {"format": "str"}, 120, None, True, {"outputPath": "C:/out/sketch.dwg"}),++    # ── Manufacturing ──────────────────────────────────────────────────────+    ("fusion_export_gerbers", "Run the bundled JLCPCB CAM job (auto 2/4-layer) to produce the gerber + drill ZIP.",+     {"outputDir": "optional str (default C:/tmp/adom-gerbers)"}, {"fileCount": "int", "files": "[{name,size}]", "zipPath": "str"}, 180, None, True, {"outputDir": "C:/tmp/jlc"}),+    ("fusion_export_bom", "Export a Bill of Materials CSV (grouped by value+package) from the open board.",+     {"outputPath": "optional str", "grouped": "optional bool (default true)"}, {"componentCount": "int", "grouped": "bool"}, 60, None, False, {"outputPath": "C:/tmp/bom.csv"}),+    ("fusion_export_cpl", "Export the Component Placement List (pick-and-place) CSV.",+     {"outputPath": "optional str", "side": "optional str (all|top|bottom)"}, {"topCount": "int", "bottomCount": "int"}, 60, None, False, {"outputPath": "C:/tmp/cpl.csv"}),+    ("fusion_set_design_rules", "Load manufacturing-grade DRC into the open board (JLCPCB 2/4-layer profiles).",+     {"action": "optional str (apply|export|show)", "layers": "optional str (auto|2|4)"}, {"activeWorkspace": "str", "description": "str"}, 90, None, False, {"action": "apply", "layers": "2"}),+    ("fusion_load_design_rules", "Load a bundled .edru design-rule set into the board.",+     {"layers": "optional str (auto|2|4)"}, {"success": "bool"}, 90, None, False, {}),+    ("fusion_detect_layers", "Detect 2-layer vs 4-layer board (ULP + CAM comparison).",+     {}, {"layerCount": "int", "copperLayers": "[int]"}, 90, None, False, {}),+    ("fusion_dismiss_blocking_dialogs", "Detect + classify (and optionally dismiss) blocking Fusion dialogs.",+     {}, {"dialogs": "[...]"}, 30, None, False, {}),+    ("fusion_electron_run", "Run an EAGLE/Electron command in the open board (the generic extension point, e.g. RUN <ulp>).",+     {"command": "required str"}, {"success": "bool", "output": "str"}, 90, None, False, {"command": "set confirm yes;RUN 'C:/tmp/jlcpcb_smta_exporter.ulp' 'C:/tmp/jlc'"}),++    # ── Self-describe ──────────────────────────────────────────────────────+    ("fusion_describe", "Self-describe every verb the bridge exposes (this list) for AD's Verbs tab + runner.",+     {}, {"success": "bool", "verbs": "[{name,summary,input,output,timeoutSeconds,statusVerb,longRunning,example}]"}, 20, None, False, {}),+]+++def _verbs():+    out = []+    for name, summary, inp, outp, timeout, status, longr, ex in _T:+        if name.endswith("_note"):+            continue+        out.append({+            "name": name,+            "summary": summary,+            "input": inp,+            "output": outp,+            "timeoutSeconds": timeout,+            "statusVerb": status,+            "longRunning": longr,+            "example": ex,+        })+    return out+++def handle_describe(args: dict) -> dict:+    verbs = _verbs()+    # `verbs` at the TOP LEVEL is what AD's Verbs tab reads from the raw /command+    # response. AD's relay strips non-standard top-level fields on the CLI proxy+    # path, so we ALSO nest under `data` (which the relay forwards) for CLI/debug.+    return {+        "success": True,+        "bridge": "fusion360",+        "verbCount": len(verbs),+        "verbs": verbs,+        "data": {"bridge": "fusion360", "verbCount": len(verbs), "verbs": verbs},+    }
server.py+2
@@ -36,6 +36,7 @@ from handlers.fusion_ui import ( from handlers.dialog_classify import classify_blocking_dialogs from install_addin import install as install_addin import aps+import describe  DEFAULT_PORT = 8773 ADDIN_PORT = 8774@@ -462,6 +463,7 @@ def _handle_relocate_recovery(fusion_info: dict, args: dict) -> dict:   COMMAND_HANDLERS = {+    "describe": lambda fi, args: describe.handle_describe(args),     "open_design": handle_open_design,     "close": handle_close_fusion,     "launch": _handle_launch,