From 57941b1b76ffbdb9a5eeb9fef5e3c2365e3a4b84 Mon Sep 17 00:00:00 2001 From: jaseg Date: Sat, 5 Feb 2022 12:34:28 +0100 Subject: Arc approx WIP --- gerbonara/graphic_objects.py | 42 +++++++++++++++++++++++++++++++++++++++++- gerbonara/ipc356.py | 7 +++++++ gerbonara/rs274x.py | 3 +++ 3 files changed, 51 insertions(+), 1 deletion(-) (limited to 'gerbonara') diff --git a/gerbonara/graphic_objects.py b/gerbonara/graphic_objects.py index 97a39c8..c14b411 100644 --- a/gerbonara/graphic_objects.py +++ b/gerbonara/graphic_objects.py @@ -20,7 +20,7 @@ import math import copy from dataclasses import dataclass, KW_ONLY, astuple, replace, field, fields -from .utils import MM, InterpMode, to_unit +from .utils import MM, InterpMode, to_unit, rotate_point from . import graphic_primitives as gp @@ -556,6 +556,46 @@ class Arc(GraphicObject): """ return self.tool.plated + def approximate(self, max_error=1e-2, unit=MM, clip_max_error=True): + """ Approximate this :py:class:`~.graphic_objects.Arc` using a list of multiple + :py:class:`~.graphic_objects.Line` instances to the given precision. + + :param float max_error: Maximum approximation error in ``unit`` units. + :param unit: Either a :py:class:`.LengthUnit` instance or one of the strings ``'mm'`` or ``'inch'``. + :param bool clip_max_error: Clip max error such that at least a square is always rendered. + + :returns: list of :py:class:`~.graphic_objects.Line` instances. + :rtype: list + """ + # TODO the max_angle calculation below is a bit off -- we over-estimate the error, and thus produce finer + # results than necessary. Fix this. + + r = math.hypot(self.cx, self.cy) + + max_error = self.unit(max_error, unit) + if clip_max_error: + # 1 - math.sqrt(1 - 0.5*math.sqrt(2)) + max_error = min(max_error, r*0.4588038998538031) + + elif max_error >= r: + return [Line(*self.p1, *self.p2, aperture=self.aperture, polarity_dark=self.polarity_dark)] + + # see https://www.mathopenref.com/sagitta.html + l = math.sqrt(r**2 - (r - max_error)**2) + + angle_max = math.asin(l/r) + sweep_angle = self.sweep_angle() + num_segments = math.ceil(sweep_angle / angle_max) + angle = sweep_angle / num_segments + + if not self.clockwise: + angle = -angle + + cx, cy = self.center + points = [ rotate_point(self.x1, self.y1, i*angle, cx, cy) for i in range(num_segments + 1) ] + return [ Line(*p1, *p2, aperture=self.aperture, polarity_dark=self.polarity_dark) + for p1, p2 in zip(points[0::], points[1::]) ] + def _rotate(self, rotation, cx=0, cy=0): # rotate center first since we need old x1, y1 here new_cx, new_cy = gp.rotate_point(*self.center, rotation, cx, cy) diff --git a/gerbonara/ipc356.py b/gerbonara/ipc356.py index 175cb5e..ae341be 100644 --- a/gerbonara/ipc356.py +++ b/gerbonara/ipc356.py @@ -95,6 +95,13 @@ class Netlist(CamFile): for obj in self.objects: obj.rotate(angle, cx, cy, unit) + def __str__(self): + name = f'{self.original_path.name} ' if self.original_path else '' + return f'' + + def __repr__(self): + return str(self) + @property def objects(self): yield from self.test_records diff --git a/gerbonara/rs274x.py b/gerbonara/rs274x.py index b355bda..51a5e19 100644 --- a/gerbonara/rs274x.py +++ b/gerbonara/rs274x.py @@ -232,6 +232,9 @@ class GerberFile(CamFile): name = f'{self.original_path.name} ' if self.original_path else '' return f'' + def __repr__(self): + return str(self) + def save(self, filename, settings=None, drop_comments=True): """ Save this Gerber file to the file system. See :py:meth:`~.GerberFile.generate_gerber` for the meaning of the arguments. """ -- cgit