AnasSarkiz/magnetic-shutter-remote-r8

A USB‑C rechargeable battery-powered ESP32‑C3 wireless controller with Li‑ion charging, protected 3.3‑V buck‑boost regulation, thermal/safety monitoring, status LEDs, buttons, programming UART, crystal clock, and 2.4‑GHz antenna matching network.

Version
0.4.0-prototype
License
unset
Stars
0

scripts/r9-filled-via-process.py

"""Validate required R9 filled/capped ground vias; no Circuit JSON edits.
Circuit/world XY in millimetres, +Z top. Gerbers cannot prove actual filling;
supplier process confirmation is an independent mandatory release gate.
"""
import json, math, importlib.util, sys, hashlib, argparse
from pathlib import Path
ROOT=Path(__file__).resolve().parents[1]
sys.path.insert(0,str(ROOT/'scripts'))
spec=importlib.util.spec_from_file_location('r9_filled_geometry',ROOT/'scripts/manufacturing-audit.py')
GEOMETRY=importlib.util.module_from_spec(spec);sys.modules[spec.name]=GEOMETRY;spec.loader.exec_module(GEOMETRY)

def classify_process(circuit, process):
    required=process['intentional_via_in_pad']
    if required['required_process'] != 'epoxy_filled_copper_capped':
        raise ValueError('R9 thermal vias require epoxy filling and plated copper caps')
    component=next(e for e in circuit if e['type']=='source_component' and e['name']==required['component'])
    if component['manufacturer_part_number']!='ESP32-C3FH4' or component['supplier_part_numbers']['jlcpcb']!=['C2858491']:
        raise ValueError('Filled process is scoped to exact U1 bare chip')
    pcb=next(e for e in circuit if e['type']=='pcb_component' and e['source_component_id']==component['source_component_id'])
    pads=[e for e in circuit if e['type']=='pcb_smtpad' and e['pcb_component_id']==pcb['pcb_component_id'] and required['terminal'] in e['port_hints']]
    if len(pads)!=1:raise ValueError('Thermal pad is not unique')
    copper,drills,widths=GEOMETRY.make_features(circuit,'gerber')
    pad=next(f for f in copper if f.id==pads[0]['pcb_smtpad_id'])
    gnd=next(e for e in circuit if e['type']=='source_net' and e['name']==required['net'])
    if required['net']!='GND' or pad.net!=gnd['subcircuit_connectivity_map_key']:
        raise ValueError('Thermal pad must be physically on declared GND')
    centers=required['centers_mm']
    if len(centers)!=4 or len({tuple(c) for c in centers})!=4:raise ValueError('Require exactly four unique thermal vias')
    vias=[e for e in circuit if e['type']=='pcb_via']
    selected=[]
    for x,y in centers:
        matches=[v for v in vias if math.hypot(v['x']-x,v['y']-y)<1e-6]
        if len(matches)!=1:raise ValueError('Declared thermal via missing or duplicated')
        via=matches[0]
        if sorted(via['layers'])!=['bottom','top'] or abs(via['hole_diameter']-.3)>1e-6 or abs(via['outer_diameter']-.45)>1e-6:
            raise ValueError('Thermal vias must be 0.3/0.45mm through vias')
        drill=next(d for d in drills if d.id==via['pcb_via_id'])
        if drill.net!=pad.net or not pad.geometry.covers(drill.outer):
            raise ValueError('Thermal via must be fully inside same-net exposed ground pad')
        drill.filled_capped_pad_id=pad.id
        selected.append(via['pcb_via_id'])
    # Every via follows the user's two-layer fabrication geometry, not just
    # the four intentional filled/capped exposed-pad vias.
    for via in vias:
        if sorted(via['layers']) != ['bottom', 'top'] or abs(via['hole_diameter']-.3)>1e-6 or abs(via['outer_diameter']-.45)>1e-6:
            raise ValueError(f"Nonconforming through-via geometry: {via['pcb_via_id']}")
    # Native board policy allows intentional via-in-pad; this independent check
    # requires exact process membership for every actual SMT overlap.
    for drill in drills:
        if drill.kind!='via':continue
        for smt in (f for f in copper if f.kind=='smt' and f.layer in drill.layers):
            if drill.geometry.intersects(smt.geometry) and drill.filled_capped_pad_id!=smt.id:
                raise ValueError(f'Unqualified via-in-pad: {drill.id}/{smt.id}')
    return copper,drills,widths,selected

def review(args):
    raw=args.circuit.read_bytes();circuit=json.loads(raw);process=json.loads(args.process.read_text())
    native_errors=[e for e in circuit if e['type'].endswith('_error')]
    if native_errors and not args.placement_only:
        raise ValueError('Routed process review requires error-free native output; use --placement-only for scoped placement diagnostics')
    copper,drills,widths,selected=classify_process(circuit,process)
    report=GEOMETRY.audit(copper,drills,widths)
    report.update({'circuit_sha256':hashlib.sha256(raw).hexdigest(),'required_filled_capped_vias':selected,'supplier_process_confirmed':process['intentional_via_in_pad']['supplier_processed_preview_confirmed'],'cost_quote_confirmed':process['intentional_via_in_pad']['cost_quote_confirmed'],'release_gate':'BLOCKED_PENDING_SUPPLIER_PROCESS_AND_COST_REVIEW','scope':'Local geometry conditional on the explicitly required filled/capped process. No physical fabrication or supplier acceptance is claimed.'})
    report.update({'placement_only':args.placement_only,'native_error_count':len(native_errors),'routed_geometry_qualified':not args.placement_only and not native_errors and not report['failures']})
    args.output.write_text(json.dumps(report,indent=2)+'\n')
    print(f"R9 conditional manufacturing geometry: {len(report['failures'])} failures; {len(selected)} required filled/capped vias; supplier/cost gate pending")
    return bool(report['failures'])
if __name__=='__main__':
    p=argparse.ArgumentParser();p.add_argument('--circuit',type=Path,default=ROOT/'dist/index/circuit.json');p.add_argument('--process',type=Path,default=ROOT/'fabrication/r9-process.json');p.add_argument('--output',type=Path,required=True);p.add_argument('--placement-only',action='store_true')
    raise SystemExit(review(p.parse_args()))