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/repair-via-drill-clearance.py
#!/usr/bin/env python3
"""Move routing vias just clear of imported solder lands after drill enlargement.
Uses original pad polygons and all-layer copper obstacles; supplier pads stay
unchanged. Independent KiCad DRC/connectivity and a drill-in-pad audit gate the
result. Unavoidable filled/capped fanouts are reported, never silently ignored.
"""
import pathlib,json,os,subprocess,math
h=pathlib.Path(__file__).with_name('finish-unrouted.py');api={'__file__':str(h)}
source=h.read_text().split('\n# A FreeRouting pattern')[0].replace('for t in board.GetTracks():\n if isinstance(t,pcbnew.PCB_VIA):','for t in board.GetTracks():\n if t.m_Uuid.AsString() in ignored_via_ids:continue\n if isinstance(t,pcbnew.PCB_VIA):')
exec(source,api);api['ignored_via_ids']=set();api['via_diameter']=.45;api['via_drill']=.3
from shapely.geometry import Point,LineString,Polygon,box
from shapely.ops import nearest_points,unary_union
pc=api['pcbnew'];path=api['path'];before=path.read_bytes();old=api['check']()
if old['violations'] or old['unconnected_items']:raise RuntimeError('Require clean 0.30 mm drill baseline')
report=api['root']/'dist/via-030-in-pad.json';subprocess.run(['/usr/bin/python3',str(api['root']/'scripts/audit-via-in-pad.py'),str(path),'--output',str(report)],stdout=subprocess.DEVNULL);audit=json.loads(report.read_text());bad=audit['unplanned_via_in_pad'];b=pc.LoadBoard(str(path));ts=list(b.GetTracks());fs=list(b.GetFootprints());ps=[p for f in fs for p in f.Pads()];byid={t.m_Uuid.AsString():t for t in ts};conn=b.GetConnectivity();plans=[]
for r in bad:
via=byid[r['uuid']];neighbors=list(conn.GetConnectedTracks(via));pads=list(conn.GetConnectedPads(via));plans.append((r,via,neighbors,pads))
records=[];unresolved=[]
for r,via,neighbors,pads in plans:
oldpos=api['xy'](via.GetPosition());api['ignored_via_ids']={r['uuid']};o,v=api['obstacles'](b,via.GetNetname());legal=box(oldpos[0]-.5,oldpos[1]-.5,oldpos[0]+.5,oldpos[1]+.5).difference(v.context).buffer(-.001)
# Require copper contact with every original attached trace, without changing
# its original path or width. Clearance is checked on every copper layer.
for t in neighbors:
if isinstance(t,pc.PCB_VIA):continue
shape=LineString([api['xy'](t.GetStart()),api['xy'](t.GetEnd())]);legal=legal.intersection(shape.buffer(pc.ToMM(t.GetWidth())/2+pc.ToMM(via.GetWidth(pc.F_Cu))/2-.002))
for p in pads:
polygon=p.GetEffectivePolygon(pc.F_Cu);polys=[]
for i in range(polygon.OutlineCount()):
c=polygon.COutline(i);polys.append(Polygon([api['xy'](c.CPoint(j)) for j in range(c.PointCount())]))
if polys:legal=legal.intersection(unary_union(polys).buffer(pc.ToMM(via.GetWidth(pc.F_Cu))/2-.002))
if legal.is_empty:unresolved.append(r);print('No local legal move:',r['uuid'],r['net'],oldpos,flush=True);continue
p=nearest_points(Point(oldpos),legal)[1];newpos=(p.x,p.y);via.SetPosition(api['vector'](newpos));records.append(dict(uuid=r['uuid'],net=via.GetNetname(),old_native_mm=oldpos,new_native_mm=newpos,shift_mm=math.dist(oldpos,newpos),drill_mm=.3,pad_mm=pc.ToMM(via.GetWidth(pc.F_Cu))))
pc.SaveBoard(str(path),b);new=api['check']()
if new['violations'] or new['unconnected_items']:
path.write_bytes(before);raise RuntimeError('Native check rejected relocated vias; restored original')
subprocess.run(['/usr/bin/python3',str(api['root']/'scripts/audit-via-in-pad.py'),str(path),'--output',str(report)],stdout=subprocess.DEVNULL);after=json.loads(report.read_text());(api['root']/'docs/via-drill-update.json').write_text(json.dumps(dict(minimum_drill_mm=.3,minimum_pad_mm=.45,minimum_radial_annulus_mm=.075,relocated_vias=records,unresolved_overlaps=after['unplanned_via_in_pad'],native_drc_violations=0,unconnected=0),indent=2)+'\n');print('Relocated',len(records),'vias;',len(after['unplanned_via_in_pad']),'overlaps remain; native DRC clean',flush=True);os._exit(0)