#!/usr/bin/env python3 """v6 T16 — square up the 4 mounting holes and give them unique references. Found 25-Sep: the board (140 x 100 mm, Edge.Cuts x 60.19–200.19, y 40.32–140.32) had MH5 (65.2, 45.3) MH6 (65.2, 135.3) <- west pair, same position since v4/v5 (proven) MH6 (195.5, 45.0) MH6 (195.5, 135.5) <- east pair off by 0.3 / 0.2 / 0.5 mm so the pattern was 130.3 x 90.0 on the west and 90.5 on the east, and three holes shared "MH6". Fix: keep the proven west pair and mirror it -> a clean 130.0 x 90.0 mm rectangle, every hole ~5.0 mm from its two edges, centred on the board: MH5 (65.2, 45.3) MH6 (65.2, 135.3) MH7 (195.2, 45.3) MH8 (195.2, 135.3) Only the footprint (at …) line and the Reference value change; the holes keep their UUIDs. python3 T16.py hbox-watchdog-v6.kicad_pcb # dry run python3 T16.py hbox-watchdog-v6.kicad_pcb --apply # writes .T16bak """ import re, sys, shutil TARGET = [ # (current ref, current x, y) -> (new ref, x, y) (('MH5', 65.2, 45.3), ('MH5', 65.2, 45.3)), (('MH6', 65.2, 135.3), ('MH6', 65.2, 135.3)), (('MH6', 195.5, 45.0), ('MH7', 195.2, 45.3)), (('MH6', 195.5, 135.5), ('MH8', 195.2, 135.3)), ] 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 holes(s): out = [] for m in re.finditer(r'\n\t\(footprint "MountingHole:[^"]*"', s): a = m.start() + 1; e = span(s, a); b = s[a:e] at = re.search(r'\n\t\t\(at (-?[\d.]+) (-?[\d.]+)([^)]*)\)', b) ref = re.search(r'\(property "Reference" "([^"]*)"', b).group(1) out.append(dict(a=a, e=e, ref=ref, x=float(at.group(1)), y=float(at.group(2)), rot=at.group(3))) return out def main(): path = sys.argv[1]; apply = '--apply' in sys.argv s0 = open(path).read(); s = s0; ch = [] hs = holes(s0) assert len(hs) == 4, f'expected 4 mounting holes, found {len(hs)}' edits = [] for (r0, x0, y0), (r1, x1, y1) in TARGET: h = [h for h in hs if abs(h['x'] - x1) < 1e-3 and abs(h['y'] - y1) < 1e-3 and h['ref'] == r1] if h: continue # already in place h = [h for h in hs if abs(h['x'] - x0) < 1e-3 and abs(h['y'] - y0) < 1e-3 and h['ref'] == r0] if len(h) != 1: raise SystemExit(f'cannot identify hole {r0} at ({x0},{y0})') edits.append((h[0], r1, x1, y1)) for h, r1, x1, y1 in sorted(edits, key=lambda t: -t[0]['a']): b = s[h['a']:h['e']] b = re.sub(r'\n\t\t\(at (-?[\d.]+) (-?[\d.]+)([^)]*)\)', f'\n\t\t(at {x1:g} {y1:g}{h["rot"]})', b, count=1) b = b.replace(f'(property "Reference" "{h["ref"]}"', f'(property "Reference" "{r1}"', 1) s = s[:h['a']] + b + s[h['e']:] ch.append(f'{h["ref"]} ({h["x"]:g},{h["y"]:g}) -> {r1} ({x1:g},{y1:g})') hs1 = holes(s) refs = [h['ref'] for h in hs1] assert sorted(refs) == ['MH5', 'MH6', 'MH7', 'MH8'], refs xs = sorted({h['x'] for h in hs1}); ys = sorted({h['y'] for h in hs1}) assert len(xs) == 2 and len(ys) == 2, 'holes not on a rectangle' print('\n'.join(ch) or 'nothing to do (already applied)') print(f'pattern {xs[1]-xs[0]:.2f} x {ys[1]-ys[0]:.2f} mm') if ch and apply: shutil.copy2(path, path + '.T16bak'); open(path, 'w').write(s); print('APPLIED (backup .T16bak)') elif ch: print('DRY RUN — re-run with --apply') if __name__ == '__main__': main()