Merge pull request #23 from curtacircuitos/macro-parse-eval
Add aperture macro parsing and evaluation.
This commit is contained in:
commit
c40683b6a2
6 changed files with 417 additions and 46 deletions
106
gerber/am_eval.py
Normal file
106
gerber/am_eval.py
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
|
||||
# copyright 2014 Paulo Henrique Silva <ph.silva@gmail.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
""" This module provides RS-274-X AM macro evaluation.
|
||||
"""
|
||||
|
||||
class OpCode:
|
||||
PUSH = 1
|
||||
LOAD = 2
|
||||
STORE = 3
|
||||
ADD = 4
|
||||
SUB = 5
|
||||
MUL = 6
|
||||
DIV = 7
|
||||
PRIM = 8
|
||||
|
||||
@staticmethod
|
||||
def str(opcode):
|
||||
if opcode == OpCode.PUSH:
|
||||
return "OPCODE_PUSH"
|
||||
elif opcode == OpCode.LOAD:
|
||||
return "OPCODE_LOAD"
|
||||
elif opcode == OpCode.STORE:
|
||||
return "OPCODE_STORE"
|
||||
elif opcode == OpCode.ADD:
|
||||
return "OPCODE_ADD"
|
||||
elif opcode == OpCode.SUB:
|
||||
return "OPCODE_SUB"
|
||||
elif opcode == OpCode.MUL:
|
||||
return "OPCODE_MUL"
|
||||
elif opcode == OpCode.DIV:
|
||||
return "OPCODE_DIV"
|
||||
elif opcode == OpCode.PRIM:
|
||||
return "OPCODE_PRIM"
|
||||
else:
|
||||
return "UNKNOWN"
|
||||
|
||||
def eval_macro(instructions, parameters={}):
|
||||
|
||||
if not isinstance(parameters, type({})):
|
||||
p = {}
|
||||
for i, val in enumerate(parameters):
|
||||
p[i+1] = val
|
||||
|
||||
parameters = p
|
||||
|
||||
stack = []
|
||||
def pop():
|
||||
return stack.pop()
|
||||
|
||||
def push(op):
|
||||
stack.append(op)
|
||||
|
||||
def top():
|
||||
return stack[-1]
|
||||
|
||||
def empty():
|
||||
return len(stack) == 0
|
||||
|
||||
for opcode, argument in instructions:
|
||||
if opcode == OpCode.PUSH:
|
||||
push(argument)
|
||||
|
||||
elif opcode == OpCode.LOAD:
|
||||
push(parameters.get(argument, 0))
|
||||
|
||||
elif opcode == OpCode.STORE:
|
||||
parameters[argument] = pop()
|
||||
|
||||
elif opcode == OpCode.ADD:
|
||||
op1 = pop()
|
||||
op2 = pop()
|
||||
push(op2 + op1)
|
||||
|
||||
elif opcode == OpCode.SUB:
|
||||
op1 = pop()
|
||||
op2 = pop()
|
||||
push(op2 - op2)
|
||||
|
||||
elif opcode == OpCode.MUL:
|
||||
op1 = pop()
|
||||
op2 = pop()
|
||||
push(op2 * op1)
|
||||
|
||||
elif opcode == OpCode.DIV:
|
||||
op1 = pop()
|
||||
op2 = pop()
|
||||
push(op2 / op1)
|
||||
|
||||
elif opcode == OpCode.PRIM:
|
||||
yield "%d,%s" % (argument, ",".join([str(x) for x in stack]))
|
||||
stack = []
|
||||
236
gerber/am_read.py
Normal file
236
gerber/am_read.py
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
|
||||
# copyright 2014 Paulo Henrique Silva <ph.silva@gmail.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
""" This module provides RS-274-X AM macro modifiers parsing.
|
||||
"""
|
||||
|
||||
from .am_eval import OpCode, eval_macro
|
||||
|
||||
import string
|
||||
|
||||
|
||||
class Token:
|
||||
ADD = "+"
|
||||
SUB = "-"
|
||||
MULT = ("x", "X") # compatibility as many gerber writes do use non compliant X
|
||||
DIV = "/"
|
||||
OPERATORS = (ADD, SUB, MULT[0], MULT[1], DIV)
|
||||
LEFT_PARENS = "("
|
||||
RIGHT_PARENS = ")"
|
||||
EQUALS = "="
|
||||
EOF = "EOF"
|
||||
|
||||
|
||||
def token_to_opcode(token):
|
||||
if token == Token.ADD:
|
||||
return OpCode.ADD
|
||||
elif token == Token.SUB:
|
||||
return OpCode.SUB
|
||||
elif token in Token.MULT:
|
||||
return OpCode.MUL
|
||||
elif token == Token.DIV:
|
||||
return OpCode.DIV
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def precedence(token):
|
||||
if token == Token.ADD or token == Token.SUB:
|
||||
return 1
|
||||
elif token in Token.MULT or token == Token.DIV:
|
||||
return 2
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
def is_op(token):
|
||||
return token in Token.OPERATORS
|
||||
|
||||
|
||||
class Scanner:
|
||||
def __init__(self, s):
|
||||
self.buff = s
|
||||
self.n = 0
|
||||
|
||||
def eof(self):
|
||||
return self.n == len(self.buff)
|
||||
|
||||
def peek(self):
|
||||
if not self.eof():
|
||||
return self.buff[self.n]
|
||||
|
||||
return Token.EOF
|
||||
|
||||
def ungetc(self):
|
||||
if self.n > 0:
|
||||
self.n -= 1
|
||||
|
||||
def getc(self):
|
||||
if self.eof():
|
||||
return ""
|
||||
|
||||
c = self.buff[self.n]
|
||||
self.n += 1
|
||||
return c
|
||||
|
||||
def readint(self):
|
||||
n = ""
|
||||
while not self.eof() and (self.peek() in string.digits):
|
||||
n += self.getc()
|
||||
return int(n)
|
||||
|
||||
def readfloat(self):
|
||||
n = ""
|
||||
while not self.eof() and (self.peek() in string.digits or self.peek() == "."):
|
||||
n += self.getc()
|
||||
return float(n)
|
||||
|
||||
def readstr(self, end="*"):
|
||||
s = ""
|
||||
while not self.eof() and self.peek() != end:
|
||||
s += self.getc()
|
||||
return s.strip()
|
||||
|
||||
|
||||
def print_instructions(instructions):
|
||||
for opcode, argument in instructions:
|
||||
print("%s %s" % (OpCode.str(opcode), str(argument) if argument is not None else ""))
|
||||
|
||||
|
||||
def read_macro(macro):
|
||||
|
||||
instructions = []
|
||||
|
||||
for block in macro.split("*"):
|
||||
|
||||
is_primitive = False
|
||||
is_equation = False
|
||||
|
||||
found_equation_left_side = False
|
||||
found_primitive_code = False
|
||||
|
||||
equation_left_side = 0
|
||||
primitive_code = 0
|
||||
|
||||
if Token.EQUALS in block:
|
||||
is_equation = True
|
||||
else:
|
||||
is_primitive = True
|
||||
|
||||
scanner = Scanner(block)
|
||||
|
||||
# inlined here for compactness and convenience
|
||||
op_stack = []
|
||||
|
||||
def pop():
|
||||
return op_stack.pop()
|
||||
|
||||
def push(op):
|
||||
op_stack.append(op)
|
||||
|
||||
def top():
|
||||
return op_stack[-1]
|
||||
|
||||
def empty():
|
||||
return len(op_stack) == 0
|
||||
|
||||
while not scanner.eof():
|
||||
|
||||
c = scanner.getc()
|
||||
|
||||
if c == ",":
|
||||
found_primitive_code = True
|
||||
|
||||
# add all instructions on the stack to finish last modifier
|
||||
while not empty():
|
||||
instructions.append((token_to_opcode(pop()), None))
|
||||
|
||||
elif c in Token.OPERATORS:
|
||||
while not empty() and is_op(top()) and precedence(top()) >= precedence(c):
|
||||
instructions.append((token_to_opcode(pop()), None))
|
||||
|
||||
push(c)
|
||||
|
||||
elif c == Token.LEFT_PARENS:
|
||||
push(c)
|
||||
|
||||
elif c == Token.RIGHT_PARENS:
|
||||
while not empty() and top() != Token.LEFT_PARENS:
|
||||
instructions.append((token_to_opcode(pop()), None))
|
||||
|
||||
if empty():
|
||||
raise ValueError("unbalanced parentheses")
|
||||
|
||||
# discard "("
|
||||
pop()
|
||||
|
||||
elif c.startswith("$"):
|
||||
n = scanner.readint()
|
||||
|
||||
if is_equation and not found_equation_left_side:
|
||||
equation_left_side = n
|
||||
else:
|
||||
instructions.append((OpCode.LOAD, n))
|
||||
|
||||
elif c == Token.EQUALS:
|
||||
found_equation_left_side = True
|
||||
|
||||
elif c == "0":
|
||||
if is_primitive and not found_primitive_code:
|
||||
instructions.append((OpCode.PUSH, scanner.readstr("*")))
|
||||
found_primitive_code = True
|
||||
else:
|
||||
# decimal or integer disambiguation
|
||||
if scanner.peek() not in '.' or scanner.peek() == Token.EOF:
|
||||
instructions.append((OpCode.PUSH, 0))
|
||||
|
||||
elif c in "123456789.":
|
||||
scanner.ungetc()
|
||||
|
||||
if is_primitive and not found_primitive_code:
|
||||
primitive_code = scanner.readint()
|
||||
else:
|
||||
instructions.append((OpCode.PUSH, scanner.readfloat()))
|
||||
|
||||
else:
|
||||
# whitespace or unknown char
|
||||
pass
|
||||
|
||||
# add all instructions on the stack to finish last modifier (if any)
|
||||
while not empty():
|
||||
instructions.append((token_to_opcode(pop()), None))
|
||||
|
||||
# at end, we either have a primitive or a equation
|
||||
if is_primitive and found_primitive_code:
|
||||
instructions.append((OpCode.PRIM, primitive_code))
|
||||
|
||||
if is_equation:
|
||||
instructions.append((OpCode.STORE, equation_left_side))
|
||||
|
||||
return instructions
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
instructions = read_macro(sys.argv[1])
|
||||
|
||||
print("insructions:")
|
||||
print_instructions(instructions)
|
||||
|
||||
print("eval:")
|
||||
for primitive in eval_macro(instructions):
|
||||
print(primitive)
|
||||
|
|
@ -160,7 +160,7 @@ class AMCirclePrimitive(AMPrimitive):
|
|||
def from_gerber(cls, primitive):
|
||||
modifiers = primitive.strip(' *').split(',')
|
||||
code = int(modifiers[0])
|
||||
exposure = 'on' if modifiers[1].strip() == '1' else 'off'
|
||||
exposure = 'on' if float(modifiers[1]) == 1 else 'off'
|
||||
diameter = float(modifiers[2])
|
||||
position = (float(modifiers[3]), float(modifiers[4]))
|
||||
return cls(code, exposure, diameter, position)
|
||||
|
|
@ -233,7 +233,7 @@ class AMVectorLinePrimitive(AMPrimitive):
|
|||
def from_gerber(cls, primitive):
|
||||
modifiers = primitive.strip(' *').split(',')
|
||||
code = int(modifiers[0])
|
||||
exposure = 'on' if modifiers[1].strip() == '1' else 'off'
|
||||
exposure = 'on' if float(modifiers[1]) == 1 else 'off'
|
||||
width = float(modifiers[2])
|
||||
start = (float(modifiers[3]), float(modifiers[4]))
|
||||
end = (float(modifiers[5]), float(modifiers[6]))
|
||||
|
|
@ -318,8 +318,8 @@ class AMOutlinePrimitive(AMPrimitive):
|
|||
modifiers = primitive.strip(' *').split(",")
|
||||
|
||||
code = int(modifiers[0])
|
||||
exposure = "on" if modifiers[1].strip() == "1" else "off"
|
||||
n = int(modifiers[2])
|
||||
exposure = "on" if float(modifiers[1]) == 1 else "off"
|
||||
n = int(float(modifiers[2]))
|
||||
start_point = (float(modifiers[3]), float(modifiers[4]))
|
||||
points = []
|
||||
for i in range(n):
|
||||
|
|
@ -405,10 +405,14 @@ class AMPolygonPrimitive(AMPrimitive):
|
|||
def from_gerber(cls, primitive):
|
||||
modifiers = primitive.strip(' *').split(",")
|
||||
code = int(modifiers[0])
|
||||
exposure = "on" if modifiers[1].strip() == "1" else "off"
|
||||
vertices = int(modifiers[2])
|
||||
exposure = "on" if float(modifiers[1]) == 1 else "off"
|
||||
vertices = int(float(modifiers[2]))
|
||||
position = (float(modifiers[3]), float(modifiers[4]))
|
||||
diameter = float(modifiers[5])
|
||||
try:
|
||||
diameter = float(modifiers[5])
|
||||
except:
|
||||
diameter = 0
|
||||
|
||||
rotation = float(modifiers[6])
|
||||
return cls(code, exposure, vertices, position, diameter, rotation)
|
||||
|
||||
|
|
@ -504,7 +508,7 @@ class AMMoirePrimitive(AMPrimitive):
|
|||
diameter = float(modifiers[3])
|
||||
ring_thickness = float(modifiers[4])
|
||||
gap = float(modifiers[5])
|
||||
max_rings = int(modifiers[6])
|
||||
max_rings = int(float(modifiers[6]))
|
||||
crosshair_thickness = float(modifiers[7])
|
||||
crosshair_length = float(modifiers[8])
|
||||
rotation = float(modifiers[9])
|
||||
|
|
@ -686,7 +690,7 @@ class AMCenterLinePrimitive(AMPrimitive):
|
|||
def from_gerber(cls, primitive):
|
||||
modifiers = primitive.strip(' *').split(",")
|
||||
code = int(modifiers[0])
|
||||
exposure = 'on' if modifiers[1].strip() == '1' else 'off'
|
||||
exposure = 'on' if float(modifiers[1]) == 1 else 'off'
|
||||
width = float(modifiers[2])
|
||||
height = float(modifiers[3])
|
||||
center= (float(modifiers[4]), float(modifiers[5]))
|
||||
|
|
@ -768,7 +772,7 @@ class AMLowerLeftLinePrimitive(AMPrimitive):
|
|||
def from_gerber(cls, primitive):
|
||||
modifiers = primitive.strip(' *').split(",")
|
||||
code = int(modifiers[0])
|
||||
exposure = 'on' if modifiers[1].strip() == '1' else 'off'
|
||||
exposure = 'on' if float(modifiers[1]) == 1 else 'off'
|
||||
width = float(modifiers[2])
|
||||
height = float(modifiers[3])
|
||||
lower_left = (float(modifiers[4]), float(modifiers[5]))
|
||||
|
|
|
|||
|
|
@ -22,7 +22,10 @@ Gerber (RS-274X) Statements
|
|||
"""
|
||||
from .utils import (parse_gerber_value, write_gerber_value, decimal_string,
|
||||
inch, metric)
|
||||
|
||||
from .am_statements import *
|
||||
from .am_read import read_macro
|
||||
from .am_eval import eval_macro
|
||||
|
||||
|
||||
class Statement(object):
|
||||
|
|
@ -340,35 +343,37 @@ class AMParamStmt(ParamStmt):
|
|||
ParamStmt.__init__(self, param)
|
||||
self.name = name
|
||||
self.macro = macro
|
||||
self.primitives = self._parsePrimitives(macro)
|
||||
|
||||
def _parsePrimitives(self, macro):
|
||||
primitives = []
|
||||
for primitive in macro.strip('%\n').split('*'):
|
||||
# Couldn't find anything explicit about leading whitespace in the spec...
|
||||
primitive = primitive.strip(' *%\n')
|
||||
if len(primitive):
|
||||
if primitive[0] == '0':
|
||||
primitives.append(AMCommentPrimitive.from_gerber(primitive))
|
||||
elif primitive[0] == '1':
|
||||
primitives.append(AMCirclePrimitive.from_gerber(primitive))
|
||||
elif primitive[0:2] in ('2,', '20'):
|
||||
primitives.append(AMVectorLinePrimitive.from_gerber(primitive))
|
||||
elif primitive[0:2] == '21':
|
||||
primitives.append(AMCenterLinePrimitive.from_gerber(primitive))
|
||||
elif primitive[0:2] == '22':
|
||||
primitives.append(AMLowerLeftLinePrimitive.from_gerber(primitive))
|
||||
elif primitive[0] == '4':
|
||||
primitives.append(AMOutlinePrimitive.from_gerber(primitive))
|
||||
elif primitive[0] == '5':
|
||||
primitives.append(AMPolygonPrimitive.from_gerber(primitive))
|
||||
elif primitive[0] =='6':
|
||||
primitives.append(AMMoirePrimitive.from_gerber(primitive))
|
||||
elif primitive[0] == '7':
|
||||
primitives.append(AMThermalPrimitive.from_gerber(primitive))
|
||||
else:
|
||||
primitives.append(AMUnsupportPrimitive.from_gerber(primitive))
|
||||
return primitives
|
||||
self.instructions = self.read(macro)
|
||||
self.primitives = []
|
||||
|
||||
def read(self, macro):
|
||||
return read_macro(macro)
|
||||
|
||||
def build(self, modifiers=[[]]):
|
||||
self.primitives = []
|
||||
|
||||
for primitive in eval_macro(self.instructions, modifiers[0]):
|
||||
if primitive[0] == '0':
|
||||
self.primitives.append(AMCommentPrimitive.from_gerber(primitive))
|
||||
elif primitive[0] == '1':
|
||||
self.primitives.append(AMCirclePrimitive.from_gerber(primitive))
|
||||
elif primitive[0:2] in ('2,', '20'):
|
||||
self.primitives.append(AMVectorLinePrimitive.from_gerber(primitive))
|
||||
elif primitive[0:2] == '21':
|
||||
self.primitives.append(AMCenterLinePrimitive.from_gerber(primitive))
|
||||
elif primitive[0:2] == '22':
|
||||
self.primitives.append(AMLowerLeftLinePrimitive.from_gerber(primitive))
|
||||
elif primitive[0] == '4':
|
||||
self.primitives.append(AMOutlinePrimitive.from_gerber(primitive))
|
||||
elif primitive[0] == '5':
|
||||
self.primitives.append(AMPolygonPrimitive.from_gerber(primitive))
|
||||
elif primitive[0] =='6':
|
||||
self.primitives.append(AMMoirePrimitive.from_gerber(primitive))
|
||||
elif primitive[0] == '7':
|
||||
self.primitives.append(AMThermalPrimitive.from_gerber(primitive))
|
||||
else:
|
||||
self.primitives.append(AMUnsupportPrimitive.from_gerber(primitive))
|
||||
|
||||
def to_inch(self):
|
||||
for primitive in self.primitives:
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@ class GerberParser(object):
|
|||
self.statements = []
|
||||
self.primitives = []
|
||||
self.apertures = {}
|
||||
self.macros = {}
|
||||
self.current_region = None
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
|
|
@ -392,6 +393,12 @@ class GerberParser(object):
|
|||
width = modifiers[0][0]
|
||||
height = modifiers[0][1]
|
||||
aperture = Obround(position=None, width=width, height=height)
|
||||
elif shape == 'P':
|
||||
# FIXME: not supported yet?
|
||||
pass
|
||||
else:
|
||||
aperture = self.macros[shape].build(modifiers)
|
||||
|
||||
self.apertures[d] = aperture
|
||||
|
||||
def _evaluate_mode(self, stmt):
|
||||
|
|
@ -414,6 +421,8 @@ class GerberParser(object):
|
|||
self.image_polarity = stmt.ip
|
||||
elif stmt.param == "LP":
|
||||
self.level_polarity = stmt.lp
|
||||
elif stmt.param == "AM":
|
||||
self.macros[stmt.name] = stmt
|
||||
elif stmt.param == "AD":
|
||||
self._define_aperture(stmt.d, stmt.shape, stmt.modifiers)
|
||||
|
||||
|
|
@ -449,9 +458,14 @@ class GerberParser(object):
|
|||
primitive = copy.deepcopy(self.apertures[self.aperture])
|
||||
# XXX: temporary fix because there are no primitives for Macros and Polygon
|
||||
if primitive is not None:
|
||||
primitive.position = (x, y)
|
||||
primitive.level_polarity = self.level_polarity
|
||||
self.primitives.append(primitive)
|
||||
# XXX: just to make it easy to spot
|
||||
if isinstance(primitive, type([])):
|
||||
print(primitive[0].to_gerber())
|
||||
else:
|
||||
primitive.position = (x, y)
|
||||
primitive.level_polarity = self.level_polarity
|
||||
self.primitives.append(primitive)
|
||||
|
||||
self.x, self.y = x, y
|
||||
|
||||
def _evaluate_aperture(self, stmt):
|
||||
|
|
|
|||
|
|
@ -333,6 +333,7 @@ def test_AMParamStmt_factory():
|
|||
8,THIS IS AN UNSUPPORTED PRIMITIVE*
|
||||
''')
|
||||
s = AMParamStmt.from_dict({'param': 'AM', 'name': name, 'macro': macro })
|
||||
s.build()
|
||||
assert_equal(len(s.primitives), 10)
|
||||
assert_true(isinstance(s.primitives[0], AMCommentPrimitive))
|
||||
assert_true(isinstance(s.primitives[1], AMCirclePrimitive))
|
||||
|
|
@ -347,29 +348,34 @@ def test_AMParamStmt_factory():
|
|||
|
||||
def testAMParamStmt_conversion():
|
||||
name = 'POLYGON'
|
||||
macro = '5,1,8,25.4,25.4,25.4,0*%'
|
||||
macro = '5,1,8,25.4,25.4,25.4,0*'
|
||||
s = AMParamStmt.from_dict({'param': 'AM', 'name': name, 'macro': macro })
|
||||
s.build()
|
||||
s.to_inch()
|
||||
assert_equal(s.primitives[0].position, (1., 1.))
|
||||
assert_equal(s.primitives[0].diameter, 1.)
|
||||
|
||||
macro = '5,1,8,1,1,1,0*%'
|
||||
macro = '5,1,8,1,1,1,0*'
|
||||
s = AMParamStmt.from_dict({'param': 'AM', 'name': name, 'macro': macro })
|
||||
s.build()
|
||||
s.to_metric()
|
||||
assert_equal(s.primitives[0].position, (25.4, 25.4))
|
||||
assert_equal(s.primitives[0].diameter, 25.4)
|
||||
|
||||
def test_AMParamStmt_dump():
|
||||
name = 'POLYGON'
|
||||
macro = '5,1,8,25.4,25.4,25.4,0*%'
|
||||
macro = '5,1,8,25.4,25.4,25.4,0*'
|
||||
s = AMParamStmt.from_dict({'param': 'AM', 'name': name, 'macro': macro })
|
||||
s.build()
|
||||
|
||||
assert_equal(s.to_gerber(), '%AMPOLYGON*5,1,8,25.4,25.4,25.4,0.0*%')
|
||||
|
||||
def test_AMParamStmt_string():
|
||||
name = 'POLYGON'
|
||||
macro = '5,1,8,25.4,25.4,25.4,0*%'
|
||||
macro = '5,1,8,25.4,25.4,25.4,0*'
|
||||
s = AMParamStmt.from_dict({'param': 'AM', 'name': name, 'macro': macro })
|
||||
assert_equal(str(s), '<Aperture Macro POLYGON: 5,1,8,25.4,25.4,25.4,0*%>')
|
||||
s.build()
|
||||
assert_equal(str(s), '<Aperture Macro POLYGON: 5,1,8,25.4,25.4,25.4,0*>')
|
||||
|
||||
def test_ASParamStmt_factory():
|
||||
stmt = {'param': 'AS', 'mode': 'AXBY'}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue