summaryrefslogtreecommitdiff
path: root/gerbonara/gerber/aperture_macros/am_statements.py
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2021-11-11 12:10:56 +0100
committerjaseg <git@jaseg.de>2021-11-11 12:10:56 +0100
commit7415f9a5848853fb7a2b5b91bbe438d85728e33a (patch)
tree8fe096b83b81e6bccbcfd3efe288af49f2d5995b /gerbonara/gerber/aperture_macros/am_statements.py
parentf833483b72cd4fdf56cd4130dc9174ebfac8673d (diff)
downloadgerbonara-7415f9a5848853fb7a2b5b91bbe438d85728e33a.tar.gz
gerbonara-7415f9a5848853fb7a2b5b91bbe438d85728e33a.tar.bz2
gerbonara-7415f9a5848853fb7a2b5b91bbe438d85728e33a.zip
Aperture macro parser works
Diffstat (limited to 'gerbonara/gerber/aperture_macros/am_statements.py')
-rw-r--r--gerbonara/gerber/aperture_macros/am_statements.py1010
1 files changed, 0 insertions, 1010 deletions
diff --git a/gerbonara/gerber/aperture_macros/am_statements.py b/gerbonara/gerber/aperture_macros/am_statements.py
deleted file mode 100644
index 61ddf42..0000000
--- a/gerbonara/gerber/aperture_macros/am_statements.py
+++ /dev/null
@@ -1,1010 +0,0 @@
-#!/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
-from .am_expression import AMConstantExpression
-
-
-
-# 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, rotation=AMConstantExpression(0)):
- VALID_CODES = (0, 1, 2, 4, 5, 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
- self.rotation = rotation
-
- def rotate(self, angle, center=None):
- self.rotation = AMOperatorExpression(AMOperatorExpression.ADD,
- self.rotation,
- AMConstantExpression(float(angle)))
- self.rotation = self.rotation.optimize()
-
- #def to_inch(self):
- # raise NotImplementedError('Subclass must implement to_inch')
-
- #def to_metric(self):
- # raise NotImplementedError('Subclass must implement to_metric')
-
- #def to_gerber(self, settings=None):
- # raise NotImplementedError('Subclass must implement to_gerber')
-
- #def to_instructions(self):
- # raise NotImplementedError('Subclass must implement to_instructions')
-
- #def to_primitive(self, units):
- # """ Return a Primitive instance based on the specified macro params.
- # """
- # raise NotImplementedError('Subclass must implement to_primitive')
-
- @property
- def _level_polarity(self):
- if self.exposure == 'off':
- return 'clear'
- return 'dark'
-
- 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().__init__(code)
- self.comment = comment.strip(' *')
-
- def to_inch(self):
- pass
-
- def to_metric(self):
- pass
-
- def to_gerber(self, settings=None):
- return f'0 {self.comment} *'
-
- def to_primitive(self, units):
- """
- Returns None - has not primitive representation
- """
- return None
-
- def to_instructions(self):
- return [(OpCode.PUSH, self.comment), (OpCode.PRIM, self.code)]
-
- 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):
- exposure = 1 if self.exposure == 'on' else 0
- x, y = self.position
- return f'{self.code},{exposure},{self.diameter},{x},{y}*'
-
- 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):
- exp = 1 if self.exposure == 'on' else 0
- start_x, start_y = self.start
- end_x, end_y = self.end
- return f'{self.code},{exp},{self.width},{start_x},{start_y},{end_x},{end_y},{self.rotation}*'
-
- 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):
- exposure = 1 if self.exposure == 'on' else 0
- x0, y0 = self.start_point
- points = ",\n".join([ f'{x:.6f},{y:.6f}' for x, y in self.points ])
- return f'{self.code},{exposure},{len(self.points)},{x0:.6f},{y0:.6f},{points},{self.rotation}*'
-
- 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):
- exposure = 1 if self.exposure == 'on' else 0
- x, y = self.position
- return f'{self.code},{exposure},{self.vertices},{x:.4f},{y:.4f},{self.diameter:.4f},{self.rotation}*'
-
- 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):
- x, y = self.position
- return f'{self.code},{x:.4f},{y:.4f},{self.diameter},{self.ring_thickness},{self.gap},{self.max_rings},{self.crosshair_thickness},{self.crosshair_length},{self.rotation}*'
-
- 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):
- x, y = self.position
- return f'{self.code},{x:.4f},{y:.4f},{self.outer_diameter},{self.inner_diameter},{self.gap},{self.rotation}*'
-
- 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):
- exposure = 1 if self.exposure == 'on' else 0
- x, y = self.center
- return f'{self.code},{exposure},{self.width},{self.height},{x:.4f},{y:.4f},{self.rotation}*'
-
- 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):
- exposure = 1 if self.exposure == 'on' else 0
- x, y = self.lower_left
- return f'{self.code},{exposure},{self.width},{self.height},{x:.4f},{y:.4f},{self.rotation}*'
-
-
-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
-