imrishabh18/pedometer

This code defines and assembles a simple radio receiver hardware circuit using specific imported capacitors, inductors, RF connectors, and oscillator components with precise footprints and schematic attributes.

Version
1.1.3
License
unset
Stars
0

scripts/audit-fabrication-geometry.py

"""Supplementary geometry audit; run from the project root with Python 3.
JLCPCB capability comparison recorded 2026-09-04; does not replace factory CAM.
Does not change layout or resolve fabrication-review findings.
"""
import json,math,hashlib
from pathlib import Path
c=json.loads(Path('dist/index/circuit.json').read_text())
bytype=lambda t:[x for x in c if x['type']==t]
nets={x['source_net_id']:x['name'] for x in bytype('source_net')}
spnet={p:nets[n] for t in bytype('source_trace') for p in t['connected_source_port_ids'] for n in t['connected_source_net_ids']}
ppnet={p['pcb_port_id']:spnet.get(p['source_port_id']) for p in bytype('pcb_port')}
refs={x['source_component_id']:x['name'] for x in bytype('source_component')}
comps={x['pcb_component_id']:refs[x['source_component_id']] for x in bytype('pcb_component')}
traces=bytype('pcb_trace');tnets={t['pcb_trace_id']:nets.get(t.get('connection_name'),spnet.get(next(iter(next((s['connected_source_port_ids'] for s in bytype('source_trace') if s['source_trace_id']==t.get('source_trace_id')),[])),None))) for t in traces}
vias=list({(round(v['x'],6),round(v['y'],6),v['hole_diameter']):v for v in bytype('pcb_via')}.values())
def netv(v):return tnets[v['pcb_trace_id']]
def pointseg(p,a,b):
 dx=b['x']-a['x'];dy=b['y']-a['y'];d=dx*dx+dy*dy; t=max(0,min(1,((p['x']-a['x'])*dx+(p['y']-a['y'])*dy)/d)) if d else 0
 return math.hypot(p['x']-a['x']-t*dx,p['y']-a['y']-t*dy)
segments=[(a,b,tnets[t['pcb_trace_id']]) for t in traces for a,b in zip(t['route'],t['route'][1:]) if a['route_type']==b['route_type']=='wire' and a['layer']==b['layer']]
def distpad(p,pad):
 if pad['shape']=='circle':return max(0,math.hypot(p['x']-pad['x'],p['y']-pad['y'])-pad['radius'])
 return math.hypot(max(abs(p['x']-pad['x'])-pad['width']/2,0),max(abs(p['y']-pad['y'])-pad['height']/2,0))
pads=bytype('pcb_smtpad')
vippairs=[(v,p) for v in vias for p in pads if distpad(v,p)<v['hole_diameter']/2-1e-8]
viaholepad=sorted((distpad(v,p)-v['hole_diameter']/2,v['pcb_via_id'],comps[p['pcb_component_id']],p.get('port_hints')) for v in vias for p in pads if ppnet.get(p.get('pcb_port_id')) and ppnet[p['pcb_port_id']]!=netv(v))
viaholetrace=sorted((pointseg(v,a,b)-v['hole_diameter']/2-max(a['width'],b['width'])/2,v['pcb_via_id'],netv(v),n,a['layer']) for v in vias for a,b,n in segments if n and n!=netv(v))
holehole=sorted((math.hypot(v['x']-w['x'],v['y']-w['y'])-(v['hole_diameter']+w['hole_diameter'])/2,v['pcb_via_id'],w['pcb_via_id']) for i,v in enumerate(vias) for w in vias[i+1:])
ko=bytype('pcb_keepout')[0];xmin=ko['center']['x']-ko['width']/2;xmax=xmin+ko['width'];ymin=ko['center']['y']-ko['height']/2;ymax=ymin+ko['height']
def clipped_area(vertices):
 pts=[(p['x'],p['y']) for p in vertices]
 for dim,value,greater in [(0,xmin,True),(0,xmax,False),(1,ymin,True),(1,ymax,False)]:
  old=pts;pts=[]
  for a,b in zip(old,old[1:]+old[:1]):
   ai=a[dim]>=value if greater else a[dim]<=value;bi=b[dim]>=value if greater else b[dim]<=value
   if ai:pts.append(a)
   if ai!=bi:
    t=(value-a[dim])/(b[dim]-a[dim]);pts.append((a[0]+t*(b[0]-a[0]),a[1]+t*(b[1]-a[1])))
 return abs(sum(a[0]*b[1]-b[0]*a[1] for a,b in zip(pts,pts[1:]+pts[:1]))/2)
pours=[]
for p in bytype('pcb_copper_pour'):
 s=p['brep_shape']; area=clipped_area(s['outer_ring']['vertices'])-sum(clipped_area(r['vertices']) for r in s['inner_rings']);pours.append({'id':p['pcb_copper_pour_id'],'layer':p['layer'],'keepoutIntersectionMm2':round(area,9)})
def inside_polygon(p,vertices):
 inside=False
 for a,b in zip(vertices,vertices[1:]+vertices[:1]):
  if (a['y']>p['y'])!=(b['y']>p['y']) and p['x']<(b['x']-a['x'])*(p['y']-a['y'])/(b['y']-a['y'])+a['x']:inside=not inside
 return inside
pourclearances=[]
for pour in bytype('pcb_copper_pour'):
 rings=[pour['brep_shape']['outer_ring']['vertices']]+[r['vertices'] for r in pour['brep_shape']['inner_rings']]
 assert nets[pour['source_net_id']]=='GND'
 for v in vias:
  if netv(v)=='GND':continue
  distance=min(pointseg(v,a,b) for vertices in rings for a,b in zip(vertices,vertices[1:]+vertices[:1]))
  inside=inside_polygon(v,rings[0]) and not any(inside_polygon(v,r) for r in rings[1:])
  pourclearances.append(((-distance if inside else distance)-v['hole_diameter']/2,v['pcb_via_id'],pour['pcb_copper_pour_id'],pour['layer']))
report={'circuitSha256':hashlib.sha256(Path('dist/index/circuit.json').read_bytes()).hexdigest(),'uniqueVias':len(vias),'viaInPadCount':len({v['pcb_via_id'] for v,p in vippairs}),'viaInPadReferences':sorted(set(comps[p['pcb_component_id']] for v,p in vippairs)),'viaInPadBalls':[{'via':v['pcb_via_id'],'reference':comps[p['pcb_component_id']],'pad':p.get('port_hints'),'net':ppnet.get(p.get('pcb_port_id'))} for v,p in vippairs], 'minimumViaHoleToOtherNetPadMm':viaholepad[0], 'viaHoleToOtherNetPadBelow02mm':len({x[1] for x in viaholepad if x[0]<.2-1e-6}), 'minimumViaHoleToHoleMm':holehole[0],'viaPairsBelow02mm':sum(x[0]<.2-1e-6 for x in holehole),'minimumViaHoleToOtherNetTraceMm':viaholetrace[0],'viaHoleToOtherNetTraceBelow02mm':len({x[1] for x in viaholetrace if x[0]<.2-1e-6}),'tightViaHoleExamples':viaholetrace[:10],'copperPoursKeepout':pours,'minimumViaHoleToOtherNetPourMm':min(pourclearances), 'viaHoleToOtherNetPourBelow02mm':len({x[1] for x in pourclearances if x[0]<.2-1e-6}), 'minimumTraceWidthMm':min(a['width'] for a,b,n in segments),'silkscreenBelow1mm':len([x for x in bytype('pcb_silkscreen_text') if x.get('font_size',1)<1])}
Path('review/fabrication-metrics.json').write_text(json.dumps(report,indent=2));print(json.dumps(report,indent=2))

violations={key:report[key] for key in ['viaPairsBelow02mm','viaHoleToOtherNetTraceBelow02mm','viaHoleToOtherNetPadBelow02mm','viaHoleToOtherNetPourBelow02mm'] if report[key]}
if violations or any(abs(p['keepoutIntersectionMm2'])>1e-8 for p in pours):
 raise SystemExit(f"Fabrication geometry audit failed: {violations}")