From f98b918634f23cf822b0d054ac4b6a0b790bb760 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 2 Feb 2015 20:03:26 -0500 Subject: Added some Aperture Macro Primitives. Moved AM primitives to seperate file --- gerber/am_statements.py | 341 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 gerber/am_statements.py (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py new file mode 100644 index 0000000..3f6ff1e --- /dev/null +++ b/gerber/am_statements.py @@ -0,0 +1,341 @@ +#!/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 .utils import validate_coordinates + + +# TODO: Add support for aperture macro variables + +class AMPrimitive(object): + """ Aperture Macro Primitive Base Class + """ + def __init__(self, code, exposure=None): + """ Initialize Aperture Macro Primitive base class + + Parameters + ---------- + code : int + primitive shape code + + exposure : str + on or off Primitives with exposure on create a slid part of + the macro aperture, and primitives with exposure off erase the + solid part created previously in the aperture macro definition. + .. note:: + The erasing effect is limited to the aperture definition in + which it occurs. + + Returns + ------- + primitive : :class: `gerber.am_statements.AMPrimitive` + + Raises + ------ + TypeError, ValueError + """ + VALID_CODES = (0, 1, 2, 4, 5, 6, 7, 20, 21, 22) + if not isinstance(code, int): + raise TypeError('Aperture Macro Primitive code must be an integer') + elif code not in VALID_CODES: + raise ValueError('Invalid Code. Valid codes are %s.' % ', '.join(map(str, VALID_CODES))) + if exposure is not None and exposure.lower() not in ('on', 'off'): + raise ValueError('Exposure must be either on or off') + self.code = code + self.exposure = exposure.lower() if exposure is not None else None + + def to_inch(self): + pass + + def to_metric(self): + pass + + +class AMCommentPrimitive(AMPrimitive): + """ Aperture Macro Comment primitive. Code 0 + """ + @classmethod + def from_gerber(cls, primitive): + primitive = primitive.strip() + code = int(primitive[0]) + comment = primitive[1:] + return cls(code, comment) + + def __init__(self, code, comment): + """ Initialize AMCommentPrimitive class + + Parameters + ---------- + code : int + Aperture Macro primitive code. 0 Indicates an AMCommentPrimitive + + comment : str + The comment as a string. + + Returns + ------- + CommentPrimitive : :class:`gerber.am_statements.AMCommentPrimitive` + An Initialized AMCommentPrimitive + + Raises + ------ + ValueError + """ + if code != 0: + raise ValueError('Not a valid Aperture Macro Comment statement') + super(AMCommentPrimitive, self).__init__(code) + self.comment = comment.strip(' *') + + def to_gerber(self, settings=None): + return '0 %s *' % self.comment + + def __str__(self): + return '' % self.comment + + +class AMCirclePrimitive(AMPrimitive): + """ Aperture macro Circle primitive. Code 1 + """ + @classmethod + def from_gerber(cls, primitive): + modifiers = primitive.strip(' *').split(',') + code = int(modifiers[0]) + exposure = 'on' if modifiers[1].strip() == '1' else 'off' + diameter = float(modifiers[2]) + position = (float(modifiers[3]), float(modifiers[4])) + return cls(code, exposure, diameter, position) + + def __init__(self, code, exposure, diameter, position): + """ Initialize AMCirclePrimitive + + Parameters + ---------- + code : int + Circle Primitive code. Must be 1 + + exposure : string + 'on' or 'off' + + diameter : float + Circle diameter + + position : tuple (, ) + Position of the circle relative to the macro origin + + Returns + ------- + CirclePrimitive : :class:`gerber.am_statements.AMCirclePrimitive` + An initialized AMCirclePrimitive + + Raises + ------ + ValueError, TypeError + """ + validate_coordinates(position) + if code != 1: + raise ValueError('Not a valid Aperture Macro Circle statement') + super(AMCirclePrimitive, self).__init__(code, exposure) + self.diameter = diameter + self.position = position + + def to_gerber(self, settings=None): + data = dict(code = self.code, + exposure = '1' if self.exposure == 'on' else 0, + diameter = self.diameter, + x = self.position[0], + y = self.position[1]) + return '{code},{exposure},{diameter},{x},{y}*'.format(**data) + + +class AMVectorLinePrimitive(AMPrimitive): + """ Aperture Macro Vector Line primitive. Code 2 or 20 + """ + @classmethod + def from_gerber(cls, primitive): + modifiers = primitive.strip(' *').split(',') + code = int(modifiers[0]) + exposure = 'on' if modifiers[1].strip() == '1' else 'off' + width = float(modifiers[2]) + start (float(modifiers[3]), float(modifiers[4])) + end = (float(modifiers[5]), float(modifiers[6])) + rotation = float(modifiers[7]) + return cls(code, exposure, width, start, end, rotation) + + def __init__(self, code, exposure, width, start, end, rotation): + """ Initialize AMVectorLinePrimitive + + Parameters + ---------- + code : int + Vector Line Primitive code. Must be either 2 or 20. + + exposure : string + 'on' or 'off' + + width : float + Line width + + start : tuple (, ) + 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 + """ + validate_coordinates(start) + validate_coordinates(end) + if code not in (2, 20): + raise ValueError('Valid VectorLinePrimitive codes are 2 or 20') + super(AMVectorLinePrimitive, self).__init__(code, exposure) + self.width = width + self.start = start + self.end = end + self.rotation = rotation + + def to_gerber(self, settings=None): + fmtstr = '{code},{exp},{width},{startx},{starty},{endx},{endy},{rot}*' + data = dict(code = self.code, + exp = 1 if self.exposure == 'on' else 0, + startx = self.start[0], + starty = self.start[1], + endx = self.end[0], + endy = self.end[1], + rotation = self.rotation) + return fmtstr.format(**data) + +# Code 4 +class AMOutlinePrimitive(AMPrimitive): + + @classmethod + def from_gerber(cls, primitive): + modifiers = primitive.strip(' *').split(",") + + code = int(modifiers[0]) + exposure = "on" if modifiers[1].strip() == "1" else "off" + n = int(modifiers[2]) + start_point = (float(modifiers[3]), float(modifiers[4])) + points = [] + + for i in range(n): + points.append((float(modifiers[5 + i*2]), float(modifiers[5 + i*2 + 1]))) + + rotation = float(modifiers[-1]) + + return cls(code, exposure, start_point, points, rotation) + + def __init__(self, code, exposure, start_point, points, rotation): + """ Initialize AMOutlinePrimitive + + Parameters + ---------- + code : int + OutlinePrimitive code. Must be 4. + + exposure : string + 'on' or 'off' + + start_point : tuple (, ) + 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 + """ + validate_coordinates(start_point) + for point in points: + validate_coordinates(point) + super(AMOutlinePrimitive, self).__init__(code, exposure) + self.start_point = start_point + self.points = points + self.rotation = rotation + + def to_inch(self): + self.start_point = tuple([x / 25.4 for x in self.start_point]) + self.points = tuple([(x / 25.4, y / 25.4) for x, y in self.points]) + + def to_metric(self): + self.start_point = tuple([x * 25.4 for x in self.start_point]) + self.points = tuple([(x * 25.4, y * 25.4) for x, y in self.points]) + + def to_gerber(self, settings=None): + data = dict( + code=self.code, + exposure="1" if self.exposure == "on" else "0", + n_points=len(self.points), + start_point="%.4f,%.4f" % self.start_point, + points=",".join(["%.4f,%.4f" % point for point in self.points]), + rotation=str(self.rotation) + ) + return "{code},{exposure},{n_points},{start_point},{points},{rotation}*".format(**data) + +# Code 5 +class AMPolygonPrimitive(AMPrimitive): + pass + + +# Code 6 +class AMMoirePrimitive(AMPrimitive): + pass + + +# Code 7 +class AMThermalPrimitive(AMPrimitive): + pass + + +# Code 21 +class AMCenterLinePrimitive(AMPrimitive): + pass + + +# Code 22 +class AMLowerLeftLinePrimitive(AMPrimitive): + pass + + +class AMUnsupportPrimitive(AMPrimitive): + @classmethod + def from_gerber(cls, primitive): + return cls(primitive) + + def __init__(self, primitive): + self.primitive = primitive + + def to_gerber(self, settings=None): + return self.primitive -- cgit From 3435fecd3b29716f91531dc2998776ab82897f09 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 8 Feb 2015 21:52:09 -0500 Subject: Add rest of Aperture Macro Primitives --- gerber/am_statements.py | 705 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 571 insertions(+), 134 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 3f6ff1e..9559424 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -23,31 +23,29 @@ from .utils import validate_coordinates 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): - """ Initialize Aperture Macro Primitive base class - - Parameters - ---------- - code : int - primitive shape code - - exposure : str - on or off Primitives with exposure on create a slid part of - the macro aperture, and primitives with exposure off erase the - solid part created previously in the aperture macro definition. - .. note:: - The erasing effect is limited to the aperture definition in - which it occurs. - - Returns - ------- - primitive : :class: `gerber.am_statements.AMPrimitive` - - Raises - ------ - TypeError, ValueError - """ VALID_CODES = (0, 1, 2, 4, 5, 6, 7, 20, 21, 22) if not isinstance(code, int): raise TypeError('Aperture Macro Primitive code must be an integer') @@ -67,6 +65,30 @@ class AMPrimitive(object): 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): @@ -76,25 +98,6 @@ class AMCommentPrimitive(AMPrimitive): return cls(code, comment) def __init__(self, code, comment): - """ Initialize AMCommentPrimitive class - - Parameters - ---------- - code : int - Aperture Macro primitive code. 0 Indicates an AMCommentPrimitive - - comment : str - The comment as a string. - - Returns - ------- - CommentPrimitive : :class:`gerber.am_statements.AMCommentPrimitive` - An Initialized AMCommentPrimitive - - Raises - ------ - ValueError - """ if code != 0: raise ValueError('Not a valid Aperture Macro Comment statement') super(AMCommentPrimitive, self).__init__(code) @@ -109,6 +112,35 @@ class AMCommentPrimitive(AMPrimitive): 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): @@ -120,31 +152,6 @@ class AMCirclePrimitive(AMPrimitive): return cls(code, exposure, diameter, position) def __init__(self, code, exposure, diameter, position): - """ Initialize AMCirclePrimitive - - Parameters - ---------- - code : int - Circle Primitive code. Must be 1 - - exposure : string - 'on' or 'off' - - diameter : float - Circle diameter - - position : tuple (, ) - Position of the circle relative to the macro origin - - Returns - ------- - CirclePrimitive : :class:`gerber.am_statements.AMCirclePrimitive` - An initialized AMCirclePrimitive - - Raises - ------ - ValueError, TypeError - """ validate_coordinates(position) if code != 1: raise ValueError('Not a valid Aperture Macro Circle statement') @@ -162,7 +169,43 @@ class AMCirclePrimitive(AMPrimitive): class AMVectorLinePrimitive(AMPrimitive): - """ Aperture Macro Vector Line primitive. Code 2 or 20 + """ 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_gerber(cls, primitive): @@ -170,43 +213,12 @@ class AMVectorLinePrimitive(AMPrimitive): code = int(modifiers[0]) exposure = 'on' if modifiers[1].strip() == '1' else 'off' width = float(modifiers[2]) - start (float(modifiers[3]), float(modifiers[4])) + start = (float(modifiers[3]), float(modifiers[4])) end = (float(modifiers[5]), float(modifiers[6])) rotation = float(modifiers[7]) return cls(code, exposure, width, start, end, rotation) def __init__(self, code, exposure, width, start, end, rotation): - """ Initialize AMVectorLinePrimitive - - Parameters - ---------- - code : int - Vector Line Primitive code. Must be either 2 or 20. - - exposure : string - 'on' or 'off' - - width : float - Line width - - start : tuple (, ) - 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 - """ validate_coordinates(start) validate_coordinates(end) if code not in (2, 20): @@ -230,7 +242,43 @@ class AMVectorLinePrimitive(AMPrimitive): # Code 4 class AMOutlinePrimitive(AMPrimitive): + """ Aperture Macro Outline primitive. Code 6. + + 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 4. + + 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_gerber(cls, primitive): modifiers = primitive.strip(' *').split(",") @@ -240,42 +288,13 @@ class AMOutlinePrimitive(AMPrimitive): n = int(modifiers[2]) start_point = (float(modifiers[3]), float(modifiers[4])) points = [] - for i in range(n): points.append((float(modifiers[5 + i*2]), float(modifiers[5 + i*2 + 1]))) - rotation = float(modifiers[-1]) - return cls(code, exposure, start_point, points, rotation) def __init__(self, code, exposure, start_point, points, rotation): """ Initialize AMOutlinePrimitive - - Parameters - ---------- - code : int - OutlinePrimitive code. Must be 4. - - exposure : string - 'on' or 'off' - - start_point : tuple (, ) - 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 """ validate_coordinates(start_point) for point in points: @@ -306,27 +325,445 @@ class AMOutlinePrimitive(AMPrimitive): # Code 5 class AMPolygonPrimitive(AMPrimitive): - pass + """ 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_gerber(cls, primitive): + modifiers = primitive.strip(' *').split(",") + code = int(modifiers[0]) + exposure = "on" if modifiers[1].strip() == "1" else "off" + vertices = int(modifiers[2]) + position = (float(modifiers[3]), float(modifiers[4])) + diameter = float(modifiers[5]) + rotation = float(modifiers[6]) + return cls(code, exposure, vertices, position, diameter, rotation) + + + def __init__(self, code, exposure, vertices, position, diameter, rotation): + """ Initialize AMPolygonPrimitive + """ + 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([x / 25.4 for x in self.position]) + self.diameter = self.diameter / 25.4 + + def to_metric(self): + self.position = tuple([x * 25.4 for x in self.position]) + self.diameter = self.diameter * 25.4 + + def to_gerber(self, settings=None): + data = dict( + code=self.code, + exposure="1" if self.exposure == "on" else "0", + vertices=self.vertices, + position="%.4f,%.4f" % self.position, + diameter = '%.4f' % self.diameter, + rotation=str(self.rotation) + ) + fmt = "{code},{exposure},{vertices},{position},{diameter},{rotation}*" + return fmt.format(**data) # Code 6 class AMMoirePrimitive(AMPrimitive): - pass + """ 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(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 + """ + 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([x / 25.4 for x in self.position]) + self.diameter = self.diameter / 25.4 + self.ring_thickness = self.ring_thickness / 25.4 + self.gap = self.gap / 25.4 + self.crosshair_thickness = self.crosshair_thickness / 25.4 + self.crosshair_length = self.crosshair_length / 25.4 + + def to_metric(self): + self.position = tuple([x * 25.4 for x in self.position]) + self.diameter = self.diameter * 25.4 + self.ring_thickness = self.ring_thickness * 25.4 + self.gap = self.gap / 25.4 + self.crosshair_thickness = self.crosshair_thickness * 25.4 + self.crosshair_length = self.crosshair_length * 25.4 + + + def to_gerber(self, settings=None): + data = dict( + code=self.code, + position="%.4f,%.4f" % self.position, + diameter = '%.4f' % self.diameter, + ring_thickness = '%.4f' % self.ring_thickness, + gap = '%.4f' % self.gap, + max_rings = str(self.max_rings), + crosshair_thickness = '%.4f' % self.crosshair_thickness, + crosshair_length = '%.4f' % self.crosshair_length, + rotation=str(self.rotation) + ) + fmt = "{code},{position},{diameter},{ring_thickness},{gap},{max_rings},{crosshair_thickness},{crosshair_length},{rotation}*" + return fmt.format(**data) # Code 7 class AMThermalPrimitive(AMPrimitive): - pass + """ 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): + 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([x / 25.4 for x in self.position]) + self.outer_diameter = self.outer_diameter / 25.4 + self.inner_diameter = self.inner_diameter / 25.4 + self.gap = self.gap / 25.4 + + + def to_metric(self): + self.position = tuple([x * 25.4 for x in self.position]) + self.outer_diameter = self.outer_diameter * 25.4 + self.inner_diameter = self.inner_diameter * 25.4 + self.gap = self.gap * 25.4 + + def to_gerber(self, settings=None): + data = dict( + code=self.code, + position="%.4f,%.4f" % self.position, + outer_diameter = '%.4f' % self.outer_diameter, + inner_diameter = '%.4f' % self.inner_diameter, + gap = '%.4f' % self.gap, + rotation=str(self.rotation) + ) + fmt = "{code},{position},{outer_diameter},{inner_diameter},{gap},{rotation}*" + return fmt.format(**data) # Code 21 class AMCenterLinePrimitive(AMPrimitive): - pass + """ 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_gerber(cls, primitive): + modifiers = primitive.strip(' *').split(",") + code = int(modifiers[0]) + exposure = 'on' if modifiers[1].strip() == '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): + 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([x / 25.4 for x in self.center]) + self.width = self.width / 25.4 + self.heignt = self.height / 25.4 + + def to_metric(self): + self.center = tuple([x * 25.4 for x in self.center]) + self.width = self.width * 25.4 + self.heignt = self.height * 25.4 + + def to_gerber(self, settings=None): + data = dict( + code=self.code, + exposure = '1' if self.exposure == 'on' else '0', + width = '%.4f' % self.width, + height = '%.4f' % self.height, + center="%.4f,%.4f" % self.center, + rotation=str(self.rotation) + ) + fmt = "{code},{exposure},{width},{height},{center},{rotation}*" + return fmt.format(**data) # Code 22 class AMLowerLeftLinePrimitive(AMPrimitive): - pass + """ 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 21. + + 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 modifiers[1].strip() == '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): + super (AMCenterLinePrimitive, 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([x / 25.4 for x in self.lower_left]) + self.width = self.width / 25.4 + self.heignt = self.height / 25.4 + + def to_metric(self): + self.lower_left = tuple([x * 25.4 for x in self.lower_left]) + self.width = self.width * 25.4 + self.heignt = self.height * 25.4 + + def to_gerber(self, settings=None): + data = dict( + code=self.code, + exposure = '1' if self.exposure == 'on' else '0', + width = '%.4f' % self.width, + height = '%.4f' % self.height, + lower_left="%.4f,%.4f" % self.lower_left, + rotation=str(self.rotation) + ) + fmt = "{code},{exposure},{width},{height},{lower_left},{rotation}*" + return fmt.format(**data) class AMUnsupportPrimitive(AMPrimitive): -- cgit From b0c55082b001a1232fb20bae25390a1514c9e8a9 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 9 Feb 2015 13:57:15 -0500 Subject: Add aperture macro statement tests --- gerber/am_statements.py | 218 +++++++++++++++++++++++++++++------------------- 1 file changed, 132 insertions(+), 86 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 9559424..0e27623 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -21,6 +21,18 @@ from .utils import validate_coordinates # TODO: Add support for aperture macro variables +__all__ = ['AMPrimitive', 'AMCommentPrimitive', 'AMCirclePrimitive', + 'AMVectorLinePrimitive', 'AMOutlinePrimitive', 'AMPolygonPrimitive', + 'AMMoirePrimitive', 'AMThermalPrimitive', 'AMCenterLinePrimitive', + 'AMLowerLeftLinePrimitive', 'AMUnsupportPrimitive'] + +def metric(value): + return value * 25.4 + +def inch(value): + return value / 25.4 + + class AMPrimitive(object): """ Aperture Macro Primitive Base Class @@ -57,10 +69,10 @@ class AMPrimitive(object): self.exposure = exposure.lower() if exposure is not None else None def to_inch(self): - pass + raise NotImplementedError('Subclass must implement `to-inch`') def to_metric(self): - pass + raise NotImplementedError('Subclass must implement `to-metric`') class AMCommentPrimitive(AMPrimitive): @@ -103,6 +115,12 @@ class AMCommentPrimitive(AMPrimitive): 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 @@ -154,11 +172,19 @@ class AMCirclePrimitive(AMPrimitive): def __init__(self, code, exposure, diameter, position): validate_coordinates(position) if code != 1: - raise ValueError('Not a valid Aperture Macro Circle statement') + 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, @@ -222,17 +248,29 @@ class AMVectorLinePrimitive(AMPrimitive): validate_coordinates(start) validate_coordinates(end) if code not in (2, 20): - raise ValueError('Valid VectorLinePrimitive codes are 2 or 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},{rot}*' + 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], @@ -240,9 +278,9 @@ class AMVectorLinePrimitive(AMPrimitive): rotation = self.rotation) return fmtstr.format(**data) -# Code 4 + class AMOutlinePrimitive(AMPrimitive): - """ Aperture Macro Outline primitive. Code 6. + """ 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. @@ -256,7 +294,7 @@ class AMOutlinePrimitive(AMPrimitive): Parameters ---------- code : int - OutlinePrimitive code. Must be 4. + OutlinePrimitive code. Must be 6. exposure : string 'on' or 'off' @@ -299,31 +337,35 @@ class AMOutlinePrimitive(AMPrimitive): 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([x / 25.4 for x in self.start_point]) - self.points = tuple([(x / 25.4, y / 25.4) for x, y in self.points]) + 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([x * 25.4 for x in self.start_point]) - self.points = tuple([(x * 25.4, y * 25.4) for x, y in self.points]) + 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="%.4f,%.4f" % self.start_point, - points=",".join(["%.4f,%.4f" % point for point in self.points]), + start_point="%.4g,%.4g" % self.start_point, + points=",".join(["%.4g,%.4g" % point for point in self.points]), rotation=str(self.rotation) ) return "{code},{exposure},{n_points},{start_point},{points},{rotation}*".format(**data) -# Code 5 + class AMPolygonPrimitive(AMPrimitive): """ Aperture Macro Polygon primitive. Code 5. @@ -378,6 +420,8 @@ class AMPolygonPrimitive(AMPrimitive): 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') @@ -388,27 +432,26 @@ class AMPolygonPrimitive(AMPrimitive): self.rotation = rotation def to_inch(self): - self.position = tuple([x / 25.4 for x in self.position]) - self.diameter = self.diameter / 25.4 + self.position = tuple([inch(x) for x in self.position]) + self.diameter = inch(self.diameter) def to_metric(self): - self.position = tuple([x * 25.4 for x in self.position]) - self.diameter = self.diameter * 25.4 + 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="%.4f,%.4f" % self.position, - diameter = '%.4f' % self.diameter, + position="%.4g,%.4g" % self.position, + diameter = '%.4g' % self.diameter, rotation=str(self.rotation) ) fmt = "{code},{exposure},{vertices},{position},{diameter},{rotation}*" return fmt.format(**data) -# Code 6 class AMMoirePrimitive(AMPrimitive): """ Aperture Macro Moire primitive. Code 6. @@ -474,6 +517,8 @@ class AMMoirePrimitive(AMPrimitive): 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 @@ -486,38 +531,38 @@ class AMMoirePrimitive(AMPrimitive): self.rotation = rotation def to_inch(self): - self.position = tuple([x / 25.4 for x in self.position]) - self.diameter = self.diameter / 25.4 - self.ring_thickness = self.ring_thickness / 25.4 - self.gap = self.gap / 25.4 - self.crosshair_thickness = self.crosshair_thickness / 25.4 - self.crosshair_length = self.crosshair_length / 25.4 + 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([x * 25.4 for x in self.position]) - self.diameter = self.diameter * 25.4 - self.ring_thickness = self.ring_thickness * 25.4 - self.gap = self.gap / 25.4 - self.crosshair_thickness = self.crosshair_thickness * 25.4 - self.crosshair_length = self.crosshair_length * 25.4 + 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="%.4f,%.4f" % self.position, - diameter = '%.4f' % self.diameter, - ring_thickness = '%.4f' % self.ring_thickness, - gap = '%.4f' % self.gap, - max_rings = str(self.max_rings), - crosshair_thickness = '%.4f' % self.crosshair_thickness, - crosshair_length = '%.4f' % self.crosshair_length, - rotation=str(self.rotation) + 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) -# Code 7 + class AMThermalPrimitive(AMPrimitive): """ Aperture Macro Thermal primitive. Code 7. @@ -565,45 +610,43 @@ class AMThermalPrimitive(AMPrimitive): 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) + return cls(code, position, outer_diameter, inner_diameter, gap) - def __init__(self, code, position, outer_diameter, inner_diameter, gap, rotation): - super(AMThermalPrimitive, self).__init(code, 'on') + def __init__(self, code, position, outer_diameter, inner_diameter, gap): + 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([x / 25.4 for x in self.position]) - self.outer_diameter = self.outer_diameter / 25.4 - self.inner_diameter = self.inner_diameter / 25.4 - self.gap = self.gap / 25.4 + 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([x * 25.4 for x in self.position]) - self.outer_diameter = self.outer_diameter * 25.4 - self.inner_diameter = self.inner_diameter * 25.4 - self.gap = self.gap * 25.4 + 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="%.4f,%.4f" % self.position, - outer_diameter = '%.4f' % self.outer_diameter, - inner_diameter = '%.4f' % self.inner_diameter, - gap = '%.4f' % self.gap, - rotation=str(self.rotation) + position="%.4g,%.4g" % self.position, + outer_diameter = self.outer_diameter, + inner_diameter = self.inner_diameter, + gap = self.gap, ) - fmt = "{code},{position},{outer_diameter},{inner_diameter},{gap},{rotation}*" + fmt = "{code},{position},{outer_diameter},{inner_diameter},{gap}*" return fmt.format(**data) -# Code 21 class AMCenterLinePrimitive(AMPrimitive): """ Aperture Macro Center Line primitive. Code 21. @@ -655,6 +698,8 @@ class AMCenterLinePrimitive(AMPrimitive): 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 @@ -663,29 +708,28 @@ class AMCenterLinePrimitive(AMPrimitive): self.rotation = rotation def to_inch(self): - self.center = tuple([x / 25.4 for x in self.center]) - self.width = self.width / 25.4 - self.heignt = self.height / 25.4 + 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([x * 25.4 for x in self.center]) - self.width = self.width * 25.4 - self.heignt = self.height * 25.4 + 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 = '%.4f' % self.width, - height = '%.4f' % self.height, - center="%.4f,%.4f" % self.center, - rotation=str(self.rotation) + 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) -# Code 22 class AMLowerLeftLinePrimitive(AMPrimitive): """ Aperture Macro Lower Left Line primitive. Code 22. @@ -698,7 +742,7 @@ class AMLowerLeftLinePrimitive(AMPrimitive): Parameters ---------- code : int - Center Line Primitive code. Must be 21. + Center Line Primitive code. Must be 22. exposure : str 'on' or 'off' @@ -736,7 +780,9 @@ class AMLowerLeftLinePrimitive(AMPrimitive): return cls(code, exposure, width, height, lower_left, rotation) def __init__(self, code, exposure, width, height, lower_left, rotation): - super (AMCenterLinePrimitive, self).__init__(code, exposure) + 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) @@ -744,23 +790,23 @@ class AMLowerLeftLinePrimitive(AMPrimitive): self.rotation = rotation def to_inch(self): - self.lower_left = tuple([x / 25.4 for x in self.lower_left]) - self.width = self.width / 25.4 - self.heignt = self.height / 25.4 + 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([x * 25.4 for x in self.lower_left]) - self.width = self.width * 25.4 - self.heignt = self.height * 25.4 + 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 = '%.4f' % self.width, - height = '%.4f' % self.height, - lower_left="%.4f,%.4f" % self.lower_left, - rotation=str(self.rotation) + 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) -- cgit From 41f9475b132001d52064392057e376c6423c33dc Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 9 Feb 2015 17:39:24 -0500 Subject: Tests and bugfixes --- gerber/am_statements.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 0e27623..dc97dfa 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -820,5 +820,11 @@ class AMUnsupportPrimitive(AMPrimitive): def __init__(self, primitive): self.primitive = primitive + def to_inch(self): + pass + + def to_metric(self): + pass + def to_gerber(self, settings=None): return self.primitive -- cgit From 288ac27084b47166ac662402ea340d0aa25d8f56 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Wed, 18 Feb 2015 04:31:23 -0500 Subject: Get unit conversion working for Gerber/Excellon files Started operations module for file operations/transforms --- gerber/am_statements.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index dc97dfa..bdb12dd 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -16,7 +16,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .utils import validate_coordinates +from .utils import validate_coordinates, inch, metric # TODO: Add support for aperture macro variables @@ -26,12 +26,6 @@ __all__ = ['AMPrimitive', 'AMCommentPrimitive', 'AMCirclePrimitive', 'AMMoirePrimitive', 'AMThermalPrimitive', 'AMCenterLinePrimitive', 'AMLowerLeftLinePrimitive', 'AMUnsupportPrimitive'] -def metric(value): - return value * 25.4 - -def inch(value): - return value / 25.4 - class AMPrimitive(object): """ Aperture Macro Primitive Base Class @@ -58,7 +52,7 @@ class AMPrimitive(object): TypeError, ValueError """ def __init__(self, code, exposure=None): - VALID_CODES = (0, 1, 2, 4, 5, 6, 7, 20, 21, 22) + 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: @@ -74,6 +68,8 @@ class AMPrimitive(object): def to_metric(self): raise NotImplementedError('Subclass must implement `to-metric`') + def __eq__(self, other): + return self.__dict__ == other.__dict__ class AMCommentPrimitive(AMPrimitive): """ Aperture Macro Comment primitive. Code 0 @@ -818,11 +814,12 @@ class AMUnsupportPrimitive(AMPrimitive): 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 -- cgit From 670d3fbbd7ebfb69bd223ac30b73ec47b195b380 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Tue, 3 Mar 2015 03:41:55 -0300 Subject: Add aperture macro parsing and evaluation. Aperture macros can get complex with arithmetical operations, variables and variables substitution. Current pcb-tools code just read each macro block as an independent unit, this cannot deal with variables that get changed after used. This patch splits the task in two: first we parse all macro content and creates a bytecode representation of all operations. This bytecode representation will be executed when an AD command is issues passing the required parameters. Parsing is heavily based on gerbv using a Shunting Yard approach to math parsing. Integration with rs274x.py code is not finished as I need to figure out how to integrate the final macro primitives with the graphical primitives already in use. --- gerber/am_statements.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index bdb12dd..c514ad7 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -406,9 +406,13 @@ class AMPolygonPrimitive(AMPrimitive): modifiers = primitive.strip(' *').split(",") code = int(modifiers[0]) exposure = "on" if modifiers[1].strip() == "1" else "off" - vertices = int(modifiers[2]) + vertices = int(float(modifiers[2])) position = (float(modifiers[3]), float(modifiers[4])) - diameter = float(modifiers[5]) + try: + diameter = float(modifiers[5]) + except: + diameter = 0 + rotation = float(modifiers[6]) return cls(code, exposure, vertices, position, diameter, rotation) -- cgit From a13b981c1c2ea9ede39e9821d9ba818566f044de Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Thu, 5 Mar 2015 14:43:30 -0300 Subject: Fix tests for macros with no variables. All AM*Primitive classes now handles float for all but the code modifiers. This simplifies the reading/parsing. --- gerber/am_statements.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index c514ad7..38f4d71 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -160,7 +160,7 @@ class AMCirclePrimitive(AMPrimitive): def from_gerber(cls, primitive): modifiers = primitive.strip(' *').split(',') code = int(modifiers[0]) - exposure = 'on' if modifiers[1].strip() == '1' else 'off' + 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) @@ -233,7 +233,7 @@ class AMVectorLinePrimitive(AMPrimitive): def from_gerber(cls, primitive): modifiers = primitive.strip(' *').split(',') code = int(modifiers[0]) - exposure = 'on' if modifiers[1].strip() == '1' else 'off' + 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])) @@ -318,8 +318,8 @@ class AMOutlinePrimitive(AMPrimitive): modifiers = primitive.strip(' *').split(",") code = int(modifiers[0]) - exposure = "on" if modifiers[1].strip() == "1" else "off" - n = int(modifiers[2]) + 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): @@ -405,7 +405,7 @@ class AMPolygonPrimitive(AMPrimitive): def from_gerber(cls, primitive): modifiers = primitive.strip(' *').split(",") code = int(modifiers[0]) - exposure = "on" if modifiers[1].strip() == "1" else "off" + exposure = "on" if float(modifiers[1]) == 1 else "off" vertices = int(float(modifiers[2])) position = (float(modifiers[3]), float(modifiers[4])) try: @@ -508,7 +508,7 @@ class AMMoirePrimitive(AMPrimitive): diameter = float(modifiers[3]) ring_thickness = float(modifiers[4]) gap = float(modifiers[5]) - max_rings = int(modifiers[6]) + max_rings = int(float(modifiers[6])) crosshair_thickness = float(modifiers[7]) crosshair_length = float(modifiers[8]) rotation = float(modifiers[9]) @@ -690,7 +690,7 @@ class AMCenterLinePrimitive(AMPrimitive): def from_gerber(cls, primitive): modifiers = primitive.strip(' *').split(",") code = int(modifiers[0]) - exposure = 'on' if modifiers[1].strip() == '1' else 'off' + exposure = 'on' if float(modifiers[1]) == 1 else 'off' width = float(modifiers[2]) height = float(modifiers[3]) center= (float(modifiers[4]), float(modifiers[5])) @@ -772,7 +772,7 @@ class AMLowerLeftLinePrimitive(AMPrimitive): def from_gerber(cls, primitive): modifiers = primitive.strip(' *').split(",") code = int(modifiers[0]) - exposure = 'on' if modifiers[1].strip() == '1' else 'off' + 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])) -- cgit From 4a815bf25ddd1d378ec6ad5af008e5bbcd362b51 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Wed, 30 Dec 2015 14:05:00 +0800 Subject: First time any macro renders --- gerber/am_statements.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 38f4d71..0e4f5f4 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -16,7 +16,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +from math import pi from .utils import validate_coordinates, inch, metric +from .primitives import Circle, Line, Rectangle # TODO: Add support for aperture macro variables @@ -67,6 +69,12 @@ class AMPrimitive(object): def to_metric(self): raise NotImplementedError('Subclass must implement `to-metric`') + + def to_primitive(self, units): + """ + Convert to a primitive, as defines the primitives module (for drawing) + """ + raise NotImplementedError('Subclass must implement `to-primitive`') def __eq__(self, other): return self.__dict__ == other.__dict__ @@ -120,6 +128,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 '' % self.comment @@ -189,6 +203,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) + class AMVectorLinePrimitive(AMPrimitive): """ Aperture Macro Vector Line primitive. Code 2 or 20. @@ -273,6 +290,9 @@ class AMVectorLinePrimitive(AMPrimitive): endy = self.end[1], rotation = self.rotation) return fmtstr.format(**data) + + def to_primitive(self, units): + return Line(self.start, self.end, Rectangle(None, self.width, self.width), units=units) class AMOutlinePrimitive(AMPrimitive): @@ -360,6 +380,9 @@ class AMOutlinePrimitive(AMPrimitive): rotation=str(self.rotation) ) return "{code},{exposure},{n_points},{start_point},{points},{rotation}*".format(**data) + + def to_primitive(self, units): + raise NotImplementedError() class AMPolygonPrimitive(AMPrimitive): @@ -450,6 +473,9 @@ class AMPolygonPrimitive(AMPrimitive): ) fmt = "{code},{exposure},{vertices},{position},{diameter},{rotation}*" return fmt.format(**data) + + def to_primitive(self, units): + raise NotImplementedError() class AMMoirePrimitive(AMPrimitive): @@ -562,6 +588,9 @@ 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() + class AMThermalPrimitive(AMPrimitive): """ Aperture Macro Thermal primitive. Code 7. @@ -646,6 +675,9 @@ class AMThermalPrimitive(AMPrimitive): fmt = "{code},{position},{outer_diameter},{inner_diameter},{gap}*" return fmt.format(**data) + def to_primitive(self, units): + raise NotImplementedError() + class AMCenterLinePrimitive(AMPrimitive): """ Aperture Macro Center Line primitive. Code 21. @@ -729,6 +761,9 @@ class AMCenterLinePrimitive(AMPrimitive): fmt = "{code},{exposure},{width},{height},{center},{rotation}*" return fmt.format(**data) + def to_primitive(self, units): + return Rectangle(self.center, self.width, self.height, rotation=self.rotation * pi / 180.0, units=units) + class AMLowerLeftLinePrimitive(AMPrimitive): """ Aperture Macro Lower Left Line primitive. Code 22. @@ -811,6 +846,9 @@ class AMLowerLeftLinePrimitive(AMPrimitive): fmt = "{code},{exposure},{width},{height},{lower_left},{rotation}*" return fmt.format(**data) + def to_primitive(self, units): + raise NotImplementedError() + class AMUnsupportPrimitive(AMPrimitive): @classmethod @@ -829,3 +867,6 @@ class AMUnsupportPrimitive(AMPrimitive): def to_gerber(self, settings=None): return self.primitive + + def to_primitive(self, units): + return None \ No newline at end of file -- cgit From 96692b22216fdfe11f2ded104ac0bdba3b7866a5 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Wed, 30 Dec 2015 15:32:44 +0800 Subject: Render primitives for some aperture macros --- gerber/am_statements.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 0e4f5f4..599d19d 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -16,9 +16,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from math import pi -from .utils import validate_coordinates, inch, metric -from .primitives import Circle, Line, Rectangle +import math +from .utils import validate_coordinates, inch, metric, rotate_point +from .primitives import Circle, Line, Outline, Rectangle # TODO: Add support for aperture macro variables @@ -382,7 +382,15 @@ class AMOutlinePrimitive(AMPrimitive): return "{code},{exposure},{n_points},{start_point},{points},{rotation}*".format(**data) def to_primitive(self, units): - raise NotImplementedError() + + lines = [] + prev_point = rotate_point(self.points[0], self.rotation) + for point in self.points[1:]: + cur_point = rotate_point(self.points[0], self.rotation) + + lines.append(Line(prev_point, cur_point, Circle((0,0), 0))) + + return Outline(lines, units=units) class AMPolygonPrimitive(AMPrimitive): @@ -762,7 +770,7 @@ class AMCenterLinePrimitive(AMPrimitive): return fmt.format(**data) def to_primitive(self, units): - return Rectangle(self.center, self.width, self.height, rotation=self.rotation * pi / 180.0, units=units) + return Rectangle(self.center, self.width, self.height, rotation=math.radians(self.rotation), units=units) class AMLowerLeftLinePrimitive(AMPrimitive): -- cgit From f61eee807f87c329f6f88645ecdb48f01b887c52 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Wed, 30 Dec 2015 18:44:07 +0800 Subject: Render polygon flashes --- gerber/am_statements.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 599d19d..e484b10 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -18,7 +18,7 @@ import math from .utils import validate_coordinates, inch, metric, rotate_point -from .primitives import Circle, Line, Outline, Rectangle +from .primitives import Circle, Line, Outline, Polygon, Rectangle # TODO: Add support for aperture macro variables @@ -483,7 +483,7 @@ class AMPolygonPrimitive(AMPrimitive): return fmt.format(**data) def to_primitive(self, units): - raise NotImplementedError() + return Polygon(self.position, self.vertices, self.diameter / 2.0, rotation=math.radians(self.rotation), units=units) class AMMoirePrimitive(AMPrimitive): -- cgit From 6a993594130c42adffa9e2d58757b66b48755aad Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 16 Jan 2016 12:28:46 +0800 Subject: Fix converting polygons to outlines for macros --- gerber/am_statements.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index e484b10..b448139 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -386,9 +386,11 @@ class AMOutlinePrimitive(AMPrimitive): lines = [] prev_point = rotate_point(self.points[0], self.rotation) for point in self.points[1:]: - cur_point = rotate_point(self.points[0], self.rotation) + cur_point = rotate_point(point, self.rotation) lines.append(Line(prev_point, cur_point, Circle((0,0), 0))) + + prev_point = cur_point return Outline(lines, units=units) -- cgit From 5476da8aa3f4ee424f56f4f2491e7af1c4b7b758 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 21 Jan 2016 03:57:44 -0500 Subject: Fix a bunch of rendering bugs. - 'clear' polarity primitives no longer erase background - Added aperture macro support for polygons - Added aperture macro rendring support - Renderer now creates a new surface for each layer and merges them instead of working directly on a single surface - Updated examples accordingly --- gerber/am_statements.py | 125 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 84 insertions(+), 41 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 38f4d71..f67b0db 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -17,9 +17,7 @@ # limitations under the License. from .utils import validate_coordinates, inch, metric - - -# TODO: Add support for aperture macro variables +from .primitives import * __all__ = ['AMPrimitive', 'AMCommentPrimitive', 'AMCirclePrimitive', 'AMVectorLinePrimitive', 'AMOutlinePrimitive', 'AMPolygonPrimitive', @@ -51,12 +49,14 @@ class AMPrimitive(object): ------ 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))) + 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 @@ -68,9 +68,15 @@ class AMPrimitive(object): def to_metric(self): raise NotImplementedError('Subclass must implement `to-metric`') + def to_primitive(self, position, level_polarity, 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 @@ -181,12 +187,19 @@ 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, - diameter = self.diameter, - x = self.position[0], - y = self.position[1]) + 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) @@ -261,17 +274,24 @@ 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, - 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) + 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) @@ -323,7 +343,8 @@ class AMOutlinePrimitive(AMPrimitive): 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]))) + 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) @@ -416,7 +437,6 @@ class AMPolygonPrimitive(AMPrimitive): rotation = float(modifiers[6]) return cls(code, exposure, vertices, position, diameter, rotation) - def __init__(self, code, exposure, vertices, position, diameter, rotation): """ Initialize AMPolygonPrimitive """ @@ -439,13 +459,21 @@ 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, vertices, self.diameter/2., + rotation=self.rotation, 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", vertices=self.vertices, position="%.4g,%.4g" % self.position, - diameter = '%.4g' % self.diameter, + diameter='%.4g' % self.diameter, rotation=str(self.rotation) ) fmt = "{code},{exposure},{vertices},{position},{diameter},{rotation}*" @@ -546,17 +574,16 @@ 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, 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, + 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}*" @@ -608,7 +635,7 @@ class AMThermalPrimitive(AMPrimitive): code = int(modifiers[0]) position = (float(modifiers[1]), float(modifiers[2])) outer_diameter = float(modifiers[3]) - inner_diameter= float(modifiers[4]) + inner_diameter = float(modifiers[4]) gap = float(modifiers[5]) return cls(code, position, outer_diameter, inner_diameter, gap) @@ -628,7 +655,6 @@ class AMThermalPrimitive(AMPrimitive): 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) @@ -639,9 +665,9 @@ class AMThermalPrimitive(AMPrimitive): data = dict( code=self.code, position="%.4g,%.4g" % self.position, - outer_diameter = self.outer_diameter, - inner_diameter = self.inner_diameter, - gap = self.gap, + outer_diameter=self.outer_diameter, + inner_diameter=self.inner_diameter, + gap=self.gap, ) fmt = "{code},{position},{outer_diameter},{inner_diameter},{gap}*" return fmt.format(**data) @@ -693,14 +719,14 @@ class AMCenterLinePrimitive(AMPrimitive): exposure = 'on' if float(modifiers[1]) == 1 else 'off' width = float(modifiers[2]) height = float(modifiers[3]) - center= (float(modifiers[4]), float(modifiers[5])) + 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) + super(AMCenterLinePrimitive, self).__init__(code, exposure) self.width = width self.height = height validate_coordinates(center) @@ -717,12 +743,19 @@ 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 ) @@ -782,7 +815,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) @@ -799,12 +832,21 @@ 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 ) @@ -813,6 +855,7 @@ class AMLowerLeftLinePrimitive(AMPrimitive): class AMUnsupportPrimitive(AMPrimitive): + @classmethod def from_gerber(cls, primitive): return cls(primitive) -- cgit From 1d9270d80981b70376eff4a8f275226969d5ebfd Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Fri, 22 Jan 2016 03:24:50 -0200 Subject: Fix NameError on Polygon primitive rendering --- gerber/am_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index f67b0db..11a6187 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -463,7 +463,7 @@ class AMPolygonPrimitive(AMPrimitive): # 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, vertices, self.diameter/2., + return Polygon(position, self.vertices, self.diameter/2., rotation=self.rotation, level_polarity=level_polarity, units=units) -- cgit From 5b93db47cd29e384ead918db1893f4cf58326f82 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Tue, 2 Feb 2016 00:11:55 +0800 Subject: Draw thermal aperture macros (as approximation) --- gerber/am_statements.py | 83 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index b448139..2bca6e6 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -19,6 +19,7 @@ import math from .utils import validate_coordinates, inch, metric, rotate_point from .primitives import Circle, Line, Outline, Polygon, Rectangle +from math import asin # TODO: Add support for aperture macro variables @@ -649,9 +650,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') @@ -660,6 +662,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]) @@ -684,9 +687,83 @@ class AMThermalPrimitive(AMPrimitive): ) fmt = "{code},{position},{outer_diameter},{inner_diameter},{gap}*" 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): - raise NotImplementedError() + + # 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)))) + + # 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)) + + return outlines class AMCenterLinePrimitive(AMPrimitive): -- cgit From 223a010831f0d9dae4bd6d2e626a603a78eb0b1d Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 27 Feb 2016 18:18:04 +0800 Subject: Fix critical issue with rotatin points (when the angle is zero the y would be flipped). Render AM with outline to gerber --- gerber/am_statements.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 2bca6e6..05ebd9d 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -334,6 +334,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,17 +389,18 @@ 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) + # 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): lines = [] - prev_point = rotate_point(self.points[0], self.rotation) - for point in self.points[1:]: + 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))) -- cgit From 20a9af279ac2217a39b73903ff94b916a3025be2 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Tue, 1 Mar 2016 00:06:14 +0800 Subject: More rendering of AMGroup to statements --- gerber/am_statements.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 05ebd9d..084439c 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -179,6 +179,10 @@ class AMCirclePrimitive(AMPrimitive): 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) @@ -247,6 +251,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(',') @@ -406,6 +415,9 @@ class AMOutlinePrimitive(AMPrimitive): 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) @@ -762,6 +774,8 @@ class AMThermalPrimitive(AMPrimitive): 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]: @@ -818,6 +832,14 @@ class AMCenterLinePrimitive(AMPrimitive): ------ 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): -- cgit From 7f47aea332ee1df45c87baa304d95ed03cc59865 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 5 Mar 2016 10:04:58 +0800 Subject: Write polygons to macros --- gerber/am_statements.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 084439c..faaed05 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -461,6 +461,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(",") -- cgit From 3fc296918e7d0d343840c5daa08eb6d564660a29 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 28 May 2016 13:06:08 +0800 Subject: Use the known macro statement to render. Fix thermal not setting rotation --- gerber/am_statements.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index faaed05..c3229ba 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -715,8 +715,9 @@ 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): -- cgit From b01c4822b6da6b7be37becb73c58f60621f6366f Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 25 Jun 2016 12:27:28 +0800 Subject: Render aperture macros with clear regions --- gerber/am_statements.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index c3229ba..f5330a5 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -76,6 +76,12 @@ class AMPrimitive(object): Convert to a primitive, as defines the primitives module (for drawing) """ 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__ @@ -209,7 +215,7 @@ class AMCirclePrimitive(AMPrimitive): return '{code},{exposure},{diameter},{x},{y}*'.format(**data) def to_primitive(self, units): - return Circle((self.position), self.diameter, units=units) + return Circle((self.position), self.diameter, units=units, level_polarity=self._level_polarity) class AMVectorLinePrimitive(AMPrimitive): @@ -302,7 +308,7 @@ class AMVectorLinePrimitive(AMPrimitive): return fmtstr.format(**data) def to_primitive(self, units): - return Line(self.start, self.end, Rectangle(None, self.width, self.width), units=units) + return Line(self.start, self.end, Rectangle(None, self.width, self.width), units=units, level_polarity=self._level_polarity) class AMOutlinePrimitive(AMPrimitive): @@ -419,7 +425,7 @@ class AMOutlinePrimitive(AMPrimitive): if lines[0].start != lines[-1].end: raise ValueError('Outline must be closed') - return Outline(lines, units=units) + return Outline(lines, units=units, level_polarity=self._level_polarity) class AMPolygonPrimitive(AMPrimitive): @@ -517,7 +523,7 @@ class AMPolygonPrimitive(AMPrimitive): return fmt.format(**data) def to_primitive(self, units): - return Polygon(self.position, self.vertices, self.diameter / 2.0, rotation=math.radians(self.rotation), units=units) + return Polygon(self.position, self.vertices, self.diameter / 2.0, rotation=math.radians(self.rotation), units=units, level_polarity=self._level_polarity) class AMMoirePrimitive(AMPrimitive): @@ -795,7 +801,7 @@ class AMThermalPrimitive(AMPrimitive): prev_point = cur_point - outlines.append(Outline(lines, units=units)) + outlines.append(Outline(lines, units=units, level_polarity=self._level_polarity)) return outlines @@ -891,7 +897,7 @@ class AMCenterLinePrimitive(AMPrimitive): return fmt.format(**data) def to_primitive(self, units): - return Rectangle(self.center, self.width, self.height, rotation=math.radians(self.rotation), units=units) + return Rectangle(self.center, self.width, self.height, rotation=math.radians(self.rotation), units=units, level_polarity=self._level_polarity) class AMLowerLeftLinePrimitive(AMPrimitive): -- cgit From efcb221fc7bd8dae583357e6c4e1c2d3fc9e9df6 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 25 Jun 2016 16:00:46 +0800 Subject: Missing * in writing aperture macro --- gerber/am_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index f5330a5..a58f1dd 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -409,7 +409,7 @@ class AMOutlinePrimitive(AMPrimitive): 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) + return "{code},{exposure},{n_points},{start_point},{points},\n{rotation}*".format(**data) def to_primitive(self, units): -- cgit From ccb6eb7a766bd6edf314978f3ec4fc0dcd61652d Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 25 Jun 2016 16:46:44 +0800 Subject: Add support for polygon apertures --- gerber/am_statements.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index a58f1dd..0d92a8c 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -523,7 +523,7 @@ class AMPolygonPrimitive(AMPrimitive): return fmt.format(**data) def to_primitive(self, units): - return Polygon(self.position, self.vertices, self.diameter / 2.0, rotation=math.radians(self.rotation), units=units, level_polarity=self._level_polarity) + return Polygon(self.position, self.vertices, self.diameter / 2.0, hole_radius=0, rotation=self.rotation, units=units, level_polarity=self._level_polarity) class AMMoirePrimitive(AMPrimitive): @@ -897,7 +897,7 @@ class AMCenterLinePrimitive(AMPrimitive): return fmt.format(**data) def to_primitive(self, units): - return Rectangle(self.center, self.width, self.height, rotation=math.radians(self.rotation), units=units, level_polarity=self._level_polarity) + return Rectangle(self.center, self.width, self.height, rotation=self.rotation, units=units, level_polarity=self._level_polarity) class AMLowerLeftLinePrimitive(AMPrimitive): -- cgit From b140f5e4767912110f69cbda8417a8e076345b70 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Tue, 28 Jun 2016 23:15:20 +0800 Subject: Don't flash G03-only commands --- gerber/am_statements.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 0d92a8c..a58f1dd 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -523,7 +523,7 @@ class AMPolygonPrimitive(AMPrimitive): return fmt.format(**data) def to_primitive(self, units): - return Polygon(self.position, self.vertices, self.diameter / 2.0, hole_radius=0, rotation=self.rotation, units=units, level_polarity=self._level_polarity) + return Polygon(self.position, self.vertices, self.diameter / 2.0, rotation=math.radians(self.rotation), units=units, level_polarity=self._level_polarity) class AMMoirePrimitive(AMPrimitive): @@ -897,7 +897,7 @@ class AMCenterLinePrimitive(AMPrimitive): return fmt.format(**data) def to_primitive(self, units): - return Rectangle(self.center, self.width, self.height, rotation=self.rotation, units=units, level_polarity=self._level_polarity) + return Rectangle(self.center, self.width, self.height, rotation=math.radians(self.rotation), units=units, level_polarity=self._level_polarity) class AMLowerLeftLinePrimitive(AMPrimitive): -- cgit From efb3703df4a9205a9476b682cd1e09e241ab8459 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Thu, 30 Jun 2016 22:46:20 +0800 Subject: Fix rotation of center line --- gerber/am_statements.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index a58f1dd..6ece68e 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -897,7 +897,28 @@ class AMCenterLinePrimitive(AMPrimitive): return fmt.format(**data) def to_primitive(self, units): - return Rectangle(self.center, self.width, self.height, rotation=math.radians(self.rotation), units=units, level_polarity=self._level_polarity) + + 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): -- cgit From 14747494b89178372c65aad1e6ef8fa431e7f24c Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Thu, 30 Jun 2016 23:08:51 +0800 Subject: Rotate vector line --- gerber/am_statements.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 6ece68e..6cb90dc 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -308,7 +308,20 @@ class AMVectorLinePrimitive(AMPrimitive): return fmtstr.format(**data) def to_primitive(self, units): - return Line(self.start, self.end, Rectangle(None, self.width, self.width), units=units, level_polarity=self._level_polarity) + + 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): -- cgit From 0107d159b5a04c282478ceb4c51fdd03af3bd8c9 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 2 Jul 2016 12:34:35 +0800 Subject: Fix crash with polygon aperture macros --- gerber/am_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 6cb90dc..ed9f71e 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -536,7 +536,7 @@ class AMPolygonPrimitive(AMPrimitive): return fmt.format(**data) def to_primitive(self, units): - return Polygon(self.position, self.vertices, self.diameter / 2.0, rotation=math.radians(self.rotation), units=units, level_polarity=self._level_polarity) + 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): -- cgit From 8cd842a41a55ab3d8f558a2e3e198beba7da58a1 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 21 Jan 2016 03:57:44 -0500 Subject: Manually mere rendering changes --- gerber/am_statements.py | 113 ++++++++++++++++++++++++++++-------------------- 1 file changed, 65 insertions(+), 48 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index ed9f71e..248542d 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -16,14 +16,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +from math import asin import math -from .utils import validate_coordinates, inch, metric, rotate_point + from .primitives import Circle, Line, Outline, Polygon, Rectangle -from math import asin +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', @@ -54,12 +54,14 @@ class AMPrimitive(object): ------ 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))) + 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 @@ -71,21 +73,21 @@ class AMPrimitive(object): def to_metric(self): raise NotImplementedError('Subclass must implement `to-metric`') - def to_primitive(self, units): - """ - Convert to a primitive, as defines the primitives module (for drawing) - """ - raise NotImplementedError('Subclass must implement `to-primitive`') - @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 @@ -207,11 +209,11 @@ class AMCirclePrimitive(AMPrimitive): 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]) + 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): @@ -294,21 +296,26 @@ 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_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) + 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 @@ -385,7 +392,8 @@ class AMOutlinePrimitive(AMPrimitive): 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]))) + 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) @@ -425,6 +433,10 @@ class AMOutlinePrimitive(AMPrimitive): 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) @@ -500,7 +512,6 @@ class AMPolygonPrimitive(AMPrimitive): rotation = float(modifiers[6]) return cls(code, exposure, vertices, position, diameter, rotation) - def __init__(self, code, exposure, vertices, position, diameter, rotation): """ Initialize AMPolygonPrimitive """ @@ -529,7 +540,7 @@ class AMPolygonPrimitive(AMPrimitive): exposure="1" if self.exposure == "on" else "0", vertices=self.vertices, position="%.4g,%.4g" % self.position, - diameter = '%.4g' % self.diameter, + diameter='%.4g' % self.diameter, rotation=str(self.rotation) ) fmt = "{code},{exposure},{vertices},{position},{diameter},{rotation}*" @@ -633,17 +644,16 @@ 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, 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, + 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}*" @@ -698,7 +708,7 @@ class AMThermalPrimitive(AMPrimitive): code = int(modifiers[0]) position = (float(modifiers[1]), float(modifiers[2])) outer_diameter = float(modifiers[3]) - inner_diameter= float(modifiers[4]) + inner_diameter = float(modifiers[4]) gap = float(modifiers[5]) rotation = float(modifiers[6]) return cls(code, position, outer_diameter, inner_diameter, gap, rotation) @@ -720,7 +730,6 @@ class AMThermalPrimitive(AMPrimitive): 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) @@ -873,14 +882,14 @@ class AMCenterLinePrimitive(AMPrimitive): exposure = 'on' if float(modifiers[1]) == 1 else 'off' width = float(modifiers[2]) height = float(modifiers[3]) - center= (float(modifiers[4]), float(modifiers[5])) + 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) + super(AMCenterLinePrimitive, self).__init__(code, exposure) self.width = width self.height = height validate_coordinates(center) @@ -900,9 +909,9 @@ class AMCenterLinePrimitive(AMPrimitive): 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 ) @@ -986,7 +995,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) @@ -1003,23 +1012,31 @@ class AMLowerLeftLinePrimitive(AMPrimitive): 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([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(self.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, + 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) - def to_primitive(self, units): - raise NotImplementedError() - class AMUnsupportPrimitive(AMPrimitive): + @classmethod def from_gerber(cls, primitive): return cls(primitive) -- cgit From 8d5e782ccf220d77f0aad5a4e5605dc5cbe0f410 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 6 Aug 2016 09:51:58 +0800 Subject: Fix multiple problems with the merge. There are still errors, but I will intentionally leave them because future merges might resolve them --- gerber/am_statements.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 248542d..9c09085 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -1015,11 +1015,10 @@ class AMLowerLeftLinePrimitive(AMPrimitive): def to_primitive(self, units): # TODO I think I have merged this wrong # 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))]) + zip(self.lower_left, (self.width/2, self.height/2))]) # Return a renderable primitive - return Rectangle(self.position, self.width, self.height, + return Rectangle(position, self.width, self.height, level_polarity=self._level_polarity, units=units) def to_gerber(self, settings=None): -- cgit From 5af19af190c1fb0f0c5be029d46d63e657dde4d9 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 21 Jan 2016 03:57:44 -0500 Subject: Commit partial merge so I can work on the plane --- gerber/am_statements.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 9c09085..726df2f 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -19,10 +19,13 @@ 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', -- cgit From 724c2b3bced319ed0b50c4302fed9b0e1aa9ce9c Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sat, 5 Nov 2016 20:56:47 -0400 Subject: Finish Merge, most tests passing --- gerber/am_statements.py | 113 ++++++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 56 deletions(-) (limited to 'gerber/am_statements.py') diff --git a/gerber/am_statements.py b/gerber/am_statements.py index 726df2f..2e3fe3d 100644 --- a/gerber/am_statements.py +++ b/gerber/am_statements.py @@ -75,7 +75,7 @@ class AMPrimitive(object): def to_metric(self): raise NotImplementedError('Subclass must implement `to-metric`') - + @property def _level_polarity(self): if self.exposure == 'off': @@ -190,9 +190,9 @@ class AMCirclePrimitive(AMPrimitive): diameter = float(modifiers[2]) position = (float(modifiers[3]), float(modifiers[4])) return cls(code, exposure, diameter, position) - + @classmethod - def from_primitive(cls, primitive): + def from_primitive(cls, primitive): return cls(1, 'on', primitive.diameter, primitive.position) def __init__(self, code, exposure, diameter, position): @@ -262,11 +262,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(',') @@ -310,27 +310,27 @@ class AMVectorLinePrimitive(AMPrimitive): 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) @@ -372,19 +372,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(",") @@ -434,25 +434,25 @@ class AMOutlinePrimitive(AMPrimitive): ) # 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) @@ -495,11 +495,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(",") @@ -548,7 +548,7 @@ 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) @@ -663,7 +663,8 @@ class AMMoirePrimitive(AMPrimitive): return fmt.format(**data) def to_primitive(self, units): - raise NotImplementedError() + #raise NotImplementedError() + return None class AMThermalPrimitive(AMPrimitive): @@ -750,70 +751,70 @@ class AMThermalPrimitive(AMPrimitive): ) 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]: @@ -821,11 +822,11 @@ class AMThermalPrimitive(AMPrimitive): 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 @@ -869,7 +870,7 @@ class AMCenterLinePrimitive(AMPrimitive): ------ ValueError, TypeError """ - + @classmethod def from_primitive(cls, primitive): width = primitive.width @@ -922,27 +923,27 @@ class AMCenterLinePrimitive(AMPrimitive): 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) @@ -1057,4 +1058,4 @@ class AMUnsupportPrimitive(AMPrimitive): return self.primitive def to_primitive(self, units): - return None \ No newline at end of file + return None -- cgit