diff options
author | jaseg <git@jaseg.de> | 2021-06-06 13:25:45 +0200 |
---|---|---|
committer | jaseg <git@jaseg.de> | 2021-06-06 13:25:45 +0200 |
commit | 5a5ba2b709f01b2100cd767a25a41737541ad53c (patch) | |
tree | 6362ca960945e08d4a77b7f059e971e6099217c9 /gerber/cam.py | |
parent | 8bad573131e4c91782425d81a141dd656b622d7b (diff) | |
parent | 72257258edf16cbda691483ef1fa722192ac0d38 (diff) | |
download | gerbonara-5a5ba2b709f01b2100cd767a25a41737541ad53c.tar.gz gerbonara-5a5ba2b709f01b2100cd767a25a41737541ad53c.tar.bz2 gerbonara-5a5ba2b709f01b2100cd767a25a41737541ad53c.zip |
Graft pcb-tools upstream onto gerbonara tree
Diffstat (limited to 'gerber/cam.py')
-rw-r--r-- | gerber/cam.py | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/gerber/cam.py b/gerber/cam.py new file mode 100644 index 0000000..4f20283 --- /dev/null +++ b/gerber/cam.py @@ -0,0 +1,286 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- + +# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be> +# +# 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. +""" +CAM File +============ +**AM file classes** + +This module provides common base classes for Excellon/Gerber CNC files +""" + + +class FileSettings(object): + """ CAM File Settings + + Provides a common representation of gerber/excellon file settings + + Parameters + ---------- + notation: string + notation format. either 'absolute' or 'incremental' + + units : string + Measurement units. 'inch' or 'metric' + + zero_suppression: string + 'leading' to suppress leading zeros, 'trailing' to suppress trailing zeros. + This is the convention used in Gerber files. + + format : tuple (int, int) + Decimal format + + zeros : string + 'leading' to include leading zeros, 'trailing to include trailing zeros. + This is the convention used in Excellon files + + Notes + ----- + Either `zeros` or `zero_suppression` should be specified, there is no need to + specify both. `zero_suppression` will take on the opposite value of `zeros` + and vice versa + """ + + def __init__(self, notation='absolute', units='inch', + zero_suppression=None, format=(2, 5), zeros=None, + angle_units='degrees'): + if notation not in ['absolute', 'incremental']: + raise ValueError('Notation must be either absolute or incremental') + self.notation = notation + + if units not in ['inch', 'metric']: + raise ValueError('Units must be either inch or metric') + self.units = units + + if zero_suppression is None and zeros is None: + self.zero_suppression = 'trailing' + + elif zero_suppression == zeros: + raise ValueError('Zeros and Zero Suppression must be different. \ + Best practice is to specify only one.') + + elif zero_suppression is not None: + if zero_suppression not in ['leading', 'trailing']: + # This is a common problem in Eagle files, so just suppress it + self.zero_suppression = 'leading' + else: + self.zero_suppression = zero_suppression + + elif zeros is not None: + if zeros not in ['leading', 'trailing']: + raise ValueError('Zeros must be either leading or trailling') + self.zeros = zeros + + if len(format) != 2: + raise ValueError('Format must be a tuple(n=2) of integers') + self.format = format + + if angle_units not in ('degrees', 'radians'): + raise ValueError('Angle units may be degrees or radians') + self.angle_units = angle_units + + @property + def zero_suppression(self): + return self._zero_suppression + + @zero_suppression.setter + def zero_suppression(self, value): + self._zero_suppression = value + self._zeros = 'leading' if value == 'trailing' else 'trailing' + + @property + def zeros(self): + return self._zeros + + @zeros.setter + def zeros(self, value): + + self._zeros = value + self._zero_suppression = 'leading' if value == 'trailing' else 'trailing' + + def __getitem__(self, key): + if key == 'notation': + return self.notation + elif key == 'units': + return self.units + elif key == 'zero_suppression': + return self.zero_suppression + elif key == 'zeros': + return self.zeros + elif key == 'format': + return self.format + elif key == 'angle_units': + return self.angle_units + else: + raise KeyError() + + def __setitem__(self, key, value): + if key == 'notation': + if value not in ['absolute', 'incremental']: + raise ValueError('Notation must be either \ + absolute or incremental') + self.notation = value + elif key == 'units': + if value not in ['inch', 'metric']: + raise ValueError('Units must be either inch or metric') + self.units = value + + elif key == 'zero_suppression': + if value not in ['leading', 'trailing']: + raise ValueError('Zero suppression must be either leading or \ + trailling') + self.zero_suppression = value + + elif key == 'zeros': + if value not in ['leading', 'trailing']: + raise ValueError('Zeros must be either leading or trailling') + self.zeros = value + + elif key == 'format': + if len(value) != 2: + raise ValueError('Format must be a tuple(n=2) of integers') + self.format = value + + elif key == 'angle_units': + if value not in ('degrees', 'radians'): + raise ValueError('Angle units may be degrees or radians') + self.angle_units = value + + else: + raise KeyError('%s is not a valid key' % key) + + def __eq__(self, other): + return (self.notation == other.notation and + self.units == other.units and + self.zero_suppression == other.zero_suppression and + self.format == other.format and + self.angle_units == other.angle_units) + + def __str__(self): + return ('<Settings: %s %s %s %s %s>' % + (self.units, self.notation, self.zero_suppression, self.format, self.angle_units)) + + +class CamFile(object): + """ Base class for Gerber/Excellon files. + + Provides a common set of settings parameters. + + Parameters + ---------- + settings : FileSettings + The current file configuration. + + primitives : iterable + List of primitives in the file. + + filename : string + Name of the file that this CamFile represents. + + layer_name : string + Name of the PCB layer that the file represents + + Attributes + ---------- + settings : FileSettings + File settings as a FileSettings object + + notation : string + File notation setting. May be either 'absolute' or 'incremental' + + units : string + File units setting. May be 'inch' or 'metric' + + zero_suppression : string + File zero-suppression setting. May be either 'leading' or 'trailling' + + format : tuple (<int>, <int>) + File decimal representation format as a tuple of (integer digits, + decimal digits) + """ + + def __init__(self, statements=None, settings=None, primitives=None, + filename=None, layer_name=None): + if settings is not None: + self.notation = settings['notation'] + self.units = settings['units'] + self.zero_suppression = settings['zero_suppression'] + self.zeros = settings['zeros'] + self.format = settings['format'] + else: + self.notation = 'absolute' + self.units = 'inch' + self.zero_suppression = 'trailing' + self.zeros = 'leading' + self.format = (2, 5) + self.statements = statements if statements is not None else [] + if primitives is not None: + self.primitives = primitives + self.filename = filename + self.layer_name = layer_name + + @property + def settings(self): + """ File settings + + Returns + ------- + settings : FileSettings (dict-like) + A FileSettings object with the specified configuration. + """ + return FileSettings(self.notation, self.units, self.zero_suppression, + self.format) + + @property + def bounds(self): + """ File boundaries + """ + pass + + @property + def bounding_box(self): + pass + + def to_inch(self): + pass + + def to_metric(self): + pass + + def render(self, ctx=None, invert=False, filename=None): + """ Generate image of layer. + + Parameters + ---------- + ctx : :class:`GerberContext` + GerberContext subclass used for rendering the image + + filename : string <optional> + If provided, save the rendered image to `filename` + """ + if ctx is None: + from .render import GerberCairoContext + ctx = GerberCairoContext() + ctx.set_bounds(self.bounding_box) + ctx.paint_background() + ctx.invert = invert + ctx.new_render_layer() + for p in self.primitives: + ctx.render(p) + ctx.flatten() + + if filename is not None: + ctx.dump(filename) |