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.55: driving badge renders via AD-native desktop_taskbar
7 files changed
+70−99
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.54+1.9.55
CHANGELOG.md+9@@ -3,6 +3,15 @@ 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.9.55 - 2026-08-17++- **The driving badge actually renders now (pup's lesson, stolen with credit).** In-process+ ITaskbarList3::SetOverlayIcon returns S_OK cross-process and the shell silently ignores it -+ pup root-caused this long ago and routes through AD-core's `desktop_taskbar` verb, which paints+ natively. taskbar_overlay.py now does the same (overlay {icon: b64 png, tooltip} / {badge:+ "none"}). PROVEN live on AdomLapper: red-box capture of Fusion's taskbar button wearing the+ teal Adom mark is in fusion-proofs.+ ## 1.9.54 - 2026-08-17 - **Ralph-test findings on 1.9.53, same day.** (1) An UNATTRIBUTED save prompt (its spawning doc
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.9.54",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.54/adom-bridge-fusion-v1.9.54.zip",- "sha256": "b8139e0bc3029fb49e791df6ec9d9b39d2a30f1c6f0163f08ffffaeace2dd660",- "size": 404402,+ "version": "1.9.55",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.55/adom-bridge-fusion-v1.9.55.zip",+ "sha256": "c71cb335452d18c2b129119e30fe999fec5b8475a9d13fd798563a6e395b1533",+ "size": 402467, "verbPrefixes": [ "fusion_" ],
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.9.54",+ "version": "1.9.55", "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.",
handlers/taskbar_overlay.py+54−92@@ -1,119 +1,81 @@ """Taskbar overlay badge: "Adom is driving Fusion" (John, 2026-08-17). -pup badges its own Chrome taskbar buttons to show state; the same signal belongs on-FUSION's taskbar button whenever this bridge is driving it, so the user can glance at-the taskbar and know why Fusion is busy. Windows exposes exactly this as-ITaskbarList3::SetOverlayIcon - callable cross-process with just an hwnd, no window-ownership needed. ctypes COM, no dependencies.+pup badges its Chrome taskbar buttons so users can tell an Adom-driven window at a+glance; the same signal belongs on FUSION's button whenever this bridge drives it.++THE LESSON (stolen from pup, who paid for it first - src/server.js): an in-bridge+ITaskbarList3::SetOverlayIcon is IN-PROCESS-ONLY. The COM call returns S_OK+cross-process and the shell silently ignores it (verified live here too: badge never+rendered). The working path is AD-core's `desktop_taskbar` verb (AD >=1.9.106): hand+it the badge PNG as base64 and AD paints the overlay natively, cross-process.++ set: desktop_taskbar {hwnd, overlay: {icon: <b64 png>, tooltip}}+ clear: desktop_taskbar {hwnd, overlay: {badge: "none"}} Wired to the background envelope's refcount: first envelope (driving starts) badges the-Fusion main window with resources/adom-overlay.ico ("Adom is driving Fusion"); when the-refcount drains to zero the badge clears. Best-effort everywhere - a missing icon, a-COM failure, or a dead hwnd must never break a verb.+Fusion main window with resources/adom-overlay-32.png; when the refcount drains to zero+the badge clears. Best-effort everywhere - a missing asset or an ab error must never+break a verb. PROVEN live on AdomLapper 2026-08-17 (red-box capture in fusion-proofs). """ -import ctypes+import base64 import os import threading _LOCK = threading.Lock()-_HICON = None+_B64 = None _BADGED_HWND = None -_CLSID_TaskbarList = "{56FDF344-FD6D-11D0-958A-006097C9A090}"-_IID_ITaskbarList3 = "{EA1AFB91-9E28-4B86-90E9-9E9F8A5EEFAF}"-# ITaskbarList3 vtable: IUnknown 0-2 | HrInit 3, AddTab 4, DeleteTab 5, ActivateTab 6,-# SetActiveAlt 7 | MarkFullscreenWindow 8 | SetProgressValue 9, SetProgressState 10,-# RegisterTab 11, UnregisterTab 12, SetTabOrder 13, SetTabActive 14, ThumbBarAddButtons 15,-# ThumbBarUpdateButtons 16, ThumbBarSetImageList 17, SetOverlayIcon 18, ...-_VTBL_HRINIT = 3-_VTBL_SETOVERLAYICON = 18---class _GUID(ctypes.Structure):- _fields_ = [("Data1", ctypes.c_ulong), ("Data2", ctypes.c_ushort),- ("Data3", ctypes.c_ushort), ("Data4", ctypes.c_ubyte * 8)]---def _guid(s: str) -> "_GUID":- g = _GUID()- ctypes.oledll.ole32.CLSIDFromString(ctypes.c_wchar_p(s), ctypes.byref(g))- return g --def _icon_path() -> str:- return os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),- "resources", "adom-overlay.ico")---def _load_icon():- global _HICON- if _HICON:- return _HICON- p = _icon_path()- if not os.path.isfile(p):- return None- IMAGE_ICON, LR_LOADFROMFILE = 1, 0x0010- h = ctypes.windll.user32.LoadImageW(None, p, IMAGE_ICON, 32, 32, LR_LOADFROMFILE)- _HICON = h or None- return _HICON+def _badge_b64():+ global _B64+ if _B64 is not None:+ return _B64+ p = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),+ "resources", "adom-overlay-32.png")+ try:+ _B64 = base64.b64encode(open(p, "rb").read()).decode()+ except Exception:+ _B64 = ""+ return _B64 -def _taskbar_call(hwnd, hicon, description):- """CoCreate ITaskbarList3 on THIS thread and call SetOverlayIcon once."""- ole32 = ctypes.oledll.ole32- ole32.CoInitialize(None)+def _taskbar(args: dict) -> bool: try:- clsid = _guid(_CLSID_TaskbarList)- iid = _guid(_IID_ITaskbarList3)- ptr = ctypes.c_void_p()- CLSCTX_INPROC_SERVER = 1- ole32.CoCreateInstance(ctypes.byref(clsid), None, CLSCTX_INPROC_SERVER,- ctypes.byref(iid), ctypes.byref(ptr))- vtbl = ctypes.cast(ptr, ctypes.POINTER(ctypes.POINTER(ctypes.c_void_p))).contents- proto_init = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)- proto_overlay = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p,- ctypes.c_void_p, ctypes.c_void_p, ctypes.c_wchar_p)- proto_release = ctypes.WINFUNCTYPE(ctypes.c_ulong, ctypes.c_void_p)- hr_init = proto_init(vtbl[_VTBL_HRINIT])(ptr)- hr = proto_overlay(vtbl[_VTBL_SETOVERLAYICON])(- ptr, ctypes.c_void_p(hwnd), ctypes.c_void_p(hicon or 0),- description if description else None)- proto_release(vtbl[2])(ptr)- return hr == 0 and hr_init == 0- finally:- ole32.CoUninitialize()+ import ad_client+ if not ad_client.available():+ return False+ r = ad_client.call("desktop_taskbar", args, timeout=6)+ raw = str(r)+ return bool(r) and ("Taskbar updated" in raw or (isinstance(r, dict) and not r.get("error")))+ except Exception:+ return False def set_driving_badge(hwnd) -> bool: """Badge the window's taskbar button with the Adom mark ('Adom is driving Fusion').""" global _BADGED_HWND- try:- if not hwnd:- return False- with _LOCK:- hicon = _load_icon()- if not hicon:- return False- ok = _taskbar_call(int(hwnd), hicon, "Adom is driving Fusion")- if ok:- _BADGED_HWND = int(hwnd)- return bool(ok)- except Exception:+ if not hwnd: return False+ icon = _badge_b64()+ if not icon:+ return False+ with _LOCK:+ ok = _taskbar({"hwnd": int(hwnd),+ "overlay": {"icon": icon, "tooltip": "Adom is driving Fusion"}})+ if ok:+ _BADGED_HWND = int(hwnd)+ return ok def clear_driving_badge(hwnd=None) -> bool: """Remove the badge (from the given hwnd, or whichever window we badged last).""" global _BADGED_HWND- try:- with _LOCK:- target = int(hwnd) if hwnd else _BADGED_HWND- if not target:- return False- ok = _taskbar_call(target, None, None)- if ok and (hwnd is None or _BADGED_HWND == int(hwnd)):- _BADGED_HWND = None- return bool(ok)- except Exception:- return False+ with _LOCK:+ target = int(hwnd) if hwnd else _BADGED_HWND+ if not target:+ return False+ ok = _taskbar({"hwnd": target, "overlay": {"badge": "none"}})+ if ok and (hwnd is None or _BADGED_HWND == int(hwnd)):+ _BADGED_HWND = None+ return ok
resources/adom-overlay-32.pngadded⋯ 1 unchanged line ⋯
tools/release-files.txt+1−1@@ -54,4 +54,4 @@ server.py start.bat uninstall.sh handlers/taskbar_overlay.py-resources/adom-overlay.ico+resources/adom-overlay-32.png