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_board_geometry.py

"""Independent R9 antenna exclusion. Board XY mm, +Z top.

Only the actual antenna terminal lands and its top RF feed are permitted;
the native allowTraces option is not an electrical clearance exemption.
"""
from shapely.geometry import box


def antenna_exclusion(circuit):
    """Return the actual antenna component and validated exclusion polygon, XY mm."""
    antenna = next(e for e in circuit if e['type'] == 'source_component' and e['name'] == 'ANT1')
    if antenna['supplier_part_numbers']['jlcpcb'] != ['C22423581']:
        raise ValueError('Antenna review requires the selected exact supplier part')
    pcb = next(e for e in circuit if e['type'] == 'pcb_component' and e['source_component_id'] == antenna['source_component_id'])
    bands = [e for e in circuit if e['type'] == 'pcb_keepout' and e.get('excluded_pcb_component_ids') == [pcb['pcb_component_id']]]
    if len(bands) != 1:
        raise ValueError('Require one actual antenna exclusion')
    band = bands[0]
    if band['shape'] != 'rect' or set(band['layers']) != {'top', 'bottom'}:
        raise ValueError('Antenna exclusion must cover both copper layers')
    x, y = band['center']['x'], band['center']['y']
    exclusion = box(x-band['width']/2, y-band['height']/2, x+band['width']/2, y+band['height']/2)
    if any(abs(a-b) > 1e-6 for a,b in zip(exclusion.bounds, (12.25,21,18.75,28))):
        raise ValueError('Unexpected R9 antenna exclusion geometry')
    return pcb, exclusion


def rf_intrusions(params):
    circuit = params['circuit']
    pcb, exclusion = antenna_exclusion(circuit)
    rf = next(e for e in circuit if e['type']=='source_net' and e['name']=='RF_50')['subcircuit_connectivity_map_key']
    allowed_pads = {e['pcb_smtpad_id'] for e in circuit if e['type']=='pcb_smtpad' and e['pcb_component_id']==pcb['pcb_component_id']}
    if len(allowed_pads) != 2:
        raise ValueError('Require both actual antenna terminal lands')
    failures = []
    for copper in params['copper']:
        if copper.geometry.intersection(exclusion).area <= params['epsilon_mm']**2:
            continue
        if copper.id in allowed_pads and copper.kind=='smt' and copper.layer=='top':
            continue
        if copper.kind=='track' and copper.layer=='top' and copper.net==rf:
            continue
        failures.append(copper.id+':'+copper.layer)
    return failures