Align component groups, repair affected routing, and add preservation audits. Assign the confirmed U2 footprint and update pull-ups to 2 kΩ across design files. Document user acceptance, switch to an A3 worksheet, and refresh PCB exports.
76 lines
3.3 KiB
Python
76 lines
3.3 KiB
Python
#!/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()
|