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/rehome-fanout.py
#!/usr/bin/env python3
"""Move a selected fanout into a shared top/bottom routing region."""
import argparse,pathlib,json,os,math
helper=pathlib.Path(__file__).with_name('finish-unrouted.py');api={'__file__':str(helper)};exec(helper.read_text().split('\n# A FreeRouting pattern')[0],api)
from shapely.geometry import Point,box
from shapely.ops import nearest_points
from precise_path import visible_path
p=argparse.ArgumentParser();p.add_argument('--connect-target',action='store_true');p.add_argument('reference');p.add_argument('pin');p.add_argument('--old-via',nargs=2,type=float,required=True);p.add_argument('--target',nargs=2,type=float,required=True);a=p.parse_args();pc=api['pcbnew'];path=api['path'];before=path.read_bytes();old=api['check']();b=pc.LoadBoard(str(path));fs=list(b.GetFootprints());f=next(f for f in fs if f.GetReference()==a.reference);ps=list(f.Pads());pad=next(p for p in ps if p.GetNumber()==a.pin);net=pad.GetNetname();start=api['xy'](pad.GetPosition());ts=list(b.GetTracks())
via=next(t for t in ts if isinstance(t,pc.PCB_VIA) and t.GetNetname()==net and math.dist(api['xy'](t.GetPosition()),a.old_via)<.00001)
remove=[via]
for t in ts:
if isinstance(t,pc.PCB_VIA) or t.GetNetname()!=net or t.GetLayer()!=pc.F_Cu:continue
ends=[api['xy'](t.GetStart()),api['xy'](t.GetEnd())]
if min(math.dist(e,a.old_via) for e in ends)<.00001:remove.append(t)
for t in remove:b.Remove(t)
o,v=api['obstacles'](b,net);outer=box(-32.25,-23.25,32.25,23.25);front=outer.difference(o[0].context);back=outer.difference(o[3].context)
fronts=list(front.geoms) if front.geom_type=='MultiPolygon' else [front];backs=list(back.geoms) if back.geom_type=='MultiPolygon' else [back];region=next(r for r in fronts if r.covers(Point(start)));target=next(r for r in backs if r.covers(Point(a.target)));legal=region.intersection(target).intersection(outer.difference(v.context).buffer(-.002))
if legal.is_empty:raise RuntimeError('No shared top/bottom fanout position; original CAD file retained')
point=nearest_points(Point(start),legal)[1];end=(point.x,point.y);points=visible_path(region,start,end)
for x,y in zip(points,points[1:]):
t=pc.PCB_TRACK(b);t.SetStart(api['vector'](x));t.SetEnd(api['vector'](y));t.SetLayer(pc.F_Cu);t.SetWidth(pc.FromMM(.15));t.SetNetCode(pad.GetNetCode());t.SetLocked(True);b.Add(t)
new=pc.PCB_VIA(b);new.SetPosition(api['vector'](end));new.SetViaType(pc.VIATYPE_THROUGH);new.SetLayerPair(pc.F_Cu,pc.B_Cu)
for layer in api['layers']:new.SetWidth(layer,pc.FromMM(.45))
new.SetDrill(pc.FromMM(.3));new.SetNetCode(pad.GetNetCode());new.SetLocked(True);b.Add(new)
if a.connect_target:
back_points=visible_path(target,end,tuple(a.target))
for x,y in zip(back_points,back_points[1:]):
if math.dist(x,y)<.000001:continue
t=pc.PCB_TRACK(b);t.SetStart(api['vector'](x));t.SetEnd(api['vector'](y));t.SetLayer(pc.B_Cu);t.SetWidth(pc.FromMM(.15));t.SetNetCode(pad.GetNetCode());b.Add(t)
pc.SaveBoard(str(path),b);after=api['check']()
if len(after['unconnected_items'])>len(old['unconnected_items']) or any(v['type'] not in ['track_dangling','via_dangling'] for v in after['violations']):path.write_bytes(before);raise RuntimeError('Native check rejected rehomed fanout')
print(f'Rehomed {net}: via {end}, target {a.target}; native geometry checked',flush=True)
(api['root']/f'docs/fanout-rehome-{net}.json').write_text(json.dumps(dict(reference=a.reference,pin=a.pin,net=net,old_via_native_mm=a.old_via,via_native_mm=end,target_native_mm=a.target,front_points_native_mm=points),indent=2)+'\n');os._exit(0)