From 27a992f1c8c0a37245168e23db160412494d0e18 Mon Sep 17 00:00:00 2001 From: jaseg Date: Sat, 1 Jan 2022 17:47:50 +0100 Subject: Add dilation code --- gerbonara/gerber/aperture_macros/expression.py | 41 ++++++++++++++++++++++++++ gerbonara/gerber/aperture_macros/parse.py | 14 +++++++++ gerbonara/gerber/aperture_macros/primitive.py | 29 ++++++++++++++++++ 3 files changed, 84 insertions(+) (limited to 'gerbonara/gerber/aperture_macros') diff --git a/gerbonara/gerber/aperture_macros/expression.py b/gerbonara/gerber/aperture_macros/expression.py index 390b7b7..fb399d3 100644 --- a/gerbonara/gerber/aperture_macros/expression.py +++ b/gerbonara/gerber/aperture_macros/expression.py @@ -90,6 +90,47 @@ class UnitExpression(Expression): else: raise ValueError(f'invalid unit {unit}, must be "inch" or "mm".') + def __add__(self, other): + if not isinstance(other, UnitExpression): + raise ValueError('Unit mismatch: Can only add/subtract UnitExpression from UnitExpression, not scalar.') + + if self.unit == other.unit or self.unit is None or other.unit is None: + return UnitExpression(self._expr + other._expr, self.unit) + + if other.unit == 'mm': # -> and self.unit == 'inch' + return UnitExpression(self._expr + (other._expr / MILLIMETERS_PER_INCH), self.unit) + else: # other.unit == 'inch' and self.unit == 'mm' + return UnitExpression(self._expr + (other._expr * MILLIMETERS_PER_INCH), self.unit) + + def __radd__(self, other): + # left hand side cannot have been an UnitExpression or __radd__ would not have been called + raise ValueError('Unit mismatch: Can only add/subtract UnitExpression from UnitExpression, not scalar.') + + def __sub__(self, other): + return (self + (-other)).optimize() + + def __rsub__(self, other): + # see __radd__ above + raise ValueError('Unit mismatch: Can only add/subtract UnitExpression from UnitExpression, not scalar.') + + def __mul__(self, other): + return UnitExpression(self._expr * other, self.unit) + + def __rmul__(self, other): + return UnitExpression(other * self._expr, self.unit) + + def __truediv__(self, other): + return UnitExpression(self._expr / other, self.unit) + + def __rtruediv__(self, other): + return UnitExpression(other / self._expr, self.unit) + + def __neg__(self): + return UnitExpression(-self._expr, self.unit) + + def __pos__(self): + return self + class ConstantExpression(Expression): def __init__(self, value): diff --git a/gerbonara/gerber/aperture_macros/parse.py b/gerbonara/gerber/aperture_macros/parse.py index 86f2882..00227c6 100644 --- a/gerbonara/gerber/aperture_macros/parse.py +++ b/gerbonara/gerber/aperture_macros/parse.py @@ -98,6 +98,20 @@ class ApertureMacro: def __hash__(self): return hash(self.to_gerber()) + def dilated(self, offset, unit='mm'): + dup = copy.deepcopy(self) + new_primitives = [] + for primitive in dup.primitives: + try: + if primitive.exposure.calculate(): + primitive.dilate(offset, unit) + new_primitives.append(primitive) + except IndexError: + warnings.warn('Cannot dilate aperture macro primitive with exposure value computed from macro variable.') + pass + dup.primitives = new_primitives + return dup + def to_gerber(self, unit=None): comments = [ c.to_gerber() for c in self.comments ] variable_defs = [ f'${var.to_gerber(unit)}={expr}' for var, expr in self.variables.items() ] diff --git a/gerbonara/gerber/aperture_macros/primitive.py b/gerbonara/gerber/aperture_macros/primitive.py index a587d7e..b28fdb5 100644 --- a/gerbonara/gerber/aperture_macros/primitive.py +++ b/gerbonara/gerber/aperture_macros/primitive.py @@ -4,6 +4,7 @@ # Copyright 2019 Hiroshi Murayama # Copyright 2022 Jan Götte +import warnings import contextlib import math @@ -20,6 +21,13 @@ def point_distance(a, b): def deg_to_rad(a): return (a / 180) * math.pi +def convert(value, src, dst): + if src == dst or src is None or dst is None or value is None: + return value + elif dst == 'mm': + return value * 25.4 + else: + return value / 25.4 class Primitive: def __init__(self, unit, args): @@ -88,6 +96,9 @@ class Circle(Primitive): x, y = x+offset[0], y+offset[1] return [ gp.Circle(x, y, calc.r, polarity_dark=bool(calc.exposure)) ] + def dilate(self, offset, unit): + self.diameter += UnitExpression(offset, unit) + class VectorLine(Primitive): code = 20 exposure : Expression @@ -112,6 +123,9 @@ class VectorLine(Primitive): return [ gp.Rectangle(center_x, center_y, length, calc.width, rotation=rotation, polarity_dark=bool(calc.exposure)) ] + def dilate(self, offset, unit): + self.width += UnitExpression(2*offset, unit) + class CenterLine(Primitive): code = 21 @@ -131,6 +145,9 @@ class CenterLine(Primitive): w, h = calc.width, calc.height return [ gp.Rectangle(x, y, w, h, rotation, polarity_dark=bool(calc.exposure)) ] + + def dilate(self, offset, unit): + self.width += UnitExpression(2*offset, unit) class Polygon(Primitive): @@ -151,6 +168,9 @@ class Polygon(Primitive): return [ gp.RegularPolygon(calc.x, calc.y, calc.diameter/2, calc.n_vertices, rotation, polarity_dark=bool(calc.exposure)) ] + def dilate(self, offset, unit): + self.diameter += UnitExpression(2*offset, unit) + class Thermal(Primitive): code = 7 @@ -178,6 +198,11 @@ class Thermal(Primitive): gp.Rectangle(x, y, gap_w, d_outer, rotation=rotation, polarity_dark=not dark), ] + def dilate(self, offset, unit): + # I'd rather print a warning and produce graphically slightly incorrect output in these few cases here than + # producing macros that may evaluate to primitives with negative values. + warnings.warn('Attempted dilation of macro aperture thermal primitive. This is not supported.') + class Outline(Primitive): code = 4 @@ -220,6 +245,10 @@ class Outline(Primitive): return gp.ArcPoly(bound_coords, bound_radii, polarity_dark=calc.exposure) + def dilate(self, offset, unit): + # we would need a whole polygon offset/clipping library here + warnings.warn('Attempted dilation of macro aperture outline primitive. This is not supported.') + class Comment: code = 0 -- cgit