Advance PCB carrier to A1 layout
Reposition the ESP32 and RS-232 reference within the notched outline, preserve the A0 snapshot, and add second-pass two-layer routing evidence. Refresh validation reports and draft previews while keeping fabrication status provisional.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/python3
|
||||
"""Compare A1 against the untouched A0 snapshot, including unrelated graphics."""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import uuid
|
||||
|
||||
ROOT=Path(__file__).resolve().parent.parent
|
||||
OUT=ROOT/'validation'
|
||||
|
||||
|
||||
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 child(item,key):
|
||||
return next(v for v in item if isinstance(v,list) and v[0]==key)
|
||||
|
||||
|
||||
def ident(item):
|
||||
return child(item,'uuid')[1].strip('"')
|
||||
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
report=json.loads(OUT.joinpath('pcb-routing-reposition.json').read_text())
|
||||
a0=OUT/'pcb-a0-before-reposition.kicad_pcb'
|
||||
a1=ROOT/'serial-carrier.kicad_pcb'
|
||||
assert sha(a0)==report['a0_snapshot_sha256'], 'A0 snapshot changed'
|
||||
assert sha(OUT/'pcb-routing-input.kicad_pcb')==report['a1_unrouted_sha256'], 'A1 routing input changed'
|
||||
# These two handoff documents are intentionally updated during A1 integration.
|
||||
protected={name:digest for name,digest in report['protected_file_sha256'].items()
|
||||
if name not in ('README.md','pcb-draft-notes.md')}
|
||||
for name,digest in protected.items():
|
||||
assert sha(ROOT/name)==digest, 'protected file changed: '+name
|
||||
old,new=parse(a0),parse(a1)
|
||||
old_fp={ident(x):x for x in old if isinstance(x,list) and x[0]=='footprint'}
|
||||
new_fp={ident(x):x for x in new if isinstance(x,list) and x[0]=='footprint'}
|
||||
assert set(old_fp)==set(new_fp)
|
||||
for key,fp in old_fp.items():
|
||||
# Footprint geometry, fields, UUIDs and connectivity must be identical;
|
||||
# only the top-level placement may differ, and only for the four moves.
|
||||
ref=next(v[2].strip('"') for v in fp if isinstance(v,list) and v[:2]==['property','"Reference"'])
|
||||
cleaned=lambda f:[v for v in f if not isinstance(v,list) or v[0]!='at']
|
||||
assert cleaned(fp)==cleaned(new_fp[key]), 'footprint content changed: '+ref
|
||||
if ref not in report['moves_local_mm']:
|
||||
assert child(fp,'at')==child(new_fp[key],'at'),ref
|
||||
else:
|
||||
expected=report['moves_local_mm'][ref][1]
|
||||
assert [round(float(v)-100,6) for v in child(new_fp[key],'at')[1:3]]==expected,ref
|
||||
ns=uuid.UUID('67964aa4-c3a5-45d8-9b54-250fb68e7c41')
|
||||
owned={str(uuid.uuid5(ns,str(i))) for i in range(4096)}
|
||||
cad_ns=uuid.UUID('ba7274a8-c35d-46bc-908d-cd86708fc6c4')
|
||||
cad_owned={str(uuid.uuid5(cad_ns,str(i))) for i in range(7)}
|
||||
changed_texts=set(report['changed_board_text_uuids'])
|
||||
for item in old:
|
||||
if isinstance(item,list) and item[0]=='gr_text':
|
||||
value=json.loads(item[1])
|
||||
if value=='82 x 80 mm / 2 copper layers' or value.startswith('DE-9 OUT /'):
|
||||
changed_texts.add(ident(item))
|
||||
|
||||
def drawings(tree):
|
||||
return {ident(x):x for x in tree if isinstance(x,list) and
|
||||
(x[0].startswith('gr_') or x[0] in ('dimension','target','image'))}
|
||||
|
||||
old_gr,new_gr=drawings(old),drawings(new)
|
||||
edge_ids={key for key,value in old_gr.items() if child(value,'layer')[1]=='"Edge.Cuts"'}
|
||||
assert cad_owned <= set(new_gr), 'missing CAD projection graphics'
|
||||
assert all(child(new_gr[key],'layer')[1]=='"Dwgs.User"' for key in cad_owned)
|
||||
excluded=owned|cad_owned|changed_texts|edge_ids
|
||||
unchanged_old={key:value for key,value in old_gr.items() if key not in excluded}
|
||||
unchanged_new={key:value for key,value in new_gr.items() if key not in excluded}
|
||||
assert unchanged_old==unchanged_new, 'unrelated board graphics changed'
|
||||
assert set(old_gr)-owned==set(new_gr)-owned-cad_owned, 'non-owned board graphic UUID set changed'
|
||||
assert [x for x in old if isinstance(x,list) and x[0]=='net']==[x for x in new if isinstance(x,list) and x[0]=='net']
|
||||
assert not (ROOT/'~serial-carrier.kicad_pcb.lck').exists(), 'PCB reopened before handoff'
|
||||
result=dict(a0_snapshot_unchanged=True,a1_unrouted_snapshot_unchanged=True,
|
||||
protected_files_unchanged=list(protected),
|
||||
intentional_documentation_updates=['README.md','pcb-draft-notes.md'],
|
||||
cad_projection_graphics=len(cad_owned),
|
||||
footprints_identical_except_authorized_placement=True,
|
||||
footprint_count=len(old_fp),unrelated_board_graphics_preserved=len(unchanged_old),
|
||||
non_owned_board_graphic_uuids_preserved=True,
|
||||
refreshed_device_silkscreen_items=len(set(new_gr)&owned),
|
||||
pcb_editor_lock_absent=True,final_board_sha256=sha(a1))
|
||||
OUT.joinpath('pcb-routing-preservation.json').write_text(json.dumps(result,indent=2)+'\n')
|
||||
print(json.dumps(result,indent=2))
|
||||
Reference in New Issue
Block a user