← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.13.37\ No newline at end of file+1.13.38\ No newline at end of file
CHANGELOG.md+1−1
@@ -3,7 +3,7 @@ All notable changes to the Adom Bridge Fusion 360 bridge, newest first. Canonical: https://wiki.adom.inc/adom/fusion-bridge ยท GitHub backup: `adom-inc/fusion-bridge` -## 1.13.38 (unreleased)+## 1.13.38 (2026-10-30)  - `fusion_export_manufacturing` (the standard's export_manufacturing): silkscreen prep, Gerbers and drill (JLCPCB CAM   job), pick and place with JLCPCB's CPL, the BOM order files, and ODB++ / IPC-2581 with `formats`, in one folder.
addin/AdomBridge/AdomBridge.manifest+1−1
@@ -6,7 +6,7 @@     "description": {         "": "Adom Desktop Conduit bridge add-in. Exposes a localhost HTTP API for external automation of Fusion 360."     },-    "version": "1.0.28",+    "version": "1.0.29",     "runOnStartup": true,     "supportedOS": "windows" }\ No newline at end of file
addin/AdomBridge/commands/app_state.py+8−16
@@ -9,22 +9,13 @@ def handle_get_app_state(app: adsk.core.Application, args: dict) -> dict:     Returns information about every open document/tab, which one is active,     the current workspace, and product type.     """-    # Active workspace-    active_workspace = "unknown"-    try:-        ws = app.executeTextCommand("DebugCommands.ActiveWorkspace")-        if ws:-            active_workspace = ws.strip()-    except Exception:-        pass--    # Check if Electronics editor is active-    is_electronics = False-    try:-        app.executeTextCommand("Electron.run DISPLAY")-        is_electronics = True-    except Exception:-        pass+    # The workspace and whether an electronics editor is in front, read from the API: this read is+    # polled (readiness, the dashboard, waits), and a text command here (Electron.run DISPLAY) ended+    # the user's MOVE in progress every time (issue #131)+    from .ui_state import active_workspace as _ws, is_electronics as _is_el, user_command_active as _busy+    active_workspace = _ws(app)+    is_electronics = _is_el(app)+    user_command = _busy(app)      # Active document     active_doc_name = None@@ -104,6 +95,7 @@ def handle_get_app_state(app: adsk.core.Application, args: dict) -> dict:             "activeDocument": active_doc_name,             "productType": product_type,             "isElectronics": is_electronics,+            "userCommandActive": user_command,             "documents": documents,         },         "_hint": hint,
addin/AdomBridge/commands/document_info.py+4−14
@@ -36,13 +36,8 @@ def handle_document_info(app: adsk.core.Application, args: dict) -> dict:     and which one is focused, without expensive cloud searches.     """     # Active workspace-    active_workspace = "unknown"-    try:-        ws = app.executeTextCommand("DebugCommands.ActiveWorkspace")-        if ws:-            active_workspace = ws.strip()-    except Exception:-        pass+    from .ui_state import active_workspace as _ws+    active_workspace = _ws(app)   # API only: this read is polled (issue #131)      # Active document name (for isActive flag)     active_doc_name = None@@ -312,13 +307,8 @@ def handle_activate_document(app: adsk.core.Application, args: dict) -> dict:         }      # Query workspace after activation-    new_workspace = "unknown"-    try:-        ws = app.executeTextCommand("DebugCommands.ActiveWorkspace")-        if ws:-            new_workspace = ws.strip()-    except Exception:-        pass+    from .ui_state import active_workspace as _ws+    new_workspace = _ws(app)      response = {         "success": True,
addin/AdomBridge/commands/ui_state.pyadded+37
@@ -0,0 +1,37 @@+"""Side-effect-free reads of what Fusion has in front, for the reads the bridge polls (get_app_state,+document_info). Text commands are not used here: Electron.run DISPLAY starts an EAGLE command, and+starting any command in an electronics editor ends the one the user has in progress (a MOVE snaps+back; issue #131). These read the API only."""++import adsk.core+++def active_workspace(app: adsk.core.Application) -> str:+    """The workspace in front by its id (BoardLayoutEnvironement, SchematicEnvironment ...: the same+    names DebugCommands.ActiveWorkspace answered), or 'unknown'."""+    try:+        ws = app.userInterface.activeWorkspace+        if ws:+            return ws.id or ws.name or "unknown"+    except Exception:+        pass+    return "unknown"+++def is_electronics(app: adsk.core.Application) -> bool:+    """True when an electronics editor (schematic, board or library) is in front: its product is an+    adsk::electron object."""+    try:+        p = app.activeProduct+        return bool(p) and str(p.objectType).startswith("adsk::electron::")+    except Exception:+        return False+++def user_command_active(app: adsk.core.Application) -> bool:+    """True while the user is in the middle of a command (anything but the plain select)."""+    try:+        cmd = app.userInterface.activeCommand or ""+        return cmd not in ("", "SelectCommand")+    except Exception:+        return False
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.13.37",+  "version": "1.13.38",   "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.",
dashboard/Cargo.lock+1−1
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "fusion-dashboard"-version = "1.13.37"+version = "1.13.38" dependencies = [  "serde_json", ]
dashboard/Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.13.37"+version = "1.13.38" edition = "2021"  [dependencies]
install.sh+1−1
@@ -21,7 +21,7 @@ echo "Installed fusion-bridge skills: $(ls "$PKG_DIR/skills" 2>/dev/null | tr '\ # binary (the three-artifact pattern: bridge zip -> ab, THIS pkg -> container skills + # this download, page media -> wiki). Per the wiki's PKG_VS_RELEASE policy, binaries are # platform-tagged RELEASE assets, never tarball content - the installer fetches them.-DASH_VERSION="1.13.37"+DASH_VERSION="1.13.38" DASH_URL="https://wiki.adom.inc/download/adom/fusion-bridge/${DASH_VERSION}/fusion-dashboard" DEST="" if [ -w /usr/local/bin ]; then DEST=/usr/local/bin
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.13.37",+  "version": "1.13.38",   "type": "app",   "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.",   "dependencies": {
page.json+2−2
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.13.37",+  "version": "1.13.38",   "type": "app",   "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.",   "dependencies": {@@ -152,4 +152,4 @@   "confirmed_user_skills": [     "fusion-design-efficiency"   ]-}\ No newline at end of file+}
rust/Cargo.lock+5−5
@@ -19,7 +19,7 @@ dependencies = [  [[package]] name = "adom-fusion-bridge"-version = "1.13.37"+version = "1.13.38" dependencies = [  "adom-eda-checks",  "base64",@@ -299,7 +299,7 @@ dependencies = [  [[package]] name = "fusion-core"-version = "1.13.37"+version = "1.13.38" dependencies = [  "serde",  "serde_json",@@ -310,7 +310,7 @@ dependencies = [  [[package]] name = "fusion-lbr"-version = "1.13.37"+version = "1.13.38" dependencies = [  "regex",  "roxmltree",@@ -322,14 +322,14 @@ dependencies = [  [[package]] name = "fusion-os-mac"-version = "1.13.37"+version = "1.13.38" dependencies = [  "fusion-core", ]  [[package]] name = "fusion-os-win"-version = "1.13.37"+version = "1.13.38" dependencies = [  "fusion-core",  "windows-sys 0.59.0",
rust/Cargo.toml+1−1
@@ -4,7 +4,7 @@ members = ["core", "lbr", "os-win", "os-mac", "bridge"]  # One version for the whole bridge, in lockstep with BRIDGE_VERSION (tools/release.sh bumps both). [workspace.package]-version = "1.13.37"+version = "1.13.38" edition = "2021" license = "MIT" 
tests/test_polled_reads_run_no_commands.pyadded+23
@@ -0,0 +1,23 @@+"""Issue #131: the reads the bridge polls must never start a Fusion command (a text command in an+electronics editor ends the user's command in progress)."""+import ast+from pathlib import Path++ADDIN = Path(__file__).resolve().parents[1] / "addin" / "AdomBridge" / "commands"+POLLED = {"app_state.py": "handle_get_app_state", "document_info.py": "handle_document_info", "ui_state.py": None}+++def calls_in(node):+    return {n.func.attr for n in ast.walk(node) if isinstance(n, ast.Call) and isinstance(n.func, ast.Attribute)}+++def test_polled_reads_never_run_a_text_command():+    for name, fn in POLLED.items():+        tree = ast.parse((ADDIN / name).read_text(encoding="utf-8"))+        scope = tree if fn is None else next(n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef) and n.name == fn)+        assert "executeTextCommand" not in calls_in(scope), f"{name}:{fn or 'module'} runs a text command"+++if __name__ == "__main__":+    test_polled_reads_never_run_a_text_command()+    print("ok")