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)