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/seed-power-planes.py

#!/usr/bin/env python3
"""Add board power planes and short off-pad supply/return connections.

Electronic pads and CAD stay exactly as imported. New exposed-pad thermal vias
are board features and require filled/capped via-in-pad fabrication.
"""
import pathlib, json, math, os
helper=pathlib.Path(__file__).with_name('finish-unrouted.py')
api={'__file__':str(helper)}
exec(helper.read_text().split('\n# A FreeRouting pattern')[0],api)
pcbnew=api['pcbnew'];path=api['path'];board=pcbnew.LoadBoard(str(path));api['width']=.2
from shapely.geometry import Point,LineString
footprints=list(board.GetFootprints());byref={f.GetReference():f for f in footprints}
netcodes={n.GetNetname():n.GetNetCode() for n in board.GetNetsByNetcode().values()}
def zone(net,layer,points,priority=0):
 z=pcbnew.ZONE(board);z.SetLayer(layer);z.SetNetCode(netcodes[net]);z.SetLocalClearance(pcbnew.FromMM(.15));z.SetMinThickness(pcbnew.FromMM(.15));z.SetPadConnection(pcbnew.ZONE_CONNECTION_FULL);z.SetAssignedPriority(priority)
 o=z.Outline();o.NewOutline()
 for x,y in points:o.Append(pcbnew.FromMM(x),pcbnew.FromMM(y))
 board.Add(z)
rectangle=[(-32.4,-23.4),(32.4,-23.4),(32.4,23.4),(-32.4,23.4)]
zone('V3_3',pcbnew.In2_Cu,rectangle)
zone('SYS',pcbnew.B_Cu,rectangle)
# Ground on Layer 3 under RF/clock supplements the complete Layer 2 ground.
for x0,y0,x1,y1 in [(-15,-7,0,-3),(-4,-12,4,-5)]:zone('GND',pcbnew.In2_Cu,[(x0,y0),(x1,y0),(x1,y1),(x0,y1)],1)
def via(at,net,diameter=.45,drill=.3):
 v=pcbnew.PCB_VIA(board);v.SetPosition(api['vector'](at));v.SetViaType(pcbnew.VIATYPE_THROUGH);v.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu)
 for l in api['layers']:v.SetWidth(l,pcbnew.FromMM(diameter))
 v.SetDrill(pcbnew.FromMM(drill));v.SetNetCode(netcodes[net]);v.SetLocked(True);board.Add(v)
thermal=[]
for ref,offsets in [('U_CHARGE',[(-.35,-.35),(.35,-.35),(-.35,.35),(.35,.35)]),('U_AMP',[(-.35,-.35),(.35,-.35),(-.35,.35),(.35,.35)]),('U_REG',[(-.35,0),(.35,0)])]:
 f=byref[ref];p=next(p for p in f.Pads() if p.GetNumber()==('11' if ref=='U_REG' else '17') and p.GetNetname()=='GND');x,y=api['xy'](p.GetPosition())
 for dx,dy in offsets:
  at=(x+dx,y+dy);via(at,'GND',.6,.3);thermal.append(dict(reference=ref,at_native_mm=at,drill_mm=.3,diameter_mm=.6,process='resin-filled and copper-capped'))
results=[]
# Local capacitor connections provide short supply/return paths. IC supply pins
# and ground lands also get direct plane access where original geometry permits.
for f in footprints:
 ref=f.GetReference()
 for p in f.Pads():
  net=p.GetNetname()
  if net not in ['GND','V3_3','SYS'] or p.GetAttribute()!=pcbnew.PAD_ATTRIB_SMD:continue
  if not (ref.startswith('C_') or ref.startswith('U_') or ref in ['MIC1','MIC2','J_USB','J_SPK','J_MOTOR','J_RF']):continue
  if ref=='U1' or p.GetNumber() in ['11','17'] and ref in ['U_REG','U_AMP','U_CHARGE']:continue
  position=api['xy'](p.GetPosition());chosen=None
  for width in [.25,.2,.15]:
   api['width']=width;occupied,drills=api['obstacles'](board,net)
   for radius in [.5,.625,.75,.875,1,1.25,1.5,1.75,2,2.5]:
    for angle in range(0,360,15):
     at=(position[0]+radius*math.cos(math.radians(angle)),position[1]+radius*math.sin(math.radians(angle)))
     if abs(at[0])>32.25 or abs(at[1])>23.25:continue
     if drills.intersects(Point(at)) or occupied[0].intersects(LineString([position,at])):continue
     chosen=(at,width);break
    if chosen:break
   if chosen:break
  if not chosen:
   results.append(dict(reference=ref,pin=p.GetNumber(),net=net,status='requires general router'));continue
  at,width=chosen;via(at,net)
  t=pcbnew.PCB_TRACK(board);t.SetStart(p.GetPosition());t.SetEnd(api['vector'](at));t.SetLayer(pcbnew.F_Cu);t.SetWidth(pcbnew.FromMM(width));t.SetNetCode(p.GetNetCode());t.SetLocked(True);board.Add(t)
  results.append(dict(reference=ref,pin=p.GetNumber(),net=net,status='local plane connection',length_mm=math.dist(position,at),width_mm=width,via_native_mm=at))
pcbnew.SaveBoard(str(path),board)
(api['root']/'docs/power-plane-seeding.json').write_text(json.dumps(dict(planes=['In1.Cu GND','In2.Cu V3_3 with RF/clock ground areas','B.Cu SYS'],thermal_vias=thermal,connections=results),indent=2)+'\n')
print('Power/ground plane connections:',sum(r['status']=='local plane connection' for r in results),'deferred:',sum(r['status']=='requires general router' for r in results),'new filled/capped thermal vias:',len(thermal),flush=True)
os._exit(0)