From 5ce88e4d1b06dcc846c94ec614fb00f64e85c125 Mon Sep 17 00:00:00 2001 From: jaseg Date: Thu, 20 Apr 2023 00:46:30 +0200 Subject: Fix a bunch of bugs on the way to electroniceel's protoboard layout --- gerbonara/aperture_macros/expression.py | 5 ++++- gerbonara/aperture_macros/primitive.py | 6 +++--- gerbonara/apertures.py | 2 +- gerbonara/cad/kicad/footprints.py | 10 ++++++---- gerbonara/cad/kicad/graphical_primitives.py | 14 +++++++------- gerbonara/cad/protoboard.py | 22 ++++++++++++++++++++-- gerbonara/graphic_objects.py | 9 +++++---- 7 files changed, 46 insertions(+), 22 deletions(-) diff --git a/gerbonara/aperture_macros/expression.py b/gerbonara/aperture_macros/expression.py index f545aac..0b2168f 100644 --- a/gerbonara/aperture_macros/expression.py +++ b/gerbonara/aperture_macros/expression.py @@ -65,7 +65,10 @@ class Expression: class UnitExpression(Expression): def __init__(self, expr, unit): - self._expr = expr + if isinstance(expr, Expression): + self._expr = expr + else: + self._expr = ConstantExpression(expr) self.unit = unit def to_gerber(self, unit=None): diff --git a/gerbonara/aperture_macros/primitive.py b/gerbonara/aperture_macros/primitive.py index 5b93971..4a991f1 100644 --- a/gerbonara/aperture_macros/primitive.py +++ b/gerbonara/aperture_macros/primitive.py @@ -113,7 +113,7 @@ class VectorLine(Primitive): start_y : UnitExpression end_x : UnitExpression end_y : UnitExpression - rotation : Expression + rotation : Expression = None def to_graphic_primitives(self, offset, rotation, variable_binding={}, unit=None, polarity_dark=True): with self.Calculator(self, variable_binding, unit) as calc: @@ -243,7 +243,7 @@ class Outline(Primitive): if len(args) > 5004: raise ValueError(f'Invalid aperture macro outline primitive, too many points ({len(args)//2-2}).') - self.exposure = args.pop(0) + self.exposure = expr(args.pop(0)) # length arg must not contain variables (that would not make sense) length_arg = (args.pop(0) * ConstantExpression(1)).calculate() @@ -252,7 +252,7 @@ class Outline(Primitive): raise ValueError(f'Invalid aperture macro outline primitive, given size {length_arg} does not match length of coordinate list({len(args)//2-1}).') if len(args) % 2 == 1: - self.rotation = args.pop() + self.rotation = expr(args.pop()) else: self.rotation = ConstantExpression(0.0) diff --git a/gerbonara/apertures.py b/gerbonara/apertures.py index b411412..d5670a2 100644 --- a/gerbonara/apertures.py +++ b/gerbonara/apertures.py @@ -474,7 +474,7 @@ class ApertureMacroInstance(Aperture): macro : object #: The parameters to the :py:class:`.ApertureMacro`. All elements should be floats or ints. The first item in the #: list is parameter ``$1``, the second is ``$2`` etc. - parameters : list + parameters : list = field(default_factory=list) #: Aperture rotation in radians. When saving, a copy of the :py:class:`.ApertureMacro` is re-written with this #: rotation. rotation : float = 0 diff --git a/gerbonara/cad/kicad/footprints.py b/gerbonara/cad/kicad/footprints.py index 4b95d4e..8377961 100644 --- a/gerbonara/cad/kicad/footprints.py +++ b/gerbonara/cad/kicad/footprints.py @@ -22,7 +22,7 @@ from ... import graphic_primitives as gp from ... import graphic_objects as go from ... import apertures as ap from ...utils import MM -from ...aperture_macros.parse import GenericMacros +from ...aperture_macros.parse import GenericMacros, ApertureMacro @sexp_type('property') @@ -376,7 +376,7 @@ class Pad: dx, dy = self.rect_delta.x, self.rect_delta.y # Note: KiCad already uses MM units, so no conversion needed here. - return ApertureMacroInstance(GenericMacros.isosceles_trapezoid, + return ap.ApertureMacroInstance(GenericMacros.isosceles_trapezoid, [x+dx, y+dy, 2*max(dx, dy), 0, 0, # no hole @@ -385,7 +385,7 @@ class Pad: elif self.shape == Atom.roundrect: x, y = self.size.x, self.size.y r = min(x, y) * self.roundrect_rratio - return ApertureMacroInstance(GenericMacros.rounded_rect, + return ap.ApertureMacroInstance(GenericMacros.rounded_rect, [x, y, r, 0, 0, # no hole @@ -398,7 +398,7 @@ class Pad: for gn_obj in obj.render(): primitives += gn_obj._aperture_macro_primitives() # todo: precision params macro = ApertureMacro(primitives=primitives) - return ApertureMacroInstance(macro) + return ap.ApertureMacroInstance(macro) def render_drill(self): if not self.drill: @@ -548,6 +548,8 @@ class Footprint: for fe in obj.render(): fe.rotate(rotation) fe.offset(x, y, MM) + if isinstance(fe, go.Flash) and fe.aperture: + fe.aperture = fe.aperture.rotated(rotation) layer_stack[layer_map[layer]].objects.append(fe) for obj in self.pads: diff --git a/gerbonara/cad/kicad/graphical_primitives.py b/gerbonara/cad/kicad/graphical_primitives.py index ed40c96..0760342 100644 --- a/gerbonara/cad/kicad/graphical_primitives.py +++ b/gerbonara/cad/kicad/graphical_primitives.py @@ -8,7 +8,7 @@ from .primitives import * from ... import graphic_objects as go from ... import apertures as ap from ...newstroke import Newstroke -from ...utils import rotate_point +from ...utils import rotate_point, MM @sexp_type('layer') class TextLayer: @@ -84,7 +84,7 @@ class TextBox: if self.stroke.type not in (None, Atom.default, Atom.solid): raise ValueError('Dashed strokes are not supported on vector text') - yield from reg.outline_objects(aperture=CircleAperture(self.stroke.width, unit=MM)) + yield from reg.outline_objects(aperture=ap.CircleAperture(self.stroke.width, unit=MM)) yield reg @@ -137,7 +137,7 @@ class Rectangle: yield rect if self.width: - yield from rect.outline_objects(aperture=CircleAperture(self.width, unit=MM)) + yield from rect.outline_objects(aperture=ap.CircleAperture(self.width, unit=MM)) @sexp_type('gr_circle') @@ -177,7 +177,7 @@ class Arc: arc = go.Arc(x1, y1, x2, y2, cx-x1, cy-y1, unit=MM) if self.width: - arc.aperture = CircleAperture(self.width, unit=MM) + arc.aperture = ap.CircleAperture(self.width, unit=MM) yield arc if self.fill: @@ -189,14 +189,14 @@ class Polygon: pts: PointList = field(default_factory=PointList) layer: Named(str) = None width: Named(float) = None - fill: FillMode= False + fill: FillMode = True tstamp: Timestamp = None def render(self): reg = go.Region([(pt.x, pt.y) for pt in self.pts.xy], unit=MM) - if width: - yield from reg.outline_objects(aperture=CircleAperture(self.width, unit=MM)) + if self.width and self.width >= 0.005: + yield from reg.outline_objects(aperture=ap.CircleAperture(self.width, unit=MM)) if self.fill: yield reg diff --git a/gerbonara/cad/protoboard.py b/gerbonara/cad/protoboard.py index 3c523bc..9c3fbd7 100644 --- a/gerbonara/cad/protoboard.py +++ b/gerbonara/cad/protoboard.py @@ -6,10 +6,13 @@ import string import itertools from copy import copy, deepcopy import warnings +import importlib.resources from .primitives import * from ..graphic_objects import Region from ..apertures import RectangleAperture, CircleAperture +from .kicad import footprints as kfp +from . import data as package_data class ProtoBoard(Board): @@ -468,6 +471,21 @@ class PoweredProto(ObjectGroup): return unit.convert_bounds_from(self.unit, ((x-p, y-p), (x+p, y+p))) +class SpikyProto(ObjectGroup): + def __init__(self, pitch=None, drill=None, clearance=None, power_pad_dia=None, via_size=None, trace_width=None, unit=MM): + super().__init__(0, 0) + res = importlib.resources.files(package_data) + + self.fp_center = kfp.Footprint.load(res.joinpath('center-pad-spikes.kicad_mod').read_text(encoding='utf-8')) + self.objects.append(kfp.FootprintInstance(1.27, 1.27, self.fp_center, unit=MM)) + + self.fp_between = kfp.Footprint.load(res.joinpath('pad-between-spiked.kicad_mod').read_text(encoding='utf-8')) + self.objects.append(kfp.FootprintInstance(1.27, 0, self.fp_between, unit=MM)) + self.objects.append(kfp.FootprintInstance(0, 1.27, self.fp_between, rotation=math.pi/2, unit=MM)) + + self.pad = kfp.Footprint.load(res.joinpath('tht-0.8.kicad_mod').read_text(encoding='utf-8')) + self.objects.append(kfp.FootprintInstance(0, 0, self.pad, unit=MM)) + def convert_to_mm(value, unit): unitl = unit.lower() if unitl == 'mm': @@ -498,7 +516,7 @@ def eval_value(value, total_length=None): def _demo(): #pattern1 = PatternProtoArea(2.54, obj=THTPad.circle(0, 0, 0.9, 1.8, paste=False)) - pattern1 = PatternProtoArea(2.54, 3.84, obj=THTPad.obround(0, 0, 0.9, 1.8, 2.5, paste=False)) + pattern1 = PatternProtoArea(2.54, 2.54, obj=SpikyProto()) pattern2 = PatternProtoArea(1.2, 2.0, obj=SMDPad.rect(0, 0, 1.0, 1.8, paste=False)) pattern3 = PatternProtoArea(2.54, 1.27, obj=SMDPad.rect(0, 0, 2.3, 1.0, paste=False)) #pattern3 = EmptyProtoArea(copper_fill=True) @@ -511,7 +529,7 @@ def _demo(): #pattern = PatternProtoArea(2.54*1.5, obj=THTFlowerProto()) #pattern = PatternProtoArea(2.54, obj=THTPad.circle(0, 0, 0.9, 1.8, paste=False)) #pattern = PatternProtoArea(2.54, obj=PoweredProto()) - pb = ProtoBoard(100, 80, pattern, mounting_hole_dia=3.2, mounting_hole_offset=5) + pb = ProtoBoard(30, 30, pattern1, mounting_hole_dia=3.2, mounting_hole_offset=5) print(pb.pretty_svg()) pb.layer_stack().save_to_directory('/tmp/testdir') diff --git a/gerbonara/graphic_objects.py b/gerbonara/graphic_objects.py index 79acd13..0d28045 100644 --- a/gerbonara/graphic_objects.py +++ b/gerbonara/graphic_objects.py @@ -370,7 +370,7 @@ class Region(GraphicObject): if points[-1] != points[0]: points.append(points[0]) - yield amp.Outline(self.unit, int(self.polarity_dark), len(points)-1, *(coord for p in points for coord in p)) + yield amp.Outline(self.unit, [int(self.polarity_dark), len(points)-1, *(coord for p in points for coord in p)]) def to_primitives(self, unit=None): if unit == self.unit: @@ -499,9 +499,10 @@ class Line(GraphicObject): def _aperture_macro_primitives(self): obj = self.converted(MM) # Gerbonara aperture macros use MM units. - yield amp.VectorLine(int(self.polarity_dark), obj.width, obj.x1, obj.y1, obj.x2, obj.y2) - yield amp.Circle(int(self.polarity_dark), obj.width, obj.x1, obj.y1) - yield amp.Circle(int(self.polarity_dark), obj.width, obj.x2, obj.y2) + width = obj.aperture.equivalent_width(MM) + yield amp.VectorLine(MM, [int(self.polarity_dark), width, obj.x1, obj.y1, obj.x2, obj.y2, 0]) + yield amp.Circle(MM, [int(self.polarity_dark), width, obj.x1, obj.y1]) + yield amp.Circle(MM, [int(self.polarity_dark), width, obj.x2, obj.y2]) def to_statements(self, gs): yield from gs.set_polarity(self.polarity_dark) -- cgit