#!/usr/bin/env python3 """v6 T15b — delete the 4 dangling track stubs DRC reported on 25-Sep (warnings track_dangling). Each target is identified by net + layer + the dangling end DRC printed + the reported length; the script refuses to delete anything that doesn't match exactly one segment. python3 T15b.py hbox-watchdog-v6.kicad_pcb # dry run python3 T15b.py hbox-watchdog-v6.kicad_pcb --apply # writes .T15bbak """ import re, sys, math, shutil STUBS = [ # (net, layer, dangling end x, y, length mm) — from the 25-Sep DRC report ('/3V3_ETH', 'F.Cu', 91.7375, 56.0800, 0.3889), ('/VEH5V', 'F.Cu', 187.5000, 116.1100, 0.7071), ('/RS485_B', 'B.Cu', 145.3500, 131.1682, 0.2107), ('/DOUT_4G', 'In2.Cu', 158.4000, 80.1000, 1.6000), # second DRC pass: the RS485_B stub was a 3-segment chain off a THT pad going nowhere ('/RS485_B', 'B.Cu', 146.2000, 132.0182, 1.2021), ('/RS485_B', 'B.Cu', 146.2000, 132.3075, 0.2894), ] def span(s, i): d = 0; instr = False; j = i while True: c = s[j] if c == '"' and s[j-1] != '\\': instr = not instr elif not instr: if c == '(': d += 1 elif c == ')': d -= 1 if d == 0: return j + 1 j += 1 def main(): path = sys.argv[1]; apply = '--apply' in sys.argv s0 = open(path).read(); cuts = []; log = [] segs = [] for m in re.finditer(r'\n\t\(segment\b', s0): a = m.start() + 1; e = span(s0, a); b = s0[a:e] st = tuple(map(float, re.search(r'\(start (-?[\d.]+) (-?[\d.]+)\)', b).groups())) en = tuple(map(float, re.search(r'\(end (-?[\d.]+) (-?[\d.]+)\)', b).groups())) segs.append(dict(a=a, e=e, st=st, en=en, l=re.search(r'\(layer "([^"]+)"\)', b).group(1), n=re.search(r'\(net "([^"]*)"\)', b).group(1))) for net, lay, x, y, L in STUBS: hit = [g for g in segs if g['n'] == net and g['l'] == lay and any(math.hypot(px - x, py - y) < 0.005 for px, py in (g['st'], g['en'])) and abs(math.dist(g['st'], g['en']) - L) < 0.002] if not hit: log.append(f'{net} {lay} @({x},{y}): not found (already removed?)'); continue if len(hit) > 1: raise SystemExit(f'{net}: {len(hit)} candidates, refusing') cuts.append(hit[0]); log.append(f'delete {net} {lay} {hit[0]["st"]}->{hit[0]["en"]} ({L} mm)') s = s0 for g in sorted(cuts, key=lambda g: -g['a']): s = s[:g['a'] - 1] + s[g['e']:] # also drops the preceding newline t = re.sub(r'"(?:[^"\\]|\\.)*"', '', s); assert t.count('(') == t.count(')'), 'unbalanced' assert len(re.findall(r'\n\t\(footprint "', s)) == len(re.findall(r'\n\t\(footprint "', s0)) print('\n'.join(log)) if not cuts: print('nothing to do'); return if apply: shutil.copy2(path, path + '.T15bbak'); open(path, 'w').write(s); print('APPLIED (backup .T15bbak)') else: print('DRY RUN — re-run with --apply') if __name__ == '__main__': main()