summaryrefslogtreecommitdiff
path: root/gerbonara
diff options
context:
space:
mode:
Diffstat (limited to 'gerbonara')
-rw-r--r--gerbonara/gerber/aperture_macros/expression.py84
-rw-r--r--gerbonara/gerber/aperture_macros/primitive.py122
2 files changed, 75 insertions, 131 deletions
diff --git a/gerbonara/gerber/aperture_macros/expression.py b/gerbonara/gerber/aperture_macros/expression.py
index 49df36c..74fbd90 100644
--- a/gerbonara/gerber/aperture_macros/expression.py
+++ b/gerbonara/gerber/aperture_macros/expression.py
@@ -7,6 +7,7 @@ import operator
import re
import ast
+
class Expression(object):
@property
def value(self):
@@ -18,6 +19,16 @@ class Expression(object):
def __str__(self):
return f'<{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
+
+
class UnitExpression(Expression):
def __init__(self, expr, unit):
self._expr = expr
@@ -32,7 +43,7 @@ class UnitExpression(Expression):
self._expr == other._expr
def __str__(self):
- return f'<{self.expr.to_gerber()} {self.unit}>'
+ return f'<{self._expr.to_gerber()} {self.unit}>'
def converted(self, unit):
if unit is None or self.unit == unit:
@@ -47,29 +58,18 @@ class UnitExpression(Expression):
else:
raise ValueError('invalid unit, must be "inch" or "mm".')
- 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}')
-
class ConstantExpression(Expression):
def __init__(self, value):
- self._value = value
-
- @property
- def value(self):
- return self._value
+ self.value = value
def __float__(self):
- return float(self._value)
+ return float(self.value)
def __eq__(self, other):
- return type(self) == type(other) and self._value == other._value
+ return type(self) == type(other) and self.value == other.value
def to_gerber(self, _unit=None):
- if isinstance(self._value, str):
- return self._value
return f'{self.value:.6f}'.rstrip('0').rstrip('.')
@@ -111,7 +111,7 @@ class OperatorExpression(Expression):
l, r = r, l
if isinstance(l, ConstantExpression) and isinstance(r, ConstantExpression):
- return ConstantExpression(self.op(float(r), float(l)))
+ return ConstantExpression(self.op(float(l), float(r)))
return OperatorExpression(self.op, l, r)
@@ -131,55 +131,3 @@ class OperatorExpression(Expression):
return f'{lval}{op}{rval}'
-
-def _map_expression(node):
- if isinstance(node, ast.Num):
- return ConstantExpression(node.n)
-
- elif isinstance(node, ast.BinOp):
- op_map = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv}
- return OperatorExpression(op_map[type(node.op)], _map_expression(node.left), _map_expression(node.right))
-
- elif isinstance(node, ast.UnaryOp):
- if type(node.op) == ast.UAdd:
- return _map_expression(node.operand)
- else:
- return OperatorExpression(operator.sub, ConstantExpression(0), _map_expression(node.operand))
-
- elif isinstance(node, ast.Name):
- return VariableExpression(int(node.id[3:])) # node.id has format var[0-9]+
-
- else:
- raise SyntaxError('Invalid aperture macro expression')
-
-def _parse_expression(expr):
- expr = expr.lower().replace('x', '*')
- expr = re.sub(r'\$([0-9]+)', r'var\1', expr)
- try:
- parsed = ast.parse(expr, mode='eval').body
- except SyntaxError as e:
- raise SyntaxError('Invalid aperture macro expression') from e
- return _map_expression(parsed)
-
-def parse_macro(macro, unit):
- blocks = re.sub(r'\s', '', macro).split('*')
- variables = {}
- for block in blocks:
- block = block.strip()
-
- if block[0:1] == '0 ': # comment
- continue
-
- elif block[0] == '$': # variable definition
- name, expr = block.partition('=')
- variables[int(name[1:])] = _parse_expression(expr)
-
- else: # primitive
- primitive, args = block.split(',')
- yield PRIMITIVE_CLASSES[int(primitive)](unit=unit, args=list(map(_parse_expression, args)))
-
-if __name__ == '__main__':
- import sys
- for line in sys.stdin:
- expr = _parse_expression(line.strip())
- print(expr, '->', expr.optimized())
diff --git a/gerbonara/gerber/aperture_macros/primitive.py b/gerbonara/gerber/aperture_macros/primitive.py
index 88552c5..f4400f5 100644
--- a/gerbonara/gerber/aperture_macros/primitive.py
+++ b/gerbonara/gerber/aperture_macros/primitive.py
@@ -4,32 +4,46 @@
# Copyright 2019 Hiroshi Murayama <opiopan@gmail.com>
from dataclasses import dataclass, fields
-
-from .utils import *
-from .am_statements import *
-from .am_expression import *
-from .am_opcode import OpCode
+from expression import Expression, UnitExpression, ConstantExpression
class Primitive:
- def __init__(self, unit, args):
+ def __init__(self, unit, args, is_abstract):
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 fieldtype == UnitExpression:
- setattr(self, name, UnitExpression(arg, unit))
+ if is_abstract:
+ if fieldtype == UnitExpression:
+ setattr(self, name, UnitExpression(arg, unit))
+ else:
+ setattr(self, name, arg)
else:
setattr(self, name, arg)
- for name, _type in type(self).__annotations__.items():
+ for name in type(self).__annotations__:
if not hasattr(self, name):
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, _type in type(self).__annotations__.items()) + '*'
+ getattr(self, name).to_gerber(unit) for name in type(self).__annotations__) + '*'
+
+ def __str__(self):
+ 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__
+ ])
class CommentPrimitive(Primitive):
code = 0
@@ -86,7 +100,7 @@ class ThermalPrimitive(Primitive):
class OutlinePrimitive(Primitive):
code = 4
- def __init__(self, code, unit, args):
+ def __init__(self, unit, args, is_abstract):
if len(args) < 11:
raise ValueError(f'Invalid aperture macro outline primitive, not enough parameters ({len(args)}).')
if len(args) > 5004:
@@ -94,31 +108,49 @@ class OutlinePrimitive(Primitive):
self.exposure = args[0]
- if args[1] != len(args)//2 - 2:
- raise ValueError(f'Invalid aperture macro outline primitive, given size does not match length of coordinate list({len(args)}).')
+ if is_abstract:
+ # length arg must not contain variabels (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 len(args) % 1 != 1:
+ self.rotation = args.pop()
+ else:
+ self.rotation = ConstantExpression(0.0)
+
+ 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 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]}')
+ self.coords = [UnitExpression(arg, unit) for arg in args[1:]]
- self.coords = [UnitExpression(arg, unit) for arg in args[1:]]
+ else:
+ if len(args) % 1 != 1:
+ self.rotation = args.pop()
+ else:
+ self.rotation = 0
+
+ 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.')
-class VariableDef(object):
- def __init__(self, number, value):
- self.number = number
- self.value = value
+ return OutlinePrimitive(self.unit, is_abstract=False, args=[None, *self.coords, self.rotation])
- def to_gerber(self, _unit=None):
- return '$%d=%s*' % (self.number, self.value.to_gerber(settings))
+class Comment:
+ def __init__(self, comment):
+ self.comment = comment
+
+ def to_gerber(self, unit=None):
+ return f'0 {self.comment}'
PRIMITIVE_CLASSES = {
**{cls.code: cls for cls in [
@@ -129,44 +161,8 @@ PRIMITIVE_CLASSES = {
OutlinePrimitive,
PolygonPrimitive,
ThermalPrimitive,
- ],
+ ]},
# alternative codes
2: VectorLinePrimitive,
}
-def eval_macro(instructions, unit):
- stack = []
- for opcode, argument in instructions:
- if opcode == OpCode.PUSH:
- stack.append(ConstantExpression(argument))
-
- elif opcode == OpCode.LOAD:
- stack.append(VariableExpression(argument))
-
- elif opcode == OpCode.STORE:
- yield VariableDef(code, stack.pop())
-
- elif opcode == OpCode.ADD:
- op1 = stack.pop()
- op2 = stack.pop()
- stack.append(OperatorExpression(OperatorExpression.ADD, op2, op1))
-
- elif opcode == OpCode.SUB:
- op1 = stack.pop()
- op2 = stack.pop()
- stack.append(OperatorExpression(OperatorExpression.SUB, op2, op1))
-
- elif opcode == OpCode.MUL:
- op1 = stack.pop()
- op2 = stack.pop()
- stack.append(OperatorExpression(OperatorExpression.MUL, op2, op1))
-
- elif opcode == OpCode.DIV:
- op1 = stack.pop()
- op2 = stack.pop()
- stack.append(OperatorExpression(OperatorExpression.DIV, op2, op1))
-
- elif opcode == OpCode.PRIM:
- yield PRIMITIVE_CLASSES[argument](unit=unit, args=stack)
- stack = []
-