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-in-pad.py
#!/usr/bin/env python3
"""Reject unplanned routing drills in imported top-side solderable lands."""
import argparse,json,pathlib,sys,os
sys.path.insert(0,'/workspace/.cache/native-python')
import pcbnew
from shapely.geometry import Point,Polygon
from shapely.ops import unary_union
p=argparse.ArgumentParser();p.add_argument('board');p.add_argument('--output',required=True);a=p.parse_args()
root=pathlib.Path(__file__).resolve().parents[1]
b=pcbnew.LoadBoard(a.board);footprints=list(b.GetFootprints());pads=[]
for f in footprints:
for pad in list(f.Pads()):
if pad.GetAttribute()!=pcbnew.PAD_ATTRIB_SMD:continue
effective=pad.GetEffectivePolygon(pcbnew.F_Cu);polygons=[]
for i in range(effective.OutlineCount()):
chain=effective.COutline(i)
exterior=[(pcbnew.ToMM(chain.CPoint(j).x),pcbnew.ToMM(chain.CPoint(j).y)) for j in range(chain.PointCount())]
interiors=[]
for h in range(effective.HoleCount(i)):
inner=effective.CHole(i,h)
interiors.append([(pcbnew.ToMM(inner.CPoint(j).x),pcbnew.ToMM(inner.CPoint(j).y)) for j in range(inner.PointCount())])
polygons.append(Polygon(exterior,interiors))
pads.append((f.GetReference(),pad.GetNumber(),pad.GetNetname(),unary_union(polygons)))
planned=json.loads((root/'docs/power-plane-seeding.json').read_text())['thermal_vias']
violations=[];accepted=[]
for via in list(b.GetTracks()):
if not isinstance(via,pcbnew.PCB_VIA):continue
at=via.GetPosition();x=pcbnew.ToMM(at.x);y=pcbnew.ToMM(at.y);radius=pcbnew.ToMM(via.GetDrillValue())/2
drill=Point(x,y).buffer(radius)
overlaps=[dict(reference=ref,pad=number,net=net) for ref,number,net,land in pads if land.intersects(drill)]
if not overlaps:continue
thermal=next((v for v in planned if abs(v['at_native_mm'][0]-x)<=.0001 and abs(v['at_native_mm'][1]-y)<=.0001 and abs(v['drill_mm']-2*radius)<.00001),None)
item=dict(uuid=via.m_Uuid.AsString(),at_native_mm=[x,y],net=via.GetNetname(),drill_mm=2*radius,overlapping_lands=overlaps)
if thermal and via.GetNetname()=='GND' and all(o['reference']==thermal['reference'] and o['pad']=={'U_CHARGE':'17','U_AMP':'17','U_REG':'11'}[thermal['reference']] for o in overlaps):accepted.append(item)
else:violations.append(item)
report=dict(board=str(a.board),planned_added_thermal_vias=len(planned),accepted_thermal_vias=accepted,unplanned_via_in_pad=violations,passed=not violations)
pathlib.Path(a.output).write_text(json.dumps(report,indent=2)+'\n')
print(f'Via-in-pad audit: {len(accepted)} planned power-IC thermal vias; {len(violations)} unplanned overlaps',flush=True)
os._exit(1 if violations else 0)