molecule
AFS_Adom flight computer molecule
Public Unreviewedby ae007
Active fin stabilization flight computer for a sounding rocket, a clean-room AI redesign of the Rice Eclipse AFS board: RP2350B, ICM-45686, KX134-1211, MMC5983MA, MS5611, TMP117, INA219A, 8 servos, 2S LiPo, USB-C; 4-layer Adom molecule, designed end to end by Claude through KiCad and adom-aiflow
main
ae007
Comparison with the Rice Eclipse AFS student board: same-rules DRC/ERC, same-load Adom Fields current and thermal scree…
3b8a4e2
7d ago
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
"""Side-by-side metrics of two KiCad boards (read-only): outline, layers, stack-up, parts, passive sizes, tracks by width
and layer, vias, zones, nets, and the copper on chosen power nets. Used for the AFS_Adom vs Rice Eclipse comparison."""
import json, math, re, sys, os
from collections import Counter, defaultdict
sys.path.insert(0, os.path.dirname(__file__))
from silk import parse_sexp, kids, kid
def metrics(path):
t = parse_sexp(open(path, encoding='utf-8').read())[0]
m = dict(file=os.path.basename(path))
lay = kid(t, 'layers'); m['copper_layers'] = [l[1] for l in lay[1:] if isinstance(l, list) and l[1].endswith('.Cu')]
setup = kid(t, 'setup'); stk = kid(setup, 'stackup') if setup else None
m['thickness'] = None
gen = kid(t, 'general')
if gen and kid(gen, 'thickness'): m['thickness'] = float(kid(gen, 'thickness')[1])
xs, ys = [], []
for n in t[1:]:
if isinstance(n, list) and n[0] in ('gr_line', 'gr_arc', 'gr_rect', 'gr_poly', 'gr_circle') and kid(n, 'layer') and kid(n, 'layer')[1] == 'Edge.Cuts':
for k in ('start', 'end', 'mid', 'center'):
p = kid(n, k)
if p: xs.append(float(p[1])); ys.append(float(p[2]))
pts = kid(n, 'pts')
if pts:
for xy in kids(pts, 'xy'): xs.append(float(xy[1])); ys.append(float(xy[2]))
m['outline_mm'] = [round(max(xs) - min(xs), 2), round(max(ys) - min(ys), 2)] if xs else None
fps = kids(t, 'footprint'); m['footprints'] = len(fps)
sides = Counter(kid(f, 'layer')[1] for f in fps); m['sides'] = dict(sides)
pk = Counter()
for f in fps:
name = f[1]
mm = re.search(r'_(0201|0402|0603|0805|1206|1210|2512)_', name)
if mm and (name.startswith('Resistor') or name.startswith('Capacitor') or ':R_' in name or ':C_' in name or 'LED' in name): pk[mm.group(1)] += 1
m['passive_sizes'] = dict(pk)
m['values'] = Counter(re.search(r'\(property "Value" "([^"]*)"', '') and '' for _ in []) # placeholder
segs = kids(t, 'segment'); arcs = kids(t, 'arc')
w = Counter(); L = defaultdict(float); nets_len = defaultdict(float); nets_minw = {}
for s in segs:
a, b = kid(s, 'start'), kid(s, 'end'); ln = math.dist((float(a[1]), float(a[2])), (float(b[1]), float(b[2])))
wd = float(kid(s, 'width')[1]); ly = kid(s, 'layer')[1]; net = kid(s, 'net'); nn = net[-1] if net else ''
w[round(wd, 3)] += 1; L[ly] += ln; nets_len[nn] += ln; nets_minw[nn] = min(nets_minw.get(nn, 9), wd)
m['segments'] = len(segs) + len(arcs); m['track_mm_by_layer'] = {k: round(v) for k, v in L.items()}; m['track_mm_total'] = round(sum(L.values()))
m['track_widths'] = dict(sorted(w.items()))
vias = kids(t, 'via'); m['vias'] = len(vias); m['via_sizes'] = dict(Counter('%s/%s' % (kid(v, 'size')[1], kid(v, 'drill')[1]) for v in vias))
zones = kids(t, 'zone'); zl = []
for z in zones:
net = kid(z, 'net_name'); layers = kid(z, 'layers') or kid(z, 'layer'); fill_area = 0.0
for fp in kids(z, 'filled_polygon'):
pts = [(float(xy[1]), float(xy[2])) for xy in kids(kid(fp, 'pts'), 'xy')]
fill_area += abs(sum(pts[i][0] * pts[i - 1][1] - pts[i - 1][0] * pts[i][1] for i in range(len(pts)))) / 2
zl.append((net[1] if net else '', ' '.join(layers[1:]) if layers else '', round(fill_area)))
m['zones'] = len(zones); m['zone_list'] = zl
m['nets'] = len({n[-1] for s in segs for n in [kid(s, 'net')] if n})
m['nets_len'] = nets_len; m['nets_minw'] = nets_minw
return m, t
if __name__ == '__main__':
out = {}
for tag, p in (('afs', sys.argv[1]), ('eclipse', sys.argv[2])):
m, t = metrics(p); out[tag] = m
print('==', tag, {k: v for k, v in m.items() if k not in ('nets_len', 'nets_minw', 'zone_list', 'values')})
print(' zones:', m['zone_list'][:40])
json.dump({k: {kk: (dict(vv) if isinstance(vv, defaultdict) else vv) for kk, vv in v.items() if kk != 'values'} for k, v in out.items()}, open(sys.argv[3], 'w'), indent=1, default=str)