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-bga-apertures.py
"""Check actual Gerber flashes against TI land/stencil dimensions, in mm."""
import hashlib, json, math, re
from pathlib import Path
root = Path('review/manufacturing')
c = json.loads((root / 'circuit.json').read_text())
sources = {e['source_component_id']: e for e in c if e['type'] == 'source_component'}
components = {e['pcb_component_id']: sources[e['source_component_id']]['name'] for e in c if e['type'] == 'pcb_component'}
def flashes(name):
data = (root / name).read_text()
assert '%FSLAX46Y46*%' in data and '%MOMM*%' in data
apertures, result, selected = {}, [], None
for line in data.splitlines():
m = re.fullmatch(r'%ADD(\d+)([A-Z]+),([^*]+)\*%', line)
if m:
apertures[int(m[1])] = {'shape': m[2], 'dimensions': [float(n) for n in m[3].split('X')]}
m = re.fullmatch(r'D(\d+)\*', line)
if m: selected = int(m[1])
m = re.fullmatch(r'X(-?\d+)Y(-?\d+)D03\*', line)
if m:
result.append({'x': int(m[1]) / 1e6, 'y': int(m[2]) / 1e6, **apertures[selected]})
return result
layers = {name: flashes(f'{name}.gbr') for name in ['F_Cu', 'F_Mask', 'F_Paste']}
def at(layer, x, y):
return [f for f in layers[layer] if math.hypot(f['x']-x, f['y']-y) < 2e-6]
def contains(fs, shape, dimensions):
return any(f['shape'] == shape and len(f['dimensions']) == len(dimensions) and
all(abs(a-b) < 2e-6 for a,b in zip(f['dimensions'], dimensions)) for f in fs)
report = []
for pad in c:
if pad['type'] != 'pcb_smtpad' or components.get(pad.get('pcb_component_id')) not in ['U2','U3']: continue
ref = components[pad['pcb_component_id']]
assert pad['shape'] == 'circle'
x, y = pad['x'], pad['y']
copper = .23 if ref == 'U2' else .245
mask = copper + 2 * pad['soldermask_margin']
assert contains(at('F_Cu',x,y),'C',[copper]), (ref,pad['port_hints'],'copper')
assert contains(at('F_Mask',x,y),'C',[mask]), (ref,pad['port_hints'],'mask')
assert contains(at('F_Paste',x,y),'R',[.25,.15])
assert contains(at('F_Paste',x,y),'R',[.15,.25])
assert not contains(at('F_Paste',x,y),'C',[.25]), 'Old circular aperture remains'
for dx in [-.075,.075]:
for dy in [-.075,.075]: assert contains(at('F_Paste',x+dx,y+dy),'C',[.1])
report.append({'reference':ref,'pad':pad['port_hints'],'copperLandDiameterMm':copper,
'copperFlashesAtCenter':at('F_Cu',x,y),'maskOpeningDiameterMm':mask,
'maskDefinition':'SMD at capped via' if pad['soldermask_margin']==0 else 'NSMD',
'pasteWidthMm':.25,'pasteHeightMm':.25,'pasteCornerRadiusMm':.05,
'pasteAreaMm2':.25**2-(4-math.pi)*.05**2})
assert len(report) == 29
bga_pads=[p for p in c if p['type']=='pcb_smtpad' and components.get(p.get('pcb_component_id')) in ['U2','U3']]
minimum_mask_bridge=min(math.hypot(p['x']-q['x'],p['y']-q['y'])-p['radius']-q['radius']-p['soldermask_margin']-q['soldermask_margin'] for i,p in enumerate(bga_pads) for q in bga_pads[i+1:])
assert minimum_mask_bridge >= .10-1e-6, ('BGA soldermask bridge',minimum_mask_bridge)
Path('review/bga-aperture-audit.json').write_text(json.dumps({
'manufacturingCircuitSha256':hashlib.sha256((root/'circuit.json').read_bytes()).hexdigest(),
'result':'PASS — dimensions verified in exported Gerber flashes; process qualification remains open',
'minimumBgaMaskBridgeMm':minimum_mask_bridge,'stencilThicknessMm':.1,'lands':report,
},indent=2))
print('PASS: all 29 BGA copper/mask/stencil openings verified from Gerber flashes')