diff options
Diffstat (limited to 'gerbonara/rs274x.py')
-rw-r--r-- | gerbonara/rs274x.py | 57 |
1 files changed, 37 insertions, 20 deletions
diff --git a/gerbonara/rs274x.py b/gerbonara/rs274x.py index dbda955..3dd8bb7 100644 --- a/gerbonara/rs274x.py +++ b/gerbonara/rs274x.py @@ -4,7 +4,7 @@ # Modified from parser.py by Paulo Henrique Silva <ph.silva@gmail.com> # Copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be> # Copyright 2019 Hiroshi Murayama <opiopan@gmail.com> -# Copyright 2021 Jan Götte <code@jaseg.de> +# Copyright 2022 Jan Götte <code@jaseg.de> # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,8 +17,7 @@ # 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. -""" This module provides an RS-274-X class and parser. -""" +# import re import math @@ -46,9 +45,7 @@ def points_close(a, b): return math.isclose(a[0], b[0]) and math.isclose(a[1], b[1]) class GerberFile(CamFile): - """ A class representing a single gerber file - - The GerberFile class represents a single gerber file. + """ A single gerber file. """ def __init__(self, objects=None, comments=None, import_settings=None, original_path=None, generator_hints=None, @@ -81,7 +78,6 @@ class GerberFile(CamFile): return def merge(self, other): - """ Merge other GerberFile into this one """ if other is None: return @@ -127,6 +123,7 @@ class GerberFile(CamFile): seen_macro_names.add(new_name) def dilate(self, offset, unit=MM, polarity_dark=True): + # TODO add tests for this self.apertures = [ aperture.dilated(offset, unit) for aperture in self.apertures ] offset_circle = CircleAperture(offset, unit=unit) @@ -154,6 +151,17 @@ class GerberFile(CamFile): @classmethod def open(kls, filename, enable_includes=False, enable_include_dir=None): + """ Load a Gerber file from the file system. The Gerber standard contains this wonderful and totally not + insecure "include file" setting. We disable it by default and do not parse Gerber includes because a) nobody + actually uses them, and b) they're a bad idea from a security point of view. In case you actually want these, + you can enable them by setting ``enable_includes=True``. + + :param filename: str or :py:class:`pathlib.Path` + :param bool enable_includes: Enable Gerber ``IF`` statement includes (default *off*, recommended *off*) + :param enable_include_dir: str or :py:class:`pathlib.Path`. Override base dir for include files. + + :rtype: :py:class:`.GerberFile` + """ filename = Path(filename) with open(filename, "r") as f: if enable_includes and enable_include_dir is None: @@ -162,12 +170,15 @@ class GerberFile(CamFile): @classmethod def from_string(kls, data, enable_include_dir=None, filename=None): + """ Parse given string as Gerber file content. For the meaning of the parameters, see + :py:meth:`~.GerberFile.open`. """ # filename arg is for error messages obj = kls() GerberParser(obj, include_dir=enable_include_dir).parse(data, filename=filename) return obj - def generate_statements(self, settings, drop_comments=True): + def _generate_statements(self, settings, drop_comments=True): + """ Export this file as Gerber code, yields one str per line. """ yield 'G04 Gerber file generated by Gerbonara*' for name, value in self.file_attrs.items(): attrdef = ','.join([name, *map(str, value)]) @@ -222,17 +233,27 @@ class GerberFile(CamFile): return f'<GerberFile {name}with {len(self.apertures)} apertures, {len(self.objects)} objects>' def save(self, filename, settings=None, drop_comments=True): + """ Save this Gerber file to the file system. See :py:meth:`~.GerberFile.generate_gerber` for the meaning + of the arguments. """ with open(filename, 'w', encoding='utf-8') as f: # Encoding is specified as UTF-8 by spec. f.write(self.generate_gerber(settings, drop_comments=drop_comments)) def generate_gerber(self, settings=None, drop_comments=True): - # Use given settings, or use same settings as original file if not given, or use defaults if not imported from a - # file + """ Export to Gerber format. Uses either the file's original settings or sane default settings if you don't give + any. + + :param FileSettings settings: override export settings. + :param bool drop_comments: If true, do not write comments to output file. This defaults to true because + otherwise there is a risk that Gerbonara does not consider some obscure magic comment semantically + meaningful while some other Excellon viewer might still parse it. + + :rtype: str + """ if settings is None: settings = self.import_settings.copy() or FileSettings() settings.zeros = None settings.number_format = (5,6) - return '\n'.join(self.generate_statements(settings, drop_comments=drop_comments)) + return '\n'.join(self._generate_statements(settings, drop_comments=drop_comments)) @property def is_empty(self): @@ -250,15 +271,6 @@ class GerberFile(CamFile): obj.with_offset(dx, dy, unit) def rotate(self, angle:'radian', center=(0,0), unit=MM): - """ Rotate file contents around given point. - - Arguments: - angle -- Rotation angle in radian clockwise. - center -- Center of rotation (default: document origin (0, 0)) - - Note that when rotating by odd angles other than 0, 90, 180 or 270 degree this method may replace standard - rect and oblong apertures by macro apertures. Existing macro apertures are re-written. - """ if math.isclose(angle % (2*math.pi), 0): return @@ -271,11 +283,14 @@ class GerberFile(CamFile): obj.rotate(angle, *center, unit) def invert_polarity(self): + """ Invert the polarity (color) of each object in this file. """ for obj in self.objects: obj.polarity_dark = not p.polarity_dark class GraphicsState: + """ Internal class used to track Gerber processing state during import and export. """ + def __init__(self, warn, file_settings=None, aperture_map=None): self.image_polarity = 'positive' # IP image polarity; deprecated self.polarity_dark = True @@ -502,6 +517,8 @@ class GraphicsState: class GerberParser: + """ Internal class that contains all of the actual Gerber parsing magic. """ + NUMBER = r"[\+-]?\d+" DECIMAL = r"[\+-]?\d+([.]?\d+)?" NAME = r"[a-zA-Z_$\.][a-zA-Z_$\.0-9+\-]+" |