hrithik18k/open-ring-flex

A flexible wearable PCB integrating inductive battery charging, BLE control with SPI flash and 32 MHz crystal, SAM L10 capacitive touch/wear sensing, haptic MOSFET drive with a large capacitor bank, RF antenna matching, and SWD programming/test connectors.

Version
1.0.4
License
CC-BY-NC-SA-4.0
Stars
0

scripts/order-native-routes.py

import json,math,collections
from pathlib import Path
raw=json.loads(Path('source-data/native-routed-paths.json').read_text());a=json.loads(Path('dist/index/circuit.json').read_text())
src={x['source_component_id']:x['name'] for x in a if x['type'].startswith('source_') and 'source_component_id' in x and 'name' in x};sp={x['source_port_id']:x for x in a if x['type']=='source_port'}
ports={x['pcb_port_id']:x for x in a if x['type']=='pcb_port'}
sel={i:'.'+src[sp[p['source_port_id']]['source_component_id']]+' > .'+sp[p['source_port_id']]['name'] for i,p in ports.items()}
selnet={pin:n['name'] for n in json.loads(Path('source-data/adapted-netlist.json').read_text()) for pin in n['pins']}
# Physical aliases normalize to the canonical source-port pin number for net lookup.
def net(i):
 p=sp[ports[i]['source_port_id']];ref=src[p['source_component_id']];possible=['.'+ref+' > .'+p['name'],'.'+ref+' > .pin'+str(p.get('pin_number',''))]+['.'+ref+' > .'+h for h in p.get('port_hints',[])]
 return next((selnet[x] for x in possible if x in selnet),None)
portnet={i:net(i) for i in ports}
groups=collections.defaultdict(list);distances=[]
for t in raw:
 endpoints=[]
 for q in [t['route'][0],t['route'][-1]]:
  i=min(ports,key=lambda i:math.hypot(ports[i]['x']-q['x'],ports[i]['y']-q['y']))
  d=math.hypot(ports[i]['x']-q['x'],ports[i]['y']-q['y']);distances.append(d)
  assert d<.1,(t['connection'],d,q)
  endpoints.append(i)
 u,v=endpoints
 assert portnet[u] and portnet[u]==portnet[v],(sel[u],portnet[u],sel[v],portnet[v])
 route=t['route']
 # Extend clipped endpoints to pad center, preserving the native route.
 for i,atStart in [(u,True),(v,False)]:
  point={'route_type':'wire','x':ports[i]['x'],'y':ports[i]['y'],'width':.075,'layer':'top'}
  q=route[0 if atStart else -1]
  if math.hypot(point['x']-q['x'],point['y']-q['y'])>1e-5:
   if atStart:route.insert(0,point)
   else:route.append(point)
 groups[portnet[u]].append((u,v,route))
ordered=[]
for name,edges in groups.items():
 while edges:
  degree=collections.Counter(p for u,v,r in edges for p in [u,v])
  leaf=next((p for p,n in degree.items() if n==1),None)
  assert leaf,(name,'cycle',len(edges))
  j=next(j for j,(u,v,r) in enumerate(edges) if leaf in [u,v]);u,v,route=edges.pop(j)
  if u!=leaf:
   route=list(reversed(route))
   for q in route:
    if q['route_type']=='via':q['from_layer'],q['to_layer']=q['to_layer'],q['from_layer']
  ordered.append({'connection':sel[leaf],'route':route})
print('Ordered',len(ordered),'routes in',len(groups),'nets; max pad-center extension',max(distances))
Path('source-data/native-routed-paths.json').write_text(json.dumps(ordered,indent=2))
p=Path('scripts/generate-board.py');s=p.read_text().replace('nets.filter(n => !["XTAL32N","XTAL32P"].includes(n.name)).map(n => <net name={n.name} routingPhaseIndex={n.name === "VDDOUT" ? 2 : 0}', 'nets.map(n => <Fragment key={n.name}><net name={n.name} routingPhaseIndex={n.name === "VDDOUT" ? 2 : ["XTAL32N","XTAL32P"].includes(n.name) ? 1 : 0}').replace(': 0} />)}',': 0} /></Fragment>)}')
p.write_text(s)