summaryrefslogtreecommitdiff
path: root/gerber/am_statements.py
diff options
context:
space:
mode:
Diffstat (limited to 'gerber/am_statements.py')
-rw-r--r--gerber/am_statements.py280
1 files changed, 226 insertions, 54 deletions
diff --git a/gerber/am_statements.py b/gerber/am_statements.py
index 11a6187..31c0ae4 100644
--- a/gerber/am_statements.py
+++ b/gerber/am_statements.py
@@ -16,9 +16,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from .utils import validate_coordinates, inch, metric
+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',
@@ -68,7 +74,13 @@ class AMPrimitive(object):
def to_metric(self):
raise NotImplementedError('Subclass must implement `to-metric`')
- def to_primitive(self, position, level_polarity, units):
+ @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__)))
@@ -126,6 +138,12 @@ class AMCommentPrimitive(AMPrimitive):
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
@@ -171,6 +189,10 @@ class AMCirclePrimitive(AMPrimitive):
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:
@@ -187,13 +209,6 @@ class AMCirclePrimitive(AMPrimitive):
self.diameter = metric(self.diameter)
self.position = tuple([metric(x) for x in self.position])
- def to_primitive(self, position, level_polarity, units):
- # Offset the primitive from macro position
- position = tuple([a + b for a , b in zip (position, self.position)])
- # Return a renderable primitive
- return Circle(position, self.diameter, level_polarity=level_polarity,
- units=units)
-
def to_gerber(self, settings=None):
data = dict(code=self.code,
exposure='1' if self.exposure == 'on' else 0,
@@ -202,6 +217,9 @@ class AMCirclePrimitive(AMPrimitive):
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.
@@ -242,6 +260,11 @@ class AMVectorLinePrimitive(AMPrimitive):
------
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(',')
@@ -274,14 +297,6 @@ class AMVectorLinePrimitive(AMPrimitive):
self.start = tuple([metric(x) for x in self.start])
self.end = tuple([metric(x) for x in self.end])
- def to_primitive(self, position, level_polarity, units):
- # Offset the primitive from macro position
- start = tuple([a + b for a , b in zip (position, self.start)])
- end = tuple([a + b for a , b in zip (position, self.end)])
- # Return a renderable primitive
- ap = Rectangle((0, 0), self.width, self.width)
- return Line(start, end, ap, level_polarity=level_polarity, units=units)
-
def to_gerber(self, settings=None):
fmtstr = '{code},{exp},{width},{startx},{starty},{endx},{endy},{rotation}*'
data = dict(code=self.code,
@@ -294,6 +309,28 @@ class AMVectorLinePrimitive(AMPrimitive):
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.
@@ -333,6 +370,19 @@ class AMOutlinePrimitive(AMPrimitive):
------
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(",")
@@ -376,12 +426,32 @@ class AMOutlinePrimitive(AMPrimitive):
code=self.code,
exposure="1" if self.exposure == "on" else "0",
n_points=len(self.points),
- start_point="%.4g,%.4g" % self.start_point,
- points=",".join(["%.4g,%.4g" % point for point in 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.
@@ -422,6 +492,11 @@ class AMPolygonPrimitive(AMPrimitive):
------
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(",")
@@ -459,14 +534,6 @@ class AMPolygonPrimitive(AMPrimitive):
self.position = tuple([metric(x) for x in self.position])
self.diameter = metric(self.diameter)
- def to_primitive(self, position, level_polarity, units):
- # Offset the primitive from macro position
- position = tuple([a + b for a , b in zip (position, self.position)])
- # Return a renderable primitive
- return Polygon(position, self.vertices, self.diameter/2.,
- rotation=self.rotation, level_polarity=level_polarity,
- units=units)
-
def to_gerber(self, settings=None):
data = dict(
code=self.code,
@@ -479,6 +546,9 @@ class AMPolygonPrimitive(AMPrimitive):
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.
@@ -574,6 +644,7 @@ class AMMoirePrimitive(AMPrimitive):
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,
@@ -589,6 +660,10 @@ class AMMoirePrimitive(AMPrimitive):
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.
@@ -637,9 +712,10 @@ class AMThermalPrimitive(AMPrimitive):
outer_diameter = float(modifiers[3])
inner_diameter = float(modifiers[4])
gap = float(modifiers[5])
- return cls(code, position, outer_diameter, inner_diameter, gap)
+ rotation = float(modifiers[6])
+ return cls(code, position, outer_diameter, inner_diameter, gap, rotation)
- def __init__(self, code, position, outer_diameter, inner_diameter, gap):
+ 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')
@@ -648,6 +724,7 @@ class AMThermalPrimitive(AMPrimitive):
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])
@@ -668,10 +745,90 @@ class AMThermalPrimitive(AMPrimitive):
outer_diameter=self.outer_diameter,
inner_diameter=self.inner_diameter,
gap=self.gap,
+ rotation=self.rotation
)
- fmt = "{code},{position},{outer_diameter},{inner_diameter},{gap}*"
+ 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.
@@ -713,6 +870,14 @@ class AMCenterLinePrimitive(AMPrimitive):
"""
@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])
@@ -743,25 +908,42 @@ class AMCenterLinePrimitive(AMPrimitive):
self.width = metric(self.width)
self.height = metric(self.height)
- def to_primitive(self, position, level_polarity, units):
- # Offset the primitive from macro position
- position = tuple([a + b for a , b in zip (position, self.center)])
- # Return a renderable primitive
- return Rectangle(position, self.width, self.height,
- level_polarity=level_polarity, units=units)
-
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,
+ 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.
@@ -815,7 +997,7 @@ class AMLowerLeftLinePrimitive(AMPrimitive):
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)
+ super (AMLowerLeftLinePrimitive, self).__init__(code, exposure)
self.width = width
self.height = height
validate_coordinates(lower_left)
@@ -832,21 +1014,12 @@ class AMLowerLeftLinePrimitive(AMPrimitive):
self.width = metric(self.width)
self.height = metric(self.height)
- def to_primitive(self, position, level_polarity, units):
- # Offset the primitive from macro position
- position = tuple([a + b for a , b in zip (position, self.lower_left)])
- position = tuple([pos + offset for pos, offset in
- zip(position, (self.width/2, self.height/2))])
- # Return a renderable primitive
- return Rectangle(position, self.width, self.height,
- level_polarity=level_polarity, units=units)
-
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,
+ exposure = '1' if self.exposure == 'on' else '0',
+ width = self.width,
+ height = self.height,
lower_left="%.4g,%.4g" % self.lower_left,
rotation=self.rotation
)
@@ -855,7 +1028,6 @@ class AMLowerLeftLinePrimitive(AMPrimitive):
class AMUnsupportPrimitive(AMPrimitive):
-
@classmethod
def from_gerber(cls, primitive):
return cls(primitive)