summaryrefslogtreecommitdiff
path: root/gerbonara/gerber/aperture_macros
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2021-12-28 21:40:22 +0100
committerjaseg <git@jaseg.de>2021-12-28 21:40:22 +0100
commit63e1eae8d81cb7940d3547511488f8ec4acd4d1c (patch)
treec0d2ddf93d8637d0df600a320cbf9d1387860163 /gerbonara/gerber/aperture_macros
parent25dd65fac05a43ef75fe75049d5b79a73a207fc0 (diff)
downloadgerbonara-63e1eae8d81cb7940d3547511488f8ec4acd4d1c.tar.gz
gerbonara-63e1eae8d81cb7940d3547511488f8ec4acd4d1c.tar.bz2
gerbonara-63e1eae8d81cb7940d3547511488f8ec4acd4d1c.zip
WIP
Diffstat (limited to 'gerbonara/gerber/aperture_macros')
-rw-r--r--gerbonara/gerber/aperture_macros/expression.py37
-rw-r--r--gerbonara/gerber/aperture_macros/primitive.py204
2 files changed, 172 insertions, 69 deletions
diff --git a/gerbonara/gerber/aperture_macros/expression.py b/gerbonara/gerber/aperture_macros/expression.py
index 74fbd90..ddd8d53 100644
--- a/gerbonara/gerber/aperture_macros/expression.py
+++ b/gerbonara/gerber/aperture_macros/expression.py
@@ -8,6 +8,10 @@ import re
import ast
+def expr(obj):
+ return obj if isinstance(obj, Expression) else ConstantExpression(obj)
+
+
class Expression(object):
@property
def value(self):
@@ -28,6 +32,35 @@ class Expression(object):
raise IndexError(f'Cannot fully resolve expression due to unresolved variables: {expr} with variables {variable_binding}')
return expr.value
+ def __add__(self, other):
+ return OperatorExpression(operator.add, self, expr(other)).optimized()
+
+ def __radd__(self, other):
+ return expr(other) + self
+
+ def __sub__(self, other):
+ return OperatorExpression(operator.sub, self, expr(other)).optimized()
+
+ def __rsub__(self, other):
+ return expr(other) - self
+
+ def __mul__(self, other):
+ return OperatorExpression(operator.mul, self, expr(other)).optimized()
+
+ def __rmul__(self, other):
+ return expr(other) * self
+
+ def __truediv__(self, other):
+ return OperatorExpression(operator.truediv, self, expr(other)).optimized()
+
+ def __rtruediv__(self, other):
+ return expr(other) / self
+
+ def __neg__(self):
+ return 0 - self
+
+ def __pos__(self):
+ return self
class UnitExpression(Expression):
def __init__(self, expr, unit):
@@ -50,10 +83,10 @@ class UnitExpression(Expression):
return self._expr
elif unit == 'mm':
- return OperatorExpression.mul(self._expr, MILLIMETERS_PER_INCH)
+ return self._expr * MILLIMETERS_PER_INCH
elif unit == 'inch':
- return OperatorExpression.div(self._expr, MILLIMETERS_PER_INCH)
+ return self._expr / MILLIMETERS_PER_INCH)
else:
raise ValueError('invalid unit, must be "inch" or "mm".')
diff --git a/gerbonara/gerber/aperture_macros/primitive.py b/gerbonara/gerber/aperture_macros/primitive.py
index f4400f5..aeb38c4 100644
--- a/gerbonara/gerber/aperture_macros/primitive.py
+++ b/gerbonara/gerber/aperture_macros/primitive.py
@@ -2,24 +2,37 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Hiroshi Murayama <opiopan@gmail.com>
+# Copyright 2022 Jan Götte <gerbonara@jaseg.de>
+
+import contextlib
+import math
+
+from expression import Expression, UnitExpression, ConstantExpression, expr
+
+from .. import graphic_primitivese as gp
+
+
+def point_distance(a, b):
+ x1, y1 = a
+ x2, y2 = b
+ return math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
+
+def deg_to_rad(a):
+ return (a / 180) * math.pi
-from dataclasses import dataclass, fields
-from expression import Expression, UnitExpression, ConstantExpression
class Primitive:
- def __init__(self, unit, args, is_abstract):
+ def __init__(self, unit, args):
self.unit = unit
- self.is_abstract = is_abstract
if len(args) > len(type(self).__annotations__):
raise ValueError(f'Too many arguments ({len(args)}) for aperture macro primitive {self.code} ({type(self)})')
for arg, (name, fieldtype) in zip(args, type(self).__annotations__.items()):
- if is_abstract:
- if fieldtype == UnitExpression:
- setattr(self, name, UnitExpression(arg, unit))
- else:
- setattr(self, name, arg)
+ arg = expr(arg) # convert int/float to Expression object
+
+ if fieldtype == UnitExpression:
+ setattr(self, name, UnitExpression(arg, unit))
else:
setattr(self, name, arg)
@@ -28,8 +41,6 @@ class Primitive:
raise ValueError(f'Too few arguments ({len(args)}) for aperture macro primitive {self.code} ({type(self)})')
def to_gerber(self, unit=None):
- if not self.is_abstract:
- raise TypeError(f"Something went wrong, tried to gerber'ize bound aperture macro primitive {self}")
return self.code + ',' + ','.join(
getattr(self, name).to_gerber(unit) for name in type(self).__annotations__) + '*'
@@ -37,27 +48,42 @@ class Primitive:
attrs = ','.join(str(getattr(self, name)).strip('<>') for name in type(self).__annotations__)
return f'<{type(self).__name__} {attrs}>'
- def bind(self, variable_binding={}):
- if not self.is_abstract:
- raise TypeError('{type(self).__name__} object is already instantiated, cannot bind again.')
- # Return instance of the same class, but replace all attributes by their actual numeric values
- return type(self)(unit=self.unit, is_abstract=False, args=[
- getattr(self, name).calculate(variable_binding) for name in type(self).__annotations__
- ])
+ @contextlib.contextmanager
+ class Calculator:
+ def __init__(self, instance, variable_binding={}, unit=None):
+ self.instance = instance
+ self.variable_binding = variable_binding
+ self.unit = unit
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, _type, _value, _traceback):
+ pass
-class CommentPrimitive(Primitive):
- code = 0
- comment : str
+ def __getattr__(self, name):
+ return getattr(self.instance, name).calculate(self.variable_binding, self.unit)
-class CirclePrimitive(Primitive):
+ def __call__(self, expr):
+ return expr.calculate(self.variable_binding, self.unit)
+
+
+class Circle(Primitive):
code = 1
exposure : Expression
diameter : UnitExpression
- center_x : UnitExpression
- center_y : UnitExpression
+ # center x/y
+ x : UnitExpression
+ y : UnitExpression
rotation : Expression = ConstantExpression(0.0)
-class VectorLinePrimitive(Primitive):
+ def to_graphic_primitives(self, offset, rotation, variable_binding={}, unit=None):
+ with self.Calculator(variable_binding, unit) as calc:
+ x, y = gp.rotate_point(calc.x, calc.y, deg_to_rad(calc.rotation) + rotation, 0, 0)
+ x, y = x+offset[0], y+offset[1]
+ return [ gp.Circle(x, y, calc.r, polarity_dark=bool(calc.exposure)) ]
+
+class VectorLine(Primitive):
code = 20
exposure : Expression
width : UnitExpression
@@ -67,40 +93,90 @@ class VectorLinePrimitive(Primitive):
end_y : UnitExpression
rotation : Expression
-class CenterLinePrimitive(Primitive):
+ def to_graphic_primitives(self, offset, rotation, variable_binding={}, unit=None):
+ with self.Calculator(variable_binding, unit) as calc:
+ center_x = (calc.end_x + calc.start_x) / 2
+ center_y = (calc.end_y + calc.start_y) / 2
+ delta_x = calc.end_x - calc.start_x
+ delta_y = calc.end_y - calc.start_y
+ length = point_distance((calc.start_x, calc.start_y), (calc.end_x, calc.end_y))
+
+ center_x, center_y = center_x+offset[0], center_y+offset[1]
+ rotation += deg_to_rad(calc.rotation) + math.atan2(delta_y, delta_x)
+
+ return [ gp.Rectangle(center_x, center_y, length, calc.width, rotation=rotation,
+ polarity_dark=bool(calc.exposure)) ]
+
+
+class CenterLine(Primitive):
code = 21
exposure : Expression
width : UnitExpression
height : UnitExpression
+ # center x/y
x : UnitExpression
y : UnitExpression
rotation : Expression
+ def to_graphic_primitives(self, variable_binding={}, unit=None):
+ with self.Calculator(variable_binding, unit) as calc:
+ rotation += deg_to_rad(calc.rotation)
+ x, y = gp.rotate_point(calc.x, calc.y, rotation, 0, 0)
+ x, y = x+offset[0], y+offset[1]
+ w, h = calc.width, calc.height
+
+ return [ gp.Rectangle(x, y, w, h, rotation, polarity_dark=bool(calc.exposure)) ]
+
-class PolygonPrimitive(Primitive):
+class Polygon(Primitive):
code = 5
exposure : Expression
n_vertices : Expression
- center_x : UnitExpression
- center_y : UnitExpression
+ # center x/y
+ x : UnitExpression
+ y : UnitExpression
diameter : UnitExpression
rotation : Expression
+ def to_graphic_primitives(self, offset, rotation, variable_binding={}, unit=None):
+ with self.Calculator(variable_binding, unit) as calc:
+ rotation += deg_to_rad(calc.rotation)
+ x, y = gp.rotate_point(calc.x, calc.y, rotation, 0, 0)
+ x, y = x+offset[0], y+offset[1]
+ return [ gp.RegularPolygon(calc.x, calc.y, calc.diameter/2, calc.n_vertices, rotation,
+ polarity_dark=bool(calc.exposure)) ]
+
-class ThermalPrimitive(Primitive):
+class Thermal(Primitive):
code = 7
- center_x : UnitExpression
- center_y : UnitExpression
+ # center x/y
+ x : UnitExpression
+ y : UnitExpression
d_outer : UnitExpression
d_inner : UnitExpression
gap_w : UnitExpression
rotation : Expression
+ def to_graphic_primitives(self, offset, rotation, variable_binding={}, unit=None):
+ with self.Calculator(variable_binding, unit) as calc:
+ rotation += deg_to_rad(calc.rotation)
+ x, y = gp.rotate_point(calc.x, calc.y, rotation, 0, 0)
+ x, y = x+offset[0], y+offset[1]
+
+ dark = bool(calc.exposure)
+
+ return [
+ gp.Circle(x, y, calc.d_outer/2, polarity_dark=dark),
+ gp.Circle(x, y, calc.d_inner/2, polarity_dark=not dark),
+ gp.Rectangle(x, y, d_outer, gap_w, rotation=rotation, polarity_dark=not dark),
+ gp.Rectangle(x, y, gap_w, d_outer, rotation=rotation, polarity_dark=not dark),
+ ]
-class OutlinePrimitive(Primitive):
+
+class Outline(Primitive):
code = 4
- def __init__(self, unit, args, is_abstract):
+ def __init__(self, unit, args):
if len(args) < 11:
raise ValueError(f'Invalid aperture macro outline primitive, not enough parameters ({len(args)}).')
if len(args) > 5004:
@@ -108,42 +184,36 @@ class OutlinePrimitive(Primitive):
self.exposure = args[0]
- if is_abstract:
- # length arg must not contain variabels (that would not make sense)
- length_arg = args[1].calculate()
-
- if length_arg != len(args)//2 - 2:
- raise ValueError(f'Invalid aperture macro outline primitive, given size does not match length of coordinate list({len(args)}).')
+ # length arg must not contain variables (that would not make sense)
+ length_arg = args[1].calculate()
- if len(args) % 1 != 1:
- self.rotation = args.pop()
- else:
- self.rotation = ConstantExpression(0.0)
+ if length_arg != len(args)//2 - 2:
+ raise ValueError(f'Invalid aperture macro outline primitive, given size does not match length of coordinate list({len(args)}).')
- if args[2] != args[-2] or args[3] != args[-1]:
- raise ValueError(f'Invalid aperture macro outline primitive, polygon is not closed {args[2:4], args[-3:-1]}')
+ if len(args) % 1 != 1:
+ self.rotation = args.pop()
+ else:
+ self.rotation = ConstantExpression(0.0)
- self.coords = [UnitExpression(arg, unit) for arg in args[1:]]
+ if args[2] != args[-2] or args[3] != args[-1]:
+ raise ValueError(f'Invalid aperture macro outline primitive, polygon is not closed {args[2:4], args[-3:-1]}')
- else:
- if len(args) % 1 != 1:
- self.rotation = args.pop()
- else:
- self.rotation = 0
+ self.coords = [(UnitExpression(x, unit), UnitExpression(y, unit)) for x, y in zip(args[1::2], args[2::2])]
- self.coords = args[1:]
-
def to_gerber(self, unit=None):
- if not self.is_abstract:
- raise TypeError(f"Something went wrong, tried to gerber'ize bound aperture macro primitive {self}")
coords = ','.join(coord.to_gerber(unit) for coord in self.coords)
return f'{self.code},{self.exposure.to_gerber()},{len(self.coords)//2-1},{coords},{self.rotation.to_gerber()}'
- def bind(self, variable_binding={}):
- if not self.is_abstract:
- raise TypeError('{type(self).__name__} object is already instantiated, cannot bind again.')
+ def to_graphic_primitives(self, offset, rotation, variable_binding={}, unit=None):
+ with self.Calculator(variable_binding, unit) as calc:
+ bound_coords = [ (calc(x)+offset[0], calc(y)+offset[1]) for x, y in self.coords ]
+ bound_radii = [None] * len(bound_coords)
+
+ rotation += deg_to_rad(calc.rotation)
+ bound_coords = [ rotate_point(*p, rotation, 0, 0) for p in bound_coords ]
+
+ return gp.ArcPoly(bound_coords, bound_radii, polarity_dark=calc.exposure)
- return OutlinePrimitive(self.unit, is_abstract=False, args=[None, *self.coords, self.rotation])
class Comment:
def __init__(self, comment):
@@ -154,13 +224,13 @@ class Comment:
PRIMITIVE_CLASSES = {
**{cls.code: cls for cls in [
- CommentPrimitive,
- CirclePrimitive,
- VectorLinePrimitive,
- CenterLinePrimitive,
- OutlinePrimitive,
- PolygonPrimitive,
- ThermalPrimitive,
+ Comment,
+ Circle,
+ VectorLine,
+ CenterLine,
+ Outline,
+ Polygon,
+ Thermal,
]},
# alternative codes
2: VectorLinePrimitive,