diff options
Diffstat (limited to 'gerbonara/gerber/cam.py')
-rw-r--r-- | gerbonara/gerber/cam.py | 199 |
1 files changed, 43 insertions, 156 deletions
diff --git a/gerbonara/gerber/cam.py b/gerbonara/gerber/cam.py index 4f20283..5da8600 100644 --- a/gerbonara/gerber/cam.py +++ b/gerbonara/gerber/cam.py @@ -14,164 +14,50 @@ # 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) +from dataclasses import dataclass + + +@dataclass +class FileSettings: + output_axes : str = 'AXBY' # For deprecated AS statement + image_polarity : str = 'positive' + image_rotation: int = 0 + mirror_image : tuple = (False, False) + scale_factor : tuple = (1.0, 1.0) # For deprecated SF statement + notation : str = 'absolute' + units : str = 'inch' + angle_units : str = 'degrees' + zeros : bool = None + number_format : tuple = (2, 5) + + # input validation + def __setattr__(self, name, value): + if name == 'output_axes' and value not in [None, 'AXBY', 'AYBX']: + raise ValueError('output_axes must be either "AXBY", "AYBX" or None') + if name == 'image_rotation' and value not in [0, 90, 180, 270]: + raise ValueError('image_rotation must be 0, 90, 180 or 270') + elif name == 'image_polarity' and value not in ['positive', 'negative']: + raise ValueError('image_polarity must be either "positive" or "negative"') + elif name == 'mirror_image' and len(value) != 2: + raise ValueError('mirror_image must be 2-tuple of bools: (mirror_a, mirror_b)') + elif name == 'scale_factor' and len(value) != 2: + raise ValueError('scale_factor must be 2-tuple of floats: (scale_a, scale_b)') + elif name == 'notation' and value not in ['inch', 'mm']: + raise ValueError('Units must be either "inch" or "mm"') + elif name == 'units' and value not in ['absolute', 'incremental']: + raise ValueError('Notation must be either "absolute" or "incremental"') + elif name == 'angle_units' and value not in ('degrees', 'radians'): + raise ValueError('Angle units may be "degrees" or "radians"') + elif name == 'zeros' and value not in [None, 'leading', 'trailing']: + raise ValueError('zero_suppression must be either "leading" or "trailing" or None') + elif name == 'number_format' and len(value) != 2: + raise ValueError('Number format must be a (integer, fractional) tuple of integers') + + super().__setattr__(name, value) def __str__(self): - return ('<Settings: %s %s %s %s %s>' % - (self.units, self.notation, self.zero_suppression, self.format, self.angle_units)) + return f'<File settings: units={self.units}/{self.angle_units} notation={self.notation} zeros={self.zeros} number_format={self.number_format}>' class CamFile(object): @@ -202,7 +88,7 @@ class CamFile(object): File notation setting. May be either 'absolute' or 'incremental' units : string - File units setting. May be 'inch' or 'metric' + File units setting. May be 'inch' or 'mm' zero_suppression : string File zero-suppression setting. May be either 'leading' or 'trailling' @@ -226,6 +112,7 @@ class CamFile(object): 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 |