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 14 file(s)
14 files changed
+29−22
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.13.57+1.13.58
CHANGELOG.md+3−3@@ -3,10 +3,10 @@ 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.57 (2026-11-02)+## 1.13.58 (2026-11-02) -`fusion_edit_tracks` sets a fine grid before moving a track's end: without it MOVE snapped the end to the board's grid, the-readback found it elsewhere, and the edit was undone.+`fusion_edit_tracks` moves a track's end in a straight line: Fusion's default bend style made an angled move into two segments, so+the readback refused it and undid the edit. `fusion_sch_wire` and the wire `fusion_sch_swap_part` adds draw straight for the same reason. ## 1.13.56 (2026-11-02)
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.13.57",+ "version": "1.13.58", "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.57"+version = "1.13.58" dependencies = [ "serde_json", ]
dashboard/Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.13.57"+version = "1.13.58" 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.57"+DASH_VERSION="1.13.58" 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.57",+ "version": "1.13.58", "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.57",+ "version": "1.13.58", "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" ]-}+}
rust/Cargo.lock+5−5@@ -19,7 +19,7 @@ dependencies = [ [[package]] name = "adom-fusion-bridge"-version = "1.13.57"+version = "1.13.58" dependencies = [ "adom-eda-checks", "base64",@@ -299,7 +299,7 @@ dependencies = [ [[package]] name = "fusion-core"-version = "1.13.57"+version = "1.13.58" dependencies = [ "serde", "serde_json",@@ -310,7 +310,7 @@ dependencies = [ [[package]] name = "fusion-lbr"-version = "1.13.57"+version = "1.13.58" dependencies = [ "regex", "roxmltree",@@ -322,14 +322,14 @@ dependencies = [ [[package]] name = "fusion-os-mac"-version = "1.13.57"+version = "1.13.58" dependencies = [ "fusion-core", ] [[package]] name = "fusion-os-win"-version = "1.13.57"+version = "1.13.58" 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.57"+version = "1.13.58" edition = "2021" license = "MIT"
rust/bridge/src/routing_script.py+2−2@@ -1200,7 +1200,8 @@ def edit_tracks(app, root, board, meta, args): for key in ('start', 'end'): if ed.get(key) is not None: to = [number(ed[key][0]), number(ed[key][1])]- step.append('MOVE (%.6fmm %.6fmm) (%.6fmm %.6fmm);' % (x[key][0], x[key][1], to[0], to[1]))+ # WIRE_BEND 2 = straight: the default bend style makes an angled move into two segments+ step.append('SET WIRE_BEND 2; MOVE (%.6fmm %.6fmm) (%.6fmm %.6fmm); SET WIRE_BEND 0;' % (x[key][0], x[key][1], to[0], to[1])) want[key] = to if ed.get('layer') is not None: want['layer'] = _layer_number(board, ed['layer'])@@ -1218,7 +1219,6 @@ def edit_tracks(app, root, board, meta, args): return {'success': False, 'mutated': False, **meta, 'errorCode': 'nothing_to_do', 'results': results, 'error': 'No track could be edited'} visible = _visible_layers(root) try:- # without a fine grid MOVE snaps a track's end to the board's grid, and it reads back somewhere else app.executeTextCommand('Electron.run GRID MM 0.001;') for c in cmds: app.executeTextCommand('Electron.run ' + c)
rust/bridge/src/sch_script.py+3−2@@ -1182,7 +1182,7 @@ def sch_swap_part(app, args): ends_after = ends(e.after, e.sheet2) join = [p_ for p_ in lost if p_ in ends_after and ends_after[p_] != ends_before.get(p_)] if join:- e.again(['SET CONFIRM YES; NET %s %s %s; SET CONFIRM OFF;' % (quote(before[p_]), mm(ends_before[p_]), mm(ends_after[p_])) for p_ in join])+ e.again(['SET CONFIRM YES; SET WIRE_BEND 2; NET %s %s %s; SET WIRE_BEND 0; SET CONFIRM OFF;' % (quote(before[p_]), mm(ends_before[p_]), mm(ends_after[p_])) for p_ in join]) after = by_pad(e.after, e.sheet2) lost = sorted(p_ for p_, n in before.items() if n and after.get(p_) != n) # every pin that ended elsewhere and kept its net: joined by Fusion's REPLACE, or by the wire above@@ -1262,7 +1262,8 @@ def sch_wire(app, args): if max(abs(v) for p_ in pts for v in p_) > 1950: sfail('off_the_sheet', 'Keep points within 1950 mm: Fusion drops drawing past about 2 m') name = args.get('net')- e.cmds.append('SET CONFIRM YES; NET %s%s; SET CONFIRM OFF;' % (quote(str(name)) + ' ' if name else '', ' '.join(mm(p_) for p_ in pts)))+ # WIRE_BEND 2 = straight: the default bend style makes an angled segment into two+ e.cmds.append('SET CONFIRM YES; SET WIRE_BEND 2; NET %s%s; SET WIRE_BEND 0; SET CONFIRM OFF;' % (quote(str(name)) + ' ' if name else '', ' '.join(mm(p_) for p_ in pts))) if dry(args): return plan_reply(e) e.run()
tests/run_verb_tests.py+4@@ -292,6 +292,10 @@ t("fusion_project_info", "daily", dict(B), {"ok": True, "fields": ["files", "boa t("fusion_schematic_data", "daily", dict(B, designator="C11", params=True), {"ok": True, "fields": ["components", "count"]}) t("fusion_part_parameters", "daily", dict(B), {"ok": True, "fields": ["names"]}) t("fusion_cross_check", "daily", dict(B), {"ok": True, "fields": ["matched", "onSheetNotOnBoard", "onBoardNotOnSheet"]})+for v in ("fusion_sch_label", "fusion_sch_wire", "fusion_edit_tracks", "fusion_place_parts"):+ t(v, "manual", note="edits a design: run live by tests/sch_drill.py (place_parts and a track's end on a board with no schematic by hand)")+t("fusion_model_check", "daily", dict(B), {"ok": True, "fields": ["parts", "checked", "ok"]}, "does each part's cloud 3D model open, read only")+t("fusion_extensions", "daily", {}, {"ok": True, "fields": ["extensions", "count"]}, "the add-ins installed for this user") t("fusion_update_pcb", "daily", dict(B), {"ok": True, "fields": ["executed", "preview", "changeOrder"]}, "the standard's update_pcb: the preview, nothing to execute") t("fusion_preferences", "daily", {}, {"ok": True}, "the standard's preferences, read only here") t("fusion_select_items", "negative", {}, {"errorCode": "bad_args"}, "refs or clear is required")
tests/sch_drill.py+3−1@@ -123,6 +123,8 @@ step('sch_delete_wire', 'fusion_sch_delete_wire', {'ids': [(wires or [{}])[-1].g # ── wires and labels by point ────────────────────────────────────────────────────────────────────── step('sch_wire', 'fusion_sch_wire', {'points': [[20, 200], [40, 200], [40, 220]], 'net': 'WIRED'}, lambda r: expect(any(w.get('net') == 'WIRED' for w in items('wires').get('wires') or []) and len(r.get('wires') or []) == 2, 'wire not on the sheet'))+step('sch_wire diagonal', 'fusion_sch_wire', {'points': [[60, 200], [75, 215]], 'net': 'DIAG'},+ lambda _r: expect(sum(w.get('net') == 'DIAG' for w in items('wires').get('wires') or []) == 1, 'a diagonal wire came back in pieces')) step('sch_label add', 'fusion_sch_label', {'text': 'WIRED', 'at': [30, 200]}, lambda _r: expect(any(l.get('text') == 'WIRED' for l in items('labels').get('labels') or []), 'label not on the sheet')) r = call('fusion_sch_label', {'text': 'OTHER', 'at': [30, 200]})@@ -205,7 +207,7 @@ def track_widths(): step('edit_tracks width', 'fusion_edit_tracks', {'expectDocument': 'Untitled', 'tracks': [{'id': tid, 'width': 0.4}]}, lambda r: expect(r.get('verified') and abs((track_widths().get(tid) or 0) - 0.4) < 1e-6, 'width now %s' % track_widths().get(tid)))-step('edit_tracks end', 'fusion_edit_tracks', {'expectDocument': 'Untitled', 'tracks': [{'id': tid, 'end': [x0 + 2, y0 + 4] if pad else [0, 0]}]},+step('edit_tracks end, at an angle', 'fusion_edit_tracks', {'expectDocument': 'Untitled', 'tracks': [{'id': tid, 'end': [x0 + 2, y0 + 4] if pad else [0, 0]}]}, lambda r: expect(r.get('verified') and any(abs(t['end']['x'] - x0 - 2) < 1e-3 for t in call('fusion_routing_state', {'expectDocument': 'Untitled'}).get('tracks') or [] if t.get('net') == 'NB'), 'end did not move')) r = call('fusion_edit_tracks', {'expectDocument': 'Untitled', 'tracks': [{'id': 'track:NOPE:1:0,0,1,1', 'width': 0.3}]}) results.append(('edit_tracks unknown id', expect(r.get('errorCode') == 'nothing_to_do', str(r.get('errorCode')))))