diff options
Diffstat (limited to 'gerbonara/aperture_macros/expression.py')
-rw-r--r-- | gerbonara/aperture_macros/expression.py | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/gerbonara/aperture_macros/expression.py b/gerbonara/aperture_macros/expression.py new file mode 100644 index 0000000..0cf055a --- /dev/null +++ b/gerbonara/aperture_macros/expression.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright 2021 Jan Götte <gerbonara@jaseg.de> + +import operator +import re +import ast + +from ..utils import MM, Inch, MILLIMETERS_PER_INCH + + +def expr(obj): + return obj if isinstance(obj, Expression) else ConstantExpression(obj) + + +class Expression: + def optimized(self, variable_binding={}): + return self + + def __str__(self): + return f'<{self.to_gerber()}>' + + def __repr__(self): + return f'<E {self.to_gerber()}>' + + def converted(self, unit): + return self + + def calculate(self, variable_binding={}, unit=None): + expr = self.converted(unit).optimized(variable_binding) + if not isinstance(expr, ConstantExpression): + 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): + self._expr = expr + self.unit = unit + + def to_gerber(self, unit=None): + return self.converted(unit).optimized().to_gerber() + + def __eq__(self, other): + return type(other) == type(self) and \ + self.unit == other.unit and\ + self._expr == other._expr + + def __str__(self): + return f'<{self._expr.to_gerber()} {self.unit}>' + + def __repr__(self): + return f'<UE {self._expr.to_gerber()} {self.unit}>' + + def converted(self, unit): + if self.unit is None or unit is None or self.unit == unit: + return self._expr + + elif MM == unit: + return self._expr * MILLIMETERS_PER_INCH + + elif Inch == unit: + return self._expr / MILLIMETERS_PER_INCH + + 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): + self.value = value + + def __float__(self): + return float(self.value) + + def __eq__(self, other): + return type(self) == type(other) and self.value == other.value + + def to_gerber(self, _unit=None): + return f'{self.value:.6f}'.rstrip('0').rstrip('.') + + +class VariableExpression(Expression): + def __init__(self, number): + self.number = number + + def optimized(self, variable_binding={}): + if self.number in variable_binding: + return ConstantExpression(variable_binding[self.number]) + return self + + def __eq__(self, other): + return type(self) == type(other) and \ + self.number == other.number + + def to_gerber(self, _unit=None): + return f'${self.number}' + + +class OperatorExpression(Expression): + def __init__(self, op, l, r): + self.op = op + self.l = ConstantExpression(l) if isinstance(l, (int, float)) else l + self.r = ConstantExpression(r) if isinstance(r, (int, float)) else r + + def __eq__(self, other): + return type(self) == type(other) and \ + self.op == other.op and \ + self.l == other.l and \ + self.r == other.r + + def optimized(self, variable_binding={}): + l = self.l.optimized(variable_binding) + r = self.r.optimized(variable_binding) + + 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) + + def to_gerber(self, unit=None): + lval = self.l.to_gerber(unit) + rval = self.r.to_gerber(unit) + + if isinstance(self.l, OperatorExpression): + lval = f'({lval})' + if isinstance(self.r, OperatorExpression): + rval = f'({rval})' + + op = {operator.add: '+', + operator.sub: '-', + operator.mul: 'X', + operator.truediv: '/'} [self.op] + + return f'{lval}{op}{rval}' + |