← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.13.62\ No newline at end of file+1.13.63\ No newline at end of file
CHANGELOG.md+6
@@ -3,6 +3,12 @@ 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.63 (2026-11-03)++`fusion_schematic_run` has a 20 minute budget in Adom Bridge. It had the 60 second default, so a long list reported `timed_out` while Fusion carried on working (#137).++`fusion_set_layer_count` repairs a board an earlier version converted: Fusion drops the bad via definition on load, so what such a board holds is a stack with no through via. Called again at the same count, it writes the stack again.+ ## 1.13.62 (2026-11-03)  `fusion_schematic_run` takes a list of any length (#137, Andy Engevold): it was refused past 400 commands, about 57 parts at 7 commands each. The bridge
bridge.json+2−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.13.62",+  "version": "1.13.63",   "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.",@@ -502,6 +502,7 @@       "aps_search": 120,       "aps_recent": 120,       "autoroute": 300,+      "schematic_run": 1200,       "set_track_width": 180,       "remove_items": 150,       "set_outline": 150,
dashboard/Cargo.lock+1−1
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "fusion-dashboard"-version = "1.13.62"+version = "1.13.63" dependencies = [  "serde_json", ]
dashboard/Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.13.62"+version = "1.13.63" 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.62"+DASH_VERSION="1.13.63" 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.62",+  "version": "1.13.63",   "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.62",+  "version": "1.13.63",   "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.62"+version = "1.13.63" dependencies = [  "adom-eda-checks",  "base64",@@ -299,7 +299,7 @@ dependencies = [  [[package]] name = "fusion-core"-version = "1.13.62"+version = "1.13.63" dependencies = [  "serde",  "serde_json",@@ -310,7 +310,7 @@ dependencies = [  [[package]] name = "fusion-lbr"-version = "1.13.62"+version = "1.13.63" dependencies = [  "regex",  "roxmltree",@@ -322,14 +322,14 @@ dependencies = [  [[package]] name = "fusion-os-mac"-version = "1.13.62"+version = "1.13.63" dependencies = [  "fusion-core", ]  [[package]] name = "fusion-os-win"-version = "1.13.62"+version = "1.13.63" 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.62"+version = "1.13.63" edition = "2021" license = "MIT" 
rust/bridge/src/routing_script.py+8−2
@@ -3108,8 +3108,14 @@ def stackup_for(count, total_mm, copper_mm, template):   def stack_has_broken_viadef(board):-    """True for a stack written by fusion_set_layer_count before 1.13.61: a via definition with no start or stop layer."""-    return any(not (v.get('startlayer') and v.get('stoplayer')) for s_ in board.findall('.//layerstackup') for v in s_.findall('viadef'))+    """True for a stack written by fusion_set_layer_count before 1.13.61. Its via definition had no start or stop+    layer, and Fusion drops such a definition on load, so what is left is a stack with no through via at all+    (live 2026-11-03). A via with a layer range lands nothing on it."""+    for s_ in board.findall('.//layerstackup'):+        defs = s_.findall('viadef')+        if not defs or any(not (v.get('startlayer') and v.get('stoplayer')) for v in defs):+            return True+    return False   def set_layer_count(app, root, board, meta, args):
tests/layer_drill.py+23−12
@@ -54,21 +54,32 @@ check("edit_pours moves a pour's net", r.get('success') and any(p.get('net') == call('fusion_close_document', {'name': doc})  print('--- a board with the stack the old conversion wrote'); doc = seed('LDrillOld')-d = tempfile.gettempdir()+r = call('fusion_set_layer_count', {'expectDocument': doc, 'count': 4}); check('convert it first', r.get('success'), r.get('error')); time.sleep(2)+READ = """+import tempfile, os, xml.etree.ElementTree as ET+p = os.path.join(tempfile.gettempdir(), 'ldrill2.edru').replace(chr(92), '/'); app.executeTextCommand("Electron.run DRC SAVE '%s';" % p)+r = ET.parse(p).getroot(); r = r.find('designrules') if r.tag != 'designrules' else r+result = {'viadefs': [dict(v.attrib) for v in r.iter('viadef')], 'setup': [q.get('value') for q in r.findall('param') if q.get('name') == 'layerSetup']}+""" r = call('fusion_run_modeling_script', {'script': """ import tempfile, os, xml.etree.ElementTree as ET-p = os.path.join(tempfile.gettempdir(), 'ldrill.edru'); app.executeTextCommand("Electron.run DRC SAVE '%s';" % p.replace(chr(92), '/'))+p = os.path.join(tempfile.gettempdir(), 'ldrill.edru').replace(chr(92), '/'); app.executeTextCommand("Electron.run DRC SAVE '%s';" % p) t = ET.parse(p); r = t.getroot(); r = r.find('designrules') if r.tag != 'designrules' else r-for q in r.findall('param'):-    if q.get('name') == 'layerSetup': q.set('value', '(1+2*15+16)')-st = r.find('layerstackup'); vs = st.find('viadef'); vs.attrib.clear(); vs.set('name', 'Thru'); vs.set('layers', '1:16'); t.write(p, encoding='utf-8', xml_declaration=True)-app.executeTextCommand("Electron.run SET CONFIRM NO; DRC LOAD '%s'; SET CONFIRM OFF;" % p.replace(chr(92), '/')); result = 'ok'-"""}); print('   old stack loaded:', r.get('success'), (r.get('error') or '')[:120]); time.sleep(2); dialogs()-b = len(st(doc).get('tracks') or [])-r, s0 = route(doc, 25); print('   route on the old stack:', r.get('success'), r.get('errorCode'), 'undone', r.get('undone'), 'viasNotLanded', r.get('viasNotLanded'))-if not r.get('success'):-    check('a route that does not land comes off', r.get('undone') and len(st(doc).get('tracks') or []) == b, {k: r.get(k) for k in ('errorCode', 'undone', 'error')})-r = call('fusion_set_layer_count', {'expectDocument': doc, 'count': 4}); check('set_layer_count repairs it', r.get('success') and r.get('repairedViaDefinition'), {k: r.get(k) for k in ('errorCode', 'error', 'note', 'mutated')}); time.sleep(2)+v = r.find('layerstackup').find('viadef'); v.attrib.clear(); v.set('name', 'Thru'); v.set('layers', '1:16'); t.write(p, encoding='utf-8', xml_declaration=True)+app.executeTextCommand("Electron.run SET CONFIRM NO; DRC LOAD '%s'; SET CONFIRM OFF;" % p); result = 'ok'+"""}); print('   old stack loaded:', r.get('success'), (r.get('error') or '')[:150]); time.sleep(2); dialogs()+print('   Fusion now holds:', (call('fusion_run_modeling_script', {'script': READ}).get('data') or {}).get('result'))+b0 = len(st(doc).get('vias') or []); call('fusion_execute_text_command', {'command': "Electron.run CHANGE DRILL 0.4mm; VIA 'MID' 0.8mm ROUND 1-16 (6mm 6mm);"})+broken = len(st(doc).get('vias') or []) == b0; print('   the bug reproduces (a via 1-16 lands nothing):', broken)+b = len(st(doc).get('tracks') or []); v0 = len(st(doc).get('vias') or [])+s = st(doc); r = call('fusion_route_net', {'expectDocument': doc, 'boardId': s.get('boardId'), 'expectedRevision': s.get('revision'), 'net': 'NB', 'layer': 'F.Cu', 'width': 0.3, 'viaDiameter': 0.8, 'viaDrill': 0.4, 'points': [[2, 25], [4, 25], {'x': 6, 'y': 25, 'layer': 'B.Cu'}, [8, 25]]})+print('   route on the old stack:', r.get('success'), r.get('errorCode'), 'undone', r.get('undone'), 'viasNotLanded', r.get('viasNotLanded'), (r.get('error') or '')[:130])+if broken:+    check('a route that does not land comes off', r.get('errorCode') == 'readback_mismatch' and r.get('undone') and len(st(doc).get('tracks') or []) == b, {k: r.get(k) for k in ('errorCode', 'undone', 'error')})+elif r.get('success'):+    call('fusion_execute_text_command', {'command': 'Electron.run UNDO;'})+r = call('fusion_set_layer_count', {'expectDocument': doc, 'count': 4}); check('set_layer_count repairs it', r.get('success') and r.get('repairedViaDefinition') == True or not broken, {k: r.get(k) for k in ('errorCode', 'error', 'note', 'mutated', 'repairedViaDefinition')}); time.sleep(2)+print('   Fusion now holds:', (call('fusion_run_modeling_script', {'script': READ}).get('data') or {}).get('result')) via_lands(doc, "CHANGE DRILL 0.3mm; VIA 'MID' 0.6mm ROUND 1-16 (10mm 5mm);", 'via 1-16 lands after the repair') r2 = call('fusion_set_layer_count', {'expectDocument': doc, 'count': 4}); check('a second call has nothing to repair', r2.get('success') and not r2.get('mutated'), r2.get('mutated')) dialogs(); call('fusion_close_document', {'name': doc})
tests/test_routing.py+2
@@ -699,6 +699,8 @@ class LayerCountTests(unittest.TestCase):         self.assertFalse(r.stack_has_broken_viadef(b))         v = st.find('viadef'); v.attrib.clear(); v.set('name', 'Thru'); v.set('layers', '1:16')         self.assertTrue(r.stack_has_broken_viadef(b))+        st.remove(v)   # what Fusion actually holds after loading that: the definition dropped+        self.assertTrue(r.stack_has_broken_viadef(b))