#!/usr/bin/env python3 """Synchronize measured U1 F.Fab graphics only; never replace the footprint.""" from pathlib import Path import uuid import pcbnew as p ROOT = Path(__file__).resolve().parent PCB = ROOT / "serial-carrier.kicad_pcb" NS = uuid.UUID("36693251-e1f8-44e9-a8a9-e201bd658ceb") def library_graphics(): fp = p.FootprintLoad(str(ROOT / "Carrier.pretty"), "HW678_2x22_Provisional") if fp is None: raise RuntimeError("Cannot load measured HW678 footprint") return fp def signature(g, origin): def point(v): return v.x-origin.x, v.y-origin.y if isinstance(g, p.PCB_SHAPE): return ("shape", g.GetShape(), point(g.GetStart()), point(g.GetEnd()), g.GetWidth()) return ("text", g.GetText(), point(g.GetPosition()), (g.GetTextSize().x, g.GetTextSize().y), g.GetTextThickness()) def measured_items(fp): # Generic library captions would overlap existing board labels. return sorted((g for g in fp.GraphicalItems() if g.GetLayer() == p.F_Fab and not (isinstance(g, p.PCB_TEXT) and g.GetText() in ( "PROVISIONAL CONNECTOR FIT", "ANTENNA UP / USB DOWN"))), key=lambda g: signature(g, fp.GetPosition())) def verify(board): u1 = next(f for f in board.GetFootprints() if f.GetReference() == "U1") assert u1.GetOrientationDegrees() == 0 and u1.GetLayer() == p.F_Cu assert len([g for g in u1.GraphicalItems() if g.GetLayer() == p.F_Fab]) == 6 library = library_graphics() expected = [signature(g, library.GetPosition()) for g in measured_items(library)] actual = [signature(g, u1.GetPosition()) for g in measured_items(u1)] assert actual == expected, "Embedded U1 F.Fab differs from measured library; run update_u1_graphics.py" def main(): lock = ROOT / "~serial-carrier.kicad_pcb.lck" if lock.exists(): raise SystemExit("Close PCB Editor before updating U1 graphics") before = PCB.read_bytes() project = ROOT / "serial-carrier.kicad_pro" project_before = project.read_bytes() board = p.LoadBoard(str(PCB)) u1 = next(f for f in board.GetFootprints() if f.GetReference() == "U1") assert u1.GetOrientationDegrees() == 0 and u1.GetLayer() == p.F_Cu library = library_graphics() sources = measured_items(library) assert len(sources) == 6, "Review changed measured library before integration" owned = {str(uuid.uuid5(NS, str(i))) for i in range(len(sources))} existing = list(u1.GraphicalItems()) assert all(g.m_Uuid.AsString() in owned for g in existing if g.GetLayer() == p.F_Fab), "Unowned U1 fabrication graphics; review manually" if [signature(g, u1.GetPosition()) for g in measured_items(u1)] == [ signature(g, library.GetPosition()) for g in sources]: verify(board) print("Embedded measured U1 graphics already current; no board write") return for g in existing: if g.m_Uuid.AsString() in owned: u1.Remove(g) for i, source in enumerate(sources): graphic = source.Duplicate() graphic.SetParent(u1) graphic.SetUuid(p.KIID(str(uuid.uuid5(NS, str(i))))) graphic.Move(u1.GetPosition() - library.GetPosition()) u1.Add(graphic) verify(board) temp = ROOT / "validation/pcb-u1-tmp.kicad_pcb" try: assert p.SaveBoard(str(temp), board, True) verify(p.LoadBoard(str(temp))) if lock.exists() or PCB.read_bytes() != before: raise RuntimeError("PCB changed or reopened; refusing to overwrite") assert project.read_bytes() == project_before, "Project settings changed" temp.replace(PCB) finally: temp.unlink(missing_ok=True) print("Embedded measured U1 graphics; existing footprint fields, pads and routing retained") if __name__ == "__main__": main()