summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gerbonara/aperture_macros/expression.py132
-rw-r--r--gerbonara/aperture_macros/parse.py2
-rw-r--r--gerbonara/aperture_macros/primitive.py9
3 files changed, 127 insertions, 16 deletions
diff --git a/gerbonara/aperture_macros/expression.py b/gerbonara/aperture_macros/expression.py
index 99d02eb..f096cf1 100644
--- a/gerbonara/aperture_macros/expression.py
+++ b/gerbonara/aperture_macros/expression.py
@@ -7,6 +7,7 @@ from dataclasses import dataclass
import operator
import re
import ast
+import math
from ..utils import LengthUnit, MM, Inch, MILLIMETERS_PER_INCH
@@ -61,7 +62,7 @@ class Expression:
return expr(other) / self
def __neg__(self):
- return 0 - self
+ return NegatedExpression(self)
def __pos__(self):
return self
@@ -155,9 +156,12 @@ class ConstantExpression(Expression):
return float(self.value)
def __eq__(self, other):
- return type(self) == type(other) and self.value == other.value
+ try:
+ return math.isclose(self.value, float(other), abs_tol=1e-9)
+ except TypeError:
+ return False
- def to_gerber(self, _unit=None):
+ def to_gerber(self, unit=None):
return f'{self.value:.6f}'.rstrip('0').rstrip('.')
@@ -174,9 +178,45 @@ class VariableExpression(Expression):
return type(self) == type(other) and \
self.number == other.number
- def to_gerber(self, _unit=None):
+ def to_gerber(self, unit=None):
return f'${self.number}'
+@dataclass(frozen=True, slots=True)
+class NegatedExpression(Expression):
+ value: Expression
+
+ def optimized(self, variable_binding={}):
+ match self.value.optimized(variable_binding):
+ # -(-x) == x
+ case NegatedExpression(inner_value):
+ return inner_value
+ # -(x) == -x
+ case ConstantExpression(inner_value):
+ return ConstantExpression(-inner_value)
+ # -(x-y) == y-x
+ case OperatorExpression(operator.sub, l, r):
+ return OperatorExpression(operator.sub, r, l)
+ # -(x [*/] y) == -x [*/] y
+ case OperatorExpression((operator.mul | operator.truediv) as op, ConstantExpression(l), r):
+ return OperatorExpression(op, ConstantExpression(-l), r)
+ # -(x [*/] y) == x [*/] -y
+ case OperatorExpression((operator.mul | operator.truediv) as op, l, ConstantExpression(r)):
+ return OperatorExpression(op, l, ConstantExpression(-r))
+
+ case x:
+ return NegatedExpression(x)
+
+ def __eq__(self, other):
+ return type(self) == type(other) and \
+ self.value == other.value
+
+ def to_gerber(self, unit=None):
+ val_str = self.value.to_gerber(unit)
+ if isinstance(self.value, VariableExpression):
+ return f'-{val_str}'
+ else:
+ return f'-({val_str})'
+
@dataclass(frozen=True, slots=True)
class OperatorExpression(Expression):
@@ -198,15 +238,83 @@ class OperatorExpression(Expression):
def optimized(self, variable_binding={}):
l = self.l.optimized(variable_binding)
r = self.r.optimized(variable_binding)
+ print(self.r, '->', r)
- #if self.op in (operator.add, operator.mul):
- # if id(r) < id(l):
- # l, r = r, l
-
- if isinstance(l, ConstantExpression) and isinstance(r, ConstantExpression):
- return ConstantExpression(self.op(float(l), float(r)))
-
- return OperatorExpression(self.op, l, r)
+ match (l, self.op, r):
+ case (ConstantExpression(), op, ConstantExpression()):
+ return ConstantExpression(self.op(float(l), float(r)))
+
+ # Minimize operations with neutral elements and zeros
+ # 0 + x == x
+ case (0, operator.add, r):
+ return r
+ # x + 0 == x
+ case (l, operator.add, 0):
+ return l
+ # 0 * x == 0
+ case (0, operator.mul, r):
+ return expr(0)
+ # x * 0 == 0
+ case (l, operator.mul, 0):
+ return expr(0)
+ # x * 1 == x
+ case (l, operator.mul, 1):
+ return l
+ # 1 * x == x
+ case (1, operator.mul, r):
+ return r
+ # x * -1 == -x
+ case (l, operator.mul, -1):
+ rv = -l
+ # -1 * x == -x
+ case (-1, operator.mul, r):
+ rv = -r
+ # x - 0 == x
+ case (l, operator.sub, 0):
+ return l
+ # 0 - x == -x (unary minus)
+ case (0, operator.sub, r):
+ rv = -r
+ # x - x == 0
+ case (l, operator.sub, r) if l == r:
+ return expr(0)
+ # x - -y == x + y
+ case (l, operator.sub, NegatedExpression(r)):
+ rv = (l + r)
+ # x / 1 == x
+ case (l, operator.truediv, 1):
+ return l
+ # x / -1 == -x
+ case (l, operator.truediv, -1):
+ rv = -l
+ # x / x == 1
+ case (l, operator.truediv, r) if l == r:
+ return expr(1)
+ # -x [*/] -y == x [*/] y
+ case (NegatedExpression(l), (operator.truediv | operator.mul) as op, NegatedExpression(r)):
+ rv = op(l, r)
+ # -x + -y == -(x + y)
+ case (NegatedExpression(l), operator.add, NegatedExpression(r)):
+ rv = -(l+r)
+ # -x + y == y - x
+ case (NegatedExpression(l), operator.add, r):
+ rv = r-l
+ # x + x == 2 * x
+ case (l, operator.add, r) if l == r:
+ rv = 2*r
+ case ((l, op, OperatorExpression(operator.mul, ConstantExpression(cons), r)) |
+ (l, op, OperatorExpression(operator.mul, r, ConstantExpression(cons)))) \
+ if l == r and op in (operator.add, operator.sub):
+ return op(1, cons) * r
+ case ((OperatorExpression(operator.mul, ConstantExpression(cons), r), op, l) |
+ (OperatorExpression(operator.mul, r, ConstantExpression(cons)), op, l)) \
+ if l == r and op in (operator.add, operator.sub):
+ return op(cons, 1) * r
+
+ case _: # default
+ return OperatorExpression(self.op, l, r)
+
+ return expr(rv).optimized(variable_binding)
def to_gerber(self, unit=None):
lval = self.l.to_gerber(unit)
diff --git a/gerbonara/aperture_macros/parse.py b/gerbonara/aperture_macros/parse.py
index 45f8c41..1527bc1 100644
--- a/gerbonara/aperture_macros/parse.py
+++ b/gerbonara/aperture_macros/parse.py
@@ -30,7 +30,7 @@ def _map_expression(node):
if type(node.op) == ast.UAdd:
return _map_expression(node.operand)
else:
- return OperatorExpression(operator.sub, ConstantExpression(0), _map_expression(node.operand))
+ return NegatedExpression(_map_expression(node.operand))
elif isinstance(node, ast.Name):
return VariableExpression(int(node.id[3:])) # node.id has format var[0-9]+
diff --git a/gerbonara/aperture_macros/primitive.py b/gerbonara/aperture_macros/primitive.py
index f575b0c..1372dfa 100644
--- a/gerbonara/aperture_macros/primitive.py
+++ b/gerbonara/aperture_macros/primitive.py
@@ -48,7 +48,7 @@ class Primitive:
def to_gerber(self, unit=None):
return f'{self.code},' + ','.join(
- getattr(self, field.name).to_gerber(unit) for field in fields(self) if field.name != 'unit')
+ getattr(self, field.name).optimized().to_gerber(unit) for field in fields(self) if field.name != 'unit')
def __str__(self):
attrs = ','.join(str(getattr(self, name)).strip('<>') for name in type(self).__annotations__)
@@ -253,6 +253,9 @@ class Outline(Primitive):
object.__setattr__(self, 'exposure', expr(self.exposure))
if self.length.calculate() != len(self.coords)//2-1:
+ print(self.length, self.length.calculate(), len(self.coords))
+ import pprint
+ pprint.pprint(self.coords)
raise ValueError('length must exactly equal number of segments, which is the number of points minus one')
if self.coords[-2:] != self.coords[:2]:
@@ -289,8 +292,8 @@ class Outline(Primitive):
rotation = ConstantExpression(0)
- coords = ','.join(coord.to_gerber(unit) for coord in coords)
- return f'{self.code},{self.exposure.to_gerber()},{len(self.coords)//2-1},{coords},{rotation.to_gerber()}'
+ coords = ','.join(coord.optimized().to_gerber(unit) for coord in coords)
+ return f'{self.code},{self.exposure.optimized().to_gerber()},{len(self.coords)//2-1},{coords},{rotation.to_gerber()}'
def to_graphic_primitives(self, offset, rotation, variable_binding={}, unit=None, polarity_dark=True):
with self.Calculator(self, variable_binding, unit) as calc: