#!/usr/bin/env python3 import re import math from dataclasses import replace import gerbonara.cad.kicad.pcb as pcb import gerbonara.cad.primitives as cad_pr r = 85 cx, cy = 150, 100 prev_next_cycle = lambda l: zip(l[-1:] + l[:-1], l, l[1:] + l[:1]) si_prefixes = {'k': 1e3, 'M': 1e6, 'G': 1e9, 'm': 1e-3, 'u': 1e-6, 'µ': 1e-6, 'n': 1e-9, 'p': 1e-12, '.': 1, 'r': 1} def parse_si(s, unit=''): if not (match := re.fullmatch(f'([0-9]*)(([.pPnNuUµmrRkKMgG])([0-9]*))?([pPnNuUµmrRkKMgG]?)({unit})?', s)): raise ValueError(f'{s} is not a valid number with an SI prefix') left, _1, dot, right, prefix, _unit = match.groups() prefix = prefix or dot or '.' multiplier = si_prefixes.get(prefix, si_prefixes.get(prefix.upper(), si_prefixes.get(prefix.lower()))) return float(f'{left}.{right or ""}0') * multiplier b = pcb.Board.open('self-balancing-test-a.kicad_pcb') b.unfill_zones() matches = list(b.find_footprints(name='Package_TO_SOT_SMD:SOT-23', sheetfile='resistor_bank.kicad_sch')) components = [(transistor, *transistor.pads_by_number[3].find_connected(name='Resistor_SMD.*')) for transistor in sorted(matches, key=lambda fp: fp.parsed_reference[1])] components = [(q, r1, r2) if parse_si(r1.value) < parse_si(r2.value) else (q, r2, r1) for q, r1, r2 in components] num_lgs = 8 lg_pitch = 1 for i, (fp, res1, res2) in enumerate(components): alpha = 2*math.pi * i/len(matches) fp.at.x, fp.at.y = cx+r, cy fp.set_rotation(0) for text in fp.texts: text.at.rotation = 0 for prop in fp.properties: prop.at.rotation = 0 res1.at.x, res1.at.y = cx+r+5, cy+2 res1.face('right', net='*/V+') res2.at.x, res2.at.y = cx+r+5, cy-2 res2.face('right', net='*/V+') gnd_via = pcb.Via(at=pcb.XYCoord(fp.pad(2)).with_offset(y=1.5), size=1.2, drill=0.6, net=fp.pad(2).net.number ) b.add(gnd_via) lg = int(re.search(r'[0-9]*$', fp.pad(1).net.name).group(0)) lg_via = pcb.Via(at=pcb.XYCoord(fp.pad(1)).with_offset(x=-2-lg_pitch*lg), size=0.8, drill=0.4, net=fp.pad(1).net.number ) b.add(lg_via) for tr, net_name in [ (cad_pr.Trace(0.5, fp.pad(3), res1.pad(2), orientation=['ccw']), fp.pad(3).net.name), (cad_pr.Trace(0.5, fp.pad(3), res2.pad(2), orientation=['cw']), fp.pad(3).net.name), (cad_pr.Trace(0.5, res1.pad(1), res2.pad(1)), res1.pad(1).net.name), (cad_pr.Trace(0.5, fp.pad(2), gnd_via), fp.pad(2).net.name), (cad_pr.Trace(0.5, fp.pad(1), lg_via), fp.pad(1).net.name), ]: for obj in b.map_gn_cad(tr, net_name=net_name): obj.rotate(alpha, cx, cy) b.add(obj) for i in range(num_lgs): p1 = pcb.XYCoord(fp.pad(1)).with_offset(x=-2-lg_pitch*i) arc = pcb.TrackArc( start=p1, end=p1.with_rotation(2*math.pi / len(matches), cx, cy), center=(cx, cy), layer='B.Cu', width=0.5, net=b.net_id(f'LG{i}'), ) arc.rotate(alpha, cx, cy) b.add(arc) gnd_via.rotate(alpha, cx, cy) lg_via.rotate(alpha, cx, cy) fp.rotate(alpha, cx, cy) res1.rotate(alpha, cx, cy) res2.rotate(alpha, cx, cy) for i, ((prev_q, prev_r1, prev_r2), (fp, res1, res2), (next_q, next_r1, next_r2)) in enumerate(prev_next_cycle(components)): if res1.pad(1).net == next_r2.pad(1).net: arc = pcb.TrackArc( end=res1.pad(1), center=(cx, cy), start=next_r2.pad(1), width=0.5, net=res2.pad(1).net.number, ) b.add(arc) b.write('self-balancing-test-a.testout.kicad_pcb')