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/prune-route-stubs.py
#!/usr/bin/env python3
"""Prune reported dead ends; isolate each KiCad mutation from SWIG finalization."""
import os,sys,subprocess,json
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)
path=api['path'];pcbnew=api['pcbnew']
allow_unconnected='--allow-unconnected' in sys.argv
if '--step' not in sys.argv:
for iteration in range(256):
report=api['check']()
if report['unconnected_items'] and not allow_unconnected:raise RuntimeError('Connect all nets before pruning')
if not report['violations']:break
subprocess.run([sys.executable,__file__,'--step']+(['--allow-unconnected'] if allow_unconnected else []),env=api['env'],check=True)
else:raise RuntimeError('Too many routing stubs')
remaining=len(report['unconnected_items'])
print(f'Pruned redundant copper: zero geometry violations; {remaining} unconnected pairs remain.',flush=True)
os._exit(0)
report=api['check']()
if any(v['type'] not in {'via_dangling','track_dangling'} for v in report['violations']):
raise RuntimeError('A geometry violation needs repair')
before=path.read_bytes();board=pcbnew.LoadBoard(str(path))
by_id={t.m_Uuid.AsString():t for t in board.GetTracks()}
# Prune reported dead-end tracks together. Dense A* paths contain many short
# segments; checking one segment at a time needlessly repeats zone filling.
# Vias retain their existing bridge treatment in isolated steps.
tracks={v['items'][0]['uuid'] for v in report['violations'] if v['type']=='track_dangling'}
if tracks:
for identifier in tracks:board.Remove(by_id[identifier])
removed=len(tracks)
else:
# Gather before mutation: removing one item can invalidate borrowed SWIG
# connectivity wrappers. All flagged vias have only one copper-layer role.
identifiers=[v['items'][0]['uuid'] for v in report['violations'] if v['type']=='via_dangling']
plans=[]
for identifier in identifiers:
item=by_id[identifier];attached=list(board.GetConnectivity().GetConnectedTracks(item))
if len({t.GetLayer() for t in attached})!=1:raise RuntimeError('Unexpected via-layer connectivity')
centre=api['xy'](item.GetPosition());bridges=[]
for track in attached:
a,z=api['xy'](track.GetStart()),api['xy'](track.GetEnd());dx,dy=z[0]-a[0],z[1]-a[1]
fraction=max(0,min(1,((centre[0]-a[0])*dx+(centre[1]-a[1])*dy)/(dx*dx+dy*dy)))
endpoint=a[0]+fraction*dx,a[1]+fraction*dy
if api['math'].dist(centre,endpoint)<.000001:continue
bridges.append((centre,endpoint,track.GetLayer(),item.GetNetCode()))
plans.append((item,bridges))
for item,bridges in plans:
for centre,endpoint,layer,netcode in bridges:
bridge=pcbnew.PCB_TRACK(board);bridge.SetStart(api['vector'](centre));bridge.SetEnd(api['vector'](endpoint))
bridge.SetWidth(pcbnew.FromMM(.15));bridge.SetLayer(layer);bridge.SetNetCode(netcode);board.Add(bridge)
board.Remove(item)
removed=len(plans)
pcbnew.SaveBoard(str(path),board)
after=api['check']()
if len(after['unconnected_items'])>len(report['unconnected_items']) or any(v['type'] not in {'via_dangling','track_dangling'} for v in after['violations']):
path.write_bytes(before);raise RuntimeError('Pruning would break connectivity/clearance; restored CAD')
print(f"Removed {removed} reported dead ends; {len(after['violations'])} checks remain",flush=True)
os._exit(0)