#!/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))