Added some Aperture Macro Primitives. Moved AM primitives to seperate file
This commit is contained in:
parent
1cc20b351c
commit
f98b918634
3 changed files with 426 additions and 75 deletions
341
gerber/am_statements.py
Normal file
341
gerber/am_statements.py
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# copyright 2015 Hamilton Kibbe <ham@hamiltonkib.be> and 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.
|
||||
|
||||
from .utils import validate_coordinates
|
||||
|
||||
|
||||
# TODO: Add support for aperture macro variables
|
||||
|
||||
class AMPrimitive(object):
|
||||
""" Aperture Macro Primitive Base Class
|
||||
"""
|
||||
def __init__(self, code, exposure=None):
|
||||
""" Initialize Aperture Macro Primitive base class
|
||||
|
||||
Parameters
|
||||
----------
|
||||
code : int
|
||||
primitive shape code
|
||||
|
||||
exposure : str
|
||||
on or off Primitives with exposure on create a slid part of
|
||||
the macro aperture, and primitives with exposure off erase the
|
||||
solid part created previously in the aperture macro definition.
|
||||
.. note::
|
||||
The erasing effect is limited to the aperture definition in
|
||||
which it occurs.
|
||||
|
||||
Returns
|
||||
-------
|
||||
primitive : :class: `gerber.am_statements.AMPrimitive`
|
||||
|
||||
Raises
|
||||
------
|
||||
TypeError, ValueError
|
||||
"""
|
||||
VALID_CODES = (0, 1, 2, 4, 5, 6, 7, 20, 21, 22)
|
||||
if not isinstance(code, int):
|
||||
raise TypeError('Aperture Macro Primitive code must be an integer')
|
||||
elif code not in VALID_CODES:
|
||||
raise ValueError('Invalid Code. Valid codes are %s.' % ', '.join(map(str, VALID_CODES)))
|
||||
if exposure is not None and exposure.lower() not in ('on', 'off'):
|
||||
raise ValueError('Exposure must be either on or off')
|
||||
self.code = code
|
||||
self.exposure = exposure.lower() if exposure is not None else None
|
||||
|
||||
def to_inch(self):
|
||||
pass
|
||||
|
||||
def to_metric(self):
|
||||
pass
|
||||
|
||||
|
||||
class AMCommentPrimitive(AMPrimitive):
|
||||
""" Aperture Macro Comment primitive. Code 0
|
||||
"""
|
||||
@classmethod
|
||||
def from_gerber(cls, primitive):
|
||||
primitive = primitive.strip()
|
||||
code = int(primitive[0])
|
||||
comment = primitive[1:]
|
||||
return cls(code, comment)
|
||||
|
||||
def __init__(self, code, comment):
|
||||
""" Initialize AMCommentPrimitive class
|
||||
|
||||
Parameters
|
||||
----------
|
||||
code : int
|
||||
Aperture Macro primitive code. 0 Indicates an AMCommentPrimitive
|
||||
|
||||
comment : str
|
||||
The comment as a string.
|
||||
|
||||
Returns
|
||||
-------
|
||||
CommentPrimitive : :class:`gerber.am_statements.AMCommentPrimitive`
|
||||
An Initialized AMCommentPrimitive
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
"""
|
||||
if code != 0:
|
||||
raise ValueError('Not a valid Aperture Macro Comment statement')
|
||||
super(AMCommentPrimitive, self).__init__(code)
|
||||
self.comment = comment.strip(' *')
|
||||
|
||||
def to_gerber(self, settings=None):
|
||||
return '0 %s *' % self.comment
|
||||
|
||||
def __str__(self):
|
||||
return '<Aperture Macro Comment: %s>' % self.comment
|
||||
|
||||
|
||||
class AMCirclePrimitive(AMPrimitive):
|
||||
""" Aperture macro Circle primitive. Code 1
|
||||
"""
|
||||
@classmethod
|
||||
def from_gerber(cls, primitive):
|
||||
modifiers = primitive.strip(' *').split(',')
|
||||
code = int(modifiers[0])
|
||||
exposure = 'on' if modifiers[1].strip() == '1' else 'off'
|
||||
diameter = float(modifiers[2])
|
||||
position = (float(modifiers[3]), float(modifiers[4]))
|
||||
return cls(code, exposure, diameter, position)
|
||||
|
||||
def __init__(self, code, exposure, diameter, position):
|
||||
""" Initialize AMCirclePrimitive
|
||||
|
||||
Parameters
|
||||
----------
|
||||
code : int
|
||||
Circle Primitive code. Must be 1
|
||||
|
||||
exposure : string
|
||||
'on' or 'off'
|
||||
|
||||
diameter : float
|
||||
Circle diameter
|
||||
|
||||
position : tuple (<float>, <float>)
|
||||
Position of the circle relative to the macro origin
|
||||
|
||||
Returns
|
||||
-------
|
||||
CirclePrimitive : :class:`gerber.am_statements.AMCirclePrimitive`
|
||||
An initialized AMCirclePrimitive
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError, TypeError
|
||||
"""
|
||||
validate_coordinates(position)
|
||||
if code != 1:
|
||||
raise ValueError('Not a valid Aperture Macro Circle statement')
|
||||
super(AMCirclePrimitive, self).__init__(code, exposure)
|
||||
self.diameter = diameter
|
||||
self.position = position
|
||||
|
||||
def to_gerber(self, settings=None):
|
||||
data = dict(code = self.code,
|
||||
exposure = '1' if self.exposure == 'on' else 0,
|
||||
diameter = self.diameter,
|
||||
x = self.position[0],
|
||||
y = self.position[1])
|
||||
return '{code},{exposure},{diameter},{x},{y}*'.format(**data)
|
||||
|
||||
|
||||
class AMVectorLinePrimitive(AMPrimitive):
|
||||
""" Aperture Macro Vector Line primitive. Code 2 or 20
|
||||
"""
|
||||
@classmethod
|
||||
def from_gerber(cls, primitive):
|
||||
modifiers = primitive.strip(' *').split(',')
|
||||
code = int(modifiers[0])
|
||||
exposure = 'on' if modifiers[1].strip() == '1' else 'off'
|
||||
width = float(modifiers[2])
|
||||
start (float(modifiers[3]), float(modifiers[4]))
|
||||
end = (float(modifiers[5]), float(modifiers[6]))
|
||||
rotation = float(modifiers[7])
|
||||
return cls(code, exposure, width, start, end, rotation)
|
||||
|
||||
def __init__(self, code, exposure, width, start, end, rotation):
|
||||
""" Initialize AMVectorLinePrimitive
|
||||
|
||||
Parameters
|
||||
----------
|
||||
code : int
|
||||
Vector Line Primitive code. Must be either 2 or 20.
|
||||
|
||||
exposure : string
|
||||
'on' or 'off'
|
||||
|
||||
width : float
|
||||
Line width
|
||||
|
||||
start : tuple (<float>, <float>)
|
||||
coordinate of line start point
|
||||
|
||||
end : tuple (<float>, <float>)
|
||||
coordinate of line end point
|
||||
|
||||
rotation : float
|
||||
Line rotation about the origin.
|
||||
|
||||
Returns
|
||||
-------
|
||||
LinePrimitive : :class:`gerber.am_statements.AMVectorLinePrimitive`
|
||||
An initialized AMVectorLinePrimitive
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError, TypeError
|
||||
"""
|
||||
validate_coordinates(start)
|
||||
validate_coordinates(end)
|
||||
if code not in (2, 20):
|
||||
raise ValueError('Valid VectorLinePrimitive codes are 2 or 20')
|
||||
super(AMVectorLinePrimitive, self).__init__(code, exposure)
|
||||
self.width = width
|
||||
self.start = start
|
||||
self.end = end
|
||||
self.rotation = rotation
|
||||
|
||||
def to_gerber(self, settings=None):
|
||||
fmtstr = '{code},{exp},{width},{startx},{starty},{endx},{endy},{rot}*'
|
||||
data = dict(code = self.code,
|
||||
exp = 1 if self.exposure == 'on' else 0,
|
||||
startx = self.start[0],
|
||||
starty = self.start[1],
|
||||
endx = self.end[0],
|
||||
endy = self.end[1],
|
||||
rotation = self.rotation)
|
||||
return fmtstr.format(**data)
|
||||
|
||||
# Code 4
|
||||
class AMOutlinePrimitive(AMPrimitive):
|
||||
|
||||
@classmethod
|
||||
def from_gerber(cls, primitive):
|
||||
modifiers = primitive.strip(' *').split(",")
|
||||
|
||||
code = int(modifiers[0])
|
||||
exposure = "on" if modifiers[1].strip() == "1" else "off"
|
||||
n = int(modifiers[2])
|
||||
start_point = (float(modifiers[3]), float(modifiers[4]))
|
||||
points = []
|
||||
|
||||
for i in range(n):
|
||||
points.append((float(modifiers[5 + i*2]), float(modifiers[5 + i*2 + 1])))
|
||||
|
||||
rotation = float(modifiers[-1])
|
||||
|
||||
return cls(code, exposure, start_point, points, rotation)
|
||||
|
||||
def __init__(self, code, exposure, start_point, points, rotation):
|
||||
""" Initialize AMOutlinePrimitive
|
||||
|
||||
Parameters
|
||||
----------
|
||||
code : int
|
||||
OutlinePrimitive code. Must be 4.
|
||||
|
||||
exposure : string
|
||||
'on' or 'off'
|
||||
|
||||
start_point : tuple (<float>, <float>)
|
||||
coordinate of outline start point
|
||||
|
||||
points : list of tuples (<float>, <float>)
|
||||
coordinates of subsequent points
|
||||
|
||||
rotation : float
|
||||
outline rotation about the origin.
|
||||
|
||||
Returns
|
||||
-------
|
||||
OutlinePrimitive : :class:`gerber.am_statements.AMOutlineinePrimitive`
|
||||
An initialized AMOutlinePrimitive
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError, TypeError
|
||||
"""
|
||||
validate_coordinates(start_point)
|
||||
for point in points:
|
||||
validate_coordinates(point)
|
||||
super(AMOutlinePrimitive, self).__init__(code, exposure)
|
||||
self.start_point = start_point
|
||||
self.points = points
|
||||
self.rotation = rotation
|
||||
|
||||
def to_inch(self):
|
||||
self.start_point = tuple([x / 25.4 for x in self.start_point])
|
||||
self.points = tuple([(x / 25.4, y / 25.4) for x, y in self.points])
|
||||
|
||||
def to_metric(self):
|
||||
self.start_point = tuple([x * 25.4 for x in self.start_point])
|
||||
self.points = tuple([(x * 25.4, y * 25.4) for x, y in self.points])
|
||||
|
||||
def to_gerber(self, settings=None):
|
||||
data = dict(
|
||||
code=self.code,
|
||||
exposure="1" if self.exposure == "on" else "0",
|
||||
n_points=len(self.points),
|
||||
start_point="%.4f,%.4f" % self.start_point,
|
||||
points=",".join(["%.4f,%.4f" % point for point in self.points]),
|
||||
rotation=str(self.rotation)
|
||||
)
|
||||
return "{code},{exposure},{n_points},{start_point},{points},{rotation}*".format(**data)
|
||||
|
||||
# Code 5
|
||||
class AMPolygonPrimitive(AMPrimitive):
|
||||
pass
|
||||
|
||||
|
||||
# Code 6
|
||||
class AMMoirePrimitive(AMPrimitive):
|
||||
pass
|
||||
|
||||
|
||||
# Code 7
|
||||
class AMThermalPrimitive(AMPrimitive):
|
||||
pass
|
||||
|
||||
|
||||
# Code 21
|
||||
class AMCenterLinePrimitive(AMPrimitive):
|
||||
pass
|
||||
|
||||
|
||||
# Code 22
|
||||
class AMLowerLeftLinePrimitive(AMPrimitive):
|
||||
pass
|
||||
|
||||
|
||||
class AMUnsupportPrimitive(AMPrimitive):
|
||||
@classmethod
|
||||
def from_gerber(cls, primitive):
|
||||
return cls(primitive)
|
||||
|
||||
def __init__(self, primitive):
|
||||
self.primitive = primitive
|
||||
|
||||
def to_gerber(self, settings=None):
|
||||
return self.primitive
|
||||
|
|
@ -21,6 +21,7 @@ Gerber (RS-274X) Statements
|
|||
|
||||
"""
|
||||
from .utils import parse_gerber_value, write_gerber_value, decimal_string
|
||||
from .am_statements import *
|
||||
|
||||
|
||||
class Statement(object):
|
||||
|
|
@ -290,77 +291,6 @@ class ADParamStmt(ParamStmt):
|
|||
return '<Aperture Definition: %d: %s>' % (self.d, shape)
|
||||
|
||||
|
||||
class AMPrimitive(object):
|
||||
|
||||
def __init__(self, code, exposure):
|
||||
self.code = code
|
||||
self.exposure = exposure
|
||||
|
||||
def to_inch(self):
|
||||
pass
|
||||
|
||||
def to_metric(self):
|
||||
pass
|
||||
|
||||
|
||||
class AMOutlinePrimitive(AMPrimitive):
|
||||
|
||||
@classmethod
|
||||
def from_gerber(cls, primitive):
|
||||
modifiers = primitive.split(",")
|
||||
|
||||
code = int(modifiers[0])
|
||||
exposure = "on" if modifiers[1] == "1" else "off"
|
||||
n = int(modifiers[2])
|
||||
start_point = (float(modifiers[3]), float(modifiers[4]))
|
||||
points = []
|
||||
|
||||
for i in range(n):
|
||||
points.append((float(modifiers[5 + i*2]), float(modifiers[5 + i*2 + 1])))
|
||||
|
||||
rotation = float(modifiers[-1])
|
||||
|
||||
return cls(code, exposure, start_point, points, rotation)
|
||||
|
||||
def __init__(self, code, exposure, start_point, points, rotation):
|
||||
super(AMOutlinePrimitive, self).__init__(code, exposure)
|
||||
|
||||
self.start_point = start_point
|
||||
self.points = points
|
||||
self.rotation = rotation
|
||||
|
||||
def to_inch(self):
|
||||
self.start_point = tuple([x / 25.4 for x in self.start_point])
|
||||
self.points = tuple([(x / 25.4, y / 25.4) for x, y in self.points])
|
||||
|
||||
def to_metric(self):
|
||||
self.start_point = tuple([x * 25.4 for x in self.start_point])
|
||||
self.points = tuple([(x * 25.4, y * 25.4) for x, y in self.points])
|
||||
|
||||
def to_gerber(self, settings=None):
|
||||
data = dict(
|
||||
code=self.code,
|
||||
exposure="1" if self.exposure == "on" else "0",
|
||||
n_points=len(self.points),
|
||||
start_point="%.4f,%.4f" % self.start_point,
|
||||
points=",".join(["%.4f,%.4f" % point for point in self.points]),
|
||||
rotation=str(self.rotation)
|
||||
)
|
||||
return "{code},{exposure},{n_points},{start_point},{points},{rotation}".format(**data)
|
||||
|
||||
|
||||
class AMUnsupportPrimitive(object):
|
||||
@classmethod
|
||||
def from_gerber(cls, primitive):
|
||||
return cls(primitive)
|
||||
|
||||
def __init__(self, primitive):
|
||||
self.primitive = primitive
|
||||
|
||||
def to_gerber(self, settings=None):
|
||||
return self.primitive
|
||||
|
||||
|
||||
class AMParamStmt(ParamStmt):
|
||||
""" AM - Aperture Macro Statement
|
||||
"""
|
||||
|
|
@ -396,9 +326,12 @@ class AMParamStmt(ParamStmt):
|
|||
|
||||
def _parsePrimitives(self, macro):
|
||||
primitives = []
|
||||
|
||||
for primitive in macro.split("*"):
|
||||
if primitive[0] == "4":
|
||||
for primitive in macro.split('*'):
|
||||
# Couldn't find anything explicit about leading whitespace in the spec...
|
||||
primitive = primitive.lstrip()
|
||||
if primitive[0] == '0':
|
||||
primitives.append(AMCommentPrimitive.from_gerber(primitive))
|
||||
if primitive[0] == '4':
|
||||
primitives.append(AMOutlinePrimitive.from_gerber(primitive))
|
||||
else:
|
||||
primitives.append(AMUnsupportPrimitive.from_gerber(primitive))
|
||||
|
|
@ -414,7 +347,7 @@ class AMParamStmt(ParamStmt):
|
|||
primitive.to_metric()
|
||||
|
||||
def to_gerber(self, settings=None):
|
||||
return '%AM{0}*{1}*%'.format(self.name, "".join([primitive.to_gerber(settings) for primitive in self.primitives]))
|
||||
return '%AM{0}*{1}%'.format(self.name, '\n'.join([primitive.to_gerber(settings) for primitive in self.primitives]))
|
||||
|
||||
def __str__(self):
|
||||
return '<Aperture Macro %s: %s>' % (self.name, self.macro)
|
||||
|
|
|
|||
77
gerber/tests/test_am_statements.py
Normal file
77
gerber/tests/test_am_statements.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Author: Hamilton Kibbe <ham@hamiltonkib.be>
|
||||
|
||||
from .tests import *
|
||||
from ..am_statements import *
|
||||
|
||||
def test_AMPrimitive_ctor():
|
||||
for exposure in ('on', 'off', 'ON', 'OFF'):
|
||||
for code in (0, 1, 2, 4, 5, 6, 7, 20, 21, 22):
|
||||
p = AMPrimitive(code, exposure)
|
||||
assert_equal(p.code, code)
|
||||
assert_equal(p.exposure, exposure.lower())
|
||||
|
||||
|
||||
def test_AMPrimitive_validation():
|
||||
assert_raises(TypeError, AMPrimitive, '1', 'off')
|
||||
assert_raises(ValueError, AMPrimitive, 0, 'exposed')
|
||||
assert_raises(ValueError, AMPrimitive, 3, 'off')
|
||||
|
||||
|
||||
def test_AMCommentPrimitive_ctor():
|
||||
c = AMCommentPrimitive(0, ' This is a comment *')
|
||||
assert_equal(c.code, 0)
|
||||
assert_equal(c.comment, 'This is a comment')
|
||||
|
||||
|
||||
def test_AMCommentPrimitive_validation():
|
||||
assert_raises(ValueError, AMCommentPrimitive, 1, 'This is a comment')
|
||||
|
||||
|
||||
def test_AMCommentPrimitive_factory():
|
||||
c = AMCommentPrimitive.from_gerber('0 Rectangle with rounded corners. *')
|
||||
assert_equal(c.code, 0)
|
||||
assert_equal(c.comment, 'Rectangle with rounded corners.')
|
||||
|
||||
|
||||
def test_AMCommentPrimitive_dump():
|
||||
c = AMCommentPrimitive(0, 'Rectangle with rounded corners.')
|
||||
assert_equal(c.to_gerber(), '0 Rectangle with rounded corners. *')
|
||||
|
||||
|
||||
def test_AMCirclePrimitive_ctor():
|
||||
test_cases = ((1, 'on', 0, (0, 0)),
|
||||
(1, 'off', 1, (0, 1)),
|
||||
(1, 'on', 2.5, (0, 2)),
|
||||
(1, 'off', 5.0, (3, 3)))
|
||||
for code, exposure, diameter, position in test_cases:
|
||||
c = AMCirclePrimitive(code, exposure, diameter, position)
|
||||
assert_equal(c.code, code)
|
||||
assert_equal(c.exposure, exposure)
|
||||
assert_equal(c.diameter, diameter)
|
||||
assert_equal(c.position, position)
|
||||
|
||||
|
||||
def test_AMCirclePrimitive_validation():
|
||||
assert_raises(ValueError, AMCirclePrimitive, 2, 'on', 0, (0, 0))
|
||||
|
||||
|
||||
def test_AMCirclePrimitive_factory():
|
||||
c = AMCirclePrimitive.from_gerber('1,0,5,0,0*')
|
||||
assert_equal(c.code, 1)
|
||||
assert_equal(c.exposure, 'off')
|
||||
assert_equal(c.diameter, 5)
|
||||
assert_equal(c.position, (0,0))
|
||||
|
||||
|
||||
def test_AMCirclePrimitive_dump():
|
||||
c = AMCirclePrimitive(1, 'off', 5, (0, 0))
|
||||
assert_equal(c.to_gerber(), '1,0,5,0,0*')
|
||||
c = AMCirclePrimitive(1, 'on', 5, (0, 0))
|
||||
assert_equal(c.to_gerber(), '1,1,5,0,0*')
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue