#!/usr/bin/env python3 """Guarded accepted-fit label cleanup and A3 worksheet; never move board items.""" import copy from pathlib import Path import tempfile import pcbnew as p ROOT = Path(__file__).resolve().parent PCB = ROOT / "serial-carrier.kicad_pcb" COMMENT = "USB / antenna geometry pending; verify module pinout / assembly" USB = "USB ACCESS - VERIFY" OLD_CAPTION = "Nominal RS-232 metal verified; ESP32 USB / antenna / assembly clearance pending" CAPTION = "Nominal RS-232 metal verified; RF qualification not established" def expected_tree(tree): result = copy.deepcopy(tree) for item in list(result): if not isinstance(item, list): continue if item[0] == "paper": assert item in (["paper", '"A4"'], ["paper", '"A3"']) item[1] = '"A3"' elif item[0] == "title_block": for field in list(item[1:]): if field == ["comment", "1", '"' + COMMENT + '"']: item.remove(field) elif item[0] == "gr_text": if item[1] == '"' + USB + '"': assert ['layer', '"Dwgs.User"'] in item result.remove(item) elif item[1] == '"' + OLD_CAPTION + '"': assert ['layer', '"Dwgs.User"'] in item item[1] = '"' + CAPTION + '"' return result def main(): from validate_u1_update import parse, canonical lock = ROOT / "~serial-carrier.kicad_pcb.lck" if lock.exists(): raise RuntimeError("Close PCB Editor before changing presentation") original = PCB.read_bytes() protected = {path: path.read_bytes() for path in ROOT.glob("serial-carrier.kicad_pr*")} expected = expected_tree(parse(PCB)) with tempfile.TemporaryDirectory(prefix="carrier-presentation-", dir=ROOT) as temporary: candidate = Path(temporary) / PCB.name # PAGE_INFO is opaque in this pcbnew binding. Load the exact paper-only # substitution, then use the native saver for all label changes. text = original.decode() assert text.count('(paper "A4")') + text.count('(paper "A3")') == 1 candidate.write_text(text.replace('(paper "A4")', '(paper "A3")')) board = p.LoadBoard(str(candidate)) title = board.GetTitleBlock() assert title.GetComment(0) in (COMMENT, "") title.SetComment(0, "") for drawing in list(board.GetDrawings()): if isinstance(drawing, p.PCB_TEXT): if drawing.GetText() == USB: assert drawing.GetLayer() == p.Dwgs_User board.Remove(drawing) elif drawing.GetText() == OLD_CAPTION: drawing.SetText(CAPTION) assert p.SaveBoard(str(candidate), board, True) assert canonical(parse(candidate)) == canonical(expected), "Unexpected physical/content change" assert canonical(expected_tree(parse(candidate))) == canonical(expected), "Not idempotent" if lock.exists() or PCB.read_bytes() != original: raise RuntimeError("PCB changed or reopened; refusing overwrite") assert all(path.read_bytes() == data for path, data in protected.items()), "Settings changed" candidate.replace(PCB) print("A3 worksheet / accepted-fit labels updated; exact preservation and idempotence PASS") if __name__ == "__main__": main()