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)