#!/usr/bin/env python3 import math import hashlib import re import itertools import datetime import tempfile import subprocess import sqlite3 import json from pathlib import Path import tqdm import gerbonara.cad.kicad.pcb as pcb import gerbonara.cad.kicad.footprints as fp import gerbonara.cad.primitives as cad_pr import gerbonara.cad.kicad.graphical_primitives as kc_gr cols = 6 rows = 4 coil_specs = [ {'n': 1, 's': True, 't': 1, 'c': 0.20, 'w': 5.00, 'd': 3.00, 'v': 5.00}, {'n': 2, 's': True, 't': 1, 'c': 0.20, 'w': 3.00, 'd': 1.50, 'v': 3.00}, {'n': 3, 's': True, 't': 1, 'c': 0.20, 'w': 1.50, 'd': 1.20, 'v': 2.00}, {'n': 5, 's': True, 't': 1, 'c': 0.20, 'w': 0.80, 'd': 0.40, 'v': 0.80}, {'n': 10, 's': True, 't': 1, 'c': 0.20, 'w': 0.50, 'd': 0.30, 'v': 0.60}, {'n': 25, 's': True, 't': 1, 'c': 0.15, 'w': 0.25, 'd': 0.30, 'v': 0.60}, {'n': 1, 's': False, 't': 3, 'c': 0.20, 'w': 5.00, 'd': 3.00, 'v': 5.00}, {'n': 2, 's': False, 't': 1, 'c': 0.20, 'w': 3.00, 'd': 1.50, 'v': 3.00}, {'n': 3, 's': False, 't': 1, 'c': 0.20, 'w': 2.50, 'd': 1.20, 'v': 2.00}, {'n': 5, 's': False, 't': 1, 'c': 0.20, 'w': 2.50, 'd': 0.40, 'v': 0.80}, {'n': 10, 's': False, 't': 1, 'c': 0.20, 'w': 1.50, 'd': 0.30, 'v': 0.60}, {'n': 25, 's': False, 't': 1, 'c': 0.15, 'w': 0.50, 'd': 0.30, 'v': 0.60}, {'n': 1, 's': False, 't': 4, 'c': 0.20, 'w': 5.00, 'd': 3.00, 'v': 5.00}, {'n': 2, 's': False, 't': 3, 'c': 0.20, 'w': 3.00, 'd': 1.50, 'v': 3.00}, {'n': 3, 's': False, 't': 4, 'c': 0.20, 'w': 2.50, 'd': 1.20, 'v': 2.00}, {'n': 5, 's': False, 't': 3, 'c': 0.20, 'w': 2.50, 'd': 0.40, 'v': 0.80}, {'n': 10, 's': False, 't': 3, 'c': 0.20, 'w': 1.50, 'd': 0.30, 'v': 0.60}, {'n': 25, 's': False, 't': 3, 'c': 0.15, 'w': 0.50, 'd': 0.30, 'v': 0.60}, {'n': 1, 's': False, 't': 5, 'c': 0.20, 'w': 5.00, 'd': 3.00, 'v': 5.00}, {'n': 2, 's': False, 't': 5, 'c': 0.20, 'w': 3.00, 'd': 1.50, 'v': 3.00}, {'n': 3, 's': False, 't': 4, 'c': 0.20, 'w': 2.50, 'd': 1.20, 'v': 2.00}, {'n': 5, 's': False, 't': 7, 'c': 0.20, 'w': 2.50, 'd': 0.40, 'v': 0.80}, {'n': 10, 's': False, 't': 7, 'c': 0.20, 'w': 1.50, 'd': 0.30, 'v': 0.60}, {'n': 25, 's': False, 't': 13, 'c': 0.15, 'w': 0.50, 'd': 0.30, 'v': 0.60}, ] cachedir = Path('/tmp/coil_test_cache') version_string = 'v1.0' coil_border = 7 # mm cut_gap = 3 # mm tooling_border = 10 # mm vscore_extra = 10 # mm mouse_bite_width = 8 # mm mouse_bite_yoff = -0.00 mouse_bite_hole_dia = 0.5 mouse_bite_hole_spacing = 0.35 hole_offset = 5 hole_dia = 3.2 coil_dia = 35 # mm coil_inner_dia = 15 # mm board_thickness = 0.80 # mm pad_offset = 2 # mm pad_dia = 2.0 # mm pad_length = 3.5 # mm pad_drill = 1.1 # mm pad_pitch = 2.54 # mm vrail_width = 10 # mm join_trace_w = 0.35 # mm v_cuts = False # FIXME DEBUG mouse_bites = True # FIXME DEBUG db = sqlite3.connect('coil_parameters.sqlite3') db.execute('CREATE TABLE IF NOT EXISTS runs (run_id INTEGER PRIMARY KEY, timestamp TEXT, version TEXT)') db.execute('CREATE TABLE IF NOT EXISTS coils (coil_id INTEGER PRIMARY KEY, run_id INTEGER, FOREIGN KEY (run_id) REFERENCES runs(run_id))') db.execute('CREATE TABLE IF NOT EXISTS results (result_id INTEGER PRIMARY KEY, coil_id INTEGER, key TEXT, value TEXT, FOREIGN KEY (coil_id) REFERENCES coils(coil_id))') cur = db.cursor() cur.execute('INSERT INTO runs(timestamp, version) VALUES (datetime("now"), ?)', (version_string,)) run_id = cur.lastrowid db.commit() coil_pitch_v = coil_dia + coil_border*2 + cut_gap coil_pitch_h = coil_dia + coil_border*2 + 2*cut_gap + vrail_width total_width = coil_pitch_h*cols + 2*tooling_border - vrail_width total_height = coil_pitch_v*rows + 2*tooling_border + cut_gap tile_width = tile_height = coil_dia + 2*coil_border drawing_text_size = 2.0 print(f'Calculated board size: {total_width:.2f} * {total_height:.2f} mm') print(f'Tile size: {tile_height:.2f} * {tile_height:.2f} mm') x0, y0 = 100, 100 xy = pcb.XYCoord b = pcb.Board.empty_board(page=pcb.PageSettings(page_format='A2')) b.add(kc_gr.Rectangle(xy(x0, y0), xy(x0+total_width, y0+total_height), layer='Edge.Cuts', stroke=pcb.Stroke(width=0.15))) def do_line(x0, y0, x1, y1, off_x=0, off_y=0): b.add(kc_gr.Line(xy(x0+off_x, y0+off_y), xy(x1+off_x, y1+off_y), layer='Edge.Cuts', stroke=pcb.Stroke(width=0.15))) if v_cuts: for y in range(rows): for off_y in [0, tile_height]: y_pos = y0 + tooling_border + cut_gap + off_y + y*coil_pitch_v do_line(x0 - vscore_extra, y_pos, x0 + total_width + vscore_extra, y_pos) b.add(kc_gr.Text(text='V-score', at=pcb.AtPos(x0 + total_width + vscore_extra + drawing_text_size/2, y_pos, 0), layer=kc_gr.TextLayer('Edge.Cuts'), effects=pcb.TextEffect( font=pcb.FontSpec(size=xy(drawing_text_size, drawing_text_size), thickness=drawing_text_size/10), justify=pcb.Justify(h=pcb.Atom.left)))) for x in range(cols): for off_x in [0, tile_width]: x_pos = x0 + tooling_border + cut_gap + off_x + x*coil_pitch_h do_line(x_pos, y0 - vscore_extra, x_pos, y0 + total_height + vscore_extra) b.add(kc_gr.Text(text='V-score', at=pcb.AtPos(x_pos, y0 + total_height + vscore_extra + drawing_text_size/2, 90), layer=kc_gr.TextLayer('Edge.Cuts'), effects=pcb.TextEffect( font=pcb.FontSpec(size=xy(drawing_text_size, drawing_text_size), thickness=drawing_text_size/10), justify=pcb.Justify(h=pcb.Atom.right)))) def draw_corner(x0, y0, spokes): right, top, left, bottom = [True if c.lower() in 'y1' else False for c in spokes] l = (tile_width - mouse_bite_width)/2 - cut_gap/2 if right: do_line(cut_gap/2, -cut_gap/2, cut_gap/2 + l, -cut_gap/2, x0, y0) do_line(cut_gap/2, cut_gap/2, cut_gap/2 + l, cut_gap/2, x0, y0) b.add(kc_gr.Arc(start=xy(x0+cut_gap/2+l, y0-cut_gap/2), end=xy(x0+cut_gap/2+l, y0+cut_gap/2), center=xy(x0+cut_gap/2+l, y0), layer='Edge.Cuts', stroke=pcb.Stroke(width=0.15))) else: do_line(cut_gap/2, -cut_gap/2, cut_gap/2, cut_gap/2, x0, y0) if left: do_line(-cut_gap/2, -cut_gap/2, -cut_gap/2 - l, -cut_gap/2, x0, y0) do_line(-cut_gap/2, cut_gap/2, -cut_gap/2 - l, cut_gap/2, x0, y0) b.add(kc_gr.Arc(end=xy(x0-cut_gap/2-l, y0-cut_gap/2), start=xy(x0-cut_gap/2-l, y0+cut_gap/2), center=xy(x0-cut_gap/2-l, y0), layer='Edge.Cuts', stroke=pcb.Stroke(width=0.15))) else: do_line(-cut_gap/2, -cut_gap/2, -cut_gap/2, cut_gap/2, x0, y0) if bottom: do_line(-cut_gap/2, cut_gap/2, -cut_gap/2, cut_gap/2 + l, x0, y0) do_line(cut_gap/2, cut_gap/2, cut_gap/2, cut_gap/2 + l, x0, y0) b.add(kc_gr.Arc(end=xy(x0-cut_gap/2, y0+cut_gap/2+l), start=xy(x0+cut_gap/2, y0+cut_gap/2+l), center=xy(x0, y0+cut_gap/2+l), layer='Edge.Cuts', stroke=pcb.Stroke(width=0.15))) else: do_line(-cut_gap/2, cut_gap/2, cut_gap/2, cut_gap/2, x0, y0) if top: do_line(-cut_gap/2, -cut_gap/2, -cut_gap/2, -cut_gap/2 - l, x0, y0) do_line(cut_gap/2, -cut_gap/2, cut_gap/2, -cut_gap/2 - l, x0, y0) b.add(kc_gr.Arc(start=xy(x0-cut_gap/2, y0-cut_gap/2-l), end=xy(x0+cut_gap/2, y0-cut_gap/2-l), center=xy(x0, y0-cut_gap/2-l), layer='Edge.Cuts', stroke=pcb.Stroke(width=0.15))) else: do_line(-cut_gap/2, -cut_gap/2, cut_gap/2, -cut_gap/2, x0, y0) def make_mouse_bite(x, y, rot=0, width=mouse_bite_width, hole_dia=mouse_bite_hole_dia, hole_spacing=mouse_bite_hole_spacing, **kwargs): pitch = hole_dia + hole_spacing num_holes = int(math.floor((width - hole_spacing) / pitch)) actual_spacing = (width - num_holes*hole_dia) / (num_holes + 1) f = fp.Footprint(name='mouse_bite', _version=None, generator=None, at=fp.AtPos(x, y, rot), **kwargs) for i in range(num_holes): f.pads.append(fp.Pad( number='1', type=fp.Atom.np_thru_hole, shape=fp.Atom.circle, at=fp.AtPos(-width/2 + actual_spacing + i*pitch + hole_dia/2, 0, 0), size=xy(hole_dia, hole_dia), drill=fp.Drill(diameter=hole_dia), footprint=f)) return f def make_hole(x, y, dia, **kwargs): f = fp.Footprint(name='hole', _version=None, generator=None, at=fp.AtPos(x, y, 0), **kwargs) f.pads.append(fp.Pad( number='1', type=fp.Atom.np_thru_hole, shape=fp.Atom.circle, at=fp.AtPos(0, 0, 0), size=xy(dia, dia), drill=fp.Drill(diameter=dia), footprint=f)) return f def make_pads(x, y, rot, n, pad_dia, pad_length, drill, pitch, **kwargs): f = fp.Footprint(name=f'conn_gen_01x{n}', _version=None, generator=None, at=fp.AtPos(x, y, rot), **kwargs) for i in range(n): f.pads.append(fp.Pad( number=str(i+1), type=fp.Atom.thru_hole, shape=fp.Atom.oval, at=fp.AtPos(-pitch*(n-1)/2 + i*pitch, 0, rot), size=xy(pad_dia, pad_length), drill=fp.Drill(diameter=drill), footprint=f)) return f corner_x0 = x0 + tooling_border + cut_gap/2 corner_y0 = y0 + tooling_border + cut_gap/2 corner_x1 = x0 + total_width - tooling_border - cut_gap/2 corner_y1 = y0 + total_height - tooling_border - cut_gap/2 # Corners draw_corner(corner_x0, corner_y0, 'YNNY') draw_corner(corner_x0, corner_y1, 'YYNN') draw_corner(corner_x1, corner_y0, 'NNYY') draw_corner(corner_x1, corner_y1, 'NYYN') # Top / bottom V rail L junctions for x in range(1, cols): draw_corner(corner_x0 + x*coil_pitch_h - cut_gap - vrail_width, corner_y0, 'NNYY') draw_corner(corner_x0 + x*coil_pitch_h, corner_y0, 'YNNY') draw_corner(corner_x0 + x*coil_pitch_h - cut_gap - vrail_width, corner_y1, 'NYYN') draw_corner(corner_x0 + x*coil_pitch_h, corner_y1, 'YYNN') # Left / right T junctions for y in range(1, rows): draw_corner(corner_x0, corner_y0 + y*coil_pitch_v, 'YYNY') draw_corner(corner_x1, corner_y0 + y*coil_pitch_v, 'NYYY') # Middle T junctions for y in range(1, rows): for x in range(1, cols): draw_corner(corner_x0 + x*coil_pitch_h - cut_gap - vrail_width, corner_y0 + y*coil_pitch_v, 'NYYY') draw_corner(corner_x0 + x*coil_pitch_h, corner_y0 + y*coil_pitch_v, 'YYNY') # Mouse bites if mouse_bites: for x in range(0, cols): for y in range(0, rows): tile_x0 = x0 + tooling_border + cut_gap + x*coil_pitch_h tile_y0 = y0 + tooling_border + cut_gap + y*coil_pitch_v b.add(make_mouse_bite(tile_x0 + tile_width/2, tile_y0 - mouse_bite_hole_dia/2, 0)) b.add(make_mouse_bite(tile_x0 + tile_width/2, tile_y0 + tile_height + mouse_bite_hole_dia/2, 0)) b.add(make_mouse_bite(tile_x0 - mouse_bite_hole_dia/2, tile_y0 + tile_height/2, 90)) b.add(make_mouse_bite(tile_x0 + tile_width + mouse_bite_hole_dia/2, tile_y0 + tile_height/2 + mouse_bite_yoff, 90)) # Mounting holes for x in range(0, cols): for y in range(0, rows): tile_x0 = x0 + tooling_border + cut_gap + x*coil_pitch_h + tile_width/2 tile_y0 = y0 + tooling_border + cut_gap + y*coil_pitch_v + tile_height/2 dx = tile_width/2 - hole_offset dy = tile_height/2 - hole_offset b.add(make_hole(tile_x0 - dx, tile_y0 - dy, hole_dia)) b.add(make_hole(tile_x0 - dx, tile_y0 + dy, hole_dia)) b.add(make_hole(tile_x0 + dx, tile_y0 - dy, hole_dia)) b.add(make_hole(tile_x0 + dx, tile_y0 + dy, hole_dia)) # border graphics c = 3 for layer in ['F.SilkS', 'B.SilkS']: b.add(kc_gr.Rectangle(start=xy(x0, y0), end=xy(x0+c, y0+total_height), layer=layer, stroke=pcb.Stroke(width=0), fill=kc_gr.FillMode(pcb.Atom.solid))) b.add(kc_gr.Rectangle(start=xy(x0, y0), end=xy(x0+total_width, y0+c), layer=layer, stroke=pcb.Stroke(width=0), fill=kc_gr.FillMode(pcb.Atom.solid))) b.add(kc_gr.Rectangle(start=xy(x0+total_width-c, y0), end=xy(x0+total_width, y0+total_height), layer=layer, stroke=pcb.Stroke(width=0), fill=kc_gr.FillMode(pcb.Atom.solid))) b.add(kc_gr.Rectangle(start=xy(x0, y0+total_height-c), end=xy(x0+total_width, y0+total_height), layer=layer, stroke=pcb.Stroke(width=0), fill=kc_gr.FillMode(pcb.Atom.solid))) a = 3 timestamp = datetime.datetime.now().strftime('%Y-%m-%d') b.add(kc_gr.Text(text=f'Planar inductor test panel {version_string} {timestamp} © 2023 Jan Götte, FG KOM, TU Darmstadt', at=pcb.AtPos(x0 + c + a/3, y0 + c + a/3), layer=kc_gr.TextLayer('F.SilkS'), effects=pcb.TextEffect( font=pcb.FontSpec(size=xy(a, a), thickness=a/5), justify=pcb.Justify(h=pcb.Atom.left, v=pcb.Atom.top)))) for index, ((y, x), spec) in tqdm.tqdm(enumerate(zip(itertools.product(range(rows), range(cols)), coil_specs), start=1)): pass with tempfile.NamedTemporaryFile(suffix='.kicad_mod') as f: tile_x0 = x0 + tooling_border + cut_gap + x*coil_pitch_h + tile_width/2 tile_y0 = y0 + tooling_border + cut_gap + y*coil_pitch_v + tile_height/2 for key, alias in { 'gen.inner_diameter': 'id', 'gen.outer_diameter': 'od', 'gen.trace_width': 'w', 'gen.turns': 'n', 'gen.twists': 't', 'gen.clearance': 'c', 'gen.single_layer': 's', 'gen.via_drill': 'd', 'gen.via_diameter': 'v'}.items(): if alias in spec: spec[key] = spec.pop(alias) if 'gen.via_diameter' not in spec: spec['gen.via_diameter'] = spec['gen.trace_width'] if 'gen.inner_diameter' not in spec: spec['gen.inner_diameter'] = coil_inner_dia if 'gen.outer_diameter' not in spec: spec['gen.outer_diameter'] = coil_dia args = ['python', '-m', 'twisted_coil_gen_twolayer', '--no-keepout-zone'] for k, v in spec.items(): prefix, _, k = k.partition('.') if (not isinstance(v, bool) or v) and prefix == 'gen': args.append('--' + k.replace('_', '-')) if v is not True: args.append(str(v)) arg_digest = hashlib.sha3_256(' / '.join(map(str, args)).encode()).hexdigest() cachedir.mkdir(exist_ok=True) cache_file = cachedir / f'C-{arg_digest}.kicad_mod' log_file = cachedir / f'Q-{arg_digest}.kicad_mod' if not cache_file.is_file(): args.append(cache_file) try: res = subprocess.run(args, check=True, capture_output=True, text=True) log_file.write_text(res.stdout + res.stderr) except subprocess.CalledProcessError as e: print(f'Error generating coil with command line {args}, rc={e.returncode}') print(e.stdout) print(e.stderr) coil = fp.Footprint.open_mod(cache_file) coil.at = fp.AtPos(tile_x0, tile_y0, 0) b.add(coil) t = [f'n={spec["gen.turns"]}', f'{spec["gen.twists"]} twists', f'w={spec["gen.trace_width"]:.2f}mm'] if spec.get('gen.single_layer'): t.append('single layer') spec['gen.board_thickness'] = board_thickness cur.execute('INSERT INTO coils(run_id) VALUES (?)', (run_id,)) coil_id = cur.lastrowid for key, value in spec.items(): if isinstance(value, bool): value = str(value) db.execute('INSERT INTO results(coil_id, key, value) VALUES (?, ?, ?)', (coil_id, key, value)) for l in log_file.read_text().splitlines(): if (m := re.fullmatch(r'Approximate inductance:\s*([-+.0-9eE]+)\s*µH', l.strip())): val = float(m.group(1)) * 1e-6 db.execute('INSERT INTO results(coil_id, key, value) VALUES (?, "calculated_approximate_inductance", ?)', (coil_id, val)) if (m := re.fullmatch(r'Approximate track length:\s*([-+.0-9eE]+)\s*mm', l.strip())): val = float(m.group(1)) * 1e-3 db.execute('INSERT INTO results(coil_id, key, value) VALUES (?, "calculated_trace_length", ?)', (coil_id, val)) if (m := re.fullmatch(r'Approximate resistance:\s*([-+.0-9eE]+)\s*Ω', l.strip())): val = float(m.group(1)) db.execute('INSERT INTO results(coil_id, key, value) VALUES (?, "calculated_approximate_resistance", ?)', (coil_id, val)) if (m := re.fullmatch(r'Fill factor:\s*([-+.0-9eE]+)', l.strip())): val = float(m.group(1)) db.execute('INSERT INTO results(coil_id, key, value) VALUES (?, "calculated_fill_factor", ?)', (coil_id, val)) db.commit() sz = 1.5 b.add(kc_gr.Text(text='\\n'.join(t), at=pcb.AtPos(tile_x0, tile_y0), layer=kc_gr.TextLayer('B.SilkS'), effects=pcb.TextEffect( font=pcb.FontSpec(size=xy(sz, sz), thickness=sz/5), justify=pcb.Justify(h=None, v=None, mirror=True)))) b.add(kc_gr.Text(text=f'{version_string} {timestamp}\\nTile {index}', at=pcb.AtPos(tile_x0, tile_y0 - tile_height/2 + sz), layer=kc_gr.TextLayer('B.SilkS'), effects=pcb.TextEffect( font=pcb.FontSpec(size=xy(sz, sz), thickness=sz/5), justify=pcb.Justify(h=None, v=pcb.Atom.top, mirror=True)))) b.add(kc_gr.Text(text=f'{index}', at=pcb.AtPos(tile_x0, tile_y0 - tile_height/2 + sz), layer=kc_gr.TextLayer('F.SilkS'), effects=pcb.TextEffect( font=pcb.FontSpec(size=xy(sz, sz), thickness=sz/5), justify=pcb.Justify(h=None, v=pcb.Atom.top, mirror=False)))) pads_x0 = tile_x0 + tile_width/2 - pad_offset pads = make_pads(pads_x0, tile_y0, 270, 2, pad_dia, pad_length, pad_drill, pad_pitch) b.add(pads) w = min(spec.get('gen.trace_width', pad_dia), pad_dia) wx, wy, _r, _f = pads.pad(2).abs_pos w2 = (wx - pad_length/2, wy) wx, wy, _r, _f = pads.pad(1).abs_pos w1 = (wx - pad_length/2, wy) b.add(cad_pr.Trace(w, coil.pad(1), pads.pad(1), waypoints=[w1], orientation=['ccw'], side='top')) b.add(cad_pr.Trace(w, coil.pad(2), pads.pad(2), waypoints=[w2], orientation=['cw'], side='bottom')) p = cut_gap + 5 q = 3 if y == 0: if x > 0 and x % 2 == 1: wx, wy, _r, _f = pads.pad(1).abs_pos w1 = (wx + p), (wy - q) w2 = (tile_x0 + tile_width/2 + cut_gap), (tile_y0 - tile_height/2 - cut_gap - q) w3 = (tile_x0 - coil_pitch_h + tile_width/2 + cut_gap + 2*q), (tile_y0 - tile_height/2 - cut_gap - q) w4 = (wx + p - coil_pitch_h), (wy - q) w5 = (wx - coil_pitch_h), (wy) b.add(cad_pr.Trace(join_trace_w, pads.pad(1), w5, waypoints=[w1, w2, w3, w4], orientation=['cw', 'cw', 'cw', 'cw'], side='top')) else: wx, wy, _r, _f = pads.pad(1).abs_pos w1 = (wx + p), (wy - q) w2 = (wx + p), (wy - coil_pitch_v + pad_pitch + q) w3 = wx, (wy - coil_pitch_v + pad_pitch) b.add(cad_pr.Trace(join_trace_w, pads.pad(1), w3, waypoints=[w1, w2], orientation=['cw', 'cw', 'cw'], side='bottom')) if y == rows-1: if x > 0 and x % 2 == 0: wx, wy, _r, _f = pads.pad(2).abs_pos w1 = (wx + p), (wy + q) w2 = (tile_x0 + tile_width/2 + cut_gap), (tile_y0 + tile_height/2 + cut_gap + q) w3 = (tile_x0 - coil_pitch_h + tile_width/2 + cut_gap + 2*q), (tile_y0 + tile_height/2 + cut_gap + q) w4 = (wx + p - coil_pitch_h), (wy + q) w5 = (wx - coil_pitch_h), (wy) b.add(cad_pr.Trace(join_trace_w, pads.pad(2), w5, waypoints=[w1, w2, w3, w4], orientation=['ccw', 'ccw', 'ccw', 'ccw', 'cw'], side='top')) elif x == 0: wx, wy, _r, _f = pads.pad(2).abs_pos w1 = (wx + p), (wy + q) w2 = (wx + p + q), (tile_y0 + tile_height/2 + cut_gap + q) w5 = (x0 + total_width/2 - pad_pitch/2), (w2[1]) b.add(cad_pr.Trace(join_trace_w, pads.pad(2), w5, waypoints=[w1, w2], orientation=['ccw', 'cw', 'ccw'], side='bottom')) elif x == cols-1: wx, wy, _r, _f = pads.pad(2).abs_pos w1 = (wx + p), (wy + q) w2 = (wx + p - q), (tile_y0 + tile_height/2 + cut_gap + q) w5 = (x0 + total_width/2 + pad_pitch/2), (w2[1]) b.add(cad_pr.Trace(join_trace_w, pads.pad(2), w5, waypoints=[w1, w2], orientation=['ccw', 'ccw', 'ccw'], side='bottom')) pads = make_pads(x0 + total_width/2, w5[1], 0, 2, pad_dia, pad_length, pad_drill, pad_pitch) b.add(pads) k = 3 for layer in ['F.SilkS', 'B.SilkS']: b.add(kc_gr.Rectangle(start=xy(x-k/2, y-pad_pitch-k/2), end=xy(x+k/2, y-pad_pitch), layer=layer, stroke=pcb.Stroke(width=0), fill=kc_gr.FillMode(pcb.Atom.solid))) b.add(pcb.Zone(layers=['F.Cu', 'B.Cu'], hatch=pcb.Hatch(), min_thickness=0.25, filled_areas_thickness=False, fill=pcb.ZoneFill(island_removal_mode=1, island_area_min=10), polygon=pcb.ZonePolygon(pts=pcb.PointList(xy=[ pcb.XYCoord(x0, y0), pcb.XYCoord(x0, y0+total_height), pcb.XYCoord(x0+total_width, y0+total_height), pcb.XYCoord(x0+total_width, y0)])))) for x in range(0, cols): tile_x0 = x0 + tooling_border + cut_gap + x*coil_pitch_h tile_y0 = y0 + tooling_border + cut_gap w = coil_dia + coil_border*2 y1 = y0 + total_height - tooling_border - cut_gap b.add(pcb.Zone(layers=['F.Cu', 'B.Cu'], hatch=pcb.Hatch(), min_thickness=0.25, filled_areas_thickness=False, fill=pcb.ZoneFill(island_removal_mode=1, island_area_min=10), keepout=pcb.ZoneKeepout(copperpour_allowed=False), polygon=pcb.ZonePolygon(pts=pcb.PointList(xy=[ pcb.XYCoord(tile_x0, tile_y0), pcb.XYCoord(tile_x0, y1), pcb.XYCoord(tile_x0+w, y1), pcb.XYCoord(tile_x0+w, tile_y0)])))) b.write('coil_test_board.kicad_pcb')