#!/usr/bin/env python # -*- coding: utf-8 -*- # copyright 2015 Hamilton Kibbe and Paulo Henrique Silva # # 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 .primitives import Circle, Line, Outline, Polygon, Rectangle from .utils import validate_coordinates, inch, metric 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 `_ **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 '' % 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 `_ **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 (, ) 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 `_ **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 (, ) coordinate of line start point end : tuple (, ) 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 `_ **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 (, ) coordinate of outline start point points : list of tuples (, ) 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) ) # TODO I removed a closing asterix - not sure if this works for items with multiple statements return "{code},{exposure},{n_points},{start_point},{points},\n{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 `_ **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 (, ) 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 `_ **Section 4.12.3.9:** Moire, primitive code 6. Parameters ---------- code : int Moire Primitive code. Must be 6. position : tuple (, ) 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 `_ **Section 4.12.3.10:** Thermal, primitive code 7. Parameters ---------- code : int Thermal Primitive code. Must be 7. position : tuple (, ) 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 `_ **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 (, ) 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 `_ **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 (, ) 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_primitive(self, units): # TODO I think I have merged this wrong # Offset the primitive from macro position position = tuple([pos + offset for pos, offset in zip(self.lower_left, (self.width/2, self.height/2))]) # Return a renderable primitive return Rectangle(position, self.width, self.height, level_polarity=self._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, 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 def to_primitive(self, units): return None