Merge pull request #7 from curtacircuitos/zeros_convention

Zeros convention
This commit is contained in:
Paulo Henrique Silva 2015-01-26 00:20:01 -02:00
commit 2586692a17
6 changed files with 140 additions and 39 deletions

View file

@ -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

View file

@ -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 = []
@ -126,7 +128,7 @@ class ExcellonParser(object):
self.pos = [0., 0.]
if settings is not None:
self.units = settings.units
self.zero_suppression = settings.zero_suppression
self.zeros = settings.zeros
self.notation = settings.notation
self.format = settings.format
@ -207,7 +209,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 +272,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 +294,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 +302,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 +317,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 +352,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 +363,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):

View file

@ -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

View file

@ -64,5 +64,28 @@ def test_camfile_settings():
"""
cf = CamFile()
assert_equal(cf.settings, FileSettings())
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')

View file

@ -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')

View file

@ -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():