From 1f841ad71b9b43cf304649d45a5cdb8524751349 Mon Sep 17 00:00:00 2001 From: jaseg Date: Wed, 26 Apr 2023 00:35:37 +0200 Subject: Fix last failing tests. Rerun pending. --- gerbonara/aperture_macros/primitive.py | 2 +- gerbonara/apertures.py | 4 +-- gerbonara/cad/kicad/footprints.py | 57 ++++++++++++++++++++++++-------- gerbonara/graphic_objects.py | 10 ++++-- gerbonara/layers.py | 11 +++++- gerbonara/tests/test_kicad_footprints.py | 27 +++++++++------ 6 files changed, 81 insertions(+), 30 deletions(-) diff --git a/gerbonara/aperture_macros/primitive.py b/gerbonara/aperture_macros/primitive.py index 8d4bf4f..25c9bd1 100644 --- a/gerbonara/aperture_macros/primitive.py +++ b/gerbonara/aperture_macros/primitive.py @@ -242,7 +242,7 @@ class Outline(Primitive): code = 4 def __init__(self, unit, args): - if len(args) < 11: + if len(args) < 10: raise ValueError(f'Invalid aperture macro outline primitive, not enough parameters ({len(args)}).') if len(args) > 5004: raise ValueError(f'Invalid aperture macro outline primitive, too many points ({len(args)//2-2}).') diff --git a/gerbonara/apertures.py b/gerbonara/apertures.py index 415eeee..feccb7f 100644 --- a/gerbonara/apertures.py +++ b/gerbonara/apertures.py @@ -387,14 +387,14 @@ class ObroundAperture(Aperture): if self.w > self.h: inst = self else: - inst = replace(self, w=self.h, h=self.w, **self._rotate_hole_90(), rotation=rotation+self.rotation-90) + inst = replace(self, w=self.h, h=self.w, **self._rotate_hole_90(), rotation=self.rotation-math.pi/2) return ApertureMacroInstance(GenericMacros.obround, [MM(inst.w, self.unit), MM(inst.h, self.unit), MM(inst.hole_dia, self.unit) or 0, MM(inst.hole_rect_h, self.unit) or 0, - inst.rotation]) + inst.rotation + rotation]) def _params(self, unit=None): return _strip_right( diff --git a/gerbonara/cad/kicad/footprints.py b/gerbonara/cad/kicad/footprints.py index 13cad7d..4066384 100644 --- a/gerbonara/cad/kicad/footprints.py +++ b/gerbonara/cad/kicad/footprints.py @@ -400,14 +400,14 @@ class Pad: return ap.CircleAperture(self.size.x+2*margin, unit=MM) elif self.shape == Atom.rect: - if margin: + if margin > 0: return ap.ApertureMacroInstance(GenericMacros.rounded_rect, [self.size.x+2*margin, self.size.y+2*margin, margin, 0, 0, # no hole rotation], unit=MM) else: - return ap.RectangleAperture(self.size.x, self.size.y, unit=MM).rotated(rotation) + return ap.RectangleAperture(self.size.x+2*margin, self.size.y+2*margin, unit=MM).rotated(rotation) elif self.shape == Atom.oval: return ap.ObroundAperture(self.size.x+2*margin, self.size.y+2*margin, unit=MM).rotated(rotation) @@ -428,11 +428,12 @@ class Pad: dy = dx rotation -= math.pi/2 - if not margin: + if margin <= 0: # Note: KiCad already uses MM units, so no conversion needed here. + alpha = math.atan(y / (dy/2)) return ap.ApertureMacroInstance(GenericMacros.isosceles_trapezoid, - [x+dy, y, + [x+dy+margin*math.cos(alpha), y+margin, dy, 0, 0, # no hole rotation], unit=MM) @@ -447,26 +448,54 @@ class Pad: elif self.shape == Atom.roundrect: x, y = self.size.x, self.size.y r = min(x, y) * self.roundrect_rratio - return ap.ApertureMacroInstance(GenericMacros.rounded_rect, - [x+2*margin, y+2*margin, - r+margin, - 0, 0, # no hole - rotation], unit=MM) + if margin > -r: + return ap.ApertureMacroInstance(GenericMacros.rounded_rect, + [x+2*margin, y+2*margin, + r+margin, + 0, 0, # no hole + rotation], unit=MM) + else: + return ap.RectangleAperture(x+margin, y+margin, unit=MM).rotated(rotation) elif self.shape == Atom.custom: primitives = [] + # One round trip through the Gerbonara APIs, please! for obj in self.primitives.all(): for gn_obj in obj.render(): - primitives += gn_obj._aperture_macro_primitives() # todo: precision params + if margin and isinstance(gn_obj, (go.Line, go.Arc)): + gn_obj = gn_obj.dilated(margin) + + if isinstance(gn_obj, go.Region) and margin > 0: + for line in gn_obj.outline_objects(ap.CircleAperture(2*margin, unit=MM)): + primitives += line._aperture_macro_primitives() + + new_primitives = list(gn_obj._aperture_macro_primitives()) # todo: precision params + primitives += new_primitives + + # inexact, only works with convex shapes. But whatever, the only other way to do this would require + # an entire polygon clipping/offsetting library. Probably a bad choice to put something this complex + # into a file format. + if isinstance(gn_obj, go.Region) and margin < 0: + for line in gn_obj.outline_objects(ap.CircleAperture(2*margin, unit=MM)): + line.polarity_dark = False + primitives += line._aperture_macro_primitives() if self.options: if self.options.anchor == Atom.rect and self.size.x > 0 and self.size.y > 0: - primitives.append(amp.CenterLine(MM, [1, self.size.x, self.size.y, 0, 0, 0])) + if margin <= 0: + primitives.append(amp.CenterLine(MM, [1, self.size.x+2*margin, self.size.y+2*margin, 0, 0, 0])) - elif self.options.anchor == Atom.circle and self.size.x > 0: - primitives.append(amp.Circle(MM, [1, self.size.x, 0, 0, 0])) + else: # margin > 0 + primitives.append(amp.CenterLine(MM, [1, self.size.x+2*margin, self.size.y, 0, 0, 0])) + primitives.append(amp.CenterLine(MM, [1, self.size.x, self.size.y+2*margin, 0, 0, 0])) + primitives.append(amp.Circle(MM, [1, 2*margin, -self.size.x/2, -self.size.y/2])) + primitives.append(amp.Circle(MM, [1, 2*margin, -self.size.x/2, +self.size.y/2])) + primitives.append(amp.Circle(MM, [1, 2*margin, +self.size.x/2, -self.size.y/2])) + primitives.append(amp.Circle(MM, [1, 2*margin, +self.size.x/2, +self.size.y/2])) + elif self.options.anchor == Atom.circle and self.size.x > 0: + primitives.append(amp.Circle(MM, [1, self.size.x+2*margin, 0, 0, 0])) macro = ApertureMacro(primitives=primitives).rotated(rotation) return ap.ApertureMacroInstance(macro, unit=MM) @@ -686,6 +715,8 @@ LAYER_MAP_K2G = { 'F.CrtYd': ('top', 'courtyard'), 'B.Fab': ('bottom', 'fabrication'), 'F.Fab': ('top', 'fabrication'), + 'B.Adhes': ('bottom', 'adhesive'), + 'F.Adhes': ('top', 'adhesive'), 'Dwgs.User': ('mechanical', 'drawings'), 'Cmts.User': ('mechanical', 'comments'), 'Edge.Cuts': ('mechanical', 'outline'), diff --git a/gerbonara/graphic_objects.py b/gerbonara/graphic_objects.py index b9217bb..432a988 100644 --- a/gerbonara/graphic_objects.py +++ b/gerbonara/graphic_objects.py @@ -295,6 +295,9 @@ class Region(GraphicObject): def __bool__(self): return bool(self.outline) + def __str__(self): + return f'