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/review-r8-trace-widths.py
"""Inventory every exported track width against the R8 project minimum.
Power-load-path current and voltage budgets are checked separately by
review-r8-power.py; a nominal net width alone is not a current rating.
"""
import argparse
import hashlib
import importlib.util
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SPEC = importlib.util.spec_from_file_location('width_geometry', ROOT/'scripts/manufacturing-audit.py')
GEOMETRY = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = GEOMETRY
SPEC.loader.exec_module(GEOMETRY)
def review(output):
source = ROOT/'dist/index/circuit.json'
circuit = json.loads(source.read_text())
if any(e['type'].endswith('_error') for e in circuit):
raise ValueError('Trace review requires an error-free routed circuit')
copper, _, widths = GEOMETRY.make_features(circuit, 'gerber')
tracks = {e.id: e for e in copper if e.kind == 'track'}
nets = {e['subcircuit_connectivity_map_key']: e for e in circuit if e['type'] == 'source_net'}
rows = []
point_joins = []
for trace in (e for e in circuit if e['type'] == 'pcb_trace'):
trace_id = trace['pcb_trace_id']
if trace_id not in tracks:
if len(trace['route']) == 1 and trace['route'][0]['route_type'] == 'wire':
point_joins.append(trace_id)
continue
raise ValueError(f'Unattributed nonzero track: {trace_id}')
measured = sorted(set(width for track_id, width in widths if track_id == trace_id))
if not measured:
raise ValueError(f'No measured track widths: {trace_id}')
net = nets[tracks[trace_id].net]
rows.append({'trace_id': trace_id, 'net': net['name'], 'actual_widths_mm': measured,
'project_minimum_mm': 0.15, 'passed': min(measured) >= 0.15-1e-9})
if not rows:
raise ValueError('No routed tracks to review')
report = {'circuit_sha256': hashlib.sha256(source.read_bytes()).hexdigest(),
'tracks': rows, 'zero_length_joins': point_joins,
'failures': [row for row in rows if not row['passed']],
'power_review': 'power-path-measurements.json',
'limitations': ['All routed track widths measured using qualified Gerber aperture semantics.',
'Power necks need the separate physical-load-path current and voltage checks.',
'Nominal 35um copper and IPC-2221 estimates do not replace loaded hardware tests.']}
output.write_text(json.dumps(report, indent=2)+'\n')
print(f'{len(rows)} physical tracks reviewed; {len(report["failures"])} undersized tracks')
if report['failures']:
raise ValueError('Actual track width is below the project requirement')
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--output', type=Path, required=True)
review(parser.parse_args().output)