Improve Excellon parsing coverage

Add some not so used codes that were generating unknown stmt.
This commit is contained in:
Paulo Henrique Silva 2015-11-13 03:31:32 -02:00
parent 944c832922
commit 9ca75f991a
3 changed files with 226 additions and 16 deletions

View file

@ -78,12 +78,12 @@ class DrillHit(object):
def __init__(self, tool, position):
self.tool = tool
self.position = position
def to_inch(self):
if self.tool.units == 'metric':
self.tool.to_inch()
self.position = tuple(map(inch, self.position))
def to_metric(self):
if self.tool.units == 'inch':
self.tool.to_metric()
@ -96,6 +96,7 @@ class ExcellonFile(CamFile):
The ExcellonFile class represents a single excellon file.
http://www.excellon.com/manuals/program.htm
(archived version at https://web.archive.org/web/20150920001043/http://www.excellon.com/manuals/program.htm)
Parameters
----------
@ -122,11 +123,11 @@ class ExcellonFile(CamFile):
filename=filename)
self.tools = tools
self.hits = hits
@property
def primitives(self):
return [Drill(hit.position, hit.tool.diameter,units=self.settings.units) for hit in self.hits]
@property
def bounds(self):
@ -169,14 +170,14 @@ class ExcellonFile(CamFile):
def write(self, filename=None):
filename = filename if filename is not None else self.filename
with open(filename, 'w') as f:
# Copy the header verbatim
for statement in self.statements:
if not isinstance(statement, ToolSelectionStmt):
f.write(statement.to_excellon(self.settings) + '\n')
else:
break
# Write out coordinates for drill hits by tool
for tool in iter(self.tools.values()):
f.write(ToolSelectionStmt(tool.number).to_excellon(self.settings) + '\n')
@ -184,7 +185,7 @@ class ExcellonFile(CamFile):
if hit.tool.number == tool.number:
f.write(CoordinateStmt(*hit.position).to_excellon(self.settings) + '\n')
f.write(EndOfProgramStmt().to_excellon() + '\n')
def to_inch(self):
"""
Convert units to inches
@ -235,7 +236,7 @@ class ExcellonFile(CamFile):
lengths[num] = 0.0 if lengths.get(num) is None else lengths[num]
lengths[num] = lengths[num] + math.hypot(*tuple(map(operator.sub, positions[num], hit.position)))
positions[num] = hit.position
if tool_number is None:
return lengths
else:
@ -270,8 +271,8 @@ class ExcellonFile(CamFile):
for hit in self.hits:
if hit.tool.number == newtool.number:
hit.tool = newtool
class ExcellonParser(object):
""" Excellon File Parser
@ -368,6 +369,15 @@ class ExcellonParser(object):
if self.state == 'HEADER':
self.state = 'DRILL'
elif line[:3] == 'M15':
self.statements.append(ZAxisRoutPositionStmt())
elif line[:3] == 'M16':
self.statements.append(RetractWithClampingStmt())
elif line[:3] == 'M17':
self.statements.append(RetractWithoutClampingStmt())
elif line[:3] == 'M30':
stmt = EndOfProgramStmt.from_excellon(line, self._settings())
self.statements.append(stmt)
@ -376,6 +386,44 @@ class ExcellonParser(object):
self.statements.append(RouteModeStmt())
self.state = 'ROUT'
stmt = CoordinateStmt.from_excellon(line[3:], self._settings())
stmt.mode = self.state
x = stmt.x
y = stmt.y
self.statements.append(stmt)
if self.notation == 'absolute':
if x is not None:
self.pos[0] = x
if y is not None:
self.pos[1] = y
else:
if x is not None:
self.pos[0] += x
if y is not None:
self.pos[1] += y
elif line[:3] == 'G01':
self.statements.append(RouteModeStmt())
self.state = 'LINEAR'
stmt = CoordinateStmt.from_excellon(line[3:], self._settings())
stmt.mode = self.state
x = stmt.x
y = stmt.y
self.statements.append(stmt)
if self.notation == 'absolute':
if x is not None:
self.pos[0] = x
if y is not None:
self.pos[1] = y
else:
if x is not None:
self.pos[0] += x
if y is not None:
self.pos[1] += y
elif line[:3] == 'G05':
self.statements.append(DrillModeStmt())
self.state = 'DRILL'
@ -404,10 +452,23 @@ class ExcellonParser(object):
stmt = FormatStmt.from_excellon(line)
self.statements.append(stmt)
elif line[:3] == 'G40':
self.statements.append(CutterCompensationOffStmt())
elif line[:3] == 'G41':
self.statements.append(CutterCompensationLeftStmt())
elif line[:3] == 'G42':
self.statements.append(CutterCompensationRightStmt())
elif line[:3] == 'G90':
self.statements.append(AbsoluteModeStmt())
self.notation = 'absolute'
elif line[0] == 'F':
infeed_rate_stmt = ZAxisInfeedRateStmt.from_excellon(line)
self.statements.append(infeed_rate_stmt)
elif line[0] == 'T' and self.state == 'HEADER':
tool = ExcellonTool.from_excellon(line, self._settings())
self.tools[tool.number] = tool
@ -475,7 +536,7 @@ def detect_excellon_format(data=None, filename=None):
detected_format = None
zeros_options = ('leading', 'trailing', )
format_options = ((2, 4), (2, 5), (3, 3),)
if data is None and filename is None:
raise ValueError('Either data or filename arguments must be provided')
if data is None:

View file

@ -31,24 +31,27 @@ __all__ = ['ExcellonTool', 'ToolSelectionStmt', 'CoordinateStmt',
'CommentStmt', 'HeaderBeginStmt', 'HeaderEndStmt',
'RewindStopStmt', 'EndOfProgramStmt', 'UnitStmt',
'IncrementalModeStmt', 'VersionStmt', 'FormatStmt', 'LinkToolStmt',
'MeasuringModeStmt', 'RouteModeStmt', 'DrillModeStmt',
'MeasuringModeStmt', 'RouteModeStmt', 'LinearModeStmt', 'DrillModeStmt',
'AbsoluteModeStmt', 'RepeatHoleStmt', 'UnknownStmt',
'ExcellonStatement',]
'ExcellonStatement', 'ZAxisRoutPositionStmt',
'RetractWithClampingStmt', 'RetractWithoutClampingStmt',
'CutterCompensationOffStmt', 'CutterCompensationLeftStmt',
'CutterCompensationRightStmt', 'ZAxisInfeedRateStmt']
class ExcellonStatement(object):
""" Excellon Statement abstract base class
"""
@classmethod
def from_excellon(cls, line):
raise NotImplementedError('from_excellon must be implemented in a '
'subclass')
def __init__(self, unit='inch', id=None):
self.units = unit
self.id = uuid.uuid4().int if id is None else id
def to_excellon(self, settings=None):
raise NotImplementedError('to_excellon must be implemented in a '
'subclass')
@ -266,6 +269,34 @@ class ToolSelectionStmt(ExcellonStatement):
return stmt
class ZAxisInfeedRateStmt(ExcellonStatement):
@classmethod
def from_excellon(cls, line, **kwargs):
""" Create a ZAxisInfeedRate from an excellon file line.
Parameters
----------
line : string
Line from an Excellon file
Returns
-------
z_axis_infeed_rate : ToolSelectionStmt
ToolSelectionStmt representation of `line.`
"""
rate = int(line[1:])
return cls(rate, **kwargs)
def __init__(self, rate, **kwargs):
super(ZAxisInfeedRateStmt, self).__init__(**kwargs)
self.rate = rate
def to_excellon(self, settings=None):
return 'F%02d' % self.rate
class CoordinateStmt(ExcellonStatement):
@classmethod
@ -290,9 +321,14 @@ class CoordinateStmt(ExcellonStatement):
super(CoordinateStmt, self).__init__(**kwargs)
self.x = x
self.y = y
self.mode = None
def to_excellon(self, settings):
stmt = ''
if self.mode == "ROUT":
stmt += "G00"
if self.mode == "LINEAR":
stmt += "G01"
if self.x is not None:
stmt += 'X%s' % write_gerber_value(self.x, settings.format,
settings.zero_suppression)
@ -431,6 +467,60 @@ class RewindStopStmt(ExcellonStatement):
return '%'
class ZAxisRoutPositionStmt(ExcellonStatement):
def __init__(self, **kwargs):
super(ZAxisRoutPositionStmt, self).__init__(**kwargs)
def to_excellon(self, settings=None):
return 'M15'
class RetractWithClampingStmt(ExcellonStatement):
def __init__(self, **kwargs):
super(RetractWithClampingStmt, self).__init__(**kwargs)
def to_excellon(self, settings=None):
return 'M16'
class RetractWithoutClampingStmt(ExcellonStatement):
def __init__(self, **kwargs):
super(RetractWithoutClampingStmt, self).__init__(**kwargs)
def to_excellon(self, settings=None):
return 'M17'
class CutterCompensationOffStmt(ExcellonStatement):
def __init__(self, **kwargs):
super(CutterCompensationOffStmt, self).__init__(**kwargs)
def to_excellon(self, settings=None):
return 'G40'
class CutterCompensationLeftStmt(ExcellonStatement):
def __init__(self, **kwargs):
super(CutterCompensationLeftStmt, self).__init__(**kwargs)
def to_excellon(self, settings=None):
return 'G41'
class CutterCompensationRightStmt(ExcellonStatement):
def __init__(self, **kwargs):
super(CutterCompensationRightStmt, self).__init__(**kwargs)
def to_excellon(self, settings=None):
return 'G42'
class EndOfProgramStmt(ExcellonStatement):
@classmethod
@ -608,6 +698,15 @@ class RouteModeStmt(ExcellonStatement):
return 'G00'
class LinearModeStmt(ExcellonStatement):
def __init__(self, **kwargs):
super(LinearModeStmt, self).__init__(**kwargs)
def to_excellon(self, settings=None):
return 'G01'
class DrillModeStmt(ExcellonStatement):
def __init__(self, **kwargs):

View file

@ -123,6 +123,28 @@ def test_toolselection_dump():
stmt = ToolSelectionStmt.from_excellon(line)
assert_equal(stmt.to_excellon(), line)
def test_z_axis_infeed_rate_factory():
""" Test ZAxisInfeedRateStmt factory method
"""
stmt = ZAxisInfeedRateStmt.from_excellon('F01')
assert_equal(stmt.rate, 1)
stmt = ZAxisInfeedRateStmt.from_excellon('F2')
assert_equal(stmt.rate, 2)
stmt = ZAxisInfeedRateStmt.from_excellon('F03')
assert_equal(stmt.rate, 3)
def test_z_axis_infeed_rate_dump():
""" Test ZAxisInfeedRateStmt to_excellon()
"""
inputs = [
('F01', 'F01'),
('F2', 'F02'),
('F00003', 'F03')
]
for input_rate, expected_output in inputs:
stmt = ZAxisInfeedRateStmt.from_excellon(input_rate)
assert_equal(stmt.to_excellon(), expected_output)
def test_coordinatestmt_factory():
""" Test CoordinateStmt factory method
"""
@ -323,6 +345,30 @@ def test_rewindstop_stmt():
stmt = RewindStopStmt()
assert_equal(stmt.to_excellon(None), '%')
def test_z_axis_rout_position_stmt():
stmt = ZAxisRoutPositionStmt()
assert_equal(stmt.to_excellon(None), 'M15')
def test_retract_with_clamping_stmt():
stmt = RetractWithClampingStmt()
assert_equal(stmt.to_excellon(None), 'M16')
def test_retract_without_clamping_stmt():
stmt = RetractWithoutClampingStmt()
assert_equal(stmt.to_excellon(None), 'M17')
def test_cutter_compensation_off_stmt():
stmt = CutterCompensationOffStmt()
assert_equal(stmt.to_excellon(None), 'G40')
def test_cutter_compensation_left_stmt():
stmt = CutterCompensationLeftStmt()
assert_equal(stmt.to_excellon(None), 'G41')
def test_cutter_compensation_right_stmt():
stmt = CutterCompensationRightStmt()
assert_equal(stmt.to_excellon(None), 'G42')
def test_endofprogramstmt_factory():
settings = FileSettings(units='inch')
stmt = EndOfProgramStmt.from_excellon('M30X01Y02', settings)
@ -579,6 +625,10 @@ def test_routemode_stmt():
stmt = RouteModeStmt()
assert_equal(stmt.to_excellon(FileSettings()), 'G00')
def test_linearmode_stmt():
stmt = LinearModeStmt()
assert_equal(stmt.to_excellon(FileSettings()), 'G01')
def test_drillmode_stmt():
stmt = DrillModeStmt()
assert_equal(stmt.to_excellon(FileSettings()), 'G05')