diff --git a/src/kicoil/cli.py b/src/kicoil/cli.py index 4e2ea28..1bc8611 100644 --- a/src/kicoil/cli.py +++ b/src/kicoil/cli.py @@ -21,11 +21,12 @@ import os import sys from pathlib import Path import warnings +import math import click from gerbonara.layers import LayerStack -from .geometry import PlanarInductor, divisors, CircleShape, TrapezoidShape +from .geometry import PlanarInductor, divisors, CircleShape, SectorShape from .kicad import footprint_to_board from .svg import make_transparent_svg @@ -87,8 +88,10 @@ 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 raise click.ClickException(*e.args) + except: + import pdb + pdb.post_mortem() data = None if format == 'kicad-footprint': @@ -164,4 +167,18 @@ def circle(ctx, inner_diameter, outer_diameter, outfile): @click.pass_context def trapezoid(ctx, outfile, **kwargs): shape = TrapezoidShape(**kwargs) - ctx.obj['write'](shape, outfile) \ No newline at end of file + 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('--angle', type=float, default=45, help='Sector angle [deg]') +@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 sector(ctx, outfile, angle, **kwargs): + angle = math.radians(angle) + shape = SectorShape(angle=angle, **kwargs) + ctx.obj['write'](shape, outfile) diff --git a/src/kicoil/geometry.py b/src/kicoil/geometry.py index b81a121..b38a412 100644 --- a/src/kicoil/geometry.py +++ b/src/kicoil/geometry.py @@ -169,6 +169,8 @@ class OffsetShape(Shape): def compute_spiral(self, a1, a2, fn=None): # Skeletonator uses a t coordinate from 0 - 1 per revolution instead of a radian angle. points = list(self.sk.do_spiral(a1/(2*pi), a2/(2*pi), self.outer_radius, self.inner_radius)) + if a2 < a1: + points = points[::-1] arm_length = sum(dist(p1, p2) for p1, p2 in zip(points, points[1:])) return points, arm_length @@ -204,6 +206,35 @@ class TrapezoidShape(OffsetShape): return f'{self.width:.2f} x {self.height:.2f} mm, {self.offset:.2f} mm offset isosceles trapezoidal' +@dataclass +class SectorShape(OffsetShape): + inner_diameter: float + outer_diameter: float + angle: float + annular_width: float + arc_tolerance: float = 0.05 # mm + polygon: list = field(init=False) + + def __post_init__(self): + # Careful: The inner/outer radius properties are relative to the polygon center and are very different from these! + r1, r2 = self.inner_diameter / 2, self.outer_diameter/2 + n1 = ceil(pi / acos(1 - self.arc_tolerance/self.inner_diameter) * self.angle / (2*pi)) + n2 = ceil(pi / acos(1 - self.arc_tolerance/self.outer_diameter) * self.angle / (2*pi)) + # center on y axis + pt = lambda r, a: (r*sin(a), r*cos(a)) + self.polygon = [pt(r2, self.angle/2 - i/n1 * self.angle) for i in range(n1+1)] + self.polygon += [pt(r1, i/n1 * self.angle - self.angle/2) for i in range(n1+1)] + super().__post_init__() + + @property + def slug(self): + return f'sector_{self.outer_diameter:.2f}x{self.inner_diameter:.2f}_{degrees(self.angle):.0f}deg' + + @property + def desc(self): + return f'{self.outer_diameter:.2f} x {self.inner_diameter:.2f} mm {degrees(self.angle):.0f} deg sector' + + @dataclass class PlanarInductor(): shape: Shape