mohan-bee/stm32-midi-keys
Clips rectangular ground-region geometry to fit within the board’s scaled rounded-edge outline using polygon intersection.
- Version
- 1.0.2
- License
- unset
- Stars
- 0
scripts/source_geometry.py
"""Read the source independently of kicadts for import parity checks."""
import json, re, math
from pathlib import Path
tokens = iter(re.findall(r'"(?:\\.|[^"\\])*"|[()]|[^\s()]+', Path('source/stm32_midi_keys.kicad_pcb').read_text()))
def expr(token):
if token != '(':
return json.loads(token) if token.startswith('"') else token
result = []
for token in tokens:
if token == ')': return result
result.append(expr(token))
raise ValueError('Unclosed expression')
root = expr(next(tokens))
def many(node, key): return [x for x in node if isinstance(x,list) and x and x[0]==key]
def one(node, key, default=None): return next(iter(many(node,key)), default)
def point(node): return {'x':float(node[1])-125,'y':70-float(node[2])}
footprints=[]
for f in many(root,'footprint'):
at=one(f,'at'); angle=float(at[3]) if len(at)>3 else 0
ref=next(t[2] for t in many(f,'fp_text') if t[1]=='reference')
value=next(t[2] for t in many(f,'fp_text') if t[1]=='value')
pads=[]
for p in many(f,'pad'):
pa=one(p,'at'); x,y=map(float,pa[1:3]); a=math.radians(angle)
pos={'x':float(at[1])+x*math.cos(a)+y*math.sin(a)-125,'y':70-(float(at[2])-x*math.sin(a)+y*math.cos(a))}
net=one(p,'net',['net','0',''])
pads.append({'number':p[1], 'kind':p[2], 'shape':p[3], 'position':pos,'size':list(map(float,one(p,'size')[1:])), 'rotation':float(pa[3]) if len(pa)>3 else 0,'net':net[2], 'drill':one(p,'drill'), 'layers':one(p,'layers')[1:]})
footprints.append({'name':ref,'value':value,'footprint':f[1],'center':point(at),'rotation':angle,'layer':one(f,'layer')[1],'pads':pads})
zones=[]
for z in many(root,'zone'):
zones.append({'name':one(z,'name',['name',''])[1],'net':one(z,'net_name')[1],'layer':one(z,'layer',one(z,'layers'))[1], 'outline':[point(x) for x in one(one(z,'polygon'),'pts')[1:]],'fills':[[point(x) for x in one(f,'pts')[1:]] for f in many(z,'filled_polygon')], 'fillSettings':one(z,'fill')})
silks=[]
for g in many(root,'gr_poly'):
if one(g,'layer')[1] not in ['F.SilkS','B.SilkS']: continue
silks.append({'layer':one(g,'layer')[1],'filled':one(g,'fill',['fill','none'])[1]=='solid','points':[point(x) for x in one(g,'pts')[1:]]})
segments=[{'start':point(one(s,'start')),'end':point(one(s,'end')),'width':float(one(s,'width')[1]),'layer':one(s,'layer')[1],'net':int(one(s,'net')[1])} for s in many(root,'segment')]
vias=[{'at':point(one(v,'at')),'size':float(one(v,'size')[1]),'drill':float(one(v,'drill')[1]),'net':int(one(v,'net')[1])} for v in many(root,'via')]
data={'centerKiCad':{'x':125,'y':70},'footprints':footprints,'zones':zones,'silkscreenPolygons':silks,'segments':segments,'vias':vias,'nets':{n[1]:n[2] for n in many(root,'net')}}
Path('source/geometry.json').write_text(json.dumps(data,indent=2))
print(json.dumps({'footprints':len(footprints),'pads':sum(len(f['pads']) for f in footprints),'zones':len(zones),'fills':sum(len(z['fills']) for z in zones),'segments':len(segments),'vias':len(vias),'silkscreenPolygons':len(silks)},indent=2))