main
TylerK Board update: 4 unconnected items (from 13), 0 electrical and 0 mechanical DRC findings, silkscreen findings 73 -> 10;… 337e32d 1mo ago
#!/usr/bin/env python3
"""Last-resort width repair: step a track down until it clears exactly.

The widening pass settles against its own model of the board. Anything that
survives it is a pair the model and the rule disagree about by a hair, and the
cheapest correct answer is to give back a ladder step of width on the offending
track rather than re-route anything.

Reads the whole file before opening it for writing -- `open(p,"w").write(...)`
truncates the file before the expression inside it is evaluated, which once
turned a fully routed board into zero bytes.
"""
import re, sys

import geomcheck as G

PCB = ("/mnt/c/Users/kirkt/Documents/mppt-power-system-mcu/"
       "mppt-power-system-mcu.kicad_pcb")
LADDER = [1.2, 1.0, 0.8, 0.6, 0.5, 0.4, 0.3, 0.25, 0.2, 0.15]
PAT = re.compile(
    r'(\(segment\s+\(start ([-\d.]+) ([-\d.]+)\)\s+\(end ([-\d.]+) ([-\d.]+)\)'
    r'\s+\(width )([\d.]+)(\)\s+\(layer "([^"]+)"\))')


def main(path=PCB, rounds=8):
    for it in range(rounds):
        objs = G.load_board(path)
        if not objs:
            print(f"ERROR: {path} parsed to zero objects"); return 1
        v = G.violations(objs)
        print(f"pass {it}: {len(v)} exact violation(s)")
        if not v:
            return 0
        fixes = {}
        for gap, rule, why, a, b in v:
            print(f"    gap {gap:+.3f}/{rule}  "
                  f"{a.kind}[{a.net}]{'@' + a.tag if a.tag else ''} vs "
                  f"{b.kind}[{b.net}]{'@' + b.tag if b.tag else ''}")
            for o in (a, b):
                if o.kind != "track":
                    continue
                w = o.r * 2
                lower = [x for x in LADDER if x < w - 1e-9]
                if not lower:
                    continue
                fixes[(round(o.ax, 4), round(o.ay, 4), round(o.bx, 4),
                       round(o.by, 4), o.layers[0], w)] = lower[0]
        if not fixes:
            print("    nothing left to narrow -- these need a re-route")
            return len(v)

        text = open(path, errors='replace').read()      # read BEFORE opening 'w'
        n = [0]

        def sub(m):
            k = (round(float(m.group(2)), 4), round(float(m.group(3)), 4),
                 round(float(m.group(4)), 4), round(float(m.group(5)), 4),
                 m.group(8), float(m.group(6)))
            if k in fixes:
                n[0] += 1
                return f"{m.group(1)}{fixes[k]:g}{m.group(7)}"
            return m.group(0)

        out = PAT.sub(sub, text)
        if n[0] == 0:
            print("    no segment text matched -- stopping"); return len(v)
        with open(path, "w") as fh:
            fh.write(out)
        print(f"    narrowed {n[0]} segment(s)")
    return 0


if __name__ == "__main__":
    sys.exit(main())