summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2023-06-14 17:29:26 +0200
committerjaseg <git@jaseg.de>2023-06-14 17:29:26 +0200
commita92177904ed165d40cc702e8dbcd849979f76709 (patch)
tree1f4c2e871d930ea01193bad93ac141dbfa1ff09b
parent0148db6249eee8721393344772040ead52666dd1 (diff)
downloadgerbonara-a92177904ed165d40cc702e8dbcd849979f76709.tar.gz
gerbonara-a92177904ed165d40cc702e8dbcd849979f76709.tar.bz2
gerbonara-a92177904ed165d40cc702e8dbcd849979f76709.zip
Coil gen done
-rw-r--r--coil_gen.py103
-rw-r--r--gerbonara/cad/kicad/footprints.py5
2 files changed, 98 insertions, 10 deletions
diff --git a/coil_gen.py b/coil_gen.py
index 16d3174..0c8d5ca 100644
--- a/coil_gen.py
+++ b/coil_gen.py
@@ -1,14 +1,21 @@
#!/usr/bin/env python3
+import subprocess
+import sys
+import os
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 footprints as kicad_fp
from gerbonara.cad.kicad import graphical_primitives as kicad_gr
import click
+__version__ = '1.0.0'
+
+
def point_line_distance(p, l1, l2):
x0, y0 = p
x1, y1 = l1
@@ -37,21 +44,50 @@ def angle_between_vectors(va, vb):
@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.argument('infile', required=False, type=click.Path(exists=True, dir_okay=False, path_type=Path))
+@click.argument('outfile', required=False, type=click.Path(writable=True, dir_okay=False, path_type=Path))
+@click.option('--footprint-name', help="Name for the generated footprint. Default: Output file name sans extension.")
+@click.option('--target-layer', default='F.Cu', help="Target KiCad layer for the generated footprint. Default: F.Cu.")
@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('--windings', type=int, default=5, help='Number of windings to generate')
@click.option('--trace-width', type=float, default=0.15)
@click.option('--clearance', type=float, default=0.15)
-def generate(infile, outfile, polygon, start_angle, windings, trace_width, clearance):
- board = kicad_pcb.Board.open(infile)
+@click.option('--clipboard/--no-clipboard', help='Use clipboard integration (requires wl-clipboard)')
+@click.option('--counter-clockwise/--clockwise', help='Direction of generated spiral. Default: clockwise when wound from the inside.')
+def generate(infile, outfile, polygon, start_angle, windings, trace_width, clearance, footprint_name, target_layer, clipboard, counter_clockwise):
+ if 'WAYLAND_DISPLAY' in os.environ:
+ copy, paste, cliputil = ['wl-copy'], ['wl-paste'], 'xclip'
+ else:
+ copy, paste, cliputil = ['xclip', '-i', '-sel', 'clipboard'], ['xclip', '-o', '-sel' 'clipboard'], 'wl-clipboard'
+
+ if clipboard:
+ try:
+ proc = subprocess.run(paste, capture_output=True, text=True, check=True)
+ except FileNotFoundError:
+ print(f'Error: --clipboard requires the {copy[0]} and {paste[0]} utilities from {cliputil} to be installed.', file=sys.stderr)
+ board = kicad_pcb.Board.load(proc.stdout)
+ elif not infile:
+ board = kicad_pcb.Board.load(sys.stdin.read())
+ else:
+ 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).')
+ print(f'Found {len(objs)} polygon(s).', file=sys.stderr)
poly = objs[polygon]
xy = [(pt.x, pt.y) for pt in poly.pts.xy]
+
+ if counter_clockwise:
+ xy = [(-x, y) for x, y in xy]
+
segments = list(zip(xy, xy[1:] + xy[:1]))
+ # normalize orientation, make xy counter-clockwise
+ if sum((x2 - x1) * (y2 + y1) for (x1, y1), (x2, y2) in segments) < 0:
+ print(f'Reversing polygon direction.', file=sys.stderr)
+ xy = xy[::-1]
+ 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
@@ -73,8 +109,6 @@ def generate(infile, outfile, polygon, start_angle, windings, trace_width, clear
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))
@@ -100,12 +134,12 @@ def generate(infile, outfile, polygon, start_angle, windings, trace_width, clear
dbg_lines1, dbg_lines2 = [], []
spiral_points = []
- dr_tot = 0
+ dr_tot = trace_width/2
for n in range(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
+ dr_tot_b = n * pitch + trace_width/2
xma, yma = ma
xna, yna = na
@@ -228,5 +262,56 @@ def generate(infile, outfile, polygon, start_angle, windings, trace_width, clear
f.write(f'<circle r="0.1" fill="red" stroke="none" cx="{cx}" cy="{cy}"/>\n')
f.write('</svg>\n')
+ if counter_clockwise:
+ spiral_points = [(-x, y) for x, y in spiral_points]
+
+ fp_lines = [
+ kicad_fp.Line(
+ start=kicad_fp.XYCoord(x=x1, y=y1),
+ end=kicad_fp.XYCoord(x=x2, y=y2),
+ layer=target_layer,
+ stroke=kicad_fp.Stroke(width=trace_width))
+ for (x1, y1), (x2, y2) in zip(spiral_points, spiral_points[1:])]
+
+ make_pad = lambda num, x, y: kicad_fp.Pad(
+ number=str(num),
+ type=kicad_fp.Atom.smd,
+ shape=kicad_fp.Atom.circle,
+ at=kicad_fp.AtPos(x=x, y=y),
+ size=kicad_fp.XYCoord(x=trace_width, y=trace_width),
+ layers=[target_layer],
+ clearance=clearance,
+ zone_connect=0,
+ )
+
+ if footprint_name:
+ name = footprint_name
+ elif outfile:
+ name = outfile.stem,
+ else:
+ name = 'generated_coil'
+
+ fp = kicad_fp.Footprint(
+ name=name,
+ generator=kicad_fp.Atom('GerbonaraCoilGenV1'),
+ layer='F.Cu',
+ descr=f"{windings} winding coil footprint generated by gerbonara'c Coil generator, version {__version__}",
+ clearance=clearance,
+ zone_connect=0,
+ lines=fp_lines,
+ pads=[make_pad(1, *spiral_points[0]), make_pad(2, *spiral_points[-1])],
+ )
+
+ if clipboard:
+ try:
+ print(f'Running {copy[0]}. Press Ctrl+C when you are done pasting.')
+ subprocess.run(copy, capture_output=True, text=True, check=True, input=fp.serialize())
+ except FileNotFoundError:
+ print(f'Error: --clipboard requires the {copy[0]} and {paste[0]} utilities from {cliputil} to be installed.', file=sys.stderr)
+ elif not outfile:
+ print(fp.serialize())
+ else:
+ fp.write(outfile)
+
if __name__ == '__main__':
generate()
diff --git a/gerbonara/cad/kicad/footprints.py b/gerbonara/cad/kicad/footprints.py
index 1be4d3c..a31d90f 100644
--- a/gerbonara/cad/kicad/footprints.py
+++ b/gerbonara/cad/kicad/footprints.py
@@ -604,7 +604,10 @@ class Footprint:
def write(self, filename=None):
with open(filename or self.original_filename, 'w') as f:
- f.write(build_sexp(sexp(self)))
+ f.write(self.serialize())
+
+ def serialize(self):
+ return build_sexp(sexp(type(self), self)[0])
@classmethod
def open_pretty(kls, pretty_dir, fp_name, *args, **kwargs):