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
Publish 1.11.31
12 files changed
+344−21
.insiders-stage/fusion-bridge/version.json+4−4.release-page-head+1−1install.shpage.json+25−9rust/Cargo.lock+5−5rust/bridge/src/fusion_default_designrules.xml+137rust/bridge/src/routing_script.py+170tests/run_verb_tests.py+2−2tools/check-naming.shtools/check-portable.shtools/promote.shtools/release.sh.insiders-stage/fusion-bridge/version.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.11.30",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.11.30/adom-bridge-fusion-v1.11.30.zip",- "sha256": "7858c3cd21549a8f11679e1a1fd05b51ac3c81eb39524dc964cc2e0f0b0383fc",- "size": 6561038,+ "version": "1.11.31",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.11.31/adom-bridge-fusion-v1.11.31.zip",+ "sha256": "72c7deee335fa6291edc1c26d0515d42a6d4a9f6d3f0950a0499458055b354be",+ "size": 6567185, "verbPrefixes": [ "fusion_" ],
.release-page-head+1−1@@ -1 +1 @@-8df58d6c6542418948403ae21b5da28189bd4b46+81fecc6c4c7bdf3775ca1f078e9dc1c514a3d4a2
install.shpage.json+25−9@@ -11,10 +11,6 @@ "uninstall": "./uninstall.sh" }, "tags": [- "ab",- "bridge",- "fusion",- "desktop-app", "fusion360", "autodesk", "cad",@@ -80,9 +76,6 @@ "add a 3d model to a component", "make a fusion 360 library with 3d", "set up the fusion bridge",- "fusion dashboard",- "fusion status page",- "open the fusion launch page", "aps not configured", "share aps client id across my org", "set up fusion cloud search for my company",@@ -115,5 +108,28 @@ "label": "Part volume", "prompt": "How much material does each printed part in this assembly use?" }- ]-}+ ],+ "agent_permissions": {+ "allow_bash": [],+ "allow": [+ "adom-desktop-fusion-bridge read-only verbs (via the adom-bridge CLI): fusion_readiness, fusion_get_app_state, fusion_addin_status, fusion_check_dialogs - state queries; they change nothing.",+ "adom-desktop-fusion-bridge read-only verbs: fusion_alerts (reads Fusion's own log for toasts), fusion_document_info, fusion_window_info, fusion_board_info, fusion_library_parts - inspect only.",+ "adom-desktop-fusion-bridge read-only verbs: fusion_prove_result (a persisted run report), fusion_plan_library_import (report-only preflight), fusion_list_cloud_projects, fusion_describe.",+ "adom-desktop-fusion-bridge evidence captures fusion_screenshot_fusion and fusion_capture_library_views read pixels and write local image files only."+ ],+ "soft_deny": [+ "adom-desktop-fusion-bridge lifecycle verbs fusion_stop, fusion_kill, fusion_restart_for_addin close or kill the Fusion application - keep the human prompt.",+ "adom-desktop-fusion-bridge fusion_install_fusion silently installs Autodesk Fusion on the user's machine.",+ "adom-desktop-fusion-bridge fusion_cleanup_cloud_files deletes files from the user's Autodesk cloud hub.",+ "adom-desktop-fusion-bridge managed-library verbs (fusion_managed_library_push/pull/create/link/unlink) publish to or overwrite the user's library.io libraries."+ ],+ "environment": [+ "adom-desktop-fusion-bridge is the Fusion 360 bridge for Adom Bridge; its fusion_* verbs are invoked through the adom-bridge CLI and drive Autodesk Fusion on the user's own desktop.",+ "adom-desktop-fusion-bridge desktop actions also pass Adom Bridge's own human-approval gate (caller identity, per-call reason, Activity Log) - a container approval is the first of two gates."+ ]+ },+ "author": {+ "name": "John Lauer",+ "email": "[email protected]"+ }+}
rust/Cargo.lock+5−5@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-fusion-bridge"-version = "1.11.30"+version = "1.11.31" dependencies = [ "base64", "chrono",@@ -273,7 +273,7 @@ dependencies = [ [[package]] name = "fusion-core"-version = "1.11.30"+version = "1.11.31" dependencies = [ "serde", "serde_json",@@ -283,7 +283,7 @@ dependencies = [ [[package]] name = "fusion-lbr"-version = "1.11.30"+version = "1.11.31" dependencies = [ "regex", "roxmltree",@@ -295,14 +295,14 @@ dependencies = [ [[package]] name = "fusion-os-mac"-version = "1.11.30"+version = "1.11.31" dependencies = [ "fusion-core", ] [[package]] name = "fusion-os-win"-version = "1.11.30"+version = "1.11.31" dependencies = [ "fusion-core", "windows-sys 0.59.0",
rust/bridge/src/fusion_default_designrules.xmladded+137@@ -0,0 +1,137 @@+<designrules name="Fusion Electronics Design Rules" version="V1">+<rules>+<rulecategory type="Copper Clearance" manualdrc="yes" livedrc="yes" />+ <rulecategory type="Keepout Clearance" manualdrc="yes" livedrc="yes" />+ <rulecategory type="Solder-Mask Clearance" manualdrc="yes" livedrc="yes" />+ <rulecategory type="Dimension Clearance" manualdrc="yes" livedrc="yes" />+ <rulecategory type="Drill Distance" manualdrc="yes" livedrc="yes" />+ <rulecategory type="Restrict Clearance" manualdrc="yes" livedrc="yes" />+ <rulecategory type="Minimum Drill Size" manualdrc="yes" livedrc="yes" />+ <rulecategory type="Minimum Copper Width" manualdrc="yes" livedrc="yes" />+ <rulecategory type="Acute Angle" manualdrc="yes" livedrc="yes" />+ <rulecategory type="Air-Wire" manualdrc="yes" livedrc="yes" />+ <rulecategory type="Small Gap" manualdrc="yes" livedrc="yes" />+ <rulecategory type="Wire Stub" manualdrc="yes" livedrc="no" />+ <rulecategory type="Matched Lengths" manualdrc="yes" livedrc="yes" />+ <rulecategory type="Via Restrict Clearance" manualdrc="yes" livedrc="yes" />+ <rulecategory type="Teardrop" manualdrc="no" livedrc="no" />+ <rule type="Minimum Copper Width" builtin_ruleid="14" enabled="yes" priority="0" value="6mil" preferredvalue="6mil" check_polywidth="no" name="minimum-copper-width-1" />+ <rule type="Teardrop" builtin_ruleid="22" enabled="yes" priority="0" onescope="is_via" lengthratio="0.5" widthratio="0.7" curved_sides="no" auto_generate="no" name="teardrop-1" />+ <rule type="Teardrop" builtin_ruleid="23" enabled="yes" priority="1" onescope="is_pad" lengthratio="0.5" widthratio="0.7" curved_sides="no" auto_generate="no" name="teardrop-2" />+ <rule type="Teardrop" builtin_ruleid="24" enabled="yes" priority="2" onescope="is_smd" lengthratio="0.5" widthratio="0.7" curved_sides="no" auto_generate="no" name="teardrop-3" />+ <rule type="Teardrop" builtin_ruleid="25" enabled="yes" priority="3" onescope="is_wire_polygon" lengthratio="0.5" widthratio="0.9" curved_sides="no" auto_generate="no" name="teardrop-4" />+ <rule type="Minimum Drill Size" builtin_ruleid="15" enabled="yes" priority="0" value="0.35mm" preferredvalue="0.35mm" name="minimum-drill-size-1" />+ <rule type="Acute Angle" builtin_ruleid="16" enabled="no" priority="0" name="acute-angle-1" />+ <rule type="Wire Stub" builtin_ruleid="17" enabled="yes" priority="0" name="wire-stub-1" />+ <rule type="Air-Wire" builtin_ruleid="19" enabled="yes" priority="0" name="air-wire-1" />+ <rule type="Matched Lengths" builtin_ruleid="20" enabled="yes" priority="0" tolerance="10mm" gapfactor="2.5" name="matched-lengths-1" />+ <rule type="Copper Clearance" builtin_ruleid="0" enabled="yes" priority="0" onescope="is_wire_polygon" otherscope="is_wire_polygon" value="6mil" preferredvalue="6mil" samesignal="no" name="copper-clearance-1" />+ <rule type="Copper Clearance" builtin_ruleid="1" enabled="yes" priority="1" onescope="is_wire_polygon" otherscope="is_pad_smd" value="6mil" preferredvalue="6mil" samesignal="no" name="copper-clearance-2" />+ <rule type="Copper Clearance" builtin_ruleid="2" enabled="yes" priority="2" onescope="is_wire_polygon" otherscope="is_via" value="6mil" preferredvalue="6mil" samesignal="no" name="copper-clearance-3" />+ <rule type="Copper Clearance" builtin_ruleid="3" enabled="yes" priority="3" onescope="is_pad_smd" otherscope="is_pad_smd" value="6mil" preferredvalue="6mil" samesignal="no" name="copper-clearance-4" />+ <rule type="Copper Clearance" builtin_ruleid="4" enabled="yes" priority="4" onescope="is_pad_smd" otherscope="is_via" value="6mil" preferredvalue="6mil" samesignal="no" name="copper-clearance-5" />+ <rule type="Copper Clearance" builtin_ruleid="5" enabled="yes" priority="5" onescope="is_via" otherscope="is_via" value="6mil" preferredvalue="6mil" samesignal="no" name="copper-clearance-6" />+ <rule type="Copper Clearance" builtin_ruleid="6" enabled="yes" priority="6" onescope="is_smd" otherscope="is_pad" value="6mil" preferredvalue="6mil" samesignal="yes" name="copper-clearance-7" />+ <rule type="Copper Clearance" builtin_ruleid="7" enabled="yes" priority="7" onescope="is_smd" otherscope="is_via" value="6mil" preferredvalue="6mil" samesignal="yes" name="copper-clearance-8" />+ <rule type="Copper Clearance" builtin_ruleid="8" enabled="yes" priority="8" onescope="is_smd" otherscope="is_smd" value="6mil" preferredvalue="6mil" samesignal="yes" name="copper-clearance-9" />+ <rule type="Dimension Clearance" builtin_ruleid="9" enabled="yes" priority="0" value="40mil" preferredvalue="40mil" name="dimension-clearance-1" />+ <rule type="Via Restrict Clearance" builtin_ruleid="21" enabled="yes" priority="0" value="0mil" preferredvalue="0mil" name="via-restrict-clearance-1" />+ <rule type="Restrict Clearance" builtin_ruleid="10" enabled="yes" priority="0" value="0mil" preferredvalue="0mil" name="restrict-clearance-1" />+ <rule type="Small Gap" builtin_ruleid="18" enabled="yes" priority="0" name="small-gap-1" />+ <rule type="Solder-Mask Clearance" builtin_ruleid="11" enabled="no" priority="0" value="0mil" preferredvalue="0mil" check_name="yes" check_value="no" name="solder-mask-clearance-1" />+ <rule type="Drill Distance" builtin_ruleid="12" enabled="yes" priority="0" value="6mil" preferredvalue="6mil" name="drill-distance-1" />+ <rule type="Keepout Clearance" builtin_ruleid="13" enabled="yes" priority="0" value="0mil" preferredvalue="0mil" name="keepout-clearance-1" />+ </rules>+<param name="mdWireWire" value="6mil" />+<param name="mdWirePad" value="6mil" />+<param name="mdWireVia" value="6mil" />+<param name="mdPadPad" value="6mil" />+<param name="mdPadVia" value="6mil" />+<param name="mdViaVia" value="6mil" />+<param name="mdSmdPad" value="6mil" />+<param name="mdSmdVia" value="6mil" />+<param name="mdSmdSmd" value="6mil" />+<param name="mdCopperDimension" value="40mil" />+<param name="mdDrill" value="6mil" />+<param name="msWidth" value="6mil" />+<param name="msDrill" value="0.35mm" />+<param name="dpMaxLengthDifference" value="10mm" />+<param name="dpGapFactor" value="2.5" />+<description language="en"><b>Fusion Electronics Design Rules</b>+<p>+The default Design Rules have been set to cover+a wide range of applications. Your particular design+may have different requirements, so please make the+necessary adjustments and save your customized+design rules under a new name.+</description>+<param name="layerSetup" value="(1*16)" />+<param name="mtCopper" value="0.035mm 0.035mm 0.035mm 0.035mm 0.035mm 0.035mm 0.035mm 0.035mm 0.035mm 0.035mm 0.035mm 0.035mm 0.035mm 0.035mm 0.035mm 0.035mm" />+<param name="mtIsolate" value="1.5mm 0.15mm 0.2mm 0.15mm 0.2mm 0.15mm 0.2mm 0.15mm 0.2mm 0.15mm 0.2mm 0.15mm 0.2mm 0.15mm 0.2mm" />+<param name="mdViaViaSameLayer" value="6mil" />+<param name="mnLayersViaInSmd" value="2" />+<param name="mdSmdStop" value="0mil" />+<param name="msMicroVia" value="9.99mm" />+<param name="msBlindViaRatio" value="0.5" />+<param name="rvPadTop" value="0.25" />+<param name="rvPadInner" value="0.25" />+<param name="rvPadBottom" value="0.25" />+<param name="rvViaOuter" value="0.25" />+<param name="rvViaInner" value="0.25" />+<param name="rvMicroViaOuter" value="0.25" />+<param name="rvMicroViaInner" value="0.25" />+<param name="rlMinPadTop" value="10mil" />+<param name="rlMaxPadTop" value="20mil" />+<param name="rlMinPadInner" value="10mil" />+<param name="rlMaxPadInner" value="20mil" />+<param name="rlMinPadBottom" value="10mil" />+<param name="rlMaxPadBottom" value="20mil" />+<param name="rlMinViaOuter" value="8mil" />+<param name="rlMaxViaOuter" value="20mil" />+<param name="rlMinViaInner" value="8mil" />+<param name="rlMaxViaInner" value="20mil" />+<param name="rlMinMicroViaOuter" value="4mil" />+<param name="rlMaxMicroViaOuter" value="20mil" />+<param name="rlMinMicroViaInner" value="4mil" />+<param name="rlMaxMicroViaInner" value="20mil" />+<param name="psTop" value="-1" />+<param name="psBottom" value="-1" />+<param name="psFirst" value="-1" />+<param name="psElongationLong" value="100" />+<param name="psElongationOffset" value="100" />+<param name="mvStopFrame" value="1" />+<param name="mvCreamFrame" value="0" />+<param name="mlMinStopFrame" value="4mil" />+<param name="mlMaxStopFrame" value="4mil" />+<param name="mlMinCreamFrame" value="0mil" />+<param name="mlMaxCreamFrame" value="0mil" />+<param name="mlViaStopLimit" value="9999mil" />+<param name="srRoundness" value="0" />+<param name="srMinRoundness" value="0mil" />+<param name="srMaxRoundness" value="0mil" />+<param name="slThermalIsolate" value="10mil" />+<param name="slThermalsForVias" value="0" />+<param name="checkAngle" value="0" />+<param name="checkRestrict" value="1" />+<param name="checkStop" value="0" />+<param name="checkValues" value="0" />+<param name="checkNames" value="1" />+<param name="checkWireStubs" value="1" />+<param name="checkPolygonWidth" value="0" />+<param name="useDiameter" value="13" />+<param name="removeAnnularRing" value="0" />+<param name="flattenRefDes" value="0" />+<param name="maxErrors" value="999999" />+<layerstackup name="Default Layer Stack" type="Standard Stackup" display_unit="mic" roughness_model="0">+ <layerdef type="Signal" id="{34728435-6319-49c4-aa4b-45205589adb0}" layer="1" name="Top" locally_modified_material="yes">+ <material name="User Defined" thickness="0.035mm" weight="1oz" process="Electro Deposited" source="User" />+</layerdef>+ <layerdef type="Core" id="{4fc97192-1c8a-45f5-afa8-b9bfaa4a1b4d}" layer="1" name="Dielectric-1" locally_modified_material="yes">+ <material name="User Defined" construction="" thickness="1.5mm" resin="50%" dielectric_constant_1g="4.5" dissipation_factor_1g="0.02" glass_transition_temperature="125degC" source="User" />+</layerdef>+ <layerdef type="Signal" id="{c0eb56e3-6a7f-4d5c-aa04-f0e1333269e1}" layer="16" name="Bottom" locally_modified_material="yes">+ <material name="User Defined" thickness="0.035mm" weight="1oz" process="Electro Deposited" source="User" />+</layerdef>+ <viadef name="Thru 1:16" startlayer="1" stoplayer="16" complement="no" />+ </layerstackup>+ </designrules>
rust/bridge/src/routing_script.py+170@@ -543,10 +543,180 @@ def board_bom(board, args): 'boardOnly': [' '.join(r['designators']) for r in lines if not r['mpn']], 'missingPartNumbers': missing} +def schematic_plan(args):+ """A grouped schematic for a netlist, as fusion_schematic_run command groups. Every block of the+ netlist gets a labelled frame; its parts sit in a row; every connected pin gets a short stub+ named for its net and a label at the stub's end (no wire ever ends on a pin: Fusion attaches a+ NET's first and middle points to pins, never its last). Pure computation from the .lbr on disk."""+ lbr = args.get('libraryPath')+ if not lbr or not Path(lbr).is_file(): fail('library_missing', 'libraryPath must be the .lbr on this machine')+ lroot = ET.parse(lbr).getroot(); lib = lroot.find('drawing/library'); libname = Path(lbr).stem+ sym = {x.get('name'): x for x in lib.iter('symbol')}+ dsets = {d.get('name').upper(): d for d in lib.iter('deviceset')}+ parts = args.get('parts')+ if not isinstance(parts, list) or not parts: fail('invalid_args', 'parts must be [{ref, deviceset, value?, block?}]')+ nets = args.get('nets') or {}+ def P(p): return '(%.3fmm %.3fmm)' % p+ info = {}+ for q in parts:+ d = dsets.get(str(q.get('deviceset', '')).upper())+ if d is None: fail('unknown_device', 'No deviceset %s in %s' % (q.get('deviceset'), lbr))+ g = d.find('gates/gate'); s_ = sym[g.get('symbol')]+ dev = d.find('devices/device'); pad2pin = {}+ for c in dev.iter('connect'):+ for pad in c.get('pad', '').split(): pad2pin[pad] = c.get('pin')+ pins = {p.get('name'): (float(p.get('x')), float(p.get('y')), p.get('rot', 'R0')) for p in s_.iter('pin')}+ xs = [v[0] for v in pins.values()] + [0]; ys = [v[1] for v in pins.values()] + [0]+ for w in s_.iter('wire'):+ xs += [float(w.get('x1')), float(w.get('x2'))]; ys += [float(w.get('y1')), float(w.get('y2'))]+ info[q['ref']] = {'ds': d.get('name'), 'pins': pins, 'pad2pin': pad2pin, 'bbox': (min(xs), min(ys), max(xs), max(ys)),+ 'value': q.get('value'), 'block': q.get('block') or 'parts'}+ # net of each (ref, pin): netlist pins may be named by pad ("U1.3") or by pin ("U1.VIN")+ pin_net = {}+ for n, members in nets.items():+ identifier(n)+ for m in members:+ ref, _, p_ = str(m).partition('.')+ if ref not in info: continue+ pn = p_ if p_ in info[ref]['pins'] else info[ref]['pad2pin'].get(p_)+ if pn: pin_net[(ref, pn)] = n+ blocks = []+ for q in parts:+ b = info[q['ref']]['block']+ if b not in blocks: blocks.append(b)+ out_dir = {'R0': (-1, 0), 'R90': (0, -1), 'R180': (1, 0), 'R270': (0, 1)}+ steps = []; cols = int(args.get('columns', 3)); cell_w, x0, y_top = 0.0, 20.32, 165.1+ col_x = [x0]; row_y = y_top; row_h = 0.0; placed_in_row = 0+ for bi, b in enumerate(blocks):+ refs = [q['ref'] for q in parts if info[q['ref']]['block'] == b]+ # parts in a row, each spaced by its own width plus room for labels+ layout = []; cx = 0.0; h = 0.0+ for r in refs:+ x1, y1, x2, y2 = info[r]['bbox']+ px = cx - x1 + 12.7+ layout.append((r, px)); cx = px + x2 + 12.7; h = max(h, y2 - y1)+ fw = max(cx + 7.62, 50.8); fh = h + 30.48+ if placed_in_row >= cols: row_y -= row_h + 10.16; placed_in_row = 0; row_h = 0.0; cell_x = x0+ cell_x = x0 if placed_in_row == 0 else cell_x+ fx1, fy2 = cell_x, row_y; fx2, fy1 = fx1 + fw, fy2 - fh+ cmds = ['LAYER 97;', 'CHANGE STYLE ShortDash;', 'WIRE 0.1524mm %s %s %s %s %s;' % (P((fx1, fy1)), P((fx2, fy1)), P((fx2, fy2)), P((fx1, fy2)), P((fx1, fy1))),+ 'CHANGE STYLE Continuous;', 'CHANGE SIZE 2.54mm;', "TEXT '%s' R0 %s;" % (str(b).replace("'", '')[:60].title(), P((fx1 + 2.54, fy2 - 5.08))), 'CHANGE SIZE 1.778mm;', 'LAYER 91;']+ cy = fy1 + 10.16 - min(info[r]['bbox'][1] for r, _ in layout) if layout else fy1+ for r, px in layout:+ at = (round(fx1 + px, 3), round(cy, 3))+ cmds.append("ADD '%s@%s' '%s' R0 %s;" % (info[r]['ds'], libname, r, P(at)))+ if info[r]['value']: cmds.append("VALUE '%s' '%s';" % (r, str(info[r]['value']).replace("'", '')[:40]))+ for pn, (x, y, rot) in info[r]['pins'].items():+ n = pin_net.get((r, pn))+ if not n: continue+ dx, dy = out_dir.get(rot.lstrip('MS'), (-1, 0))+ a = (round(at[0] + x, 3), round(at[1] + y, 3)); e = (round(a[0] + dx * 5.08, 3), round(a[1] + dy * 5.08, 3))+ cmds += ["NET '%s' %s %s;" % (n, P(a), P(e)), 'LABEL %s %s;' % (P(e), P(e))]+ steps.append({'group': b, 'parts': refs, 'commands': cmds})+ cell_x = fx2 + 10.16; placed_in_row += 1; row_h = max(row_h, fh)+ unplaced = sorted({m for ms in nets.values() for m in ms if str(m).partition('.')[0] not in info})+ return {'success': True, 'mutated': False, 'libraryPath': lbr, 'groups': steps,+ 'commandCount': sum(len(x['commands']) for x in steps), 'notInParts': unplaced,+ '_hint': 'Run each group with fusion_schematic_run {libraryPath, commands} (one call per group, so a viewer sees the sheet grow block by block), then read the nets it returns against your netlist.'}+++def board_seed(args):+ """The design a board starts from, written as EAGLE source for fusion_new_electronics_from_eagle:+ the outline, the fab's design rules (Fusion's own rules block with the given values set), the+ fixed parts at their places with their nets, and the library they come from. Fusion's defaults+ (0.35 mm drill, 1 mm to the edge, 25 % pad restring) are the wrong rules for most fabs."""+ lbr = args.get('libraryPath'); out = args.get('outDir'); name = args.get('name') or 'Board'+ if not lbr or not Path(lbr).is_file(): fail('library_missing', 'libraryPath must be the .lbr on this machine')+ if not out: fail('invalid_args', 'outDir (a folder on this machine) is required')+ if not re.fullmatch(r'[A-Za-z0-9_-]{1,48}', name): fail('invalid_args', 'name: letters, digits, _ and - only')+ w, h = [number(v, True) for v in (args.get('outlineMm') or [0, 0])]+ root = ET.parse(lbr).getroot(); layers = ET.tostring(root.find('drawing/layers'), encoding='unicode')+ lib = root.find('drawing/library'); LIB = Path(lbr).stem; lib.set('name', LIB)+ fixed = args.get('fixed') or []+ keep = {str(f.get('deviceset')).upper() for f in fixed}+ for d in list(lib.find('devicesets')):+ if d.get('name').upper() not in keep: lib.find('devicesets').remove(d)+ used_sym = {g.get('symbol') for d in lib.iter('deviceset') for g in d.iter('gate')}+ used_pkg = {x.get('package') for d in lib.iter('deviceset') for x in d.iter('device')}+ for x in list(lib.find('symbols')):+ if x.get('name') not in used_sym: lib.find('symbols').remove(x)+ for x in list(lib.find('packages')):+ if x.get('name') not in used_pkg: lib.find('packages').remove(x)+ p3 = lib.find('packages3d'); urn = {}+ if p3 is not None:+ for q in list(p3):+ names = [pi.get('name') for pi in q.iter('packageinstance')]+ if not any(n in used_pkg for n in names): p3.remove(q); continue+ for n in names: urn[n] = q.get('wip_urn') or q.get('urn')+ lib_sch = ET.tostring(lib, encoding='unicode'); lib_b = ET.fromstring(lib_sch)+ for tag in ('symbols', 'devicesets'):+ n = lib_b.find(tag)+ if n is not None: lib_b.remove(n)+ lib_brd = ET.tostring(lib_b, encoding='unicode')+ # the rules: Fusion's own block, with the values the caller gives+ rules = FUSION_DEFAULT_RULES; r = args.get('rules') or {}+ def setp(k, v):+ nonlocal rules; rules = re.sub(r'(<param name="%s" value=")[^"]*"' % k, lambda m: m.group(1) + v + '"', rules)+ def setr(t, v):+ nonlocal rules; rules = re.sub(r'(<rule type="%s"[^>]*?) value="[^"]*" preferredvalue="[^"]*"' % t, lambda m: m.group(1) + ' value="%s" preferredvalue="%s"' % (v, v), rules)+ mm = lambda k: '%gmm' % number(r[k], True)+ if 'clearance' in r:+ for k in ('mdWireWire', 'mdWirePad', 'mdWireVia', 'mdPadPad', 'mdPadVia', 'mdViaVia', 'mdSmdPad', 'mdSmdVia', 'mdSmdSmd', 'mdViaViaSameLayer'): setp(k, mm('clearance'))+ setr('Copper Clearance', mm('clearance'))+ if 'edge' in r: setp('mdCopperDimension', mm('edge')); setr('Dimension Clearance', mm('edge'))+ if 'minWidth' in r: setp('msWidth', mm('minWidth')); setr('Minimum Copper Width', mm('minWidth'))+ if 'minDrill' in r: setp('msDrill', mm('minDrill')); setr('Minimum Drill Size', mm('minDrill'))+ if 'holeToHole' in r: setp('mdDrill', mm('holeToHole')); setr('Drill Distance', mm('holeToHole'))+ if 'padRestringRatio' in r:+ for k in ('rvPadTop', 'rvPadInner', 'rvPadBottom'): setp(k, '%g' % number(r['padRestringRatio'], True))+ if 'padRestring' in r:+ for k in ('rlMinPadTop', 'rlMinPadInner', 'rlMinPadBottom'): setp(k, mm('padRestring'))+ if 'viaRestring' in r:+ for k in ('rlMinViaOuter', 'rlMinViaInner'): setp(k, mm('viaRestring'))+ if 'copperUm' in r: setp('mtCopper', ' '.join(['%gmm' % (number(r['copperUm'], True) / 1000.0)] * 16))+ rules = rules.replace('name="Fusion Electronics Design Rules"', 'name="%s rules"' % name)+ parts, inst, nets, elements, signals = [], [], {}, [], {}+ for i, f in enumerate(fixed):+ ref = identifier(f.get('ref')); dsn = str(f.get('deviceset'))+ d = next((x for x in lib.iter('deviceset') if x.get('name').upper() == dsn.upper()), None)+ g = d.find('gates/gate').get('name'); dev = d.find('devices/device'); pkg = dev.get('package')+ val = str(f.get('value', '')).replace('"', '')+ sym_pins = {pp.get('name'): pp for pp in lib.find("symbols/symbol[@name='%s']" % d.find('gates/gate').get('symbol')).iter('pin')}+ parts.append('<part name="%s" library="%s" deviceset="%s" device="%s" value="%s"%s/>' % (ref, LIB, d.get('name'), dev.get('name') or '', val, ' package3d_urn="%s"' % urn[pkg] if pkg in urn else ''))+ sx, sy = 20.32, 152.4 - i * 7.62+ inst.append('<instance part="%s" gate="%s" x="%.2f" y="%.2f"/>' % (ref, g, sx, sy))+ tech = dev.find('technologies/technology')+ atts = ''.join('<attribute name="%s" value="%s" x="%.3f" y="%.3f" size="1.778" layer="27" display="off"/>' % (a.get('name'), a.get('value'), number(f['x']), number(f['y'])) for a in (tech.findall('attribute') if tech is not None else []))+ elements.append('<element name="%s" library="%s" package="%s" value="%s" x="%.3f" y="%.3f"%s%s>%s</element>' % (ref, LIB, pkg, val, number(f['x']), number(f['y']), ' rot="%s"' % rot_of(f['rot']) if f.get('rot') else '', ' package3d_urn="%s"' % urn[pkg] if pkg in urn else '', atts))+ for pin, net in (f.get('nets') or {}).items():+ identifier(net); pp = sym_pins.get(pin)+ if pp is None: fail('unknown_pin', '%s has no pin %s' % (ref, pin))+ px, py = sx + float(pp.get('x')), sy + float(pp.get('y'))+ nets.setdefault(net, []).append('<segment><pinref part="%s" gate="%s" pin="%s"/><wire x1="%.2f" y1="%.2f" x2="%.2f" y2="%.2f" width="0.1524" layer="91"/><label x="%.2f" y="%.2f" size="1.778" layer="95"/></segment>' % (ref, g, pin, px, py, px + 7.62, py, px + 7.62, py))+ for c in dev.iter('connect'):+ if c.get('pin') == pin:+ for pad in c.get('pad').split(): signals.setdefault(net, []).append('<contactref element="%s" pad="%s"/>' % (ref, pad))+ head = '<?xml version="1.0" encoding="utf-8"?>\n<!DOCTYPE eagle SYSTEM "eagle.dtd">\n<eagle version="9.6.2"><drawing><settings><setting alwaysvectorfont="no"/><setting verticaltext="up"/></settings><grid distance="0.1" unitdist="inch" unit="inch" style="lines" multiple="1" display="no" altdistance="0.01" altunitdist="inch" altunit="inch"/>'+ title = str(args.get('title', name)).replace('<', '').replace('&', 'and')[:80]+ sch = (head + layers + '<schematic xreflabel="%F%N/%S.%C%R" xrefpart="/%S.%C%R"><libraries>' + lib_sch + '</libraries><attributes/><variantdefs/><classes><class number="0" name="default" width="0" drill="0"/></classes><parts>' + ''.join(parts) ++ '</parts><sheets><sheet><plain><text x="20.32" y="175.26" size="3.81" layer="97">' + title + '</text></plain><instances>' + ''.join(inst) + '</instances><busses/><nets>' ++ ''.join('<net name="%s" class="0">%s</net>' % (n, ''.join(v)) for n, v in nets.items()) + '</nets></sheet></sheets></schematic></drawing></eagle>\n')+ outline = ''.join('<wire x1="%g" y1="%g" x2="%g" y2="%g" width="0" layer="20"/>' % s_ for s_ in [(0, 0, w, 0), (w, 0, w, h), (w, h, 0, h), (0, h, 0, 0)])+ brd = (head + layers + '<board><plain>' + outline + '</plain><libraries>' + lib_brd + '</libraries><attributes/><variantdefs/><classes><class number="0" name="default" width="0" drill="0"/></classes>' + rules ++ '<autorouter></autorouter><elements>' + ''.join(elements) + '</elements><signals>' + ''.join('<signal name="%s">%s</signal>' % (n, ''.join(v)) for n, v in signals.items()) + '</signals></board></drawing></eagle>\n')+ Path(out).mkdir(parents=True, exist_ok=True)+ sp, bp = Path(out) / (name + '.sch'), Path(out) / (name + '.brd')+ sp.write_text(sch, encoding='utf-8'); bp.write_text(brd, encoding='utf-8')+ return {'success': True, 'mutated': False, 'schPath': sp.as_posix(), 'brdPath': bp.as_posix(), 'fixedParts': len(fixed), 'outlineMm': [w, h],+ '_hint': 'Import it: fusion_new_electronics_from_eagle {schPath, brdPath} with an electronics document in front, poll fusion_eagle_import_status, then fusion_schematic {action:"show"} and fusion_schematic_plan / fusion_schematic_run for the rest of the circuit. Start from a clean Fusion session: a design imported while half-finished imports are open can come up with schematic/board sync off.'}++ def handle(app,args,action): attempted=False; commands=[] try: if action=='schematic_run': return sch_run(app,args)+ if action=='schematic_plan': return schematic_plan(args)+ if action=='board_seed': return board_seed(args) root,board,meta=snapshot(app,args) if action=='routing_state': return {'success':True,**state(board,meta)} if action=='pour_state': return {'success':True,'mutated':False,**meta,**pour_state(board)}
tests/run_verb_tests.py+2−2@@ -332,7 +332,7 @@ t("fusion_route_net#guard", "negative", {"expectDocument": "ZZZ_NoSuchDoc_TEST", "board/revision guards must refuse before any copper is drawn") t("fusion_routing_drc#guard", "negative", {"expectDocument": "ZZZ_NoSuchDoc_TEST", "boardId": "x", "expectedRevision": "x"}, {"pred": "must_refuse"}, "same guards as route_net") t("fusion_plan_route#guard", "negative", {"expectDocument": "ZZZ_NoSuchDoc_TEST", "net": "NET_1"}, {"pred": "must_refuse"}, "planner needs the live board it was asked about")-for v in ["fusion_routing_state", "fusion_route_net", "fusion_routing_drc", "fusion_plan_route", "fusion_move_parts", "fusion_place_vias", "fusion_apply_pours", "fusion_pour_state", "fusion_board_source", "fusion_ripup_nets", "fusion_schematic_run", "fusion_board_bom", "fusion_change_pours"]:+for v in ["fusion_routing_state", "fusion_route_net", "fusion_routing_drc", "fusion_plan_route", "fusion_move_parts", "fusion_place_vias", "fusion_apply_pours", "fusion_pour_state", "fusion_board_source", "fusion_ripup_nets", "fusion_schematic_run", "fusion_board_bom", "fusion_change_pours", "fusion_board_seed", "fusion_schematic_plan"]: t(v, "manual", note="live routing on a fixture board: demo/routing/run_demo.py is the weekly proof (needs a board open)") for v in ["fusion_apply_fusion_update", "fusion_resolve_seat_dialog", "fusion_interactive", "fusion_narrate", "fusion_view", "fusion_electronics_query"]: t(v, "manual", note="lifecycle, seat or presentation verbs: need a human-visible Fusion state; manual tier")@@ -351,7 +351,7 @@ _SECTION_BY_VERB = { "fusion_verb_times": "bridge-lifecycle", "fusion_live_feed": "bridge-lifecycle", "fusion_account": "auth-tokens", "fusion_claim_seat": "auth-tokens", "fusion_release_seat": "auth-tokens", "fusion_prewarm": "bridge-lifecycle", "fusion_prove_result": "cad-3d-bom", "fusion_set_driving_badge": "bridge-lifecycle", "fusion_set_window_sizing": "gui-driving", "fusion_cancel_run": "cad-3d-bom", "fusion_preflight_lbr": "cad-3d-bom", "fusion_library_parts": "cad-3d-bom", "fusion_take_screenshot": "gui-driving",- "fusion_schematic": "gui-driving", "fusion_show_attributes": "cad-3d-bom", "fusion_make_showcase_project": "cad-3d-bom", "fusion_eagle_import_status": "cad-3d-bom", "fusion_routing_state": "routing", "fusion_route_net": "routing", "fusion_routing_drc": "routing", "fusion_plan_route": "routing", "fusion_move_parts": "routing", "fusion_place_vias": "routing", "fusion_apply_pours": "routing", "fusion_pour_state": "routing", "fusion_board_source": "routing", "fusion_ripup_nets": "routing", "fusion_schematic_run": "routing", "fusion_board_bom": "routing", "fusion_change_pours": "routing",+ "fusion_schematic": "gui-driving", "fusion_show_attributes": "cad-3d-bom", "fusion_make_showcase_project": "cad-3d-bom", "fusion_eagle_import_status": "cad-3d-bom", "fusion_routing_state": "routing", "fusion_route_net": "routing", "fusion_routing_drc": "routing", "fusion_plan_route": "routing", "fusion_move_parts": "routing", "fusion_place_vias": "routing", "fusion_apply_pours": "routing", "fusion_pour_state": "routing", "fusion_board_source": "routing", "fusion_ripup_nets": "routing", "fusion_schematic_run": "routing", "fusion_board_bom": "routing", "fusion_change_pours": "routing", "fusion_board_seed": "routing", "fusion_schematic_plan": "routing", "fusion_apply_fusion_update": "bridge-lifecycle", "fusion_resolve_seat_dialog": "bridge-lifecycle", "fusion_interactive": "gui-driving", "fusion_narrate": "gui-driving", "fusion_view": "gui-driving", "fusion_electronics_query": "cad-3d-bom", "fusion_activate_document": "gui-driving", "fusion_library_manager": "gui-driving", "fusion_plan_library_import": "cad-3d-bom", "fusion_create_library": "cad-3d-bom", "fusion_install_library_bundle": "cad-3d-bom",
tools/check-naming.shtools/check-portable.shtools/promote.shtools/release.sh