"""Cobre de las EinarBoard en blanco sobre noche (21-oct): portada (V5.6) y miniaturas por versión. Lo nuevo de cada revisión va en blanco sólido; el resto del cobre en blanco tenue. Uso: python3 tarjetas_blanco.py DIR_JSON DIR_SALIDA (pcb_v55/v56/v57/v6.json, de leer_pcb.py) Genera SVG; la conversión a WebP la hace rasterizar.py.""" import json, os, re, sys SRC, OUT = sys.argv[1], sys.argv[2] D = {v: json.load(open(os.path.join(SRC, f'pcb_{v}.json'))) for v in ('v55', 'v56', 'v57', 'v6')} def nets(d): return {p[7] for p in d['pads'] if p[7]} | {s[6] for s in d['segments'] if s[6]} def ok(n): return n and n != 'GND' and not n.startswith('unconnected') NUEVO = {'v55': set(), 'v56': {n for n in nets(D['v56']) - nets(D['v55']) if ok(n)}, 'v57': {n for n in nets(D['v57']) - nets(D['v56']) if ok(n)}, 'v6': {n for n in nets(D['v6']) - nets(D['v57']) if ok(n) and re.search(r'GNSS|MODEM|4G|ANT|RF_|NETLIGHT|1V8|F9P|LTE', n)}} def contorno(d): xs, ys = [], [] for e in d['edges']: for k in e[1:]: if isinstance(k, list) and k and k[0] in ('start', 'end'): xs.append(float(k[1])); ys.append(float(k[2])) return min(xs), min(ys), max(xs) - min(xs), max(ys) - min(ys) # opacidades: (zonas, B.Cu, capas internas, F.Cu, pads, vías) y lo nuevo (F.Cu, internas, pads) TEMAS = { 'mini': dict(fondo='#111A2C', z=0.035, b=0.12, i=0.08, f=0.28, pad=0.38, via=0.32, nf=1.0, ni=0.55, npad=0.95, min_w=0.55), 'portada': dict(fondo='#0E1524', z=0.03, b=0.10, i=0.07, f=0.22, pad=0.30, via=0.26, nf=0.95, ni=0.5, npad=0.9, min_w=0.8), } def svg(d, hl, crop, size, tema): T = TEMAS[tema] x0, y0, W, H = crop OW, OH = size s = max(OW / W, OH / H) # llena el recuadro (la portada recorta arriba y abajo) ox, oy = (OW - W * s) / 2, (OH - H * s) / 2 X = lambda x: ox + (x - x0) * s Y = lambda y: oy + (y - y0) * s o = [f'', f''] def grupo(op, partes): if partes: o.append(f'' + ''.join(partes) + '') def lineas(capas, solo=None, sin=None): r = [] for x1, y1, x2, y2, w, l, n in d['segments']: if l not in capas or (solo is not None and n not in solo) or (sin is not None and n in sin): continue r.append(f'') return r def pads(solo=None, sin=None): r = [] for ax, ay, w, h, prot, shape, typ, n, ref, layer, lays in d['pads']: if not any(l in ('F.Cu', '*.Cu') for l in lays): continue if (solo is not None and n not in solo) or (sin is not None and n in sin): continue cx, cy, ww, hh = X(ax), Y(ay), w * s, h * s if shape == 'circle': r.append(f'') else: rr = min(ww, hh) / 2 if shape == 'oval' else (min(ww, hh) * 0.18 if shape == 'roundrect' else 0) r.append(f'') return r def vias(solo=None, sin=None): return [f'' for x, y, sz, n in d['vias'] if not ((solo is not None and n not in solo) or (sin is not None and n in sin))] zonas = [] for lay, net, pts in d['zones']: if lay in ('F.Cu', 'B.Cu') and pts: zonas.append('') grupo(T['z'], zonas) grupo(T['b'], lineas({'B.Cu'}, sin=hl)) grupo(T['i'], lineas({'In1.Cu', 'In2.Cu'}, sin=hl)) grupo(T['f'], lineas({'F.Cu'}, sin=hl)) grupo(T['pad'], pads(sin=hl)) grupo(T['via'], vias(sin=hl)) if hl: grupo(T['ni'], lineas({'B.Cu', 'In1.Cu', 'In2.Cu'}, solo=hl)) grupo(T['nf'], lineas({'F.Cu'}, solo=hl)) grupo(T['npad'], pads(solo=hl) + vias(solo=hl)) # barrenos: del color del fondo, encima de todo hoyos = [f'' for x, y, sz, n in d['vias']] for ax, ay, w, h, prot, shape, typ, n, ref, layer, lays in d['pads']: if typ == 'thru_hole': hoyos.append(f'') o.append(f'' + ''.join(hoyos) + '') # contorno de la tarjeta (solo en miniaturas) if tema == 'mini': bx, by, bw, bh = contorno(d) o.append(f'') o.append('') return '\n'.join(o) os.makedirs(OUT, exist_ok=True) for v, d in D.items(): bx, by, bw, bh = contorno(d) m = 2.0 open(os.path.join(OUT, f'{v}.svg'), 'w').write(svg(d, NUEVO[v], (bx - m, by - m, bw + 2 * m, bh + 2 * m), (420, 300), 'mini')) print(v, 'nuevo:', len(NUEVO[v]), 'redes; contorno', round(bw, 1), 'x', round(bh, 1), 'mm') # portada: franja de la V5.6 donde más cobre nuevo hay (ventana de 20 mm de alto) d = D['v56']; hl = NUEVO['v56'] bx, by, bw, bh = contorno(d) W = bw - 16; H = W * 400 / 2400 mejor = max((sum(abs(s[2] - s[0]) + abs(s[3] - s[1]) for s in d['segments'] if s[6] in hl and y <= (s[1] + s[3]) / 2 <= y + H), y) for y in [by + 1 + k * 0.5 for k in range(int((bh - H - 2) / 0.5))]) y0 = mejor[1] print('portada: y0 =', round(y0, 1), 'mm; mm de cobre nuevo en la franja:', round(mejor[0], 1)) open(os.path.join(OUT, 'portada-v56.svg'), 'w').write(svg(d, hl, (bx + 8, y0, W, H), (2400, 400), 'portada'))