main
John Lauer Publish independent Astra ESC source and full-pad Kelvin review lesson d057af8 22d ago
import re,json,math
from pathlib import Path
from shapely.geometry import Point,box,Polygon,LineString
from shapely import affinity
from shapely.ops import unary_union
def parse(s):
 toks=re.findall(r'"(?:\\.|[^"\\])*"|[()]|[^\s()]+',s); stack=[];root=None
 for t in toks:
  if t=='(':stack.append([])
  elif t==')':
   v=stack.pop()
   if stack:stack[-1].append(v)
   else:root=v
  else:
   try:v=json.loads(t) if t.startswith('"') else float(t)
   except:v=t
   stack[-1].append(v)
 return root
def allof(x,k):return [v for v in x if isinstance(v,list) and v and v[0]==k]
def one(x,k,d=None):return next(iter(allof(x,k)),d)
def val(x,k,d=None):
 a=one(x,k);return a[1] if a else d
root=parse(Path('route-input.kicad_pcb').read_text())
pads=[];keepouts=[]
for f in allof(root,'footprint'):
 ref=next(p[2] for p in allof(f,'property') if p[1]=='Reference'); fa=one(f,'at');fx,fy=fa[1:3];ang=fa[3] if len(fa)>3 else 0
 for p in allof(f,'pad'):
  a=one(p,'at');px,py=a[1:3];pa=a[3] if len(a)>3 else 0;sz=one(p,'size');w,h=sz[1:3];shape=p[3]
  if shape=='circle':g=Point(0,0).buffer(w/2,quad_segs=16)
  elif shape=='oval':
   g=LineString([(-(w-h)/2,0),((w-h)/2,0)]).buffer(h/2) if w>h else LineString([(0,-(h-w)/2),(0,(h-w)/2)]).buffer(w/2)
  elif shape=='roundrect':
   r=min(w,h)*val(p,'roundrect_rratio',.25);g=box(-w/2+r,-h/2+r,w/2-r,h/2-r).buffer(r)
  else:g=box(-w/2,-h/2,w/2,h/2)
  if shape=='custom':
   shapes=[g]; prim=one(p,'primitives',[])
   for q in allof(prim,'gr_poly'):
    pts=[v[1:3] for v in one(q,'pts')[1:]];pg=Polygon(pts);shapes.append(pg.buffer(val(q,'width',0)/2))
   g=unary_union(shapes)
  # pad angle stored board absolute angle, coordinates footprint local
  g=affinity.rotate(g,-pa,origin=(0,0));cx=fx+px*math.cos(math.radians(ang))+py*math.sin(math.radians(ang));cy=fy-px*math.sin(math.radians(ang))+py*math.cos(math.radians(ang));g=affinity.translate(g,cx,cy)
  n=one(p,'net');net=n[-1] if n else ''; layers=one(p,'layers')[1:];dr=one(p,'drill');hole=0
  if dr:
   hole=max([v for v in dr[1:] if isinstance(v,(int,float))],default=0)
  pads.append(dict(name=ref+'.'+str(int(p[1]) if isinstance(p[1],float) else p[1]),net=net,x=cx,y=cy,g=g,layers=layers,type=p[2],clearance=max(val(p,'clearance',0),val(f,'clearance',0)),hole=hole))
if __name__=='__main__':
 print(len(pads));print('shapes',[(p['name'],p['g'].bounds,p['clearance']) for p in pads if p['clearance']]);print('zones',[(val(z,'net_name'),val(z,'layer')) for z in allof(root,'zone')]);print('edges',[(a[0],one(a,'start'),one(a,'end')) for a in root if isinstance(a,list) and val(a,'layer')=='Edge.Cuts'])
 st=json.load(open('evidence/initial-state.json'));live={p['name']:p for p in st['pads']};print('maxcoorderror',max(math.hypot(p['x']-live[p['name']]['x'],p['y']-live[p['name']]['y']) for p in pads))