pixalynx/nrf9151-gps-tracker
A compact 44×32 mm, 4-layer nRF9151 LTE-M/NB-IoT/GNSS tracker PCB with nPM1300 LiPo/USB-C power, nano-SIM/eSIM switching, nRF7002 Wi‑Fi scanning, accelerometer, antennas, buzzer, LEDs, SOS button, and SWD/debug connectors.
- Version
- 0.1.4
- License
- unset
- Stars
- 0
scripts/widen-power-legs.py
#!/usr/bin/env python3
"""Widen the imported power legs after a Freerouting pass.
The router works at 0.20 mm for the power rails so it can leave 0.30 mm QFN lands; afterwards every power leg is
set to TARGET width, the board is rebuilt, and any leg named in a clearance error is stepped back down until the
build is DRC clean. Usage: python3 scripts/widen-power-legs.py tracker
"""
import json, subprocess, sys, re
board = sys.argv[1] if len(sys.argv) > 1 else 'tracker'
POWER = {'VSYS', 'VBAT', 'VBUS', 'VDD_NRF', 'V3V3', 'V1V8', 'VBUSOUT', 'WIFI_VBAT'}
STEPS = [0.3, 0.25, 0.2]
path = f'routing/{board}-routes.json'
routes = json.load(open(path))
legs = {l['id']: l for l in routes['legs']}
orig = {n: l['width'] for n, l in legs.items()}
for l in legs.values():
if l['net'] in POWER and l['width'] < STEPS[0]: l['width'] = STEPS[0]
def build():
json.dump(routes, open(path, 'w'), indent=1)
subprocess.run(['bunx', 'tsci', 'build', f'{board}.circuit.tsx'], capture_output=True, text=True)
c = json.load(open(f'dist/{board}/circuit.json'))
other = [e['type'] + ': ' + (e.get('message') or '')[:160] for e in c if e['type'].endswith('_error')]
subprocess.run(['python3', 'scripts/check-clearance.py', f'dist/{board}/circuit.json', '--json', f'.tmp/{board}-clearance.json'], capture_output=True, text=True)
findings = json.load(open(f'.tmp/{board}-clearance.json'))
offenders = set()
for d, layer, kind, a, b in findings:
# only legs this script widened can step back (never below the width the router laid them at)
cands = [n for n in (a, b) if n in legs and legs[n]['width'] > orig[n] + 1e-9]
if not cands: other.append(f'clearance {kind} {a} / {b}: {d} mm (not a widened leg)'); continue
# narrow the wider widened leg first; a tie narrows both
w = max(legs[n]['width'] for n in cands)
offenders |= {n for n in cands if legs[n]['width'] >= w - 1e-9}
return offenders, other
for it in range(8):
offenders, other = build()
print(f'iteration {it}: {len(offenders)} legs in clearance errors, {len(other)} other errors')
if not offenders: break
for name in offenders:
l = legs[name]; nxt = sorted({s for s in STEPS + [orig[name]] if orig[name] - 1e-9 <= s < l['width'] - 1e-9}, reverse=True) # last resort: the routed width
if not nxt: print(' cannot narrow', name, l['width']); continue
l['width'] = nxt[0]; print(f" {name} ({l['net']}) -> {l['width']}")
json.dump(routes, open(path, 'w'), indent=1)
widths = {}
for l in legs.values():
if l['net'] in POWER: widths.setdefault(l['net'], []).append(l['width'])
print({n: sorted(set(w)) for n, w in widths.items()})
if other: print('other errors:', *other[:6], sep='\n ')