fix excellon render

This commit is contained in:
Hamilton Kibbe 2014-10-07 22:44:08 -04:00
parent 5ff44efbcf
commit af97dcf2a8
7 changed files with 157 additions and 85 deletions

View file

@ -16,21 +16,20 @@
# the License.
if __name__ == '__main__':
from .gerber import GerberFile
from .excellon import ExcellonParser
import gerber
import excellon
from .render import GerberSvgContext
#import sys
#
#if len(sys.argv) < 2:
#if len(sys.argv) < 2:`
# print >> sys.stderr, "Usage: python -m gerber <filename> <filename>..."
# sys.exit(1)
#
##for filename in sys.argv[1]:
## print "parsing %s" % filename
ctx = GerberSvgContext()
g = GerberFile.read('SCB.GTL')
g = gerber.read('examples/test.gtl')
g.render('test.svg', ctx)
p = ExcellonParser(ctx)
p.parse('ncdrill.txt')
p.dump('testwithdrill.svg')
p = excellon.read('ncdrill.txt')
p.render('testwithdrill.svg', ctx)

View file

@ -21,9 +21,9 @@ Excellon module
This module provides Excellon file classes and parsing utilities
"""
import re
from .excellon_statements import *
from .utils import parse_gerber_value
from .cnc import CncFile, FileSettings
@ -70,15 +70,18 @@ class ExcellonFile(CncFile):
def render(self, filename, ctx):
""" Generate image of file
"""
count = 0
for tool, pos in self.hits:
ctx.drill(pos[0], pos[1], tool.diameter)
count += 1
print('Drilled %d hits' % count)
ctx.dump(filename)
def write(self, filename):
with open(filename, 'w') as f:
for statement in self.statements:
f.write(statement.to_excellon() + '\n')
class ExcellonParser(object):
""" Excellon File Parser
@ -95,27 +98,21 @@ class ExcellonParser(object):
self.hits = []
self.active_tool = None
self.pos = [0., 0.]
if ctx is not None:
self.ctx.set_coord_format(zero_suppression='trailing',
format=(2, 5), notation='absolute')
def parse(self, filename):
with open(filename, 'r') as f:
for line in f:
self._parse(line)
return ExcellonFile(self.statements, self.tools, self.hits, self._settings(), filename)
def dump(self, filename):
if self.ctx is not None:
self.ctx.dump(filename)
return ExcellonFile(self.statements, self.tools, self.hits,
self._settings(), filename)
def _parse(self, line):
line = line.strip()
zs = self._settings()['zero_suppression']
fmt = self._settings()['format']
if line[0] == ';':
self.statements.append(CommentStmt.from_excellon(line))
elif line[:3] == 'M48':
self.statements.append(HeaderBeginStmt())
self.state = 'HEADER'
@ -130,29 +127,41 @@ class ExcellonParser(object):
if self.state == 'HEADER':
self.state = 'DRILL'
elif line[:3] == 'M30':
stmt = EndOfProgramStmt.from_excellon(line)
self.statements.append(stmt)
elif line[:3] == 'G00':
self.state = 'ROUT'
elif line[:3] == 'G05':
self.state = 'DRILL'
elif ('INCH' in line or 'METRIC' in line) and ('LZ' in line or 'TZ' in line):
elif (('INCH' in line or 'METRIC' in line) and
('LZ' in line or 'TZ' in line)):
stmt = UnitStmt.from_excellon(line)
self.units = stmt.units
self.zero_suppression = stmt.zero_suppression
self.statements.append(stmt)
elif line[:3] == 'M71' or line [:3] == 'M72':
stmt = MeasuringModeStmt.from_excellon(line)
self.units = stmt.units
self.statements.append(stmt)
elif line[:3] == 'ICI':
stmt = IncrementalModeStmt.from_excellon(line)
self.notation = 'incremental' if stmt.mode == 'on' else 'absolute'
self.statements.append(stmt)
# tool definition
elif line[:3] == 'VER':
stmt = VersionStmt.from_excellon(line)
self.statements.append(stmt)
elif line[:4] == 'FMAT':
stmt = FormatStmt.from_excellon(line)
self.statements.append(stmt)
elif line[0] == 'T' and self.state == 'HEADER':
tool = ExcellonTool.from_excellon(line, self._settings())
self.tools[tool.number] = tool
@ -161,7 +170,6 @@ class ExcellonParser(object):
elif line[0] == 'T' and self.state != 'HEADER':
stmt = ToolSelectionStmt.from_excellon(line)
self.active_tool = self.tools[stmt.tool]
#self.active_tool = self.tools[int(line.strip().split('T')[1])]
self.statements.append(stmt)
elif line[0] in ['X', 'Y']:
@ -169,15 +177,6 @@ class ExcellonParser(object):
x = stmt.x
y = stmt.y
self.statements.append(stmt)
#x = None
#y = None
#if line[0] == 'X':
# splitline = line.strip('X').split('Y')
# x = parse_gerber_value(splitline[0].strip(), fmt, zs)
# if len(splitline) == 2:
# y = parse_gerber_value(splitline[1].strip(), fmt, zs)
#else:
# y = parse_gerber_value(line.strip(' Y'), fmt, zs)
if self.notation == 'absolute':
if x is not None:
self.pos[0] = x
@ -189,11 +188,8 @@ class ExcellonParser(object):
if y is not None:
self.pos[1] += y
if self.state == 'DRILL':
self.hits.append((self.active_tool, self.pos))
self.hits.append((self.active_tool, tuple(self.pos)))
self.active_tool._hit()
if self.ctx is not None:
self.ctx.drill(self.pos[0], self.pos[1],
self.active_tool.diameter)
else:
self.statements.append(UnknownStmt.from_excellon(line))
@ -201,7 +197,7 @@ class ExcellonParser(object):
return FileSettings(units=self.units, format=self.format,
zero_suppression=self.zero_suppression,
notation=self.notation)
if __name__ == '__main__':
p = ExcellonParser()

View file

@ -256,7 +256,7 @@ class GerberParser(object):
elif param["param"] == "IN":
yield INParamStmt.from_dict(param)
elif param["param"] == "LN":
yield LNParamStmtfrom_dict(param)
yield LNParamStmt.from_dict(param)
else:
yield UnknownStmt(line)
did_something = True

View file

@ -16,8 +16,8 @@ __all__ = ['FSParamStmt', 'MOParamStmt', 'IPParamStmt', 'OFParamStmt',
class Statement(object):
def __init__(self, type):
self.type = type
def __init__(self, stype):
self.type = stype
def __str__(self):
s = "<{0} ".format(self.__class__.__name__)
@ -47,8 +47,8 @@ class FSParamStmt(ParamStmt):
zeros = 'leading' if stmt_dict.get('zero') == 'L' else 'trailing'
notation = 'absolute' if stmt_dict.get('notation') == 'A' else 'incremental'
x = map(int, stmt_dict.get('x').strip())
format = (x[0], x[1])
return cls(param, zeros, notation, format)
fmt = (x[0], x[1])
return cls(param, zeros, notation, fmt)
def __init__(self, param, zero_suppression='leading',
notation='absolute', format=(2, 4)):
@ -88,9 +88,9 @@ class FSParamStmt(ParamStmt):
def to_gerber(self):
zero_suppression = 'L' if self.zero_suppression == 'leading' else 'T'
notation = 'A' if self.notation == 'absolute' else 'I'
format = ''.join(map(str, self.format))
fmt = ''.join(map(str, self.format))
return '%FS{0}{1}X{2}Y{3}*%'.format(zero_suppression, notation,
format, format)
fmt, fmt)
def __str__(self):
return ('<Format Spec: %d:%d %s zero suppression %s notation>' %
@ -588,6 +588,28 @@ class EofStmt(Statement):
def __str__(self):
return '<EOF Statement>'
class QuadrantModeStmt(Statement):
@classmethod
def from_gerber(cls, line):
line = line.strip()
if 'G74' not in line and 'G75' not in line:
raise ValueError('%s is not a valid quadrant mode statement'
% line)
return (cls('single-quadrant') if line[:3] == 'G74'
else cls('multi-quadrant'))
def __init__(self, mode):
super(QuadrantModeStmt, self).__init__('Quadrant Mode')
mode = mode.lower
if mode not in ['single-quadrant', 'multi-quadrant']:
raise ValueError('Quadrant mode must be "single-quadrant" \
or "multi-quadrant"')
self.mode = mode
def to_gerber(self):
return 'G74*' if self.mode == 'single-quadrant' else 'G75*'
class UnknownStmt(Statement):
""" Unknown Statement

View file

@ -3,7 +3,7 @@
# Author: Hamilton Kibbe <ham@hamiltonkib.be>
from .tests import *
from .tests import assert_equal, assert_raises
from ..excellon_statements import *
@ -65,6 +65,8 @@ def test_toolselection_dump():
def test_coordinatestmt_factory():
""" Test CoordinateStmt factory method
"""
line = 'X0278207Y0065293'
stmt = CoordinateStmt.from_excellon(line)
assert_equal(stmt.x, 2.78207)
@ -80,6 +82,8 @@ def test_coordinatestmt_factory():
def test_coordinatestmt_dump():
""" Test CoordinateStmt to_excellon()
"""
lines = ['X0278207Y0065293', 'X0243795', 'Y0082528', 'Y0086028',
'X0251295Y0081528', 'X02525Y0078', 'X0255Y00575', 'Y0052',
'X02675', 'Y00575', 'X02425', 'Y0052', 'X023', ]
@ -89,6 +93,8 @@ def test_coordinatestmt_dump():
def test_commentstmt_factory():
""" Test CommentStmt factory method
"""
line = ';Layer_Color=9474304'
stmt = CommentStmt.from_excellon(line)
assert_equal(stmt.comment, line[1:])
@ -103,6 +109,8 @@ def test_commentstmt_factory():
def test_commentstmt_dump():
""" Test CommentStmt to_excellon()
"""
lines = [';Layer_Color=9474304', ';FILE_FORMAT=2:5', ';TYPE=PLATED', ]
for line in lines:
stmt = CommentStmt.from_excellon(line)
@ -110,6 +118,8 @@ def test_commentstmt_dump():
def test_unitstmt_factory():
""" Test UnitStmt factory method
"""
line = 'INCH,LZ'
stmt = UnitStmt.from_excellon(line)
assert_equal(stmt.units, 'inch')
@ -122,6 +132,8 @@ def test_unitstmt_factory():
def test_unitstmt_dump():
""" Test UnitStmt to_excellon()
"""
lines = ['INCH,LZ', 'INCH,TZ', 'METRIC,LZ', 'METRIC,TZ', ]
for line in lines:
stmt = UnitStmt.from_excellon(line)
@ -129,6 +141,8 @@ def test_unitstmt_dump():
def test_incrementalmode_factory():
""" Test IncrementalModeStmt factory method
"""
line = 'ICI,ON'
stmt = IncrementalModeStmt.from_excellon(line)
assert_equal(stmt.mode, 'on')
@ -139,6 +153,8 @@ def test_incrementalmode_factory():
def test_incrementalmode_dump():
""" Test IncrementalModeStmt to_excellon()
"""
lines = ['ICI,ON', 'ICI,OFF', ]
for line in lines:
stmt = IncrementalModeStmt.from_excellon(line)
@ -146,10 +162,14 @@ def test_incrementalmode_dump():
def test_incrementalmode_validation():
""" Test IncrementalModeStmt input validation
"""
assert_raises(ValueError, IncrementalModeStmt, 'OFF-ISH')
def test_versionstmt_factory():
""" Test VersionStmt factory method
"""
line = 'VER,1'
stmt = VersionStmt.from_excellon(line)
assert_equal(stmt.version, 1)
@ -160,16 +180,22 @@ def test_versionstmt_factory():
def test_versionstmt_dump():
""" Test VersionStmt to_excellon()
"""
lines = ['VER,1', 'VER,2', ]
for line in lines:
stmt = VersionStmt.from_excellon(line)
assert_equal(stmt.to_excellon(), line)
def test_versionstmt_validation():
""" Test VersionStmt input validation
"""
assert_raises(ValueError, VersionStmt, 3)
def test_formatstmt_factory():
""" Test FormatStmt factory method
"""
line = 'FMAT,1'
stmt = FormatStmt.from_excellon(line)
assert_equal(stmt.format, 1)
@ -180,6 +206,8 @@ def test_formatstmt_factory():
def test_formatstmt_dump():
""" Test FormatStmt to_excellon()
"""
lines = ['FMAT,1', 'FMAT,2', ]
for line in lines:
stmt = FormatStmt.from_excellon(line)
@ -187,10 +215,14 @@ def test_formatstmt_dump():
def test_formatstmt_validation():
""" Test FormatStmt input validation
"""
assert_raises(ValueError, FormatStmt, 3)
def test_linktoolstmt_factory():
""" Test LinkToolStmt factory method
"""
line = '1/2/3/4'
stmt = LinkToolStmt.from_excellon(line)
assert_equal(stmt.linked_tools, [1, 2, 3, 4])
@ -201,13 +233,17 @@ def test_linktoolstmt_factory():
def test_linktoolstmt_dump():
""" Test LinkToolStmt to_excellon()
"""
lines = ['1/2/3/4', '5/6/7', ]
for line in lines:
stmt = LinkToolStmt.from_excellon(line)
assert_equal(stmt.to_excellon(), line)
def test_measuringmodestmt_factory():
def test_measmodestmt_factory():
""" Test MeasuringModeStmt factory method
"""
line = 'M72'
stmt = MeasuringModeStmt.from_excellon(line)
assert_equal(stmt.units, 'inch')
@ -217,13 +253,17 @@ def test_measuringmodestmt_factory():
assert_equal(stmt.units, 'metric')
def test_measuringmodestmt_dump():
def test_measmodestmt_dump():
""" Test MeasuringModeStmt to_excellon()
"""
lines = ['M71', 'M72', ]
for line in lines:
stmt = MeasuringModeStmt.from_excellon(line)
assert_equal(stmt.to_excellon(), line)
def test_measuringmodestmt_validation():
def test_measmodestmt_validation():
""" Test MeasuringModeStmt input validation
"""
assert_raises(ValueError, MeasuringModeStmt.from_excellon, 'M70')
assert_raises(ValueError, MeasuringModeStmt, 'millimeters')

View file

@ -3,6 +3,7 @@
# Author: Hamilton Kibbe <ham@hamiltonkib.be>
from .tests import assert_equal
from ..utils import decimal_string, parse_gerber_value, write_gerber_value
@ -10,59 +11,73 @@ def test_zero_suppression():
""" Test gerber value parser and writer handle zero suppression correctly.
"""
# Default format
format = (2, 5)
fmt = (2, 5)
# Test leading zero suppression
zero_suppression = 'leading'
test_cases = [('1', 0.00001), ('10', 0.0001), ('100', 0.001),
('1000', 0.01), ('10000', 0.1), ('100000', 1.0),('1000000', 10.0),
('-1', -0.00001), ('-10', -0.0001), ('-100', -0.001),
('-1000', -0.01), ('-10000', -0.1), ('-100000', -1.0),('-1000000', -10.0),]
('1000', 0.01), ('10000', 0.1), ('100000', 1.0),
('1000000', 10.0), ('-1', -0.00001), ('-10', -0.0001),
('-100', -0.001), ('-1000', -0.01), ('-10000', -0.1),
('-100000', -1.0), ('-1000000', -10.0), ]
for string, value in test_cases:
assert(value == parse_gerber_value(string,format,zero_suppression))
assert(string == write_gerber_value(value,format,zero_suppression))
assert(value == parse_gerber_value(string, fmt, zero_suppression))
assert(string == write_gerber_value(value, fmt, zero_suppression))
# Test trailing zero suppression
zero_suppression = 'trailing'
test_cases = [('1', 10.0), ('01', 1.0), ('001', 0.1), ('0001', 0.01),
('00001', 0.001), ('000001', 0.0001), ('0000001', 0.00001),
('-1', -10.0), ('-01', -1.0), ('-001', -0.1), ('-0001', -0.01),
('-00001', -0.001), ('-000001', -0.0001), ('-0000001', -0.00001)]
('00001', 0.001), ('000001', 0.0001),
('0000001', 0.00001), ('-1', -10.0), ('-01', -1.0),
('-001', -0.1), ('-0001', -0.01), ('-00001', -0.001),
('-000001', -0.0001), ('-0000001', -0.00001)]
for string, value in test_cases:
assert(value == parse_gerber_value(string,format,zero_suppression))
assert(string == write_gerber_value(value,format,zero_suppression))
assert(value == parse_gerber_value(string, fmt, zero_suppression))
assert(string == write_gerber_value(value, fmt, zero_suppression))
def test_format():
""" Test gerber value parser and writer handle format correctly
"""
zero_suppression = 'leading'
test_cases = [((2,7),'1',0.0000001), ((2,6),'1',0.000001),
((2,5),'1',0.00001), ((2,4),'1',0.0001), ((2,3),'1',0.001),
((2,2),'1',0.01), ((2,1),'1',0.1), ((2,7),'-1',-0.0000001),
((2,6),'-1',-0.000001), ((2,5),'-1',-0.00001), ((2,4),'-1',-0.0001),
((2,3),'-1',-0.001), ((2,2),'-1',-0.01), ((2,1),'-1',-0.1),]
for format, string, value in test_cases:
assert(value == parse_gerber_value(string,format,zero_suppression))
assert(string == write_gerber_value(value,format,zero_suppression))
test_cases = [((2, 7), '1', 0.0000001), ((2, 6), '1', 0.000001),
((2, 5), '1', 0.00001), ((2, 4), '1', 0.0001),
((2, 3), '1', 0.001), ((2, 2), '1', 0.01),
((2, 1), '1', 0.1), ((2, 7), '-1', -0.0000001),
((2, 6), '-1', -0.000001), ((2, 5), '-1', -0.00001),
((2, 4), '-1', -0.0001), ((2, 3), '-1', -0.001),
((2, 2), '-1', -0.01), ((2, 1), '-1', -0.1), ]
for fmt, string, value in test_cases:
assert(value == parse_gerber_value(string, fmt, zero_suppression))
assert(string == write_gerber_value(value, fmt, zero_suppression))
zero_suppression = 'trailing'
test_cases = [((6, 5), '1' , 100000.0), ((5, 5), '1', 10000.0),
((4, 5), '1', 1000.0), ((3, 5), '1', 100.0),((2, 5), '1', 10.0),
((1, 5), '1', 1.0), ((6, 5), '-1' , -100000.0),
((5, 5), '-1', -10000.0), ((4, 5), '-1', -1000.0),
((3, 5), '-1', -100.0),((2, 5), '-1', -10.0), ((1, 5), '-1', -1.0),]
for format, string, value in test_cases:
assert(value == parse_gerber_value(string,format,zero_suppression))
assert(string == write_gerber_value(value,format,zero_suppression))
test_cases = [((6, 5), '1', 100000.0), ((5, 5), '1', 10000.0),
((4, 5), '1', 1000.0), ((3, 5), '1', 100.0),
((2, 5), '1', 10.0), ((1, 5), '1', 1.0),
((6, 5), '-1', -100000.0), ((5, 5), '-1', -10000.0),
((4, 5), '-1', -1000.0), ((3, 5), '-1', -100.0),
((2, 5), '-1', -10.0), ((1, 5), '-1', -1.0), ]
for fmt, string, value in test_cases:
assert(value == parse_gerber_value(string, fmt, zero_suppression))
assert(string == write_gerber_value(value, fmt, zero_suppression))
def test_decimal_truncation():
""" Test decimal string truncates value to the correct precision
""" Test decimal_string truncates value to the correct precision
"""
value = 1.123456789
for x in range(10):
result = decimal_string(value, precision=x)
calculated = '1.' + ''.join(str(y) for y in range(1,x+1))
assert(result == calculated)
assert(result == calculated)
def test_decimal_padding():
""" Test decimal_string padding
"""
value = 1.123
assert_equal(decimal_string(value, precision=3, padding=True), '1.123')
assert_equal(decimal_string(value, precision=4, padding=True), '1.1230')
assert_equal(decimal_string(value, precision=5, padding=True), '1.12300')
assert_equal(decimal_string(value, precision=6, padding=True), '1.123000')

View file

@ -113,7 +113,7 @@ def write_gerber_value(value, format=(2, 5), zero_suppression='trailing'):
# Edge case...
if value == 0:
return '00'
# negative sign affects padding, so deal with it at the end...
negative = value < 0.0
if negative: