AnasSarkiz/muse-voice-remote
Muse Voice Remote — bare ESP32-S3R8, 155 TOP JLCPCB parts with real supplier CAD, native 3D assembly and GLB exports. Revised 17 mm case allocation; 0.30 mm via drill /0.45 mm pad. Checked prototype PCB; mechanical, RF and MUSE qualification pending.
- Version
- 1.3.2
- License
- unset
- Stars
- 0
scripts/audit-via-sizes.py
#!/usr/bin/env python3
"""Check actual native routing and MCU thermal vias against fabrication minima."""
import argparse,json,pathlib,hashlib,os
import pcbnew
p=argparse.ArgumentParser();p.add_argument('board');p.add_argument('--output',required=True);a=p.parse_args();path=pathlib.Path(a.board);board=pcbnew.LoadBoard(str(path));tracks=list(board.GetTracks());footprints=list(board.GetFootprints());layers=[pcbnew.F_Cu,pcbnew.In1_Cu,pcbnew.In2_Cu,pcbnew.B_Cu];records=[];errors=[]
for v in tracks:
if not isinstance(v,pcbnew.PCB_VIA):continue
drill=pcbnew.ToMM(v.GetDrillValue());sizes=[pcbnew.ToMM(v.GetWidth(l)) for l in layers];annulus=(min(sizes)-drill)/2
item={'uuid':v.m_Uuid.AsString(),'net':v.GetNetname(),'at_native_mm':[pcbnew.ToMM(v.GetPosition().x),pcbnew.ToMM(v.GetPosition().y)],'drill_mm':drill,'pad_diameters_mm':sizes,'minimum_radial_annulus_mm':annulus};records.append(item)
if drill<.3-1e-7 or min(sizes)<.45-1e-7 or annulus<.075-1e-7:errors.append(item)
thermal=[]
for f in footprints:
if f.GetReference()!='U1':continue
for pad in list(f.Pads()):
if pad.GetAttribute()!=pcbnew.PAD_ATTRIB_PTH or pad.GetNumber()!='57':continue
drill=pcbnew.ToMM(pad.GetDrillSize().x);diameter=min(pcbnew.ToMM(pad.GetSize().x),pcbnew.ToMM(pad.GetSize().y));thermal.append({'drill_mm':drill,'pad_diameter_mm':diameter})
if drill<.3-1e-7 or diameter<.45-1e-7:errors.append({'reference':'U1','pad':'57','drill_mm':drill,'pad_diameter_mm':diameter})
if len(thermal)!=9:errors.append({'missing_imported_mcu_thermal_holes':len(thermal)})
report={'board':a.board,'board_sha256':hashlib.sha256(path.read_bytes()).hexdigest(),'required_minimum_drill_mm':.3,'required_minimum_pad_mm':.45,'required_minimum_radial_annulus_mm':.075,'routing_via_count':len(records),'actual_minimum_drill_mm':min(v['drill_mm'] for v in records),'actual_minimum_pad_mm':min(min(v['pad_diameters_mm']) for v in records),'actual_minimum_radial_annulus_mm':min(v['minimum_radial_annulus_mm'] for v in records),'imported_mcu_thermal_holes':thermal,'vias':records,'errors':errors,'passed':not errors};pathlib.Path(a.output).write_text(json.dumps(report,indent=2)+'\n');print(f"Via size audit: {len(records)} routing/added thermal vias and {len(thermal)} original MCU thermal holes; {len(errors)} errors",flush=True);os._exit(1 if errors else 0)