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-passive-gerbers.py

"""Verify imported passive copper, mask, paste and placement in the final exports."""
import csv, hashlib, json, math, re
from pathlib import Path

read = lambda name: json.loads(Path(name).read_text())
sha = lambda name: hashlib.sha256(Path(name).read_bytes()).hexdigest()
audit = read('review/imported-footprint-audit.json')
assert audit['circuitSha256'] == sha('dist/index/circuit.json'), 'Stale footprint audit'
c = read('review/manufacturing/circuit.json')

def flashes(filename):
    text = Path(filename).read_text()
    assert '%FSLAX46Y46*%' in text and '%MOMM*%' in text
    apertures, result, selected = {}, [], None
    for line in text.splitlines():
        m = re.fullmatch(r'%ADD(\d+)([A-Z]+),([^*]+)\*%', line)
        if m:
            apertures[int(m[1])] = (m[2], [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((int(m[1])/1e6, int(m[2])/1e6, *apertures[selected]))
    return result

layers = {layer: flashes(f'review/manufacturing/{layer}.gbr') for layer in ['F_Cu', 'F_Mask', 'F_Paste']}

def require_flash(layer, x, y, width, height, label):
    assert any(math.hypot(fx-x, fy-y) < 2e-6 and shape == 'R' and len(dims) == 2
               and abs(dims[0]-width) < 2e-6 and abs(dims[1]-height) < 2e-6
               for fx, fy, shape, dims in layers[layer]), (label, layer, x, y, width, height)

checked = []
for item in audit['records']:
    for pad in item['pads']:
        p = pad['built']
        label = f"{item['reference']}.{pad['pin']}"
        require_flash('F_Cu', **p, label=label)
        actual = next(e for e in c if e['type'] == 'pcb_smtpad' and
                      math.hypot(e['x']-p['x'], e['y']-p['y']) < 1e-6)
        margin = actual.get('soldermask_margin', 0)
        require_flash('F_Mask', p['x'], p['y'], p['width']+2*margin, p['height']+2*margin, label)
        pastes = [e for e in c if e['type'] == 'pcb_solder_paste' and
                  e.get('pcb_smtpad_id') == actual['pcb_smtpad_id']]
        assert len(pastes) == 1 and pastes[0]['shape'] == 'rect', label
        paste = pastes[0]
        require_flash('F_Paste', paste['x'], paste['y'], paste['width'], paste['height'], label)
        checked.append(label)

sources = {e['source_component_id']: e for e in c if e['type'] == 'source_component'}
components = {sources[e['source_component_id']]['name']: e for e in c if e['type'] == 'pcb_component'}
cpl = list(csv.DictReader(Path('review/jlc-cpl.csv').open()))
bom = list(csv.DictReader(Path('review/jlc-bom.csv').open()))
assert len(cpl) == len(components) == 62
assert {row['Designator'] for row in cpl} == set(components)
for row in cpl:
    pcb = components[row['Designator']]
    assert abs(float(row['Mid X'])-pcb['center']['x']) <= .000051
    assert abs(float(row['Mid Y'])-pcb['center']['y']) <= .000051
    assert float(row['Rotation']) == pcb['rotation'] and row['Layer'] == pcb['layer']
refs = [ref for row in bom for ref in row['Designator'].split(',')]
assert len(refs) == len(set(refs)) == 62 and set(refs) == set(components)
for row in bom:
    for ref in row['Designator'].split(','):
        source = sources[components[ref]['source_component_id']]
        assert row['LCSC Part #'] == source['supplier_part_numbers']['jlcpcb'][0]

report = {'result': 'PASS', 'circuitSha256': audit['circuitSha256'],
          'manufacturingCircuitSha256': sha('review/manufacturing/circuit.json'),
          'importedPadCopperMaskPasteCount': len(checked), 'pads': checked,
          'bomRows': len(bom), 'bomReferences': len(refs), 'cplReferences': len(cpl),
          'fileHashes': {name: sha(name) for name in ['review/jlc-bom.csv', 'review/jlc-cpl.csv'] +
                         [f'review/manufacturing/{layer}.gbr' for layer in layers]}}
Path('review/passive-gerber-audit.json').write_text(json.dumps(report, indent=2)+'\n')
print(f'PASS: {len(checked)} imported pads in copper/mask/paste Gerbers; all 62 BOM codes and CPL placements match.')