From a95aacac483e548ae6bfcf89564a84439c7e05fa Mon Sep 17 00:00:00 2001 From: jaseg Date: Tue, 4 Apr 2023 01:35:38 +0200 Subject: Add missing WIP changes --- gerbonara/aperture_macros/parse.py | 12 +++++++- gerbonara/apertures.py | 58 ++++++++++++++++++++++---------------- gerbonara/cad/primitives.py | 14 ++++++--- gerbonara/layers.py | 48 ++++++++++++++++++++----------- 4 files changed, 86 insertions(+), 46 deletions(-) diff --git a/gerbonara/aperture_macros/parse.py b/gerbonara/aperture_macros/parse.py index 727b950..f0ff8d6 100644 --- a/gerbonara/aperture_macros/parse.py +++ b/gerbonara/aperture_macros/parse.py @@ -166,7 +166,17 @@ class GenericMacros: rect = ApertureMacro('GNR', [ ap.CenterLine('mm', [1, var(1), var(2), 0, 0, var(5) * -deg_per_rad]), - *_generic_hole(3) ]) + *_generic_hole(3)]) + + # params: width, height, corner radius, *hole, rotation + rounded_rect = ApertureMacro('GRR', [ + ap.CenterLine('mm', [1, var(1)-2*var(3), var(2), 0, 0, var(6) * -deg_per_rad]), + ap.CenterLine('mm', [1, var(1), var(2)-2*var(3), 0, 0, var(6) * -deg_per_rad]), + ap.Circle('mm', [1, var(3)*2, +(var(1)/2-var(3)), +(var(2)/2-var(3)), 0]), + ap.Circle('mm', [1, var(3)*2, +(var(1)/2-var(3)), -(var(2)/2-var(3)), 0]), + ap.Circle('mm', [1, var(3)*2, -(var(1)/2-var(3)), +(var(2)/2-var(3)), 0]), + ap.Circle('mm', [1, var(3)*2, -(var(1)/2-var(3)), -(var(2)/2-var(3)), 0]), + *_generic_hole(4)]) # w must be larger than h obround = ApertureMacro('GNO', [ diff --git a/gerbonara/apertures.py b/gerbonara/apertures.py index d9137da..c792e0a 100644 --- a/gerbonara/apertures.py +++ b/gerbonara/apertures.py @@ -134,7 +134,7 @@ class Aperture: # we emulate this parameter. Our circle, rectangle and oblong classes below have a rotation parameter. Only at # export time during to_gerber, this parameter is evaluated. unit = settings.unit if settings else None - actual_inst = self._rotated() + actual_inst = self.rotated() params = 'X'.join(f'{float(par):.4}' for par in actual_inst._params(unit) if par is not None) if params: return f'{actual_inst._gerber_shape_code},{params}' @@ -204,7 +204,7 @@ class ExcellonTool(Aperture): offset = unit(offset, self.unit) return replace(self, diameter=self.diameter+2*offset) - def _rotated(self): + def rotated(self, angle=0): return self def to_macro(self): @@ -245,11 +245,11 @@ class CircleAperture(Aperture): offset = self.unit(offset, unit) return replace(self, diameter=self.diameter+2*offset, hole_dia=None, hole_rect_h=None) - def _rotated(self): - if math.isclose(self.rotation % (2*math.pi), 0) or self.hole_rect_h is None: + def rotated(self, angle=0): + if math.isclose((self.rotation+angle) % (2*math.pi), 0, abs_tol=1e-6) or self.hole_rect_h is None: return self else: - return self.to_macro(self.rotation) + return self.to_macro(self.rotation+angle) def scaled(self, scale): return replace(self, @@ -300,13 +300,14 @@ class RectangleAperture(Aperture): offset = self.unit(offset, unit) return replace(self, w=self.w+2*offset, h=self.h+2*offset, hole_dia=None, hole_rect_h=None) - def _rotated(self): - if math.isclose(self.rotation % math.pi, 0): + def rotated(self, angle=0): + angle += self.rotation + if math.isclose(angle % math.pi, 0): return self - elif math.isclose(self.rotation % math.pi, math.pi/2): + elif math.isclose(angle % math.pi, math.pi/2): return replace(self, w=self.h, h=self.w, **self._rotate_hole_90(), rotation=0) else: # odd angle - return self.to_macro() + return self.to_macro(angle) def scaled(self, scale): return replace(self, @@ -315,13 +316,13 @@ class RectangleAperture(Aperture): hole_dia=None if self.hole_dia is None else self.hole_dia*scale, hole_rect_h=None if self.hole_rect_h is None else self.hole_rect_h*scale) - def to_macro(self): + def to_macro(self, rotation=0): return ApertureMacroInstance(GenericMacros.rect, [MM(self.w, self.unit), MM(self.h, self.unit), MM(self.hole_dia, self.unit) or 0, MM(self.hole_rect_h, self.unit) or 0, - self.rotation]) + self.rotation + rotation]) def _params(self, unit=None): return _strip_right( @@ -365,13 +366,13 @@ class ObroundAperture(Aperture): offset = self.unit(offset, unit) return replace(self, w=self.w+2*offset, h=self.h+2*offset, hole_dia=None, hole_rect_h=None) - def _rotated(self): - if math.isclose(self.rotation % math.pi, 0): + def rotated(self, angle=0): + if math.isclose((angle + self.rotation) % math.pi, 0, abs_tol=1e-6): return self - elif math.isclose(self.rotation % math.pi, math.pi/2): + elif math.isclose((angle + self.rotation) % math.pi, math.pi/2, abs_tol=1e-6): return replace(self, w=self.h, h=self.w, **self._rotate_hole_90(), rotation=0) else: - return self.to_macro() + return self.to_macro(angle) def scaled(self, scale): return replace(self, @@ -380,12 +381,13 @@ class ObroundAperture(Aperture): hole_dia=None if self.hole_dia is None else self.hole_dia*scale, hole_rect_h=None if self.hole_rect_h is None else self.hole_rect_h*scale) - def to_macro(self): + def to_macro(self, rotation=0): # generic macro only supports w > h so flip x/y if h > w if self.w > self.h: inst = self else: - inst = replace(self, w=self.h, h=self.w, **self._rotate_hole_90(), rotation=self.rotation-90) + inst = replace(self, w=self.h, h=self.w, **self._rotate_hole_90(), rotation=rotation+self.rotation-90) + return ApertureMacroInstance(GenericMacros.obround, [MM(inst.w, self.unit), MM(inst.h, self.unit), @@ -433,8 +435,11 @@ class PolygonAperture(Aperture): flash = _flash_hole - def _rotated(self): - return self + def rotated(self, angle=0): + if angle != 0: + return replace(self, rotatio=self.rotation + angle) + else: + return self def scaled(self, scale): return replace(self, @@ -445,7 +450,10 @@ class PolygonAperture(Aperture): return ApertureMacroInstance(GenericMacros.polygon, self._params(MM)) def _params(self, unit=None): - rotation = self.rotation % (2*math.pi / self.n_vertices) if self.rotation is not None else None + rotation = self.rotation % (2*math.pi / self.n_vertices) + if math.isclose(rotation, 0, abs_tol=1-e6): + rotation = None + if self.hole_dia is not None: return self.unit.convert_to(unit, self.diameter), self.n_vertices, rotation, self.unit.convert_to(unit, self.hole_dia) elif rotation is not None and not math.isclose(rotation, 0): @@ -483,14 +491,14 @@ class ApertureMacroInstance(Aperture): def dilated(self, offset, unit=MM): return replace(self, macro=self.macro.dilated(offset, unit)) - def _rotated(self): - if math.isclose(self.rotation % (2*math.pi), 0): + def rotated(self, angle=0): + if math.isclose((self.rotation+angle) % (2*math.pi), 0): return self else: - return self.to_macro() + return self.to_macro(angle) - def to_macro(self): - return replace(self, macro=self.macro.rotated(self.rotation), rotation=0) + def to_macro(self, rotation=0): + return replace(self, macro=self.macro.rotated(self.rotation+rotation), rotation=0) def scaled(self, scale): return replace(self, macro=self.macro.scaled(scale)) diff --git a/gerbonara/cad/primitives.py b/gerbonara/cad/primitives.py index f757e67..d232d20 100644 --- a/gerbonara/cad/primitives.py +++ b/gerbonara/cad/primitives.py @@ -167,10 +167,16 @@ class Trace: else: yield (sgn(dx)*abs(dy), dy) else: # self.style == 'ortho' - pass - #if p == (orientation == 'cw'): - - #else: + if p == (orientation == 'cw'): + if abs(dy) > abs(dx): + yield (0, dy) + else: + yield (dx, 0) + else: + if abs(dy) > abs(dx): + yield (dx, 0) + else: + yield (0, dy) yield p2 diff --git a/gerbonara/layers.py b/gerbonara/layers.py index a25a6a9..6200ef5 100644 --- a/gerbonara/layers.py +++ b/gerbonara/layers.py @@ -261,6 +261,16 @@ def _layername_autoguesser(fn): return f'{side} {use}' +def _sort_layername(val): + (side, use), _layer = val + if side == 'top': + return -1 + if side == 'bottom': + return 1e99 + assert side.startswith('inner_') + return int(side[len('inner_'):]) + + class LayerStack: """ :py:class:`LayerStack` represents a set of Gerber files that describe different layers of the same board. @@ -279,7 +289,16 @@ class LayerStack: :py:obj:`"altium"` """ - def __init__(self, graphic_layers, drill_pth=None, drill_npth=None, drill_layers=(), netlist=None, board_name=None, original_path=None, was_zipped=False, generator=None): + def __init__(self, graphic_layers=None, drill_pth=None, drill_npth=None, drill_layers=(), netlist=None, board_name=None, original_path=None, was_zipped=False, generator=None): + if not drill_layers and (graphic_layers, drill_pth, drill_npth) == (None, None, None): + graphic_layers = {tuple(layer.split()): GerberFile() + for layer in ('top paste', 'top silk', 'top mask', 'top copper', + 'bottom copper', 'bottom mask', 'bottom silk', 'bottom paste', + 'mechanical outline')} + + drill_pth = ExcellonFile(plated=True) + drill_npth = ExcellonFile(plated=False) + self.graphic_layers = graphic_layers self.drill_pth = drill_pth self.drill_npth = drill_npth @@ -926,24 +945,21 @@ class LayerStack: elif isinstance(index, tuple): return self.graphic_layers[index] - return self.copper_layers[index] + return self.copper_layers[index][1] @property def copper_layers(self): - """ Return all copper layers of this board as a list. Returns an empty list if the board does not have any - copper layers. """ - copper_layers = [ ((side, use), layer) for (side, use), layer in self.graphic_layers.items() if use == 'copper' ] - - def sort_layername(val): - (side, use), _layer = val - if side == 'top': - return -1 - if side == 'bottom': - return 1e99 - assert side.startswith('inner_') - return int(side[len('inner_'):]) - - return [ layer for _key, layer in sorted(copper_layers, key=sort_layername) ] + """ Return all copper layers of this board as a list of ((side, use), layer) tuples. Returns an empty list if + the board does not have any copper layers. """ + layers = [((side, use), layer) for (side, use), layer in self.graphic_layers.items() if use == 'copper'] + return sorted(layers, key=_sort_layername) + + @property + def inner_layers(self): + """ Return all inner copper layers of this board as a list of ((side, use), layer) tuples. Returns an empty list + if the board does not have any inner layers. """ + layers = [((side, use), layer) for (side, use), layer in self.graphic_layers.items() if side.startswith('inner')] + return sorted(layers, key=_sort_layername) @property def top_side(self): -- cgit