#!/usr/bin/env python3 from math import * from pathlib import Path from itertools import cycle from gerbonara.cad.kicad import pcb as kicad_pcb from gerbonara.cad.kicad import graphical_primitives as kicad_gr import click def point_line_distance(p, l1, l2): x0, y0 = p x1, y1 = l1 x2, y2 = l2 # https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line return abs((x2-x1)*(y1-y0) - (x1-x0)*(y2-y1)) / sqrt((x2-x1)**2 + (y2-y1)**2) def line_line_intersection(l1, l2): p1, p2 = l1 p3, p4 = l2 x1, y1 = p1 x2, y2 = p2 x3, y3 = p3 x4, y4 = p4 # https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection px = ((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4))/((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4)) py = ((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4))/((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4)) return px, py @click.command() @click.argument('infile', type=click.Path(exists=True, dir_okay=False, path_type=Path)) @click.argument('outfile', type=click.Path(writable=True, dir_okay=False, path_type=Path)) @click.option('--polygon', type=int, default=0, help="Use n'th polygon instead of first one. 0-based index.") @click.option('--start-angle', type=float, default=0, help='Angle for the start at the outermost layer of the spiral in degree') @click.option('--stop-radius', type=float, default=1, help='Inner radius of spiral') @click.option('--trace-width', type=float, default=0.15) @click.option('--clearance', type=float, default=0.15) def generate(infile, outfile, polygon, start_angle, stop_radius, trace_width, clearance): board = kicad_pcb.Board.open(infile) objs = [obj for obj in board.objects() if isinstance(obj, kicad_gr.Polygon)] print(f'Found {len(objs)} polygon(s).') poly = objs[polygon] xy = [(pt.x, pt.y) for pt in poly.pts.xy] segments = list(zip(xy, xy[1:] + xy[:1])) vbx, vby = min(x for x, y in xy), min(y for x, y, in xy) vbw, vbh = max(x for x, y in xy), max(y for x, y, in xy) vbw, vbh = vbw-vbx, vbh-vby vbx -= 5 vby -= 5 vbw += 10 vbh += 10 cx, cy = 0, 0 ls = 0 for (x1, y1), (x2, y2) in segments: l = dist((x1, y1), (x2, y2)) cx += x1*l/2 + x2*l/2 cy += y1*l/2 + y2*l/2 ls += l cx /= ls cy /= ls segment_angles = [(atan2(y1-cy, x1-cx) - atan2(y2-cy, x2-cx) + 2*pi) % (2*pi) for (x1, y1), (x2, y2) in segments] angle_strs = [f'{degrees(a):.2f}' for a in segment_angles] print(f'Segment angles: {" ".join(angle_strs)}') print(f'Sum of segment angles: {degrees(sum(segment_angles)):.2f}') segment_heights = [point_line_distance((cx, cy), (x1, y1), (x2, y2)) for (x1, y1), (x2, y2) in segments] segment_foo = list(zip(segment_heights, segments)) midpoints = [] for h, ((x1, y1), (x2, y2)) in segment_foo: xb = (x1 + x2) / 2 yb = (y1 + y2) / 2 midpoints.append((xb, yb)) normals = [] for h, ((x1, y1), (x2, y2)) in segment_foo: d12 = dist((x1, y1), (x2, y2)) dx = x2 - x1 dy = y2 - y1 normals.append((-dy/d12, dx/d12)) smallest_radius = min(segment_heights) #trace_radius = smallest_radius - stop_radius trace_radius = smallest_radius num_windings = floor((trace_radius - trace_width) / (clearance + trace_width)) print(f'Going for {num_windings} windings') segment_foo = list(zip(segment_heights, segments, segment_angles, midpoints, normals)) dbg_lines1, dbg_lines2 = [], [] spiral_points = [] dr_tot = 0 for n in range(num_windings): for (ha, (pa1, pa2), aa, ma, na), (hb, (pb1, pb2), ab, mb, nb) in zip(segment_foo[-1:] + segment_foo[:-1], segment_foo): pitch = clearance + trace_width dr_tot_a = dr_tot dr_tot_b = dr_tot + ab/(2*pi) * pitch xma, yma = ma xna, yna = na xmb, ymb = mb xnb, ynb = nb xa1, ya1 = pa1 xa2, ya2 = pa2 xb1, yb1 = pb1 xb2, yb2 = pb2 dma = dist(pa2, ma) dmb = dist(pb1, mb) x_cons_a, y_cons_a = p_cons_a = line_line_intersection((pa2, (cx, cy)), (ma, (xma-xna, yma-yna))) d_cons_a = dist(p_cons_a, ma) qa = dma * dr_tot_a / d_cons_a dra = hypot(qa, dr_tot_a) nrax = (xa2 - cx) / dist((cx, cy), pa2) nray = (ya2 - cy) / dist((cx, cy), pa2) xea = xa2 - nrax*dra yea = ya2 - nray*dra x_cons_b, y_cons_b = p_cons_b = line_line_intersection((pb1, (cx, cy)), (mb, (xmb-xnb, ymb-ynb))) d_cons_b = dist(p_cons_b, mb) qb = dmb * dr_tot_b / d_cons_b drb = hypot(qb, dr_tot_b) nrbx = (xb1 - cx) / dist((cx, cy), pb1) nrby = (yb1 - cy) / dist((cx, cy), pb1) xeb = xb1 - nrbx*drb yeb = yb1 - nrby*drb xsa = xma - xna*dr_tot_a ysa = yma - yna*dr_tot_a xsb = xmb - xnb*dr_tot_b ysb = ymb - ynb*dr_tot_b l1 = (xsa, ysa), (xea, yea) l2 = (xsb, ysb), (xeb, yeb) dbg_lines1.append(l1) dbg_lines2.append(l2) pic = line_line_intersection(l1, l2) spiral_points.append(pic) dr_tot = dr_tot_b #spiral_points = [] #r_now = 0 #for winding in range(num_windings): # for angle, ((x1, y1), (x2, y2)) in zip(segment_angles, segments): # angle_frac = angle/(2*pi) # d_r = angle_frac * (clearance + trace_width) # r_pt = dist((cx, cy), (x1, y1)) * (num_windings - winding) / num_windings # # x1, y1 = x1-cx, y1-cy # x2, y2 = x2-cx, y2-cy # l1, l2 = hypot(x1, y1), hypot(x2, y2) # x1, y1 = x1/l1, y1/l1 # x2, y2 = x2/l2, y2/l2 # # r_now += d_r # spiral_points.append((cx + x1*r_pt, cy + y1*r_pt)) path_d = ' '.join([f'M {xy[0][0]} {xy[0][1]}', *[f'L {x} {y}' for x, y in xy[1:]], 'Z']) path_d2 = ' '.join(f'M {cx} {cy} L {x} {y}' for x, y in xy) path_d3 = ' '.join([f'M {spiral_points[0][0]} {spiral_points[0][1]}', *[f'L {x} {y}' for x, y in spiral_points[1:]]]) with open('/tmp/test.svg', 'w') as f: f.write('\n') f.write('\n') f.write(f'>\n') f.write(f'\n') f.write(f'\n') f.write(f'\n') for (x1, y1), (x2, y2) in dbg_lines1: f.write(f'') for (x1, y1), (x2, y2) in dbg_lines2: f.write(f'') for x, y in midpoints: f.write(f'') f.write(f'\n') f.write(f'\n') f.write('\n') if __name__ == '__main__': generate()