From b495d51354eff7b858dbbd41740865eba7f39100 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 25 Jan 2015 14:19:48 -0500 Subject: Changed zeros/zero suppression conventions to match file format specs --- gerber/cam.py | 84 +++++++++++++++++++++++++++++--- gerber/excellon.py | 32 ++++++------ gerber/excellon_statements.py | 10 ++-- gerber/tests/test_cam.py | 27 +++++++++- gerber/tests/test_excellon.py | 13 ++--- gerber/tests/test_excellon_statements.py | 14 +++++- 6 files changed, 141 insertions(+), 39 deletions(-) diff --git a/gerber/cam.py b/gerber/cam.py index 051c3b5..a4057bc 100644 --- a/gerber/cam.py +++ b/gerber/cam.py @@ -27,9 +27,34 @@ 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='trailing', format=(2, 5)): + zero_suppression=None, format=(2, 5), zeros=None): if notation not in ['absolute', 'incremental']: raise ValueError('Notation must be either absolute or incremental') self.notation = notation @@ -38,15 +63,52 @@ class FileSettings(object): raise ValueError('Units must be either inch or metric') self.units = units - if zero_suppression not in ['leading', 'trailing']: - raise ValueError('Zero suppression must be either leading or \ - trailling') - self.zero_suppression = zero_suppression + + 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']: + raise ValueError('Zero suppression must be either leading or \ + trailling') + 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 + else: + self.zeros = 'leading' + if len(format) != 2: raise ValueError('Format must be a tuple(n=2) of integers') self.format = format + @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 @@ -54,6 +116,8 @@ class FileSettings(object): return self.units elif key == 'zero_suppression': return self.zero_suppression + elif key == 'zeros': + return self.zeros elif key == 'format': return self.format else: @@ -69,11 +133,18 @@ class FileSettings(object): 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') @@ -86,7 +157,6 @@ class FileSettings(object): self.format == other.format) - class CamFile(object): """ Base class for Gerber/Excellon files. @@ -131,11 +201,13 @@ class CamFile(object): 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 [] self.primitives = primitives diff --git a/gerber/excellon.py b/gerber/excellon.py index 17b870a..235f966 100755 --- a/gerber/excellon.py +++ b/gerber/excellon.py @@ -43,7 +43,9 @@ def read(filename): An ExcellonFile created from the specified file. """ - return ExcellonParser(None).parse(filename) + # File object should use settings from source file by default. + settings = FileSettings(**detect_excellon_format(filename)) + return ExcellonParser(settings).parse(filename) class ExcellonFile(CamFile): @@ -116,7 +118,7 @@ class ExcellonParser(object): def __init__(self, settings=None): self.notation = 'absolute' self.units = 'inch' - self.zero_suppression = 'leading' + self.zeros = 'leading' self.format = (2, 4) self.state = 'INIT' self.statements = [] @@ -125,8 +127,9 @@ class ExcellonParser(object): self.active_tool = None self.pos = [0., 0.] if settings is not None: + print('Setting shit from settings. zeros: %s' %settings.zeros) self.units = settings.units - self.zero_suppression = settings.zero_suppression + self.zeros = settings.zeros self.notation = settings.notation self.format = settings.format @@ -207,7 +210,7 @@ class ExcellonParser(object): elif 'INCH' in line or 'METRIC' in line: stmt = UnitStmt.from_excellon(line) self.units = stmt.units - self.zero_suppression = stmt.zero_suppression + self.zeros = stmt.zeros self.statements.append(stmt) elif line[:3] == 'M71' or line [:3] == 'M72': @@ -270,8 +273,7 @@ class ExcellonParser(object): def _settings(self): return FileSettings(units=self.units, format=self.format, - zero_suppression=self.zero_suppression, - notation=self.notation) + zeros=self.zeros, notation=self.notation) def detect_excellon_format(filename): @@ -293,7 +295,7 @@ def detect_excellon_format(filename): results = {} detected_zeros = None detected_format = None - zs_options = ('leading', 'trailing', ) + zeros_options = ('leading', 'trailing', ) format_options = ((2, 4), (2, 5), (3, 3),) # Check for obvious clues: @@ -301,7 +303,7 @@ def detect_excellon_format(filename): p.parse(filename) # Get zero_suppression from a unit statement - zero_statements = [stmt.zero_suppression for stmt in p.statements + zero_statements = [stmt.zeros for stmt in p.statements if isinstance(stmt, UnitStmt)] # get format from altium comment @@ -316,19 +318,19 @@ def detect_excellon_format(filename): # Bail out here if possible if detected_format is not None and detected_zeros is not None: - return {'format': detected_format, 'zero_suppression': detected_zeros} + return {'format': detected_format, 'zeros': detected_zeros} # Only look at remaining options if detected_format is not None: format_options = (detected_format,) if detected_zeros is not None: - zs_options = (detected_zeros,) + zeros_options = (detected_zeros,) # Brute force all remaining options, and pick the best looking one... - for zs in zs_options: + for zeros in zeros_options: for fmt in format_options: - key = (fmt, zs) - settings = FileSettings(zero_suppression=zs, format=fmt) + key = (fmt, zeros) + settings = FileSettings(zeros=zeros, format=fmt) try: p = ExcellonParser(settings) p.parse(filename) @@ -351,7 +353,7 @@ def detect_excellon_format(filename): # Bail out here if we got everything.... if detected_format is not None and detected_zeros is not None: - return {'format': detected_format, 'zero_suppression': detected_zeros} + return {'format': detected_format, 'zeros': detected_zeros} # Otherwise score each option and pick the best candidate else: @@ -362,7 +364,7 @@ def detect_excellon_format(filename): minscore = min(scores.values()) for key in scores.iterkeys(): if scores[key] == minscore: - return {'format': key[0], 'zero_suppression': key[1]} + return {'format': key[0], 'zeros': key[1]} def _layer_size_score(size, hole_count, hole_area): diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 02bb923..71009d8 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -360,16 +360,16 @@ class UnitStmt(ExcellonStatement): @classmethod def from_excellon(cls, line): units = 'inch' if 'INCH' in line else 'metric' - zero_suppression = 'trailing' if 'LZ' in line else 'leading' - return cls(units, zero_suppression) + zeros = 'leading' if 'LZ' in line else 'trailing' + return cls(units, zeros) - def __init__(self, units='inch', zero_suppression='trailing'): + def __init__(self, units='inch', zeros='leading'): self.units = units.lower() - self.zero_suppression = zero_suppression + self.zeros = zeros def to_excellon(self, settings=None): stmt = '%s,%s' % ('INCH' if self.units == 'inch' else 'METRIC', - 'LZ' if self.zero_suppression == 'trailing' + 'LZ' if self.zeros == 'leading' else 'TZ') return stmt diff --git a/gerber/tests/test_cam.py b/gerber/tests/test_cam.py index ce4ec44..1aeb18c 100644 --- a/gerber/tests/test_cam.py +++ b/gerber/tests/test_cam.py @@ -64,5 +64,28 @@ def test_camfile_settings(): """ cf = CamFile() assert_equal(cf.settings, FileSettings()) - - \ No newline at end of file + +def test_zeros(): + + fs = FileSettings() + assert_equal(fs.zero_suppression, 'trailing') + assert_equal(fs.zeros, 'leading') + + + fs['zero_suppression'] = 'leading' + assert_equal(fs.zero_suppression, 'leading') + assert_equal(fs.zeros, 'trailing') + + fs.zero_suppression = 'trailing' + assert_equal(fs.zero_suppression, 'trailing') + assert_equal(fs.zeros, 'leading') + + fs['zeros'] = 'trailing' + assert_equal(fs.zeros, 'trailing') + assert_equal(fs.zero_suppression, 'leading') + + + fs.zeros= 'leading' + assert_equal(fs.zeros, 'leading') + assert_equal(fs.zero_suppression, 'trailing') + diff --git a/gerber/tests/test_excellon.py b/gerber/tests/test_excellon.py index 72e3d7d..70e4560 100644 --- a/gerber/tests/test_excellon.py +++ b/gerber/tests/test_excellon.py @@ -15,18 +15,13 @@ def test_format_detection(): """ settings = detect_excellon_format(NCDRILL_FILE) assert_equal(settings['format'], (2, 4)) - assert_equal(settings['zero_suppression'], 'leading') + assert_equal(settings['zeros'], 'trailing') def test_read(): ncdrill = read(NCDRILL_FILE) assert(isinstance(ncdrill, ExcellonFile)) - + def test_read_settings(): ncdrill = read(NCDRILL_FILE) - assert_equal(ncdrill.settings.format, (2, 4)) - assert_equal(ncdrill.settings.zero_suppression, 'leading') - - - - - + assert_equal(ncdrill.settings['format'], (2, 4)) + assert_equal(ncdrill.settings['zeros'], 'trailing') diff --git a/gerber/tests/test_excellon_statements.py b/gerber/tests/test_excellon_statements.py index 4c3201b..2e508ff 100644 --- a/gerber/tests/test_excellon_statements.py +++ b/gerber/tests/test_excellon_statements.py @@ -141,12 +141,22 @@ def test_unitstmt_factory(): line = 'INCH,LZ' stmt = UnitStmt.from_excellon(line) assert_equal(stmt.units, 'inch') - assert_equal(stmt.zero_suppression, 'trailing') + assert_equal(stmt.zeros, 'leading') + + line = 'INCH,TZ' + stmt = UnitStmt.from_excellon(line) + assert_equal(stmt.units, 'inch') + assert_equal(stmt.zeros, 'trailing') + + line = 'METRIC,LZ' + stmt = UnitStmt.from_excellon(line) + assert_equal(stmt.units, 'metric') + assert_equal(stmt.zeros, 'leading') line = 'METRIC,TZ' stmt = UnitStmt.from_excellon(line) assert_equal(stmt.units, 'metric') - assert_equal(stmt.zero_suppression, 'leading') + assert_equal(stmt.zeros, 'trailing') def test_unitstmt_dump(): -- cgit