Integrate Measured ESP32 PCB Geometry

- Add measured F.Fab bounds and USB mouth markers
- Add guarded preservation and regression validation
- Refresh review exports and remove draft presentation labels
This commit is contained in:
2026-09-21 22:54:37 +02:00
parent ab6a8010ba
commit 901afbe754
25 changed files with 20490 additions and 850 deletions
+108
View File
@@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""Measured U1 integration regression and exact pre-update preservation audit."""
import hashlib
import json
from pathlib import Path
import re
import tempfile
import uuid
import pcbnew as p
from shapely.geometry import Polygon
from add_device_silkscreen import add_outlines, NS as SILK_NS, xy, outline_lines
from update_u1_graphics import verify, NS as U1_NS
from remove_draft_markers import expected_tree
ROOT = Path(__file__).resolve().parent
PCB = ROOT / "serial-carrier.kicad_pcb"
BASE = ROOT / "validation/pcb-before-u1-measured.kicad_pcb"
def parse(path):
tokens = iter(re.findall(r'"(?:\\.|[^"\\])*"|[()]|[^\s()]+', path.read_text()))
def group():
result = []
for token in tokens:
if token == ')':
return result
result.append(group() if token == '(' else token)
raise ValueError("Unterminated S-expression")
assert next(tokens) == '('
return group()
def ident(item):
return next((v[1].strip('"') for v in item if isinstance(v,list) and v[0]=='uuid'), None)
def canonical(item, excluded=frozenset()):
# KiCad can reorder independent board items when saving; compare their full
# parsed contents, not byte order. Pad coordinates/netnames/UUIDs remain exact.
if not isinstance(item, list):
return item
values = [canonical(v, excluded) for v in item
if not isinstance(v,list) or ident(v) not in excluded]
if item and item[0] in ("kicad_pcb", "footprint"):
return ([v for v in values if not isinstance(v,list)] +
sorted((v for v in values if isinstance(v,list)), key=repr))
# Coordinates, polygon vertex order, text and other ordered values must
# remain ordered: sorting these could conceal an X/Y swap or zone edit.
return values
def main():
assert hashlib.sha256(BASE.read_bytes()).hexdigest() == "53e36538c0669604457b9e1a9ff1b1e20422970e8f9f356088c4581868ad5045", "Pre-update snapshot changed"
assert hashlib.sha256((ROOT/"serial-carrier.kicad_pro").read_bytes()).hexdigest() == "9ad5b992785ebfe01fa7ea4e1aa391a2c7d7f1f2c3b1c13136173c301ecb6516", "Project settings changed"
before = PCB.read_bytes()
board = p.LoadBoard(str(PCB))
verify(board)
silk_ids = {str(uuid.uuid5(SILK_NS,str(i))) for i in range(4096)}
u1_ids = {str(uuid.uuid5(U1_NS,str(i))) for i in range(6)}
# The original baseline retains historical draft labels. Permit only the
# exact requested title/revision/comment edits and removal of the silk label.
assert canonical(expected_tree(parse(BASE)), silk_ids) == canonical(parse(PCB), silk_ids | u1_ids), "Change outside owned U1 additions / device silk / exact presentation labels"
generated = [g for g in board.GetDrawings() if g.m_Uuid.AsString() in silk_ids]
assert not any(isinstance(g,p.PCB_TEXT) and g.GetText()=="HEADER GUIDES" for g in board.GetDrawings())
carrier = Polygon([(136,100),(182,100),(182,180),(100,180),(100,119),(136,119)])
mouths = []
for g in generated:
assert g.GetLayer() == p.F_SilkS
for edge in outline_lines(g):
assert carrier.covers(edge.buffer(p.ToMM(g.GetWidth())/2)), "Silk outside carrier/notch"
if g.GetWidth() == p.FromMM(0.3):
mouths.append(g)
assert len(mouths) == 2
assert sorted((xy(g.GetStart()),xy(g.GetEnd())) for g in mouths) == [
((110.795,177.705),(119.665,177.705)),
((122.195,177.705),(131.065,177.705))]
# A second refresh must reproduce all drawing geometry and UUIDs.
add_outlines(board)
with tempfile.TemporaryDirectory(prefix="u1-regression-") as temporary:
result = Path(temporary)/PCB.name
assert p.SaveBoard(str(result),board,True)
assert canonical(parse(result)) == canonical(parse(PCB)), "Silkscreen refresh not idempotent"
u1 = next(f for f in board.GetFootprints() if f.GetReference()=="U1")
shape = next(g for g in u1.GraphicalItems() if isinstance(g,p.PCB_SHAPE))
shape.SetWidth(shape.GetWidth()+1)
try:
verify(board)
except AssertionError:
pass
else:
raise AssertionError("Stale embedded geometry was not rejected")
assert PCB.read_bytes() == before
assert not (ROOT/"~serial-carrier.kicad_pcb.lck").exists()
summary = dict(measured_library_matches_embedded_u1=True,
existing_uuids_pads_nets_placement_routing_settings_and_unrelated_graphics_preserved=True,
presentation_label_cleanup=True,
silk_refresh_idempotent=True, stale_geometry_negative_control=True,
silk_clipped_to_carrier=True, usb_mouth_markers=2,
generated_silk_items=len(generated),
baseline_sha256=hashlib.sha256(BASE.read_bytes()).hexdigest(),
board_sha256=hashlib.sha256(before).hexdigest(),
project_sha256=hashlib.sha256((ROOT/"serial-carrier.kicad_pro").read_bytes()).hexdigest())
(ROOT/"validation/pcb-u1-update.json").write_text(json.dumps(summary,indent=2)+"\n")
print(json.dumps(summary,indent=2))
if __name__ == "__main__":
main()