2026-06-29 16:31:02 -04:00
#!/usr/bin/env python3
""" retire_block.py — remove a circuit block from a CM5-carrier KiCad sheet cleanly.
For the diff - from - reference port ( see . claude / skills / kicad - port ) . Given a sheet and a
set of reference designators to drop plus the net - name globs they own , this :
1. excises each named ( symbol . . . ) block ( paren - matched , by Reference property ) ,
2. floods the retired nets from their ( label . . . ) coords through ( wire . . . ) segments ,
3. removes those labels + every wire in the flooded nets ,
4. places ( no_connect . . . ) markers on the exposed module pins that sat on those nets ,
5. writes back only if parentheses stay balanced .
Module pin coordinates are computed from the lib_symbol pin locals + the placement
transform ( validated abs = origin + ( lx , - ly ) for rot0 / no - mirror ; other orientations
are rejected rather than guessed ) . Idempotent - ish : re - running finds nothing to do .
Usage :
python3 tools / retire_block . py < sheet . kicad_sch > \
- - symbols J7 , U18 - - nets ' SD_* ' \
- - module - lib CM5IO : ComputeModule5 - CM5 [ - - apply ]
Without - - apply it ' s a dry run (prints the plan, writes nothing).
"""
import re , sys , argparse , fnmatch , math , uuid as uuidlib
S = r ' \ s* '
def xform ( px , py , rot , mirror , lx , ly ) :
""" Schematic abs coord of a lib pin local (lx,ly) for a placement. Validated:
mirror , then CCW rotate , then lib - Y - up - > schematic - Y - down flip . """
X , Y = lx , ly
if mirror == ' y ' : X = - X
if mirror == ' x ' : Y = - Y
r = math . radians ( rot )
rx = X * math . cos ( r ) - Y * math . sin ( r )
ry = X * math . sin ( r ) + Y * math . cos ( r )
return ( round ( px + rx , 2 ) , round ( py - ry , 2 ) )
def paren_end ( t , s ) :
d = 0
for i in range ( s , len ( t ) ) :
if t [ i ] == ' ( ' : d + = 1
elif t [ i ] == ' ) ' :
d - = 1
if d == 0 : return i + 1
raise ValueError ( " unbalanced from offset %d " % s )
def fnum ( x ) : return round ( float ( x ) , 2 )
def find_symbol_blocks ( t ) :
""" Yield (start,end,reference) for every placed (symbol (lib_id ...)) block. """
for m in re . finditer ( r ' \ (symbol ' + S + r ' \ (lib_id ' + S + r ' " ' , t ) :
s = m . start ( ) ; e = paren_end ( t , s ) ; seg = t [ s : e ]
ref = re . search ( r ' \ (property ' + S + r ' " Reference " ' + S + r ' " ([^ " ]+) " ' , seg )
if ref : yield ( s , e , ref . group ( 1 ) )
def module_pins_abs ( t , module_lib ) :
""" Compute { pin_number:(x,y)} for the placed module instance on this sheet. """
inst = re . search ( r ' \ (symbol ' + S + r ' \ (lib_id ' + S + r ' " ' + re . escape ( module_lib ) + r ' " \ )(. { 0,500}?) \ (unit ' + S + r ' ( \ d+) \ ) ' , t , re . S )
if not inst : return { } , None
body , unit = inst . group ( 1 ) , inst . group ( 2 )
at = re . search ( r ' \ (at ' + S + r ' ([ \ d.-]+) ' + S + r ' ([ \ d.-]+) ' + S + r ' ([ \ d.-]+) \ ) ' , body )
mir = re . search ( r ' \ (mirror ' + S + r ' ( \ w+) \ ) ' , body )
px , py , rot = float ( at . group ( 1 ) ) , float ( at . group ( 2 ) ) , float ( at . group ( 3 ) )
if rot != 0 or mir :
sys . exit ( f " REFUSING: module instance has rot= { rot } mirror= { mir and mir . group ( 1 ) } — transform only validated for rot0/no-mirror. " )
libname = module_lib . split ( ' : ' ) [ - 1 ]
pins = { }
for pm in re . finditer ( r ' \ (pin \ b.*? \ (at ' + S + r ' ([ \ d.-]+) ' + S + r ' ([ \ d.-]+) ' + S + r ' [ \ d.-]+ \ ).*? \ (number ' + S + r ' " ([^ " ]+) " ' , t , re . S ) :
lx , ly , num = float ( pm . group ( 1 ) ) , float ( pm . group ( 2 ) ) , pm . group ( 3 )
pins . setdefault ( num , ( round ( px + lx , 2 ) , round ( py - ly , 2 ) ) )
return pins , unit
def collect ( t , pat ) :
""" Return list of (start,end,coord,text) for top-level objects named `pat` token. """
out = [ ]
for m in re . finditer ( r ' \ ( ' + pat + r ' \ b ' , t ) :
s = m . start ( ) ; e = paren_end ( t , s ) ; seg = t [ s : e ]
at = re . search ( r ' \ (at ' + S + r ' ([ \ d.-]+) ' + S + r ' ([ \ d.-]+) ' , seg )
out . append ( ( s , e , ( fnum ( at . group ( 1 ) ) , fnum ( at . group ( 2 ) ) ) if at else None , seg ) )
return out
def wire_pts ( seg ) :
return [ ( fnum ( a ) , fnum ( b ) ) for a , b in re . findall ( r ' \ (xy ' + S + r ' ([ \ d.-]+) ' + S + r ' ([ \ d.-]+) \ ) ' , seg ) ]
def lib_pins ( t , lib_id ) :
""" Local (lx,ly,angle) for every pin of a lib_symbol definition. """
name = lib_id . split ( ' : ' ) [ - 1 ]
lm = re . search ( r ' \ (symbol ' + S + r ' " ' + re . escape ( lib_id ) + r ' " ' , t ) or \
re . search ( r ' \ (symbol ' + S + r ' " ' + re . escape ( name ) + r ' " ' , t )
if not lm : return [ ]
seg = t [ lm . start ( ) : paren_end ( t , lm . start ( ) ) ]
return [ ( float ( a ) , float ( b ) , float ( c ) ) for a , b , c in
re . findall ( r ' \ (pin \ b.*? \ (at ' + S + r ' ([ \ d.-]+) ' + S + r ' ([ \ d.-]+) ' + S + r ' ([ \ d.-]+) \ ) ' , seg , re . S ) ]
def placed_symbols ( t ) :
""" Yield dict per placed symbol: start,end,ref,lib,at(px,py,rot),mirror. """
for m in re . finditer ( r ' \ (symbol ' + S + r ' \ (lib_id ' + S + r ' " ([^ " ]+) " \ ) ' , t ) :
s = m . start ( ) ; e = paren_end ( t , s ) ; seg = t [ s : e ]
ref = re . search ( r ' \ (property ' + S + r ' " Reference " ' + S + r ' " ([^ " ]+) " ' , seg )
at = re . search ( r ' \ (at ' + S + r ' ([ \ d.-]+) ' + S + r ' ([ \ d.-]+) ' + S + r ' ([ \ d.-]+) \ ) ' , seg )
mir = re . search ( r ' \ (mirror ' + S + r ' ( \ w+) \ ) ' , seg )
yield dict ( s = s , e = e , ref = ref . group ( 1 ) if ref else None , lib = m . group ( 1 ) ,
at = ( float ( at . group ( 1 ) ) , float ( at . group ( 2 ) ) , float ( at . group ( 3 ) ) ) if at else None ,
mirror = mir . group ( 1 ) if mir else None )
def sym_pin_coords ( t , sym ) :
if not sym [ ' at ' ] : return [ ]
px , py , rot = sym [ ' at ' ]
return [ xform ( px , py , rot , sym [ ' mirror ' ] , lx , ly ) for lx , ly , _ in lib_pins ( t , sym [ ' lib ' ] ) ]
Strip PCIe-M2 (M-key/NVMe) sheet; re-baseline stripped carrier
Drops the unused M.2 M-key/NVMe block — design is 1x M.2 E-key (Wi-Fi)
only, since the CM5 exposes a single PCIe x1 lane (datasheet SC1466),
reserved for the E-key. The 10 freed PCIE_* lanes are NC on Module1
unit 2, to be rewired to a fresh E-key sheet in the add phase.
- CM5_HighSpeed: retire PCIE_* labels/wires, NC pins 102,104,106,109,
110,112,116,118,122,124
- root CM5IO: drop whole PCIe-M2 (sheet) block; strip dangling PCIE_*
pins off the kept CM5_HighSpeed sheet symbol
- delete carrier/PCIe-M2.kicad_sch; clean .kicad_pro sheets array + ERC
exclusion
- retire_block.py: add --drop-sheet FILE (remove whole (sheet) block +
sheet_instances path)
- analysis/baseline/carrier_stripped.json: new add-phase diff anchor
Verify: ERC 136->133 (zero new violation types); analyzer diff +0/~0
(cumulative -41 removed vs original ref); netlist confirms +5v/+3.3v/
GND/+1.8v intact (U8's M2_3v3 was a local rail, not board +3.3v).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 11:27:41 -04:00
def drop_sheet_spans ( t , fname ) :
""" Remove an entire (sheet ...) block by its Sheetfile property, plus any
( path . . . ) in ( sheet_instances ) referencing the sheet ' s UUID. Run on the parent
( root ) sheet . Returns list of ( start , end ) spans to delete . """
spans = [ ]
for m in re . finditer ( r ' \ (sheet \ b ' , t ) :
s = m . start ( ) ; e = paren_end ( t , s ) ; seg = t [ s : e ]
sf = re . search ( r ' Sheetfile " ' + S + r ' " ([^ " ]+) " ' , seg )
if not sf or sf . group ( 1 ) != fname : continue
spans . append ( ( s , e ) )
u = re . search ( r ' \ (uuid ' + S + r ' " ([0-9a-fA-F-]+) " ' , seg )
if u :
si = re . search ( r ' \ (sheet_instances \ b ' , t )
if si :
ss = si . start ( ) ; se = paren_end ( t , ss )
for pm in re . finditer ( r ' \ (path ' + S + r ' " [^ " ]* ' + re . escape ( u . group ( 1 ) ) + r ' [^ " ]* " ' , t [ ss : se ] ) :
ps = ss + pm . start ( ) ; spans . append ( ( ps , paren_end ( t , ps ) ) )
return spans
2026-06-29 16:31:02 -04:00
def sheet_port_spans ( t , names ) :
""" On a ROOT sheet, find (pin " NAME " ...) hierarchical ports inside (sheet ...) blocks
whose name is in ` names ` . Returns ( pin spans to remove , their connection coords ) . """
spans , coords = [ ] , set ( )
for m in re . finditer ( r ' \ (sheet \ b ' , t ) :
s = m . start ( ) ; e = paren_end ( t , s )
for pm in re . finditer ( r ' \ (pin ' + S + r ' " ([^ " ]+) " ' , t [ s : e ] ) :
if pm . group ( 1 ) in names :
ps = s + pm . start ( ) ; pe = paren_end ( t , ps )
at = re . search ( r ' \ (at ' + S + r ' ([ \ d.-]+) ' + S + r ' ([ \ d.-]+) ' , t [ ps : pe ] )
spans . append ( ( ps , pe ) )
if at : coords . add ( ( fnum ( at . group ( 1 ) ) , fnum ( at . group ( 2 ) ) ) )
return spans , coords
def main ( ) :
ap = argparse . ArgumentParser ( )
ap . add_argument ( ' sheet ' )
ap . add_argument ( ' --symbols ' , default = ' ' )
ap . add_argument ( ' --nets ' , default = ' ' )
ap . add_argument ( ' --nc-pins ' , default = ' ' , help = ' module pin NUMBERS to no-connect (from analyzer regressions) ' )
ap . add_argument ( ' --sheet-ports ' , default = ' ' , help = ' hierarchical net names: remove matching sheet-symbol pins + connecting root wires (run on the ROOT sheet) ' )
Strip PCIe-M2 (M-key/NVMe) sheet; re-baseline stripped carrier
Drops the unused M.2 M-key/NVMe block — design is 1x M.2 E-key (Wi-Fi)
only, since the CM5 exposes a single PCIe x1 lane (datasheet SC1466),
reserved for the E-key. The 10 freed PCIE_* lanes are NC on Module1
unit 2, to be rewired to a fresh E-key sheet in the add phase.
- CM5_HighSpeed: retire PCIE_* labels/wires, NC pins 102,104,106,109,
110,112,116,118,122,124
- root CM5IO: drop whole PCIe-M2 (sheet) block; strip dangling PCIE_*
pins off the kept CM5_HighSpeed sheet symbol
- delete carrier/PCIe-M2.kicad_sch; clean .kicad_pro sheets array + ERC
exclusion
- retire_block.py: add --drop-sheet FILE (remove whole (sheet) block +
sheet_instances path)
- analysis/baseline/carrier_stripped.json: new add-phase diff anchor
Verify: ERC 136->133 (zero new violation types); analyzer diff +0/~0
(cumulative -41 removed vs original ref); netlist confirms +5v/+3.3v/
GND/+1.8v intact (U8's M2_3v3 was a local rail, not board +3.3v).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 11:27:41 -04:00
ap . add_argument ( ' --drop-sheet ' , default = ' ' , help = ' Sheetfile name: remove the whole (sheet ...) block + its sheet_instances path (run on the ROOT/parent sheet) ' )
2026-06-29 16:31:02 -04:00
ap . add_argument ( ' --module-lib ' , default = ' CM5IO:ComputeModule5-CM5 ' )
ap . add_argument ( ' --apply ' , action = ' store_true ' )
a = ap . parse_args ( )
drop_refs = set ( filter ( None , a . symbols . split ( ' , ' ) ) )
net_globs = list ( filter ( None , a . nets . split ( ' , ' ) ) )
nc_nums = list ( filter ( None , a . nc_pins . split ( ' , ' ) ) )
port_names = set ( filter ( None , a . sheet_ports . split ( ' , ' ) ) )
t = open ( a . sheet ) . read ( )
modpins , unit = module_pins_abs ( t , a . module_lib )
pin_at = { xy : n for n , xy in modpins . items ( ) }
nc_pins = [ ( n , modpins [ n ] ) for n in nc_nums if n in modpins ]
missing_nc = [ n for n in nc_nums if n not in modpins ]
if missing_nc : sys . exit ( f " NC pins not found on module: { missing_nc } " )
# 1) symbols to remove + their abs pin coords (for stub cleanup)
placed = list ( placed_symbols ( t ) )
removed_syms = [ sy for sy in placed if sy [ ' ref ' ] in drop_refs ]
found_refs = { sy [ ' ref ' ] for sy in removed_syms }
sym_spans = [ ( sy [ ' s ' ] , sy [ ' e ' ] ) for sy in removed_syms ]
removed_pin_coords = set ( )
for sy in removed_syms :
removed_pin_coords | = set ( sym_pin_coords ( t , sy ) )
# 2) retired labels by glob — match ANY label type (local / hierarchical / global)
labels = collect ( t , ' label ' ) + collect ( t , ' hierarchical_label ' ) + collect ( t , ' global_label ' )
retired_labels = [ ( s , e , c ) for s , e , c , seg in labels
for nm in [ re . search ( r ' " ([^ " ]+) " ' , seg ) . group ( 1 ) ]
if any ( fnmatch . fnmatch ( nm , g ) for g in net_globs ) ]
retired_coords = { c for _ , _ , c in retired_labels }
# 3) anchor-aware wire removal via connected components.
# A wire is removed only if its component (a) is orphaned by THIS edit (touches a
# removed pin, retired label, or target NC pin) AND (b) reaches no KEPT anchor —
# a kept non-power component pin or a kept label. Shared nets (e.g. a camera FFC's
# I2C/power pins) stay anchored by the kept side, so they survive; pre-existing
# floating wires aren't seeded, so they're untouched.
wires = collect ( t , ' wire ' )
wpts = [ ( s , e , wire_pts ( seg ) ) for s , e , _ , seg in wires ]
parent = { }
def find ( x ) :
parent . setdefault ( x , x )
root = x
while parent [ root ] != root : root = parent [ root ]
while parent [ x ] != root : parent [ x ] , x = root , parent [ x ]
return root
def union ( a , b ) : parent [ find ( a ) ] = find ( b )
for s , e , pts in wpts :
for p in pts [ 1 : ] :
union ( pts [ 0 ] , p )
nc_coords = { xy for _ , xy in nc_pins }
kept_label_coords = { c for s , e , c , seg in labels
if not any ( fnmatch . fnmatch ( re . search ( r ' " ([^ " ]+) " ' , seg ) . group ( 1 ) , g )
for g in net_globs ) }
kept_pin_coords = set ( )
for sy in placed :
if sy [ ' ref ' ] in drop_refs or sy [ ' lib ' ] . startswith ( ' power: ' ) : continue
kept_pin_coords | = set ( sym_pin_coords ( t , sy ) )
kept_pin_coords - = nc_coords # pins we are NC'ing are being disconnected
anchors = kept_pin_coords | kept_label_coords
# sheet-symbol hierarchical ports to drop (root sheet) + their connection coords
port_spans , port_coords = sheet_port_spans ( t , port_names ) if port_names else ( [ ] , set ( ) )
Strip PCIe-M2 (M-key/NVMe) sheet; re-baseline stripped carrier
Drops the unused M.2 M-key/NVMe block — design is 1x M.2 E-key (Wi-Fi)
only, since the CM5 exposes a single PCIe x1 lane (datasheet SC1466),
reserved for the E-key. The 10 freed PCIE_* lanes are NC on Module1
unit 2, to be rewired to a fresh E-key sheet in the add phase.
- CM5_HighSpeed: retire PCIE_* labels/wires, NC pins 102,104,106,109,
110,112,116,118,122,124
- root CM5IO: drop whole PCIe-M2 (sheet) block; strip dangling PCIE_*
pins off the kept CM5_HighSpeed sheet symbol
- delete carrier/PCIe-M2.kicad_sch; clean .kicad_pro sheets array + ERC
exclusion
- retire_block.py: add --drop-sheet FILE (remove whole (sheet) block +
sheet_instances path)
- analysis/baseline/carrier_stripped.json: new add-phase diff anchor
Verify: ERC 136->133 (zero new violation types); analyzer diff +0/~0
(cumulative -41 removed vs original ref); netlist confirms +5v/+3.3v/
GND/+1.8v intact (U8's M2_3v3 was a local rail, not board +3.3v).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 11:27:41 -04:00
sheet_spans = drop_sheet_spans ( t , a . drop_sheet ) if a . drop_sheet else [ ]
2026-06-29 16:31:02 -04:00
seeds = set ( removed_pin_coords ) | set ( retired_coords ) | nc_coords | port_coords
anchored_roots , seeded_roots = set ( ) , set ( )
for c in anchors :
if c in parent : anchored_roots . add ( find ( c ) )
for c in seeds :
if c in parent : seeded_roots . add ( find ( c ) )
removable_roots = seeded_roots - anchored_roots
wire_remove = [ ( s , e , pts ) for s , e , pts in wpts if find ( pts [ 0 ] ) in removable_roots ]
wire_remove_spans = { ( s , e ) for s , e , _ in wire_remove }
freed = set ( removed_pin_coords )
for _ , _ , pts in wire_remove :
freed | = set ( pts )
kept_wire_pts = set ( )
for s , e , pts in wpts :
if ( s , e ) not in wire_remove_spans :
kept_wire_pts | = set ( pts )
flooded_wires = wire_remove # naming compat for the summary print
stub_wires = [ ]
# 5) orphaned power flags (power:*): pin freed by my edit, no kept wire left on it
power_spans = [ ]
for sy in placed :
if sy [ ' ref ' ] in drop_refs or not sy [ ' lib ' ] . startswith ( ' power: ' ) : continue
pcs = sym_pin_coords ( t , sy )
if pcs and all ( pc in freed and pc not in kept_wire_pts for pc in pcs ) :
power_spans . append ( ( sy [ ' s ' ] , sy [ ' e ' ] ) )
# 6) dangling no_connects sitting on a removed symbol's pin
dangling_nc = [ ( s , e ) for s , e , c , _ in collect ( t , ' no_connect ' ) if c in removed_pin_coords ]
# 7) labels orphaned by my edit (any label type): coord freed, no kept wire left,
# not already retired. Covers local (label), and cross-sheet (hierarchical_label /
# global_label) that only fed a removed connector (e.g. a camera FFC's SCL0/CAM_GPIO).
retired_label_spans = { ( s , e ) for s , e , _ in retired_labels }
all_label_objs = collect ( t , ' label ' ) + collect ( t , ' hierarchical_label ' ) + collect ( t , ' global_label ' )
orphan_labels = [ ( s , e ) for s , e , c , _ in all_label_objs
if ( s , e ) not in retired_label_spans and c in freed and c not in kept_wire_pts ]
nc_pins = sorted ( nc_pins , key = lambda kv : int ( re . sub ( r ' \ D ' , ' ' , kv [ 0 ] ) or 0 ) )
print ( f " sheet: { a . sheet } (module unit { unit } , { len ( modpins ) } pins) " )
print ( f " symbols to remove: { sorted ( found_refs ) } (requested { sorted ( drop_refs ) } ; missing { sorted ( drop_refs - found_refs ) } ) " )
print ( f " retired-net labels: { len ( retired_labels ) } | wires removed (anchor-aware): { len ( wire_remove ) } " )
print ( f " orphaned power flags: { len ( power_spans ) } | dangling NC removed: { len ( dangling_nc ) } | orphaned labels: { len ( orphan_labels ) } " )
if port_names : print ( f " sheet-symbol ports removed: { len ( port_spans ) } ( { sorted ( port_names ) } ) " )
Strip PCIe-M2 (M-key/NVMe) sheet; re-baseline stripped carrier
Drops the unused M.2 M-key/NVMe block — design is 1x M.2 E-key (Wi-Fi)
only, since the CM5 exposes a single PCIe x1 lane (datasheet SC1466),
reserved for the E-key. The 10 freed PCIE_* lanes are NC on Module1
unit 2, to be rewired to a fresh E-key sheet in the add phase.
- CM5_HighSpeed: retire PCIE_* labels/wires, NC pins 102,104,106,109,
110,112,116,118,122,124
- root CM5IO: drop whole PCIe-M2 (sheet) block; strip dangling PCIE_*
pins off the kept CM5_HighSpeed sheet symbol
- delete carrier/PCIe-M2.kicad_sch; clean .kicad_pro sheets array + ERC
exclusion
- retire_block.py: add --drop-sheet FILE (remove whole (sheet) block +
sheet_instances path)
- analysis/baseline/carrier_stripped.json: new add-phase diff anchor
Verify: ERC 136->133 (zero new violation types); analyzer diff +0/~0
(cumulative -41 removed vs original ref); netlist confirms +5v/+3.3v/
GND/+1.8v intact (U8's M2_3v3 was a local rail, not board +3.3v).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 11:27:41 -04:00
if a . drop_sheet : print ( f " whole sheet dropped: { a . drop_sheet } ( { len ( sheet_spans ) } block(s)/instance-path(s)) " )
2026-06-29 16:31:02 -04:00
print ( f " module pins to NC ( { len ( nc_pins ) } ): " + " , " . join ( f " { n } @ { xy } " for n , xy in nc_pins ) )
# build new text: remove all spans, then append fresh no_connects on exposed module pins
spans = sorted ( set ( sym_spans ) | { ( s , e ) for s , e , _ in retired_labels }
| wire_remove_spans | set ( power_spans ) | set ( dangling_nc ) | set ( orphan_labels )
Strip PCIe-M2 (M-key/NVMe) sheet; re-baseline stripped carrier
Drops the unused M.2 M-key/NVMe block — design is 1x M.2 E-key (Wi-Fi)
only, since the CM5 exposes a single PCIe x1 lane (datasheet SC1466),
reserved for the E-key. The 10 freed PCIE_* lanes are NC on Module1
unit 2, to be rewired to a fresh E-key sheet in the add phase.
- CM5_HighSpeed: retire PCIE_* labels/wires, NC pins 102,104,106,109,
110,112,116,118,122,124
- root CM5IO: drop whole PCIe-M2 (sheet) block; strip dangling PCIE_*
pins off the kept CM5_HighSpeed sheet symbol
- delete carrier/PCIe-M2.kicad_sch; clean .kicad_pro sheets array + ERC
exclusion
- retire_block.py: add --drop-sheet FILE (remove whole (sheet) block +
sheet_instances path)
- analysis/baseline/carrier_stripped.json: new add-phase diff anchor
Verify: ERC 136->133 (zero new violation types); analyzer diff +0/~0
(cumulative -41 removed vs original ref); netlist confirms +5v/+3.3v/
GND/+1.8v intact (U8's M2_3v3 was a local rail, not board +3.3v).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 11:27:41 -04:00
| set ( port_spans ) | set ( sheet_spans ) , key = lambda x : - x [ 0 ] )
2026-06-29 16:31:02 -04:00
nt = t
for s , e in spans :
p = s
while p > 0 and nt [ p - 1 ] in ' \t ' : p - = 1
if p > 0 and nt [ p - 1 ] == ' \n ' : p - = 1
nt = nt [ : p ] + nt [ e : ]
# insert no_connect markers before the final closing paren of the sheet
nc_block = " " . join (
f ' \t (no_connect \n \t \t (at { xy [ 0 ] } { xy [ 1 ] } ) \n \t \t (uuid " { uuidlib . uuid5 ( uuidlib . NAMESPACE_DNS , a . sheet + n ) } " ) \n \t ) \n '
for n , xy in nc_pins )
last = nt . rstrip ( ) . rfind ( ' ) ' )
nt = nt [ : last ] + nc_block + nt [ last : ]
assert nt . count ( ' ( ' ) == nt . count ( ' ) ' ) , " UNBALANCED — aborting "
if a . apply :
open ( a . sheet , ' w ' ) . write ( nt )
print ( f " APPLIED. parens balanced ( { nt . count ( ' ( ' ) } ). + { len ( nc_pins ) } no_connect, - { len ( spans ) } objects. " )
else :
print ( f " DRY RUN ok. parens would balance ( { nt . count ( ' ( ' ) } ). Re-run with --apply to write. " )
if __name__ == ' __main__ ' :
main ( )