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
"""Replay the offline routing plan on the live KiCad board through the bridge (kicad_route_net), one net per commit."""
import sys, json, subprocess, time
PLAN = '/tmp/claude-1001/-home-adom-project/8819fb5f-c2c6-4524-9528-99428cd6b88e/scratchpad/AFS_Adom/outputs/route_plan.json'
BOARD = 'C:/Users/angus/Documents/aiflow/AFS_Adom/AFS_Adom.kicad_pcb'
AB = ['adom-bridge', '--ai-thread', 'AFS_Adom-flight-computer']

def call(verb, args, timeout=180):
    r = subprocess.run(AB + [verb, json.dumps(args)], capture_output=True, text=True, timeout=timeout)
    try:
        return json.loads(r.stdout)
    except Exception:
        return {'success': False, 'error': r.stdout[:500] + r.stderr[:300]}

def polylines(entry):
    """Rebuild polylines [(x,y,layer_out)] from ordered segments + vias."""
    segs = [tuple(s) for s in entry['segs']]
    vias = set((round(x, 3), round(y, 3)) for x, y in entry['vias'])
    paths = []
    cur = []
    for seg in segs:
        layer, x0, y0, x1, y1 = seg[:5]
        a = (round(x0, 3), round(y0, 3)); b = (round(x1, 3), round(y1, 3))
        if cur and (cur[-1][0], cur[-1][1]) == a:
            cur[-1] = (a[0], a[1], layer)          # outgoing layer at this point
            cur.append((b[0], b[1], layer))
        else:
            if cur:
                paths.append(cur)
            cur = [(a[0], a[1], layer), (b[0], b[1], layer)]
    if cur:
        paths.append(cur)
    # a stub ends with a via into the plane: mark the final point with the other layer
    if entry['kind'] == 'stub' and paths:
        last = paths[-1][-1]
        other = 'B.Cu' if last[2] == 'F.Cu' else 'F.Cu'
        paths[-1][-1] = (last[0], last[1], other)
    return paths

def main(start=0, limit=None, dry=False):
    plan = json.load(open(PLAN))['plan']
    st = call('kicad_routing_state', {'filePath': BOARD})
    rev = st.get('revision')
    print('revision', rev, 'nets remaining', st.get('netsRemaining'))
    done = fail = 0
    for i, e in enumerate(plan[start:(start + limit) if limit else None], start):
        if e['kind'] == 'epvias':
            continue
        paths = polylines(e)
        if not paths:
            continue
        args = {'filePath': BOARD, 'expectedRevision': rev, 'net': e['net'], 'width': e['width'], 'viaSize': e['via'][0], 'viaDrill': e['via'][1],
                'paths': [[{'x': x, 'y': y, 'layer': l} for (x, y, l) in p] for p in paths]}
        if dry:
            args['dryRun'] = True
        r = call('kicad_route_net', args)
        if r.get('success'):
            rev = r.get('revision', rev); done += 1
        else:
            fail += 1
            print('FAIL', i, e['net'], e.get('ref', ''), (r.get('error') or '')[:200], (r.get('errorCode') or ''), json.dumps(r.get('drc', ''))[:300])
            if r.get('errorCode') == 'stale_board':
                st = call('kicad_routing_state', {'filePath': BOARD}); rev = st.get('revision')
        if i % 20 == 0:
            print('progress', i, 'ok', done, 'fail', fail, 'rev', rev, flush=True)
    print('done', done, 'fail', fail)

if __name__ == '__main__':
    a = sys.argv[1:]
    main(int(a[0]) if a else 0, int(a[1]) if len(a) > 1 else None, '--dry' in a)