main
ae007 Project page: AFS_Adom rev A (Rice Eclipse AFS clean-room AI redesign): highlight reel and narrated process video with… cbe460e 7d ago
"""Route the AFS_Adom board offline: EP vias, plane stubs for passives, ordered escapes, nets, retry. Writes board + plan."""
import sys, json, math, time
sys.path.insert(0, '/tmp/claude-1001/-home-adom-project/8819fb5f-c2c6-4524-9528-99428cd6b88e/scratchpad/gen')
import pcb as B
from router import *

t0 = time.time()
R = Router()
log = open(B.OUT + '/outputs/route_log.txt', 'w')
def L(*a):
    print(*a, flush=True); print(*a, file=log); log.flush()

VBAT_POLY = [(B.BX0, B.BY0), (B.BX1, B.BY0), (B.BX1, 110.0), (126.2, 110.0), (126.2, 150.6), (122.0, 150.6), (122.0, 110.0), (B.BX0, 110.0)]
lx, ly, _ = B.PL['L3']
LX_KEEP = [(lx - 2.6, ly - 1.6), (lx + 0.2, ly - 1.6), (lx + 0.2, ly + 1.6), (lx - 2.6, ly + 1.6)]
def in_3v3(x, y): return (not R.in_poly(x, y, VBAT_POLY)) and (not R.in_poly(x, y, LX_KEEP))
def in_vbat(x, y): return R.in_poly(x, y, VBAT_POLY)
def in_gnd(x, y): return not R.in_poly(x, y, LX_KEEP)

# 1) RP2350 exposed pad thermal vias
ux, uy, _ = B.PL['U1']
epv = [(ux + dx, uy + dy) for dx in (-1.1, 0, 1.1) for dy in (-1.1, 0, 1.1)]
for (x, y) in epv:
    R.vias.append(('GND', x, y, VIA_D, VIA_DRILL)); R.add_via_obstacle('GND', x, y, VIA_D)
R.plan.append(dict(net='GND', kind='epvias', segs=[], vias=epv, width=0.3, via=(VIA_D, VIA_DRILL)))

# 2) plane stubs for passives/connectors only (IC pins are connected through their capacitors)
fails = []
for net, fn, via in (('GND', in_gnd, (VIA_D, VIA_DRILL)), ('+3V3', in_3v3, (VIA_D, VIA_DRILL)), ('VBAT', in_vbat, (PVIA_D, PVIA_DRILL))):
    pads = [p for p in R.netpads.get(net, []) if not p.tht and not p.ref.startswith('U')]
    n_ok = 0
    for p in pads:
        if R.plane_stub_fn(p, fn, via=via, maxr=4.0 if net != 'VBAT' else 14.0):
            n_ok += 1
        else:
            fails.append((net, p.ref, p.num))
    L('%s stubs: %d/%d ok (%.0fs)' % (net, n_ok, len(pads), time.time() - t0))
L('stub failures:', fails)

# 3) ordered escapes from the RP2350 bottom row (outer pins first) and the left-column bottom pins
def pin_net(ref, num):
    return B.COMPS[ref]['pads'].get(num)
escape = [('U1', '80'), ('U1', '79'), ('U1', '78'), ('U1', '77'), ('U1', '76'), ('U1', '75'), ('U1', '74'), ('U1', '73'), ('U1', '72'), ('U1', '71'), ('U1', '70'),
          ('U1', '69'), ('U1', '68'), ('U1', '67'), ('U1', '66'), ('U1', '65'), ('U1', '64'), ('U1', '63'), ('U1', '62'), ('U1', '61'), ('U1', '60'), ('U1', '59')]
routed_nets = set()
for ref, num in escape:
    net = pin_net(ref, num)
    if net in ('GND', '+3V3'):
        ok = R.connect_pad(ref, num)
        L('%-5s %-4s %-12s %s' % (ref, num, net, 'ok' if ok else 'FAILED'))
    else:
        if net in routed_nets:
            continue
        ok = R.route_net(net)
        routed_nets.add(net)
        L('%-5s %-4s %-12s %s (net)' % (ref, num, net, 'ok' if ok else 'FAILED'))

def mst_len(net):
    pads = R.netpads[net]
    if len(pads) < 2:
        return 0
    rem = pads[1:]; conn = [pads[0]]; tot = 0
    while rem:
        best = min(rem, key=lambda p: min(math.hypot(p.x - q.x, p.y - q.y) for q in conn))
        tot += min(math.hypot(best.x - q.x, best.y - q.y) for q in conn)
        rem.remove(best); conn.append(best)
    return tot

plane = {'GND', '+3V3', 'VBAT'}
first = ['VREG_LX', 'DVDD', 'VREG_AVDD', 'XIN', 'XOUT_R', 'XOUT', 'USB_DP', 'USB_DM', 'USB_DP_C', 'USB_DM_C',
         'SERVO1', 'SERVO2', 'SERVO3', 'SERVO4', 'SERVO5', 'SERVO6', 'SERVO7', 'SERVO8',
         'SW_5V', 'BST_5V', 'SW_3V3', 'BST_3V3', 'VSYS', 'VBAT_IN', 'ISNS_P', 'ISNS_N', 'MAG_CAP', '+5V']
rest = sorted([n for n in R.netpads if n not in plane and n not in first and n not in routed_nets], key=mst_len)
order = [n for n in first if n in R.netpads and n not in routed_nets] + rest
done = 0
for net in order:
    ok = R.route_net(net)
    done += 1
    L('%-12s %s  (%d/%d, %.0fs)' % (net, 'ok' if ok else 'FAILED', done, len(order), time.time() - t0))
# 4) plane nets: connect every remaining pad (IC power pins) to the plane copper
for net in ('GND', '+3V3', 'VBAT'):
    ok = R.route_net(net, max_nodes=400000)
    L('%-12s %s  (%.0fs)' % (net, 'ok' if ok else 'FAILED', time.time() - t0))
# 5) retry failures with a wider search
retry = list(R.unrouted); R.unrouted = []
L('retrying', len(retry))
for (net, ref, num) in retry:
    ok = R.connect_pad(ref, num, via_cost=15.0, max_nodes=2500000)
    L('retry %-12s %s.%s %s (%.0fs)' % (net, ref, num, 'ok' if ok else 'FAILED', time.time() - t0))

L('UNROUTED:', R.unrouted)
tr, vi = R.emit()
out = B.build(tracks=tr, vias=vi, out=B.OUT + '/AFS_Adom.kicad_pcb')
json.dump(dict(plan=R.plan, unrouted=R.unrouted, stub_failures=fails), open(B.OUT + '/outputs/route_plan.json', 'w'))
L('wrote', out, len(tr), 'segments', len(vi), 'vias in %.0fs' % (time.time() - t0))