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.6.28: fusion_set_preference / fusion_get_preferences (theme/dark mode, scroll-invert, orbit scheme, orientation, units) - live, no restart
7 files changed
+178−8
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.27+1.6.28
CHANGELOG.md+17@@ -3,6 +3,23 @@ All notable changes to the Adom Desktop Fusion 360 bridge, newest first. Canonical: https://wiki.adom.inc/adom/adom-desktop-fusion-bridge · GitHub backup: `adom-inc/adom-desktop-fusion-bridge` +## 1.6.28 - 2026-07-05++- **New `fusion_set_preference` + `fusion_get_preferences` verbs** - drive Fusion's appearance and+ navigation settings programmatically, applied LIVE (no restart). Server-only orchestrators (they+ compose an `adsk` preferences script and run it via the existing add-in `run_modeling_script`, so+ no add-in redeploy). Friendly keys: `theme` (light/darkblue/darkgray/classic/device/dark),+ `invertScrollZoom` (mouse scroll/zoom direction), `orbitScheme`+ (fusion360/alias/inventor/solidworks/tinkercad/powermill), `modelingOrientation` (yup/zup),+ `gestureNav`, `cameraPivot`, `lengthUnit` (mm/cm/m/in/ft, active design). Per-key outcome is+ reported in `applied` and the resulting state in `current`. Discovered via a live introspection of+ `app.preferences.generalPreferences` on ADOMBASELINE.+- **Note on theme availability:** not every Fusion build ships every theme. On the current webdeploy+ only **LightGray, DarkBlue, Device** were available (`Dark Gray theme not available` / `Classic+ theme not available` raise on assignment), so the verb tries candidates and reports per-key+ failures; `dark` resolves to Dark Gray then falls back to Dark Blue. Dark mode was set live on the+ VM's Fusion this way (Dark Blue).+ ## 1.6.27 - 2026-07-05 - **Fixed a false-positive in 1.6.26's new `needsSignin` readiness detection** (caught live before
SKILL.md+2@@ -127,6 +127,8 @@ are dimmed). So: | `fusion_send_key` | `send_key` | Send a keyboard shortcut to Fusion | `hwnd`, `key` | | `fusion_addin_status` | `addin_status` | Report whether the in-process Adom add-in is loaded + healthy | — | | `fusion_generate_package` | `generate_package` | Generate an IPC package via Fusion's built-in EPG (~50 families) + raised-WHITE marking (longest-axis, measured 10% margin, multi-line MPN+variant) + colored STEP + sidecar manifest - in ONE call | `type`, `params` (mm), `etch`, `etchStyle`, `outputStep` |+| `fusion_set_preference` | `set_preference` | Set appearance/navigation prefs LIVE (no restart): `theme` (light/darkblue/darkgray/classic/device/dark), `invertScrollZoom`, `orbitScheme` (fusion360/alias/inventor/solidworks/tinkercad/powermill), `modelingOrientation` (yup/zup), `gestureNav`, `cameraPivot`, `lengthUnit` (mm/cm/m/in/ft). Per-key result in `applied`; some themes aren't in every build (falls back) | any of the keys |+| `fusion_get_preferences` | `get_preferences` | Read current appearance/navigation prefs as the friendly values `set_preference` accepts | — | ## Extended commands via the in-process add-in
adom-bridge-fusion-manifest.json+5−5@@ -1,13 +1,13 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.6.27",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.27/adom-bridge-fusion-v1.6.27.zip",- "sha256": "73e9591b4d902770827920f1d059a232609945401b2524de6bf8d842137ad4c9",- "size": 323919,+ "version": "1.6.28",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.28/adom-bridge-fusion-v1.6.28.zip",+ "sha256": "099cd9e3c87ae9f0b638eab24cfd3807f173c485c3c46578baa669eec3fecb86",+ "size": 327488, "verbPrefixes": [ "fusion_" ], "healthEndpoint": "/status",- "released_at": "2026-07-05T12:24:43Z"+ "released_at": "2026-07-05T12:39:42Z" }
bridge.json+6−2@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.6.27",+ "version": "1.6.28", "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/adom-desktop-fusion-bridge", "author": "Adom Inc.",@@ -59,6 +59,7 @@ "fusion_export_usdz", "fusion_generate_package", "fusion_get_app_state",+ "fusion_get_preferences", "fusion_install_fusion", "fusion_kill", "fusion_load_design_rules",@@ -69,6 +70,7 @@ "fusion_run_modeling_script", "fusion_search_cloud_files", "fusion_set_design_rules",+ "fusion_set_preference", "fusion_show_2d_board", "fusion_show_3d_board", "fusion_show_schematic",@@ -168,7 +170,9 @@ "build_library_3d": 1800, "capture_library_views": 600, "start": 90,- "install_fusion": 90+ "install_fusion": 90,+ "set_preference": 40,+ "get_preferences": 25 }, "prefixes": [ {
describe.py+7@@ -100,6 +100,13 @@ _T = [ {"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..."}), + ("fusion_set_preference", "Set Fusion appearance/navigation preferences in the LIVE session - applies immediately, NO restart. Keys: theme (light/darkblue/darkgray/classic/device/dark; some builds only ship LightGray/DarkBlue/Device, failures reported per-key), invertScrollZoom (bool - mouse scroll/zoom direction), orbitScheme (fusion360/alias/inventor/solidworks/tinkercad/powermill), modelingOrientation (yup/zup), gestureNav (bool), cameraPivot (bool), lengthUnit (mm/cm/m/in/ft - active design). Per-key outcome in `applied`, resulting state in `current`.",+ {"theme": "optional str", "invertScrollZoom": "optional bool", "orbitScheme": "optional str", "modelingOrientation": "optional str", "gestureNav": "optional bool", "cameraPivot": "optional bool", "lengthUnit": "optional str"},+ {"applied": "obj", "current": "obj"}, 30, None, True, {"theme": "dark"}),++ ("fusion_get_preferences", "Read current Fusion appearance/navigation preferences (theme, activeTheme, invertScrollZoom, orbitScheme, modelingOrientation, gestureNav, cameraPivot, lengthUnit) as friendly values - the same keys fusion_set_preference accepts.",+ {}, {"current": "obj"}, 20, None, False, {}),+ # ── 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"}),
server.py+140@@ -837,9 +837,149 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict: return result +# ── Fusion preferences (theme / navigation / units) ──────────────────────────+# Set via the adsk preferences API inside the live session (run_modeling_script),+# so this is a SERVER-only orchestrator - no add-in redeploy. Friendly keys map to+# adsk enums; unknown/failed keys are reported per-key (some themes, e.g. Dark Gray+# / Classic, aren't shipped in every Fusion build - proven live 2026-07-05, only+# LightGray/DarkBlue/Device were available). Theme changes apply LIVE (no restart).+_PREF_SCRIPT = r'''+import json+gp = app.preferences.generalPreferences+T = adsk.core.UserInterfaceThemes+PZ = adsk.core.PanZoomOrbitShortcuts+MO = adsk.core.DefaultModelingOrientations+DU = adsk.fusion.DistanceUnits++THEME = {'light':[T.LightGrayUserInterfaceTheme],'lightgray':[T.LightGrayUserInterfaceTheme],+ 'dark':[T.DarkGrayUserInterfaceTheme,T.DarkBlueUserInterfaceTheme],+ 'darkgray':[T.DarkGrayUserInterfaceTheme],'darkblue':[T.DarkBlueUserInterfaceTheme],+ 'classic':[T.ClassicUserInterfaceTheme],'device':[T.DeviceUserInterfaceTheme],+ 'auto':[T.DeviceUserInterfaceTheme]}+THEME_NAME = {0:'classic',1:'lightgray',2:'darkblue',3:'darkgray',4:'device'}+ORBIT = {'fusion360':PZ.Fusion360PanZoomOrbitShortcut,'fusion':PZ.Fusion360PanZoomOrbitShortcut,+ 'alias':PZ.AliasPanZoomOrbitShortcut,'inventor':PZ.InventorPanZoomOrbitShortcut,+ 'solidworks':PZ.SolidWorksPanZoomOrbitShortcut,'tinkercad':PZ.TinkercadPanZoomOrbitShortcut,+ 'powermill':PZ.PowerMillPanZoomOrbitShortcut}+ORBIT_NAME = {0:'fusion360',1:'alias',2:'inventor',3:'solidworks',4:'tinkercad',5:'powermill'}+ORIENT = {'yup':MO.YUpModelingOrientation,'zup':MO.ZUpModelingOrientation}+ORIENT_NAME = {0:'yup',1:'zup'}+UNITS = {'mm':DU.MillimeterDistanceUnits,'cm':DU.CentimeterDistanceUnits,'m':DU.MeterDistanceUnits,+ 'in':DU.InchDistanceUnits,'inch':DU.InchDistanceUnits,'ft':DU.FootDistanceUnits}+UNITS_NAME = {0:'mm',1:'cm',2:'m',3:'in',4:'ft'}++def read_current():+ cur = {+ 'theme': THEME_NAME.get(gp.userInterfaceTheme, gp.userInterfaceTheme),+ 'activeTheme': THEME_NAME.get(gp.activeUserInterfaceTheme, gp.activeUserInterfaceTheme),+ 'invertScrollZoom': bool(gp.isZoomDirectionReversed),+ 'orbitScheme': ORBIT_NAME.get(gp.panZoomOrbitShortcuts, gp.panZoomOrbitShortcuts),+ 'modelingOrientation': ORIENT_NAME.get(gp.defaultModelingOrientation, gp.defaultModelingOrientation),+ 'gestureNav': bool(gp.isGestureBasedViewNavigationUsed),+ 'cameraPivot': bool(gp.isCameraPivotEnabled),+ }+ try:+ cur['lengthUnit'] = UNITS_NAME.get(design.fusionUnitsManager.distanceDisplayUnits,+ design.fusionUnitsManager.distanceDisplayUnits)+ except Exception:+ pass+ return cur++def try_set(obj, attr, cands, want):+ err = None+ for c in cands:+ if c is None: continue+ try:+ setattr(obj, attr, c)+ return {'requested': want, 'ok': True}+ except Exception as e:+ err = str(e)+ return {'requested': want, 'ok': False, 'error': err or 'no valid value'}++reqs = json.loads(__REQS__)+applied = {}+for k, v in reqs.items():+ kv = v if isinstance(v, bool) else str(v).strip().lower()+ if k == 'theme':+ c = THEME.get(kv); applied[k] = try_set(gp,'userInterfaceTheme', c or [], v) if c else {'requested':v,'ok':False,'error':'unknown theme (use light/darkblue/darkgray/classic/device/dark)'}+ elif k in ('invertScrollZoom','reverseZoom'):+ applied['invertScrollZoom'] = try_set(gp,'isZoomDirectionReversed',[bool(v)],v)+ elif k == 'orbitScheme':+ c = ORBIT.get(kv); applied[k] = try_set(gp,'panZoomOrbitShortcuts',[c],v) if c is not None else {'requested':v,'ok':False,'error':'unknown orbitScheme'}+ elif k == 'modelingOrientation':+ c = ORIENT.get(kv); applied[k] = try_set(gp,'defaultModelingOrientation',[c],v) if c is not None else {'requested':v,'ok':False,'error':'unknown modelingOrientation (yup/zup)'}+ elif k == 'gestureNav':+ applied[k] = try_set(gp,'isGestureBasedViewNavigationUsed',[bool(v)],v)+ elif k == 'cameraPivot':+ applied[k] = try_set(gp,'isCameraPivotEnabled',[bool(v)],v)+ elif k == 'lengthUnit':+ c = UNITS.get(kv)+ if c is None: applied[k] = {'requested':v,'ok':False,'error':'unknown unit (mm/cm/m/in/ft)'}+ else:+ try: applied[k] = try_set(design.fusionUnitsManager,'distanceDisplayUnits',[c],v)+ except Exception as e: applied[k] = {'requested':v,'ok':False,'error':str(e)}+ else:+ applied[k] = {'requested': v, 'ok': False, 'error': 'unknown preference key'}++result = {'applied': applied, 'current': read_current()}+print(json.dumps(result))+'''+++def _shape_pref_result(res: dict, wrote: bool) -> dict:+ if not res or not res.get("success"):+ return {"success": False,+ "error": (res or {}).get("error", "preferences script did not run"),+ "_hint": "fusion_set_preference/get_preferences need Fusion running + signed in "+ "(fusion_readiness -> ready:true). If needsSignin, drive the sign-in first."}+ data = (res.get("data") or {}).get("result") or {}+ applied = data.get("applied", {}) or {}+ current = data.get("current", {}) or {}+ out = {"success": True, "applied": applied, "current": current}+ if wrote:+ oks = [k for k, v in applied.items() if v.get("ok")]+ fails = {k: v.get("error") for k, v in applied.items() if not v.get("ok")}+ if fails:+ out["partial"] = True+ out["_hint"] = ("Applied live: %s. FAILED: %s. Note: some themes (Dark Gray/Classic) "+ "aren't shipped in every Fusion build - use 'darkblue' or 'device'. See "+ "`current` for the resulting state." %+ (", ".join(oks) or "(none)",+ "; ".join("%s=%s" % (k, e) for k, e in fails.items())))+ else:+ out["_hint"] = ("Applied LIVE (no restart needed): %s. See `current` for resulting state."+ % ", ".join(oks))+ else:+ out["_hint"] = ("Current Fusion preferences. Change any with fusion_set_preference, keys: "+ "theme (light/darkblue/darkgray/classic/device/dark), invertScrollZoom (bool), "+ "orbitScheme (fusion360/alias/inventor/solidworks/tinkercad/powermill), "+ "modelingOrientation (yup/zup), gestureNav (bool), cameraPivot (bool), "+ "lengthUnit (mm/cm/m/in/ft - applies to the active design).")+ return out+++def _handle_set_preference(fusion_info: dict, args: dict) -> dict:+ reqs = {k: v for k, v in (args or {}).items() if k not in ("settle",)}+ if not reqs:+ return {"success": False, "error": "No preferences given.",+ "_hint": "Pass e.g. {\"theme\":\"dark\"} or {\"invertScrollZoom\":true,"+ "\"orbitScheme\":\"solidworks\"}. Call fusion_get_preferences to see current values + keys."}+ script = _PREF_SCRIPT.replace("__REQS__", json.dumps(json.dumps(reqs)))+ res = _proxy_to_addin("run_modeling_script", {"script": script}, timeout=30)+ return _shape_pref_result(res, wrote=True)+++def _handle_get_preferences(fusion_info: dict, args: dict) -> dict:+ script = _PREF_SCRIPT.replace("__REQS__", json.dumps(json.dumps({})))+ res = _proxy_to_addin("run_modeling_script", {"script": script}, timeout=20)+ return _shape_pref_result(res, wrote=False)++ COMMAND_HANDLERS = { "describe": lambda fi, args: describe.handle_describe(args), "readiness": _handle_fusion_readiness,+ "set_preference": _handle_set_preference,+ "get_preferences": _handle_get_preferences, "install_fusion": _handle_install_fusion, "open_design": handle_open_design, "close": handle_close_fusion, # deprecated alias - use stop (graceful) / kill (force)