from pathlib import Path
import re,json,xml.etree.ElementTree as E,math,csv
from sexps import forms
p=Path(__file__).resolve().parent;d=p/'schematic';lib=d/'Lamp_Driver.pretty'
def load(path):
 r=E.parse(path).getroot()
 comps={c.get('ref'):c for c in r.findall('./components/comp')}
 pins={(c.get('ref'),c.get('pin')):n.get('name') for n in r.findall('./nets/net') for c in n}
 return comps,pins
comps,pins=load(d/'netlist.xml');old,oldpins=load(p/'comparison_only/A47_netlist.xml')
assert pins==oldpins,'Electrical connections changed'
assert set(comps)==set(old)
e=json.loads((d/'erc.json').read_text());assert not [v for s in e['sheets'] for v in s.get('violations',[])]
def pads(s):
 rows=[]
 for _,_,form in forms(s,'pad'):
  n=re.match(r'\(pad "([^"]*)"',form).group(1)
  xy=re.search(r'\(at ([^ )]+) ([^ )]+)',form);size=re.search(r'\(size ([^ )]+) ([^ )]+)',form)
  rows.append((n,float(xy[1]),float(xy[2]),float(size[1]),float(size[2]),form))
 return rows
rows=[]
for ref,c in comps.items():
 if ref.startswith('#'):continue
 fp=c.findtext('footprint');assert fp.startswith('Lamp_Driver:')
 path=lib/(fp.split(':',1)[1]+'.kicad_mod');assert path.is_file()
 unique={n for n,*_ in pads(path.read_text()) if n}
 symbol={n.get('num') for n in c.findall('./units/unit/pins/pin')}
 assert unique==symbol,(ref,unique,symbol)
 assert c.findtext('value')==old[ref].findtext('value')
 dnp=any(v.get('name')=='dnp' for v in c.findall('property'))
 assert dnp==(ref in ['R6','R7'])
 rows.append([ref,c.findtext('value'),fp,dnp,len(unique)])
for ref in ['U1','U2','U3']:
 assert pins[ref,'39']=='/GND'
 for pin in ['14','18','21','25','34']:assert pins[ref,pin]=='/GND'
fp=lib/'PCA9956BTW_SOT1331-1_NXP_land_candidate.kicad_mod';a=pads(fp.read_text());named={n:(x,y,w,h,f) for n,x,y,w,h,f in a if n};assert set(named)=={str(i) for i in range(1,40)}
assert named['39'][:4]==(0,0,3,5.04);assert 'F.Paste' not in named['39'][4]
apertures=[(x,y,w,h) for n,x,y,w,h,f in a if not n];assert len(apertures)==24
for x,y,w,h in apertures:
 assert abs(w-.6)<1e-9 and abs(h-.7)<1e-9
 assert abs(x)+w/2<=1.5 and abs(y)+h/2<=2.52
for k,v in enumerate(apertures):
 for u in apertures[k+1:]:
  assert abs(v[0]-u[0])>=(v[2]+u[2])/2 or abs(v[1]-u[1])>=(v[3]+u[3])/2
assert math.isclose(sum(w*h for x,y,w,h in apertures)/(3*5.04),2/3)
assert math.isclose(max(abs(x)+w/2 for x,y,w,h in apertures)*2,2.76)
assert math.isclose(max(abs(y)+h/2 for x,y,w,h in apertures)*2,4.8)
for n,(x,y,w,h,f) in named.items():
 if n=='39':continue
 assert math.isclose(abs(x),3.05) and math.isclose(w,1.35)
 assert math.isclose(h,.45 if n in ['1','19','20','38'] else .25)
assert named['1'][1]==named['38'][1]==-4.6 and named['19'][1]==named['20'][1]==4.6
assert all(named[str(i)][0]<0 for i in range(1,20)) and all(named[str(i)][0]>0 for i in range(20,39))
_,safetypins=load(p/'comparison_only/A39_safety_netlist.xml')
for ref,busnet in [('R2','/I2C_SDA'),('R3','/I2C_SCL')]:
 assert set(safetypins[ref,k] for k in ['1','2'])=={busnet,'/CTRL_3V3'}
with (p/'candidate_BOM_and_footprints.csv').open('w',newline='') as f:
 w=csv.writer(f);w.writerow(['reference','value','footprint','DNP','unique_pad_count']);w.writerows(sorted(rows))
checks={'native_ERC':0,'components_assigned':len(rows),'electrical_pin_net_map_unchanged':True,'symbol_vs_footprint_pad_set':'PASS','thermal_pad39_GND_chips':3,'thermal_land_mm':[3,5.04],'paste_apertures':24,'paste_mm':[.6,.7],'paste_coverage_fraction':2/3,'DNP_actual_properties':['R6','R7'],'footprint_SVG_native_exports':len(list((p/'footprint_svg').glob('*.svg'))),'PCB_layout':False,'thermal_vias':'REQUIRED_ON_FUTURE_PCB','fabrication':'HOLD','physical_validation':False}
(p/'checks.json').write_text(json.dumps(checks,indent=2)+'\n');print(json.dumps(checks))
