From 299527fa7232b8c3865f4c845150b017da53308b Mon Sep 17 00:00:00 2001 From: jaseg Date: Fri, 12 Dec 2025 15:04:24 +0100 Subject: [PATCH] Polygon layout works pretty well now, add star shape --- src/kicoil/cli.py | 18 ++++++++-- src/kicoil/geometry.py | 74 +++++++++++++++++++++++++++----------- src/kicoil/skeletonator.py | 10 +++--- 3 files changed, 74 insertions(+), 28 deletions(-) diff --git a/src/kicoil/cli.py b/src/kicoil/cli.py index 0ae1480..fd8e2d4 100644 --- a/src/kicoil/cli.py +++ b/src/kicoil/cli.py @@ -26,7 +26,7 @@ import math import click from gerbonara.layers import LayerStack -from .geometry import PlanarInductor, divisors, CircleShape, SectorShape +from .geometry import PlanarInductor, divisors, CircleShape, SectorShape, StarShape from .kicad import footprint_to_board from .svg import make_transparent_svg @@ -88,7 +88,8 @@ def cli(ctx, footprint_name, clipboard, single_layer, arc_tolerance, circle_segm footprint = model.render_footprint(footprint_name, arc_tolerance, circle_segments) except ValueError as e: - raise click.ClickException(*e.args) + #raise click.ClickException(*e.args) + raise data = None if format == 'kicad-footprint': @@ -179,3 +180,16 @@ def sector(ctx, outfile, angle, **kwargs): angle = math.radians(angle) shape = SectorShape(angle=angle, **kwargs) ctx.obj['write'](shape, outfile) + + +@cli.command() +@click.option('--inner-diameter', type=float, default=25, help='Inner diameter [mm]') +@click.option('--outer-diameter', type=float, default=50, help='Outer diameter [mm]') +@click.option('--points', type=int, default=5, help='Number of points') +@click.option('--arc-tolerance', type=float, default=0.05, help='Tolerance for splitting arc into straight segments [mm] (default: 0.05 mm)') +@click.option('--annular-width', type=float, default=5, help='Width of the trace area on the outside of the shape [mm]') +@click.argument('outfile', required=False, type=click.Path(writable=True, dir_okay=False, path_type=Path)) +@click.pass_context +def star(ctx, outfile, **kwargs): + shape = StarShape(**kwargs) + ctx.obj['write'](shape, outfile) diff --git a/src/kicoil/geometry.py b/src/kicoil/geometry.py index b4b6406..884b9c4 100644 --- a/src/kicoil/geometry.py +++ b/src/kicoil/geometry.py @@ -136,7 +136,7 @@ class CircleShape(Shape): points.append((xn, yn)) dists.append(dist((xp, yp), (xn, yn))) - return points, sum(dists), None + return points, sum(dists), [None]*len(points) def project_point(self, r, a, r_ref=None): @@ -182,7 +182,7 @@ class OffsetShape(Shape): def project_point(self, r, a, r_ref=None): # Skeletonator uses a t coordinate from 0 - 1 per revolution instead of a radian angle. - return self.sk.project_point(a/(2*pi), r, r_ref=r_ref) + return self.sk.project_point(a/(2*pi) % 1, r, r_ref=r_ref) def offset_exterior(self, margin): @@ -240,6 +240,32 @@ class SectorShape(OffsetShape): return f'{self.outer_diameter:.2f} x {self.inner_diameter:.2f} mm {degrees(self.angle):.0f} deg sector' +@dataclass +class StarShape(OffsetShape): + inner_diameter: float + outer_diameter: float + annular_width: float + points: int = 5 + arc_tolerance: float = 0.05 # mm + polygon: list = field(init=False) + + def __post_init__(self): + # center on y axis + pt = lambda r, a: (-r*sin(a), r*cos(a)) + circle = lambda r, n, phase: [pt(r, (i + phase)*2*pi/n) for i in range(n)] + self.polygon = [x for pair in zip(circle(self.outer_diameter/2, self.points, 0), circle(self.inner_diameter/2, self.points, 0.5)) for x in pair] + super().__post_init__() + + @property + def slug(self): + return f'star_{self.outer_diameter:.2f}x{self.inner_diameter:.2f}' + + @property + def desc(self): + purpose = ', for demonic purposes' if self.points == 5 else '' + return f'{self.outer_diameter:.2f} x {self.inner_diameter:.2f} mm star shape{purpose}' + + @dataclass class PlanarInductor(): shape: Shape @@ -436,12 +462,14 @@ class PlanarInductor(): else: # Add a straight connecting segment connecting the inner point to the outside of the spiral. - xq, yq = shape.project_point(shape.outer_radius, fold_angle) + ref = angle_refs_layer0[-1] + xq, yq = self.shape.project_point(self.shape.outer_radius, fold_angle, r_ref=ref) + angle_refs_layer1 = [ref, ref] points_layer1 = [(xn, yn), (xq, yq)] footprint.lines.append(kicad.make_line(xn, yn, xq, yq, self.trace_width, self.layer_pair[1])) - arms_layers[0].append(points_layer0) - arms_layers[1].append(points_layer1) + arms_layers[0].append((points_layer0, angle_refs_layer0)) + arms_layers[1].append((points_layer1, angle_refs_layer1)) for i in range(self.twists): start_angle = i*self.sector_angle @@ -454,11 +482,13 @@ class PlanarInductor(): if i%2 != 0: r -= 2*self.via_offset - xv, yv = self.shape.project_point(r, fold_angle, r_ref=ref_n) + points_layer0, refs_layer0 = arms_layers[0][i] + points_layer1, refs_layer1 = arms_layers[1][i] - if self.via_offset: - footprint.lines.append(kicad.make_line(*arms_layers[0][i][-1], xv, yv, self.trace_width, self.layer_pair[0])) - footprint.lines.append(kicad.make_line(xv, yv, *arms_layers[1][i][0], self.trace_width, self.layer_pair[1])) + xv, yv = self.shape.project_point(r, fold_angle, r_ref=refs_layer0[-1]) + + footprint.lines.append(kicad.make_line(*points_layer0[-1], xv, yv, self.trace_width, self.layer_pair[0])) + footprint.lines.append(kicad.make_line(xv, yv, *points_layer1[0], self.trace_width, self.layer_pair[1])) footprint.pads.append(kicad.make_via(xv, yv, self.via_diameter, self.via_drill, self.clearance, @@ -466,19 +496,21 @@ class PlanarInductor(): # Handle outer via ring and process staggering if enabled unless we are at the start of the coil, where we will # place pads below. + r = self.outer_via_ring_radius + + if self.stagger_outer_vias: + if i%2 != 0: + r += 2*self.via_offset + + points_layer0, refs_layer0 = arms_layers[0][i] + points_layer1, refs_layer1 = arms_layers[1][(i - self.turns) % self.twists] + + xv, yv = self.shape.project_point(r, start_angle, r_ref=refs_layer0[0]) + + footprint.lines.append(kicad.make_line(*points_layer0[0], xv, yv, self.trace_width, self.layer_pair[0])) + footprint.lines.append(kicad.make_line(*points_layer1[-1], xv, yv, self.trace_width, self.layer_pair[1])) + if i > 0: - r = self.outer_via_ring_radius - - if self.stagger_outer_vias: - if i%2 != 0: - r += 2*self.via_offset - - xv, yv = self.shape.project_point(r, start_angle, r_ref=ref_0) - - if self.via_offset: - footprint.lines.append(kicad.make_line(*arms_layers[0][i][0], xv, yv, self.trace_width, self.layer_pair[0])) - footprint.lines.append(kicad.make_line(*arms_layers[1][(i - self.turns) % self.twists][-1], xv, yv, self.trace_width, self.layer_pair[1])) - footprint.pads.append(kicad.make_via(xv, yv, self.via_diameter, self.via_drill, self.clearance, self.layer_pair)) diff --git a/src/kicoil/skeletonator.py b/src/kicoil/skeletonator.py index 09aa678..1c2b661 100644 --- a/src/kicoil/skeletonator.py +++ b/src/kicoil/skeletonator.py @@ -122,8 +122,8 @@ class Skeletonator: t_end = t_start + 1 r_outer = r_interpolate(t_start) r_inner = r_interpolate(t_end) - oc_arcs, outer_circumference = self.map_circumference(r_outer) - ic_arcs, inner_circumference = self.map_circumference(r_inner) + r_ref = min(r_inner, r_outer) # Handle outward spirals where the radii are swapped + _ic_arcs, inner_circumference = self.map_circumference(r_ref) angle = t_start circumference_angles = [] @@ -142,8 +142,8 @@ class Skeletonator: _arc, p2_proj = self.project_arc(p2, rp2) if approx_in_range(t1, tp1, tp2): - yield interpolate(p1_proj, p2_proj, t1, tp1, tp2), r_inner + yield interpolate(p1_proj, p2_proj, t1, tp1, tp2), r_ref if approx_in_range(t2, tp1, tp2): - yield interpolate(p1_proj, p2_proj, t2, tp1, tp2), r_inner + yield interpolate(p1_proj, p2_proj, t2, tp1, tp2), r_ref elif approx_in_range(tp2, t1, t2): - yield p2_proj, r_inner \ No newline at end of file + yield p2_proj, r_ref \ No newline at end of file