From 25dd65fac05a43ef75fe75049d5b79a73a207fc0 Mon Sep 17 00:00:00 2001 From: jaseg Date: Thu, 11 Nov 2021 13:04:27 +0100 Subject: Aperture macros WIP --- gerbonara/gerber/aperture_macros/expression.py | 84 ++++------------- gerbonara/gerber/aperture_macros/primitive.py | 122 ++++++++++++------------- 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 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 = [] - -- cgit