Merge pull request #7 from curtacircuitos/zeros_convention
Zeros convention
This commit is contained in:
commit
2586692a17
6 changed files with 140 additions and 39 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue