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/finish-mic-data.py

#!/usr/bin/env python3
"""Finish the two microphone data branches left by FreeRouting.

Route on the back copper with a conservative grid search around existing
copper and imported pad bounds; original component geometry is unchanged.
The independent KiCad DRC is the acceptance check for the resulting copper.
"""
import sys
sys.path.insert(0,"/workspace/.cache/native-python")
import heapq
import math
import pathlib
import pcbnew
from shapely.geometry import Point, LineString, box
from shapely.ops import unary_union
from shapely.prepared import prep

root = pathlib.Path(__file__).resolve().parents[1]
path = root / "dist/MuseVoiceRemote-library.kicad_pcb"
board = pcbnew.LoadBoard(str(path))
net = next(n for n in board.GetNetsByNetcode().values() if n.GetNetname()=="MIC_SD")
def xy(point): return pcbnew.ToMM(point.x),pcbnew.ToMM(point.y)
def vector(point): return pcbnew.VECTOR2I(pcbnew.FromMM(point[0]),pcbnew.FromMM(point[1]))
obstacles = []
layer = pcbnew.B_Cu
clearance = 0.175  # 0.15 mm required, plus margin for grid interpolation.
width = 0.15
for track in board.GetTracks():
    if track.GetNetname()=="MIC_SD": continue
    if isinstance(track,pcbnew.PCB_VIA):
        obstacles.append(Point(xy(track.GetPosition())).buffer(pcbnew.ToMM(track.GetWidth(pcbnew.B_Cu))/2+clearance+width/2))
    elif track.GetLayer()==layer:
        obstacles.append(LineString([xy(track.GetStart()),xy(track.GetEnd())]).buffer(pcbnew.ToMM(track.GetWidth())/2+clearance+width/2))
for footprint in board.GetFootprints():
    for pad in footprint.Pads():
        if pad.GetNetname()=="MIC_SD" or not pad.GetLayerSet().Contains(layer): continue
        bounds = pad.GetBoundingBox()
        lo=xy(bounds.GetOrigin());hi=xy(bounds.GetEnd())
        obstacles.append(box(*lo,*hi).buffer(clearance+width/2))
occupied=prep(unary_union(obstacles))
step=0.125
def snap(point): return round(point[0]/step),round(point[1]/step)
def point(node): return node[0]*step,node[1]*step
def search(start,end):
    start_node,end_node=snap(start),snap(end)
    todo=[(0,0,start_node)];cost={start_node:0};previous={}
    directions=[(1,0),(-1,0),(0,1),(0,-1),(1,1),(1,-1),(-1,1),(-1,-1)]
    while todo:
        _,g,node=heapq.heappop(todo)
        if g>cost[node]:continue
        if node==end_node:
            path=[node]
            while path[-1] in previous:path.append(previous[path[-1]])
            path.reverse();return [start]+[point(n) for n in path]+[end]
        for dx,dy in directions:
            nxt=(node[0]+dx,node[1]+dy);pos=point(nxt)
            if abs(pos[0])>32.3 or abs(pos[1])>23.3:continue
            if occupied.intersects(LineString([point(node),pos])):continue
            new=g+math.hypot(dx,dy)
            if new>=cost.get(nxt,float("inf")):continue
            cost[nxt]=new;previous[nxt]=node
            h=math.hypot(nxt[0]-end_node[0],nxt[1]-end_node[1])
            heapq.heappush(todo,(new+h*1.05,new,nxt))
    raise RuntimeError("No clear back-copper route for microphone data")
def add_track(a,b,layer):
    if math.dist(a,b)<0.0001:return
    track=pcbnew.PCB_TRACK(board);track.SetStart(vector(a));track.SetEnd(vector(b))
    track.SetLayer(layer);track.SetWidth(pcbnew.FromMM(width));track.SetNet(net);board.Add(track)
for x in [-28,28]:
    landing=(x,-23.1 if x<0 else -23.2)
    via=pcbnew.PCB_VIA(board);via.SetPosition(vector(landing))
    for copper_layer in [pcbnew.F_Cu,pcbnew.In1_Cu,pcbnew.In2_Cu,pcbnew.B_Cu]: via.SetWidth(copper_layer,pcbnew.FromMM(0.6))
    via.SetDrill(pcbnew.FromMM(0.3));via.SetViaType(pcbnew.VIATYPE_THROUGH)
    via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNet(net);board.Add(via)
    add_track((x,-22.864),landing,pcbnew.F_Cu)
    route=search(landing,(-15.7367,-19.0))
    for a,b in zip(route,route[1:]):add_track(a,b,layer)
    print(f"Finished MIC_SD branch at X={x}: {len(route)-1} grid segments")
pcbnew.SaveBoard(str(path),board)