kopia lustrzana https://gitlab.com/gerbolyze/gerbonara
1047 wiersze
33 KiB
Python
1047 wiersze
33 KiB
Python
#!/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 math import asin
|
|
import math
|
|
|
|
from .primitives import *
|
|
from .utils import validate_coordinates, inch, metric, rotate_point
|
|
|
|
|
|
|
|
# TODO: Add support for aperture macro variables
|
|
__all__ = ['AMPrimitive', 'AMCommentPrimitive', 'AMCirclePrimitive',
|
|
'AMVectorLinePrimitive', 'AMOutlinePrimitive', 'AMPolygonPrimitive',
|
|
'AMMoirePrimitive', 'AMThermalPrimitive', 'AMCenterLinePrimitive',
|
|
'AMLowerLeftLinePrimitive', 'AMUnsupportPrimitive']
|
|
|
|
|
|
class AMPrimitive(object):
|
|
""" 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
|
|
"""
|
|
|
|
def __init__(self, code, exposure=None):
|
|
VALID_CODES = (0, 1, 2, 4, 5, 6, 7, 20, 21, 22, 9999)
|
|
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):
|
|
raise NotImplementedError('Subclass must implement `to-inch`')
|
|
|
|
def to_metric(self):
|
|
raise NotImplementedError('Subclass must implement `to-metric`')
|
|
|
|
@property
|
|
def _level_polarity(self):
|
|
if self.exposure == 'off':
|
|
return 'clear'
|
|
return 'dark'
|
|
|
|
def to_primitive(self, units):
|
|
""" Return a Primitive instance based on the specified macro params.
|
|
"""
|
|
print('Rendering {}s is not supported yet.'.format(str(self.__class__)))
|
|
|
|
def __eq__(self, other):
|
|
return self.__dict__ == other.__dict__
|
|
|
|
|
|
class AMCommentPrimitive(AMPrimitive):
|
|
""" Aperture Macro Comment primitive. Code 0
|
|
|
|
The comment primitive has no image meaning. It is used to include human-
|
|
readable comments into the AM command.
|
|
|
|
.. seealso::
|
|
`The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
|
|
**Section 4.12.3.1:** Comment, primitive code 0
|
|
|
|
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
|
|
"""
|
|
@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):
|
|
if code != 0:
|
|
raise ValueError('Not a valid Aperture Macro Comment statement')
|
|
super(AMCommentPrimitive, self).__init__(code)
|
|
self.comment = comment.strip(' *')
|
|
|
|
def to_inch(self):
|
|
pass
|
|
|
|
def to_metric(self):
|
|
pass
|
|
|
|
def to_gerber(self, settings=None):
|
|
return '0 %s *' % self.comment
|
|
|
|
def to_primitive(self, units):
|
|
"""
|
|
Returns None - has not primitive representation
|
|
"""
|
|
return None
|
|
|
|
def __str__(self):
|
|
return '<Aperture Macro Comment: %s>' % self.comment
|
|
|
|
|
|
class AMCirclePrimitive(AMPrimitive):
|
|
""" Aperture macro Circle primitive. Code 1
|
|
|
|
A circle primitive is defined by its center point and diameter.
|
|
|
|
.. seealso::
|
|
`The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
|
|
**Section 4.12.3.2:** Circle, primitive code 1
|
|
|
|
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
|
|
"""
|
|
@classmethod
|
|
def from_gerber(cls, primitive):
|
|
modifiers = primitive.strip(' *').split(',')
|
|
code = int(modifiers[0])
|
|
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)
|
|
|
|
@classmethod
|
|
def from_primitive(cls, primitive):
|
|
return cls(1, 'on', primitive.diameter, primitive.position)
|
|
|
|
def __init__(self, code, exposure, diameter, position):
|
|
validate_coordinates(position)
|
|
if code != 1:
|
|
raise ValueError('CirclePrimitive code is 1')
|
|
super(AMCirclePrimitive, self).__init__(code, exposure)
|
|
self.diameter = diameter
|
|
self.position = position
|
|
|
|
def to_inch(self):
|
|
self.diameter = inch(self.diameter)
|
|
self.position = tuple([inch(x) for x in self.position])
|
|
|
|
def to_metric(self):
|
|
self.diameter = metric(self.diameter)
|
|
self.position = tuple([metric(x) for x in self.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)
|
|
|
|
def to_primitive(self, units):
|
|
return Circle((self.position), self.diameter, units=units, level_polarity=self._level_polarity)
|
|
|
|
|
|
class AMVectorLinePrimitive(AMPrimitive):
|
|
""" Aperture Macro Vector Line primitive. Code 2 or 20.
|
|
|
|
A vector line is a rectangle defined by its line width, start, and end
|
|
points. The line ends are rectangular.
|
|
|
|
.. seealso::
|
|
`The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
|
|
**Section 4.12.3.3:** Vector Line, primitive code 2 or 20.
|
|
|
|
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
|
|
"""
|
|
|
|
@classmethod
|
|
def from_primitive(cls, primitive):
|
|
return cls(2, 'on', primitive.aperture.width, primitive.start, primitive.end, 0)
|
|
|
|
@classmethod
|
|
def from_gerber(cls, primitive):
|
|
modifiers = primitive.strip(' *').split(',')
|
|
code = int(modifiers[0])
|
|
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]))
|
|
rotation = float(modifiers[7])
|
|
return cls(code, exposure, width, start, end, rotation)
|
|
|
|
def __init__(self, code, exposure, width, start, end, rotation):
|
|
validate_coordinates(start)
|
|
validate_coordinates(end)
|
|
if code not in (2, 20):
|
|
raise ValueError('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_inch(self):
|
|
self.width = inch(self.width)
|
|
self.start = tuple([inch(x) for x in self.start])
|
|
self.end = tuple([inch(x) for x in self.end])
|
|
|
|
def to_metric(self):
|
|
self.width = metric(self.width)
|
|
self.start = tuple([metric(x) for x in self.start])
|
|
self.end = tuple([metric(x) for x in self.end])
|
|
|
|
def to_gerber(self, settings=None):
|
|
fmtstr = '{code},{exp},{width},{startx},{starty},{endx},{endy},{rotation}*'
|
|
data = dict(code=self.code,
|
|
exp=1 if self.exposure == 'on' else 0,
|
|
width=self.width,
|
|
startx=self.start[0],
|
|
starty=self.start[1],
|
|
endx=self.end[0],
|
|
endy=self.end[1],
|
|
rotation=self.rotation)
|
|
return fmtstr.format(**data)
|
|
|
|
def to_primitive(self, units):
|
|
"""
|
|
Convert this to a primitive. We use the Outline to represent this (instead of Line)
|
|
because the behaviour of the end caps is different for aperture macros compared to Lines
|
|
when rotated.
|
|
"""
|
|
|
|
# Use a line to generate our vertices easily
|
|
line = Line(self.start, self.end, Rectangle(None, self.width, self.width))
|
|
vertices = line.vertices
|
|
|
|
aperture = Circle((0, 0), 0)
|
|
|
|
lines = []
|
|
prev_point = rotate_point(vertices[-1], self.rotation, (0, 0))
|
|
for point in vertices:
|
|
cur_point = rotate_point(point, self.rotation, (0, 0))
|
|
|
|
lines.append(Line(prev_point, cur_point, aperture))
|
|
|
|
return Outline(lines, units=units, level_polarity=self._level_polarity)
|
|
|
|
|
|
class AMOutlinePrimitive(AMPrimitive):
|
|
""" Aperture Macro Outline primitive. Code 4.
|
|
|
|
An outline primitive is an area enclosed by an n-point polygon defined by
|
|
its start point and n subsequent points. The outline must be closed, i.e.
|
|
the last point must be equal to the start point. Self intersecting
|
|
outlines are not allowed.
|
|
|
|
.. seealso::
|
|
`The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
|
|
**Section 4.12.3.6:** Outline, primitive code 4.
|
|
|
|
Parameters
|
|
----------
|
|
code : int
|
|
OutlinePrimitive code. Must be 6.
|
|
|
|
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
|
|
"""
|
|
|
|
@classmethod
|
|
def from_primitive(cls, primitive):
|
|
|
|
start_point = (round(primitive.primitives[0].start[0], 6), round(primitive.primitives[0].start[1], 6))
|
|
points = []
|
|
for prim in primitive.primitives:
|
|
points.append((round(prim.end[0], 6), round(prim.end[1], 6)))
|
|
|
|
rotation = 0.0
|
|
|
|
return cls(4, 'on', start_point, points, rotation)
|
|
|
|
@classmethod
|
|
def from_gerber(cls, primitive):
|
|
modifiers = primitive.strip(' *').split(",")
|
|
|
|
code = int(modifiers[0])
|
|
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):
|
|
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
|
|
"""
|
|
validate_coordinates(start_point)
|
|
for point in points:
|
|
validate_coordinates(point)
|
|
if code != 4:
|
|
raise ValueError('OutlinePrimitive code is 4')
|
|
super(AMOutlinePrimitive, self).__init__(code, exposure)
|
|
self.start_point = start_point
|
|
if points[-1] != start_point:
|
|
raise ValueError('OutlinePrimitive must be closed')
|
|
self.points = points
|
|
self.rotation = rotation
|
|
|
|
def to_inch(self):
|
|
self.start_point = tuple([inch(x) for x in self.start_point])
|
|
self.points = tuple([(inch(x), inch(y)) for x, y in self.points])
|
|
|
|
def to_metric(self):
|
|
self.start_point = tuple([metric(x) for x in self.start_point])
|
|
self.points = tuple([(metric(x), metric(y)) 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="%.6g,%.6g" % self.start_point,
|
|
points=",\n".join(["%.6g,%.6g" % point for point in self.points]),
|
|
rotation=str(self.rotation)
|
|
)
|
|
return "{code},{exposure},{n_points},{start_point},{points},{rotation}*".format(**data)
|
|
|
|
def to_primitive(self, units):
|
|
"""
|
|
Convert this to a drawable primitive. This uses the Outline instead of Line
|
|
primitive to handle differences in end caps when rotated.
|
|
"""
|
|
|
|
lines = []
|
|
prev_point = rotate_point(self.start_point, self.rotation)
|
|
for point in self.points:
|
|
cur_point = rotate_point(point, self.rotation)
|
|
|
|
lines.append(Line(prev_point, cur_point, Circle((0,0), 0)))
|
|
|
|
prev_point = cur_point
|
|
|
|
if lines[0].start != lines[-1].end:
|
|
raise ValueError('Outline must be closed')
|
|
|
|
return Outline(lines, units=units, level_polarity=self._level_polarity)
|
|
|
|
|
|
class AMPolygonPrimitive(AMPrimitive):
|
|
""" Aperture Macro Polygon primitive. Code 5.
|
|
|
|
A polygon primitive is a regular polygon defined by the number of
|
|
vertices, the center point, and the diameter of the circumscribed circle.
|
|
|
|
.. seealso::
|
|
`The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
|
|
**Section 4.12.3.8:** Polygon, primitive code 5.
|
|
|
|
Parameters
|
|
----------
|
|
code : int
|
|
PolygonPrimitive code. Must be 5.
|
|
|
|
exposure : string
|
|
'on' or 'off'
|
|
|
|
vertices : int, 3 <= vertices <= 12
|
|
Number of vertices
|
|
|
|
position : tuple (<float>, <float>)
|
|
X and Y coordinates of polygon center
|
|
|
|
diameter : float
|
|
diameter of circumscribed circle.
|
|
|
|
rotation : float
|
|
polygon rotation about the origin.
|
|
|
|
Returns
|
|
-------
|
|
PolygonPrimitive : :class:`gerber.am_statements.AMPolygonPrimitive`
|
|
An initialized AMPolygonPrimitive
|
|
|
|
Raises
|
|
------
|
|
ValueError, TypeError
|
|
"""
|
|
|
|
@classmethod
|
|
def from_primitive(cls, primitive):
|
|
return cls(5, 'on', primitive.sides, primitive.position, primitive.diameter, primitive.rotation)
|
|
|
|
@classmethod
|
|
def from_gerber(cls, primitive):
|
|
modifiers = primitive.strip(' *').split(",")
|
|
code = int(modifiers[0])
|
|
exposure = "on" if float(modifiers[1]) == 1 else "off"
|
|
vertices = int(float(modifiers[2]))
|
|
position = (float(modifiers[3]), float(modifiers[4]))
|
|
try:
|
|
diameter = float(modifiers[5])
|
|
except:
|
|
diameter = 0
|
|
|
|
rotation = float(modifiers[6])
|
|
return cls(code, exposure, vertices, position, diameter, rotation)
|
|
|
|
def __init__(self, code, exposure, vertices, position, diameter, rotation):
|
|
""" Initialize AMPolygonPrimitive
|
|
"""
|
|
if code != 5:
|
|
raise ValueError('PolygonPrimitive code is 5')
|
|
super(AMPolygonPrimitive, self).__init__(code, exposure)
|
|
if vertices < 3 or vertices > 12:
|
|
raise ValueError('Number of vertices must be between 3 and 12')
|
|
self.vertices = vertices
|
|
validate_coordinates(position)
|
|
self.position = position
|
|
self.diameter = diameter
|
|
self.rotation = rotation
|
|
|
|
def to_inch(self):
|
|
self.position = tuple([inch(x) for x in self.position])
|
|
self.diameter = inch(self.diameter)
|
|
|
|
def to_metric(self):
|
|
self.position = tuple([metric(x) for x in self.position])
|
|
self.diameter = metric(self.diameter)
|
|
|
|
def to_gerber(self, settings=None):
|
|
data = dict(
|
|
code=self.code,
|
|
exposure="1" if self.exposure == "on" else "0",
|
|
vertices=self.vertices,
|
|
position="%.4g,%.4g" % self.position,
|
|
diameter='%.4g' % self.diameter,
|
|
rotation=str(self.rotation)
|
|
)
|
|
fmt = "{code},{exposure},{vertices},{position},{diameter},{rotation}*"
|
|
return fmt.format(**data)
|
|
|
|
def to_primitive(self, units):
|
|
return Polygon(self.position, self.vertices, self.diameter / 2.0, 0, rotation=math.radians(self.rotation), units=units, level_polarity=self._level_polarity)
|
|
|
|
|
|
class AMMoirePrimitive(AMPrimitive):
|
|
""" Aperture Macro Moire primitive. Code 6.
|
|
|
|
The moire primitive is a cross hair centered on concentric rings (annuli).
|
|
Exposure is always on.
|
|
|
|
.. seealso::
|
|
`The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
|
|
**Section 4.12.3.9:** Moire, primitive code 6.
|
|
|
|
Parameters
|
|
----------
|
|
code : int
|
|
Moire Primitive code. Must be 6.
|
|
|
|
position : tuple (<float>, <float>)
|
|
X and Y coordinates of moire center
|
|
|
|
diameter : float
|
|
outer diameter of outer ring.
|
|
|
|
ring_thickness : float
|
|
thickness of concentric rings.
|
|
|
|
gap : float
|
|
gap between concentric rings.
|
|
|
|
max_rings : float
|
|
maximum number of rings
|
|
|
|
crosshair_thickness : float
|
|
thickness of crosshairs
|
|
|
|
crosshair_length : float
|
|
length of crosshairs
|
|
|
|
rotation : float
|
|
moire rotation about the origin.
|
|
|
|
Returns
|
|
-------
|
|
MoirePrimitive : :class:`gerber.am_statements.AMMoirePrimitive`
|
|
An initialized AMMoirePrimitive
|
|
|
|
Raises
|
|
------
|
|
ValueError, TypeError
|
|
"""
|
|
@classmethod
|
|
def from_gerber(cls, primitive):
|
|
modifiers = primitive.strip(' *').split(",")
|
|
code = int(modifiers[0])
|
|
position = (float(modifiers[1]), float(modifiers[2]))
|
|
diameter = float(modifiers[3])
|
|
ring_thickness = float(modifiers[4])
|
|
gap = float(modifiers[5])
|
|
max_rings = int(float(modifiers[6]))
|
|
crosshair_thickness = float(modifiers[7])
|
|
crosshair_length = float(modifiers[8])
|
|
rotation = float(modifiers[9])
|
|
return cls(code, position, diameter, ring_thickness, gap, max_rings, crosshair_thickness, crosshair_length, rotation)
|
|
|
|
def __init__(self, code, position, diameter, ring_thickness, gap, max_rings, crosshair_thickness, crosshair_length, rotation):
|
|
""" Initialize AMoirePrimitive
|
|
"""
|
|
if code != 6:
|
|
raise ValueError('MoirePrimitive code is 6')
|
|
super(AMMoirePrimitive, self).__init__(code, 'on')
|
|
validate_coordinates(position)
|
|
self.position = position
|
|
self.diameter = diameter
|
|
self.ring_thickness = ring_thickness
|
|
self.gap = gap
|
|
self.max_rings = max_rings
|
|
self.crosshair_thickness = crosshair_thickness
|
|
self.crosshair_length = crosshair_length
|
|
self.rotation = rotation
|
|
|
|
def to_inch(self):
|
|
self.position = tuple([inch(x) for x in self.position])
|
|
self.diameter = inch(self.diameter)
|
|
self.ring_thickness = inch(self.ring_thickness)
|
|
self.gap = inch(self.gap)
|
|
self.crosshair_thickness = inch(self.crosshair_thickness)
|
|
self.crosshair_length = inch(self.crosshair_length)
|
|
|
|
def to_metric(self):
|
|
self.position = tuple([metric(x) for x in self.position])
|
|
self.diameter = metric(self.diameter)
|
|
self.ring_thickness = metric(self.ring_thickness)
|
|
self.gap = metric(self.gap)
|
|
self.crosshair_thickness = metric(self.crosshair_thickness)
|
|
self.crosshair_length = metric(self.crosshair_length)
|
|
|
|
|
|
def to_gerber(self, settings=None):
|
|
data = dict(
|
|
code=self.code,
|
|
position="%.4g,%.4g" % self.position,
|
|
diameter=self.diameter,
|
|
ring_thickness=self.ring_thickness,
|
|
gap=self.gap,
|
|
max_rings=self.max_rings,
|
|
crosshair_thickness=self.crosshair_thickness,
|
|
crosshair_length=self.crosshair_length,
|
|
rotation=self.rotation
|
|
)
|
|
fmt = "{code},{position},{diameter},{ring_thickness},{gap},{max_rings},{crosshair_thickness},{crosshair_length},{rotation}*"
|
|
return fmt.format(**data)
|
|
|
|
def to_primitive(self, units):
|
|
#raise NotImplementedError()
|
|
return None
|
|
|
|
|
|
class AMThermalPrimitive(AMPrimitive):
|
|
""" Aperture Macro Thermal primitive. Code 7.
|
|
|
|
The thermal primitive is a ring (annulus) interrupted by four gaps.
|
|
Exposure is always on.
|
|
|
|
.. seealso::
|
|
`The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
|
|
**Section 4.12.3.10:** Thermal, primitive code 7.
|
|
|
|
Parameters
|
|
----------
|
|
code : int
|
|
Thermal Primitive code. Must be 7.
|
|
|
|
position : tuple (<float>, <float>)
|
|
X and Y coordinates of thermal center
|
|
|
|
outer_diameter : float
|
|
outer diameter of thermal.
|
|
|
|
inner_diameter : float
|
|
inner diameter of thermal.
|
|
|
|
gap : float
|
|
gap thickness
|
|
|
|
rotation : float
|
|
thermal rotation about the origin.
|
|
|
|
Returns
|
|
-------
|
|
ThermalPrimitive : :class:`gerber.am_statements.AMThermalPrimitive`
|
|
An initialized AMThermalPrimitive
|
|
|
|
Raises
|
|
------
|
|
ValueError, TypeError
|
|
"""
|
|
@classmethod
|
|
def from_gerber(cls, primitive):
|
|
modifiers = primitive.strip(' *').split(",")
|
|
code = int(modifiers[0])
|
|
position = (float(modifiers[1]), float(modifiers[2]))
|
|
outer_diameter = float(modifiers[3])
|
|
inner_diameter = float(modifiers[4])
|
|
gap = float(modifiers[5])
|
|
rotation = float(modifiers[6])
|
|
return cls(code, position, outer_diameter, inner_diameter, gap, rotation)
|
|
|
|
def __init__(self, code, position, outer_diameter, inner_diameter, gap, rotation):
|
|
if code != 7:
|
|
raise ValueError('ThermalPrimitive code is 7')
|
|
super(AMThermalPrimitive, self).__init__(code, 'on')
|
|
validate_coordinates(position)
|
|
self.position = position
|
|
self.outer_diameter = outer_diameter
|
|
self.inner_diameter = inner_diameter
|
|
self.gap = gap
|
|
self.rotation = rotation
|
|
|
|
def to_inch(self):
|
|
self.position = tuple([inch(x) for x in self.position])
|
|
self.outer_diameter = inch(self.outer_diameter)
|
|
self.inner_diameter = inch(self.inner_diameter)
|
|
self.gap = inch(self.gap)
|
|
|
|
def to_metric(self):
|
|
self.position = tuple([metric(x) for x in self.position])
|
|
self.outer_diameter = metric(self.outer_diameter)
|
|
self.inner_diameter = metric(self.inner_diameter)
|
|
self.gap = metric(self.gap)
|
|
|
|
def to_gerber(self, settings=None):
|
|
data = dict(
|
|
code=self.code,
|
|
position="%.4g,%.4g" % self.position,
|
|
outer_diameter=self.outer_diameter,
|
|
inner_diameter=self.inner_diameter,
|
|
gap=self.gap,
|
|
rotation=self.rotation
|
|
)
|
|
fmt = "{code},{position},{outer_diameter},{inner_diameter},{gap},{rotation}*"
|
|
return fmt.format(**data)
|
|
|
|
def _approximate_arc_cw(self, start_angle, end_angle, radius, center):
|
|
"""
|
|
Get an arc as a series of points
|
|
|
|
Parameters
|
|
----------
|
|
start_angle : The start angle in radians
|
|
end_angle : The end angle in radians
|
|
radius`: Radius of the arc
|
|
center : The center point of the arc (x, y) tuple
|
|
|
|
Returns
|
|
-------
|
|
array of point tuples
|
|
"""
|
|
|
|
# The total sweep
|
|
sweep_angle = end_angle - start_angle
|
|
num_steps = 10
|
|
|
|
angle_step = sweep_angle / num_steps
|
|
|
|
radius = radius
|
|
center = center
|
|
|
|
points = []
|
|
|
|
for i in range(num_steps + 1):
|
|
current_angle = start_angle + (angle_step * i)
|
|
|
|
nextx = (center[0] + math.cos(current_angle) * radius)
|
|
nexty = (center[1] + math.sin(current_angle) * radius)
|
|
|
|
points.append((nextx, nexty))
|
|
|
|
return points
|
|
|
|
def to_primitive(self, units):
|
|
|
|
# We start with calculating the top right section, then duplicate it
|
|
|
|
inner_radius = self.inner_diameter / 2.0
|
|
outer_radius = self.outer_diameter / 2.0
|
|
|
|
# Calculate the start angle relative to the horizontal axis
|
|
inner_offset_angle = asin(self.gap / 2.0 / inner_radius)
|
|
outer_offset_angle = asin(self.gap / 2.0 / outer_radius)
|
|
|
|
rotation_rad = math.radians(self.rotation)
|
|
inner_start_angle = inner_offset_angle + rotation_rad
|
|
inner_end_angle = math.pi / 2 - inner_offset_angle + rotation_rad
|
|
|
|
outer_start_angle = outer_offset_angle + rotation_rad
|
|
outer_end_angle = math.pi / 2 - outer_offset_angle + rotation_rad
|
|
|
|
outlines = []
|
|
aperture = Circle((0, 0), 0)
|
|
|
|
points = (self._approximate_arc_cw(inner_start_angle, inner_end_angle, inner_radius, self.position)
|
|
+ list(reversed(self._approximate_arc_cw(outer_start_angle, outer_end_angle, outer_radius, self.position))))
|
|
# Add in the last point since outlines should be closed
|
|
points.append(points[0])
|
|
|
|
# There are four outlines at rotated sections
|
|
for rotation in [0, 90.0, 180.0, 270.0]:
|
|
|
|
lines = []
|
|
prev_point = rotate_point(points[0], rotation, self.position)
|
|
for point in points[1:]:
|
|
cur_point = rotate_point(point, rotation, self.position)
|
|
|
|
lines.append(Line(prev_point, cur_point, aperture))
|
|
|
|
prev_point = cur_point
|
|
|
|
outlines.append(Outline(lines, units=units, level_polarity=self._level_polarity))
|
|
|
|
return outlines
|
|
|
|
|
|
class AMCenterLinePrimitive(AMPrimitive):
|
|
""" Aperture Macro Center Line primitive. Code 21.
|
|
|
|
The center line primitive is a rectangle defined by its width, height, and center point.
|
|
|
|
.. seealso::
|
|
`The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
|
|
**Section 4.12.3.4:** Center Line, primitive code 21.
|
|
|
|
Parameters
|
|
----------
|
|
code : int
|
|
Center Line Primitive code. Must be 21.
|
|
|
|
exposure : str
|
|
'on' or 'off'
|
|
|
|
width : float
|
|
Width of rectangle
|
|
|
|
height : float
|
|
Height of rectangle
|
|
|
|
center : tuple (<float>, <float>)
|
|
X and Y coordinates of line center
|
|
|
|
rotation : float
|
|
rectangle rotation about its center.
|
|
|
|
Returns
|
|
-------
|
|
CenterLinePrimitive : :class:`gerber.am_statements.AMCenterLinePrimitive`
|
|
An initialized AMCenterLinePrimitive
|
|
|
|
Raises
|
|
------
|
|
ValueError, TypeError
|
|
"""
|
|
|
|
@classmethod
|
|
def from_primitive(cls, primitive):
|
|
width = primitive.width
|
|
height = primitive.height
|
|
center = primitive.position
|
|
rotation = math.degrees(primitive.rotation)
|
|
return cls(21, 'on', width, height, center, rotation)
|
|
|
|
@classmethod
|
|
def from_gerber(cls, primitive):
|
|
modifiers = primitive.strip(' *').split(",")
|
|
code = int(modifiers[0])
|
|
exposure = 'on' if float(modifiers[1]) == 1 else 'off'
|
|
width = float(modifiers[2])
|
|
height = float(modifiers[3])
|
|
center = (float(modifiers[4]), float(modifiers[5]))
|
|
rotation = float(modifiers[6])
|
|
return cls(code, exposure, width, height, center, rotation)
|
|
|
|
def __init__(self, code, exposure, width, height, center, rotation):
|
|
if code != 21:
|
|
raise ValueError('CenterLinePrimitive code is 21')
|
|
super(AMCenterLinePrimitive, self).__init__(code, exposure)
|
|
self.width = width
|
|
self.height = height
|
|
validate_coordinates(center)
|
|
self.center = center
|
|
self.rotation = rotation
|
|
|
|
def to_inch(self):
|
|
self.center = tuple([inch(x) for x in self.center])
|
|
self.width = inch(self.width)
|
|
self.height = inch(self.height)
|
|
|
|
def to_metric(self):
|
|
self.center = tuple([metric(x) for x in self.center])
|
|
self.width = metric(self.width)
|
|
self.height = metric(self.height)
|
|
|
|
def to_gerber(self, settings=None):
|
|
data = dict(
|
|
code=self.code,
|
|
exposure = '1' if self.exposure == 'on' else '0',
|
|
width = self.width,
|
|
height = self.height,
|
|
center="%.4g,%.4g" % self.center,
|
|
rotation=self.rotation
|
|
)
|
|
fmt = "{code},{exposure},{width},{height},{center},{rotation}*"
|
|
return fmt.format(**data)
|
|
|
|
def to_primitive(self, units):
|
|
|
|
x = self.center[0]
|
|
y = self.center[1]
|
|
half_width = self.width / 2.0
|
|
half_height = self.height / 2.0
|
|
|
|
points = []
|
|
points.append((x - half_width, y + half_height))
|
|
points.append((x - half_width, y - half_height))
|
|
points.append((x + half_width, y - half_height))
|
|
points.append((x + half_width, y + half_height))
|
|
|
|
aperture = Circle((0, 0), 0)
|
|
|
|
lines = []
|
|
prev_point = rotate_point(points[3], self.rotation, self.center)
|
|
for point in points:
|
|
cur_point = rotate_point(point, self.rotation, self.center)
|
|
|
|
lines.append(Line(prev_point, cur_point, aperture))
|
|
|
|
return Outline(lines, units=units, level_polarity=self._level_polarity)
|
|
|
|
|
|
class AMLowerLeftLinePrimitive(AMPrimitive):
|
|
""" Aperture Macro Lower Left Line primitive. Code 22.
|
|
|
|
The lower left line primitive is a rectangle defined by its width, height, and the lower left point.
|
|
|
|
.. seealso::
|
|
`The Gerber File Format Specification <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_
|
|
**Section 4.12.3.5:** Lower Left Line, primitive code 22.
|
|
|
|
Parameters
|
|
----------
|
|
code : int
|
|
Center Line Primitive code. Must be 22.
|
|
|
|
exposure : str
|
|
'on' or 'off'
|
|
|
|
width : float
|
|
Width of rectangle
|
|
|
|
height : float
|
|
Height of rectangle
|
|
|
|
lower_left : tuple (<float>, <float>)
|
|
X and Y coordinates of lower left corner
|
|
|
|
rotation : float
|
|
rectangle rotation about its origin.
|
|
|
|
Returns
|
|
-------
|
|
LowerLeftLinePrimitive : :class:`gerber.am_statements.AMLowerLeftLinePrimitive`
|
|
An initialized AMLowerLeftLinePrimitive
|
|
|
|
Raises
|
|
------
|
|
ValueError, TypeError
|
|
"""
|
|
@classmethod
|
|
def from_gerber(cls, primitive):
|
|
modifiers = primitive.strip(' *').split(",")
|
|
code = int(modifiers[0])
|
|
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]))
|
|
rotation = float(modifiers[6])
|
|
return cls(code, exposure, width, height, lower_left, rotation)
|
|
|
|
def __init__(self, code, exposure, width, height, lower_left, rotation):
|
|
if code != 22:
|
|
raise ValueError('LowerLeftLinePrimitive code is 22')
|
|
super (AMLowerLeftLinePrimitive, self).__init__(code, exposure)
|
|
self.width = width
|
|
self.height = height
|
|
validate_coordinates(lower_left)
|
|
self.lower_left = lower_left
|
|
self.rotation = rotation
|
|
|
|
def to_inch(self):
|
|
self.lower_left = tuple([inch(x) for x in self.lower_left])
|
|
self.width = inch(self.width)
|
|
self.height = inch(self.height)
|
|
|
|
def to_metric(self):
|
|
self.lower_left = tuple([metric(x) for x in self.lower_left])
|
|
self.width = metric(self.width)
|
|
self.height = metric(self.height)
|
|
|
|
def to_gerber(self, settings=None):
|
|
data = dict(
|
|
code=self.code,
|
|
exposure = '1' if self.exposure == 'on' else '0',
|
|
width = self.width,
|
|
height = self.height,
|
|
lower_left="%.4g,%.4g" % self.lower_left,
|
|
rotation=self.rotation
|
|
)
|
|
fmt = "{code},{exposure},{width},{height},{lower_left},{rotation}*"
|
|
return fmt.format(**data)
|
|
|
|
|
|
class AMUnsupportPrimitive(AMPrimitive):
|
|
@classmethod
|
|
def from_gerber(cls, primitive):
|
|
return cls(primitive)
|
|
|
|
def __init__(self, primitive):
|
|
super(AMUnsupportPrimitive, self).__init__(9999)
|
|
self.primitive = primitive
|
|
|
|
def to_inch(self):
|
|
pass
|
|
|
|
def to_metric(self):
|
|
pass
|
|
|
|
def to_gerber(self, settings=None):
|
|
return self.primitive
|