← Commit history
.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\ No newline at end of file+81fecc6c4c7bdf3775ca1f078e9dc1c514a3d4a2\ No newline at end of file
install.sh

  
page.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]"+  }+}\ No newline at end of file
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">&lt;b&gt;Fusion Electronics Design Rules&lt;/b&gt;+&lt;p&gt;+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>\ No newline at end of file
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.sh

  
tools/check-portable.sh

  
tools/promote.sh

  
tools/release.sh