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)