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
Update 102 file(s)
11 files changed
+59−24
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.284+1.9.285
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.11",+ "version": "1.0.12", "runOnStartup": true, "supportedOS": "windows" }
addin/AdomBridge/commands/activity_palette.py+30−4@@ -32,6 +32,19 @@ PALETTE_ID = "AdomActivityPalette" ADOM_LOGO_SVG = '<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1072.9 236"><defs><style>.cls-1{fill:#00b6af;stroke-width:0px;}</style></defs><path class="cls-1" d="M212.94,23.05c-25.84-25.84-62.87-30.69-82.73-10.84-8.06,8.06-12.04,18.94-12.21,30.76h0s0,.02,0,.02c0,.04,0,.08,0,.12v73.29c0,.89.72,1.61,1.61,1.61h72.5l-.02-.02c12.17.03,23.42-3.94,31.69-12.2,19.85-19.85,15-56.89-10.84-82.73Z"/><path class="cls-1" d="M23.05,212.95c25.84,25.84,62.87,30.69,82.73,10.84,8.06-8.06,12.04-18.94,12.21-30.76h0s0-.02,0-.02c0-.04,0-.08,0-.12v-73.29c0-.89-.72-1.61-1.61-1.61H43.89l.02.02c-12.17-.03-23.42,3.94-31.69,12.2-19.85,19.85-15,56.89,10.84,82.73Z"/><path class="cls-1" d="M1065.5,25.22h-58.49c-2.98,0-5.68,1.87-6.73,4.65l-44.4,117.54c-.06.17-.31.17-.37,0l-45.44-117.59c-1.07-2.75-3.76-4.6-6.72-4.6h-58.79c-4.09,0-7.4,3.31-7.4,7.4v35.06l-.03-.12c-1.24,4.99-5.89,6-5.89,6,0,0-4.51,1.02-7.61-2.53v.03c-17.82-29.81-50.31-48.93-89.04-48.93-41.5,0-75.86,21.85-92.76,55.23v-.07c-2.86,5.19-8.74,5.13-8.74,5.13,0,0-5.63-.01-8.41-4.61-16.49-32.68-50.56-52.59-94.79-52.59h-73.46c-4.09,0-7.4,3.31-7.4,7.4v121.27s0,.03,0,.04c-.95,5.15-4.75,5.94-4.75,5.94,0,0-3.88.85-6.77-3.32l.05.23-46.05-126.83c-1.03-2.84-3.75-4.74-6.77-4.74h-57.14c-3.01,0-5.73,1.9-6.76,4.73l-68.67,188.38c-1.05,2.88-.18,6.21,2.4,8.19,1.31,1.01,2.98,1.47,4.64,1.47h34.96s6.43,0,6.43,0c2.5,0,4.83-1.39,5.9-3.65,8.49-17.86,26.5-29.64,48.37-29.64s39.71,11.73,48.19,29.53c1.1,2.32,3.49,3.76,6.06,3.76h120.78c31.22,0,57.85-9.95,77.02-28.77,7.38-7.25,13.43-15.72,18.05-25.1v.09c2.74-5.19,8.37-5.13,8.37-5.13,0,0,5.43,0,8.08,4.64,4.88,9.88,11.26,18.78,18.91,26.42,18.6,18.6,44.61,29.78,74.26,29.78,38.74,0,71.23-19.18,89.05-49.07l-.02.07c3.1-3.55,7.61-2.53,7.61-2.53,0,0,4.65,1,5.89,6l.03-.12v36.32c0,4.09,3.31,7.4,7.4,7.4h45.79c4.09,0,7.4-3.31,7.4-7.4v-86.26c0-.23.32-.28.39-.06l30.37,88.86c1,2.91,3.74,4.87,6.81,4.87h39.69c3.08,0,5.82-1.96,6.81-4.87l30.1-88.02c.07-.21.39-.16.39.06v85.43c0,4.09,3.31,7.4,7.4,7.4h45.79c4.09,0,7.4-3.31,7.4-7.4V32.62c0-4.09-3.31-7.4-7.4-7.4ZM372.31,147.57c-.15.21-.5.57-1.1.57h-30.33c-.61,0-.95-.35-1.1-.56-.15-.21-.38-.65-.19-1.23l15.06-45.79c.27-.81.92-.94,1.29-.94h0c.37,0,1.02.12,1.29.93l15.26,45.8c.19.58-.04,1.01-.19,1.23ZM525.72,173.84h-14.52c-.86,0-1.56-.7-1.56-1.56v-92.16c0-.86.7-1.56,1.56-1.56h14.52c30.81,0,48.48,17.47,48.48,47.92s-17.67,47.36-48.48,47.36ZM734.6,173.26c-26.51,0-42.33-17.71-42.34-47.36,0-14.59,3.88-26.68,11.21-34.98,7.33-8.29,18.09-12.67,31.13-12.67,12.11,0,22.28,3.84,29.58,11.13.5.5.98,1.01,1.45,1.54,7.4,8.33,11.31,20.42,11.31,34.97,0,29.21-16.22,47.36-42.34,47.36Z"/></svg>' _palette = None _last_reload_ts = 0.0+_user_closed = [False] # the user clicked the palette's X: stay hidden until the next run+_handlers = [] # keep event handler references alive (Fusion drops unreferenced ones)+++class _PaletteClosedHandler(adsk.core.UserInterfaceGeneralEventHandler):+ """The user closed the palette with its X (John, 2026-08-30: "it has no close button so i+ can't get rid of it when its in the way"). Remember that so the run's periodic updates do+ not pop it straight back; the next run (action 'show') brings it back."""+ def notify(self, args):+ try:+ _user_closed[0] = True+ except Exception:+ pass def _html(title, detail, percent, eta_sec, tone):@@ -163,12 +176,15 @@ def handle_activity_banner(app: adsk.core.Application, args: dict) -> dict: try: ui = app.userInterface if action == "hide":+ _user_closed[0] = False if _palette: try: _palette.isVisible = False except Exception: pass return {"success": True, "visible": False}+ if action == "show":+ _user_closed[0] = False # a new run starts: the palette is wanted again payload = _payload(args.get("title") or "Working", args.get("detail") or "", args.get("percent"), args.get("etaSec"), args.get("tone") or "working")@@ -187,10 +203,16 @@ def handle_activity_banner(app: adsk.core.Application, args: dict) -> dict: _palette = ui.palettes.add( PALETTE_ID, "Adom", path, True, # isVisible- False, # showCloseButton - the CHAIN hides it; a user closing it mid-run- # would just lose the one warning meant for them+ True, # showCloseButton - the user must be able to get it out of the way+ # (John, 2026-08-30); a close sticks until the next run's 'show' True, # isResizable 360, 190)+ try:+ _h = _PaletteClosedHandler()+ _palette.closed.add(_h)+ _handlers.append(_h)+ except Exception:+ pass # Dock AFTER add, and verified on hardware: add() then dockingState=Right # reads back as 4 (docked right). Setting it any earlier is the pArea error. try:@@ -198,7 +220,11 @@ def handle_activity_banner(app: adsk.core.Application, args: dict) -> dict: except Exception: pass else:- if not _palette.isVisible:+ if _user_closed[0]:+ # The user closed it during this run: honour that, keep the content current+ # (so reopening shows the truth) but never pop it back in their way.+ pass+ elif not _palette.isVisible: _palette.isVisible = True # DATA, NOT A RELOAD: the page applies the payload in place. Right after creation # the page may not have run its script yet; retry briefly, and only if the message@@ -236,7 +262,7 @@ def handle_activity_banner(app: adsk.core.Application, args: dict) -> dict: q = ui.palettes.itemById(PALETTE_ID) return {"success": q is not None, "visible": bool(q.isVisible) if q else False, "dockingState": int(q.dockingState) if q else None,- "delivered": delivered, "created": created,+ "delivered": delivered, "created": created, "userClosed": _user_closed[0], "deliveryError": (last_err or None)} except Exception as e: # Advisory only: a banner that cannot be drawn must never fail the import.
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.9.284",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.284/adom-bridge-fusion-v1.9.284.zip",- "sha256": "beaceda7864e3628e419faf44649fc315d9900c7d9e0560ca6b6cee5df9d9286",- "size": 577266,+ "version": "1.9.285",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.285/adom-bridge-fusion-v1.9.285.zip",+ "sha256": "fe650871145ec0b659ffa813b1c9076aab7352dfb7f63014c93148c95f5d3b76",+ "size": 577894, "verbPrefixes": [ "fusion_" ],
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.9.284",+ "version": "1.9.285", "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.9.284"+version = "1.9.285" dependencies = [ "serde_json", ]
dashboard/Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.9.284"+version = "1.9.285" 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.9.284"+DASH_VERSION="1.9.285" 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.9.284",+ "version": "1.9.285", "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+11−8@@ -1,6 +1,6 @@ { "slug": "fusion-bridge",- "version": "1.9.284",+ "version": "1.9.285", "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": {@@ -11,6 +11,10 @@ "uninstall": "./uninstall.sh" }, "tags": [+ "ab",+ "bridge",+ "fusion",+ "desktop-app", "fusion360", "autodesk", "cad",@@ -75,7 +79,10 @@ "build a basic parts library", "add a 3d model to a component", "make a fusion 360 library with 3d",- "set up the fusion bridge"+ "set up the fusion bridge",+ "fusion dashboard",+ "fusion status page",+ "open the fusion launch page" ], "discovery_pitch": "Use the Adom Bridge Fusion 360 bridge when the user wants to DRIVE Autodesk Fusion 360 from the cloud: open/search their team's Fusion cloud designs, lay out a PCB or do electronics, export STEP/IGES/STL/Gerbers/BOM, or build a component library with real rendering 3D. It runs Fusion on the user's own machine via Adom Bridge. NOT for web/browser/login/form-filling (that's the native-browser extension) and NOT for KiCad (that's the kicad bridge).", "brief": "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.",@@ -104,9 +111,5 @@ "label": "Part volume", "prompt": "How much material does each printed part in this assembly use?" }- ],- "author": {- "name": "John Lauer",- "email": "[email protected]"- }-}+ ]+}
server.py+7−1@@ -4822,7 +4822,10 @@ def _banner(action, title="", detail="", percent=None, eta_sec=None, tone="worki _BANNER_STATE["active"] = False def _send():- for _to in (8, 20, 30):+ # A terminal hide must land even if the add-in is busy saving/reopening the library+ # for a while: keep trying for a few minutes (a palette left behind after a run is+ # what John saw, 2026-08-30).+ for _to in ((8, 20, 30, 30, 30, 30, 30, 30) if action == "hide" else (8, 20, 30)): try: r = _proxy_to_addin("activity_banner", {"action": action, "title": title, "detail": detail,@@ -12902,6 +12905,9 @@ def _handle_create_library(fusion_info: dict, args: dict) -> dict: # ceiling) a non-pumping main thread is WORK, not a hang (#49 mid-mint kills). _LONG_OP["name"] = "create_library:" + run_id _banner_watchdog_start(run_id) # keep the in-Fusion palette present for the whole run+ # 'show' (not 'update') at the start of a run: it re-opens a palette the user closed+ # with its X during an earlier run; every later update respects a close.+ _banner("show", title="%s: starting" % _lib_label(lbr_p), detail="checking the bundle and Fusion") _LONG_OP["since"] = __import__("time").time() _docs_before = _open_doc_names() # chain hygiene: everything opened after this is ours # OWNERSHIP (John, 2026-08-30): every document this chain opens is the BRIDGE's, so a