Aperture macros WIP
This commit is contained in:
parent
7415f9a584
commit
25dd65fac0
2 changed files with 75 additions and 131 deletions
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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 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]}')
|
||||
|
||||
self.coords = [UnitExpression(arg, unit) for arg in args[1:]]
|
||||
|
||||
if len(args) % 1 != 1:
|
||||
self.rotation = args.pop()
|
||||
else:
|
||||
self.rotation = ConstantExpression(0.0)
|
||||
if len(args) % 1 != 1:
|
||||
self.rotation = args.pop()
|
||||
else:
|
||||
self.rotation = 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]}')
|
||||
|
||||
self.coords = [UnitExpression(arg, unit) for arg in args[1:]]
|
||||
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 = []
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue