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/joint-charger-fanout.py
#!/usr/bin/env python3
"""Plan the two adjacent charger analog fanouts together before closing nets."""
from pathlib import Path
helper=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"]
from shapely.geometry import Point
path=api["path"];before=path.read_bytes();board=pcbnew.LoadBoard(str(path))
connect=board.GetConnectivity()
charger=next(f for f in board.GetFootprints() if f.GetReference()=="U_CHARGE")
iterm=next(p for p in charger.Pads() if p.GetNumber()=="15")
tracks={};todo=list(connect.GetConnectedTracks(iterm))
while todo:
t=todo.pop()
if isinstance(t,pcbnew.PCB_VIA) or t.GetLayer()!=pcbnew.F_Cu or t.GetNetname()!="CHG_ITERM" or t.m_Uuid.AsString() in tracks:continue
tracks[t.m_Uuid.AsString()]=t;todo.extend(connect.GetConnectedTracks(t))
vias=[v for v in board.GetTracks() if isinstance(v,pcbnew.PCB_VIA) and v.GetNetname()=="CHG_ITERM" and
any(t.m_Uuid.AsString() in tracks for t in connect.GetConnectedTracks(v))]
if len(vias)!=1:raise RuntimeError("Inspect the charger fanout before using this specific finish")
for t in tracks.values():board.Remove(t)
board.Remove(vias[0]);board.GetConnectivity().Build(board)
for name,number in [("CHG_TMR","14"),("CHG_ITERM","15")]:
pad=next(p for p in charger.Pads() if p.GetNumber()==number)
blocked=api["obstacles"](board,name)[1]
candidates=[(x/8,y/8) for x in range(48,85) for y in range(88,122)
if (name!="CHG_TMR" or x/8>=8.625) and (name!="CHG_ITERM" or x/8<=8.375)
and not blocked.intersects(Point(x/8,y/8))]
candidates.sort(key=lambda p:(p[0]-pcbnew.ToMM(pad.GetPosition().x))**2+(p[1]-pcbnew.ToMM(pad.GetPosition().y))**2)
completed=False
for point in candidates[:80]:
via=pcbnew.PCB_VIA(board);via.SetPosition(api["vector"](point))
for layer in api["layers"]:via.SetWidth(layer,pcbnew.FromMM(.6))
via.SetDrill(pcbnew.FromMM(.3));via.SetViaType(pcbnew.VIATYPE_THROUGH)
via.SetLayerPair(pcbnew.F_Cu,pcbnew.B_Cu);via.SetNetCode(pad.GetNetCode());board.Add(via)
board.GetConnectivity().Build(board)
api["step"]=.05
try:
result=api["route"](board,pad,via,front_only=True,
min_x=8.625 if name=="CHG_TMR" else None,
max_x=8.375 if name=="CHG_ITERM" else None)
print(f"Fanout {name} to {point}: {result}",flush=True);completed=True;break
except RuntimeError:
board.Remove(via);board.GetConnectivity().Build(board)
if not completed:raise RuntimeError(f"No accessible escape via for {name}")
pcbnew.SaveBoard(str(path),board)
report=api["check"]()
if any(v["type"] not in {"via_dangling","track_dangling"} for v in report["violations"]):
path.write_bytes(before);raise RuntimeError("Joint fanout violates native geometry rules; restored CAD")
print("Joint fanouts accepted by independent native geometry checks.")
# KiCad 9's SWIG wrappers can double-free detached track objects during Python
# shutdown. CAD is saved and independently checked above; end without invoking
# that broken finalization path in this standalone routing utility.
import os,sys
sys.stdout.flush()
os._exit(0)