summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHamilton Kibbe <hamilton.kibbe@gmail.com>2015-02-15 02:20:02 -0500
committerHamilton Kibbe <hamilton.kibbe@gmail.com>2015-02-15 02:20:02 -0500
commit5cf1fa74b42eb8feaab23078bef6f31f6d647c33 (patch)
tree5758024d03d41fb9c8010314bbdb4f8f597db333
parent5e23d07bcb5103b4607c6ad591a2a547c97ee1f6 (diff)
downloadgerbonara-5cf1fa74b42eb8feaab23078bef6f31f6d647c33.tar.gz
gerbonara-5cf1fa74b42eb8feaab23078bef6f31f6d647c33.tar.bz2
gerbonara-5cf1fa74b42eb8feaab23078bef6f31f6d647c33.zip
Tests and bugfixes
-rw-r--r--gerber/cam.py7
-rwxr-xr-xgerber/excellon.py29
-rw-r--r--gerber/excellon_statements.py61
-rw-r--r--gerber/gerber_statements.py10
-rw-r--r--gerber/primitives.py8
-rw-r--r--gerber/render/svgwrite_backend.py2
-rw-r--r--gerber/tests/test_excellon.py112
-rw-r--r--gerber/tests/test_excellon_statements.py129
-rw-r--r--gerber/tests/test_gerber_statements.py73
-rw-r--r--gerber/tests/test_primitives.py25
10 files changed, 393 insertions, 63 deletions
diff --git a/gerber/cam.py b/gerber/cam.py
index f49f5dd..caca517 100644
--- a/gerber/cam.py
+++ b/gerber/cam.py
@@ -21,7 +21,6 @@ CAM File
This module provides common base classes for Excellon/Gerber CNC files
"""
-from operator import mul
class FileSettings(object):
""" CAM File Settings
@@ -62,14 +61,14 @@ class FileSettings(object):
if units not in ['inch', 'metric']:
raise ValueError('Units must be either inch or metric')
self.units = units
-
+
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 \
diff --git a/gerber/excellon.py b/gerber/excellon.py
index 79a6e1f..87eaf03 100755
--- a/gerber/excellon.py
+++ b/gerber/excellon.py
@@ -95,11 +95,27 @@ class ExcellonFile(CamFile):
ymax = max(y + radius, ymax)
return ((xmin, xmax), (ymin, ymax))
- def report(self):
- """ Print drill report
+ def report(self, filename=None):
+ """ Print or save drill report
"""
- pass
-
+ toolfmt = ' T%%02d %%%d.%df %%d\n' % self.settings.format
+ rprt = 'Excellon Drill Report\n\n'
+ if self.filename is not None:
+ rprt += 'NC Drill File: %s\n\n' % self.filename
+ rprt += 'Drill File Info:\n\n'
+ rprt += (' Data Mode %s\n' % 'Absolute'
+ if self.settings.notation == 'absolute' else 'Incremental')
+ rprt += (' Units %s\n' % 'Inches'
+ if self.settings.units == 'inch' else 'Millimeters')
+ rprt += '\nTool List:\n\n'
+ rprt += ' Code Size Hits\n'
+ rprt += ' --------------------------\n'
+ for tool in self.tools.itervalues():
+ rprt += toolfmt % (tool.number, tool.diameter, tool.hit_count)
+ if filename is not None:
+ with open(filename, 'w') as f:
+ f.write(rprt)
+ return rprt
def write(self, filename):
with open(filename, 'w') as f:
@@ -195,7 +211,7 @@ class ExcellonParser(object):
self.state = 'DRILL'
elif line[:3] == 'M30':
- stmt = EndOfProgramStmt.from_excellon(line)
+ stmt = EndOfProgramStmt.from_excellon(line, self._settings())
self.statements.append(stmt)
elif line[:3] == 'G00':
@@ -230,8 +246,9 @@ class ExcellonParser(object):
stmt = FormatStmt.from_excellon(line)
self.statements.append(stmt)
- elif line[:4] == 'G90':
+ elif line[:3] == 'G90':
self.statements.append(AbsoluteModeStmt())
+ self.notation = 'absolute'
elif line[0] == 'T' and self.state == 'HEADER':
tool = ExcellonTool.from_excellon(line, self._settings())
diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py
index 71009d8..a56c4a5 100644
--- a/gerber/excellon_statements.py
+++ b/gerber/excellon_statements.py
@@ -29,7 +29,7 @@ __all__ = ['ExcellonTool', 'ToolSelectionStmt', 'CoordinateStmt',
'RewindStopStmt', 'EndOfProgramStmt', 'UnitStmt',
'IncrementalModeStmt', 'VersionStmt', 'FormatStmt', 'LinkToolStmt',
'MeasuringModeStmt', 'RouteModeStmt', 'DrillModeStmt', 'AbsoluteModeStmt',
- 'RepeatHoleStmt', 'UnknownStmt',
+ 'RepeatHoleStmt', 'UnknownStmt', 'ExcellonStatement'
]
@@ -38,10 +38,10 @@ class ExcellonStatement(object):
"""
@classmethod
def from_excellon(cls, line):
- pass
+ raise NotImplementedError('`from_excellon` must be implemented in a subclass')
def to_excellon(self, settings=None):
- pass
+ raise NotImplementedError('`to_excellon` must be implemented in a subclass')
class ExcellonTool(ExcellonStatement):
@@ -144,7 +144,7 @@ class ExcellonTool(ExcellonStatement):
tool : ExcellonTool
An ExcellonTool initialized with the parameters in tool_dict.
"""
- return cls(settings, tool_dict)
+ return cls(settings, **tool_dict)
def __init__(self, settings, **kwargs):
self.settings = settings
@@ -159,7 +159,7 @@ class ExcellonTool(ExcellonStatement):
def to_excellon(self, settings=None):
fmt = self.settings.format
- zs = self.settings.format
+ zs = self.settings.zero_suppression
stmt = 'T%02d' % self.number
if self.retract_rate is not None:
stmt += 'B%s' % write_gerber_value(self.retract_rate, fmt, zs)
@@ -171,7 +171,7 @@ class ExcellonTool(ExcellonStatement):
if self.rpm < 100000.:
stmt += 'S%s' % write_gerber_value(self.rpm / 1000., fmt, zs)
else:
- stmt += 'S%g' % self.rpm / 1000.
+ stmt += 'S%g' % (self.rpm / 1000.)
if self.diameter is not None:
stmt += 'C%s' % decimal_string(self.diameter, fmt[1], True)
if self.depth_offset is not None:
@@ -191,7 +191,8 @@ class ExcellonTool(ExcellonStatement):
def __repr__(self):
unit = 'in.' if self.settings.units == 'inch' else 'mm'
- return '<ExcellonTool %02d: %0.3f%s dia.>' % (self.number, self.diameter, unit)
+ fmtstr = '<ExcellonTool %%02d: %%%d.%dg%%s dia.>' % self.settings.format
+ return fmtstr % (self.number, self.diameter, unit)
class ToolSelectionStmt(ExcellonStatement):
@@ -273,9 +274,9 @@ class CoordinateStmt(ExcellonStatement):
def __str__(self):
coord_str = ''
if self.x is not None:
- coord_str += 'X: %f ' % self.x
+ coord_str += 'X: %g ' % self.x
if self.y is not None:
- coord_str += 'Y: %f ' % self.y
+ coord_str += 'Y: %g ' % self.y
return '<Coordinate Statement: %s>' % coord_str
@@ -284,16 +285,32 @@ class RepeatHoleStmt(ExcellonStatement):
@classmethod
def from_excellon(cls, line, settings):
- return cls(line)
-
- def __init__(self, line):
- self.line = line
+ match = re.compile(r'R(?P<rcount>[0-9]*)X?(?P<xdelta>\d*\.?\d*)?Y?(?P<ydelta>\d*\.?\d*)?').match(line)
+ stmt = match.groupdict()
+ count = int(stmt['rcount'])
+ xdelta = (parse_gerber_value(stmt['xdelta'], settings.format,
+ settings.zero_suppression)
+ if stmt['xdelta'] is not '' else None)
+ ydelta = (parse_gerber_value(stmt['ydelta'], settings.format,
+ settings.zero_suppression)
+ if stmt['ydelta'] is not '' else None)
+ return cls(count, xdelta, ydelta)
+
+ def __init__(self, count, xdelta=None, ydelta=None):
+ self.count = count
+ self.xdelta = xdelta
+ self.ydelta = ydelta
def to_excellon(self, settings):
- return self.line
+ stmt = 'R%d' % self.count
+ if self.xdelta is not None:
+ stmt += 'X%s' % write_gerber_value(self.xdelta, settings.format, settings.zero_suppression)
+ if self.ydelta is not None:
+ stmt += 'Y%s' % write_gerber_value(self.ydelta, settings.format, settings.zero_suppression)
+ return stmt
def __str__(self):
- return '<Repeat Hole: %s>' % self.line
+ return '<Repeat Hole: %d times>' % self.count
class CommentStmt(ExcellonStatement):
@@ -339,8 +356,16 @@ class RewindStopStmt(ExcellonStatement):
class EndOfProgramStmt(ExcellonStatement):
@classmethod
- def from_excellon(cls, line):
- return cls()
+ def from_excellon(cls, line, settings):
+ match = re.compile(r'M30X?(?P<x>\d*\.?\d*)?Y?(?P<y>\d*\.?\d*)?').match(line)
+ stmt = match.groupdict()
+ x = (parse_gerber_value(stmt['x'], settings.format,
+ settings.zero_suppression)
+ if stmt['x'] is not '' else None)
+ y = (parse_gerber_value(stmt['y'], settings.format,
+ settings.zero_suppression)
+ if stmt['y'] is not '' else None)
+ return cls(x, y)
def __init__(self, x=None, y=None):
self.x = x
@@ -495,7 +520,7 @@ class UnknownStmt(ExcellonStatement):
return self.stmt
def __str__(self):
- return "<UnknownStmt: %s >" % self.stmt
+ return "<Unknown Statement: %s>" % self.stmt
def pairwise(iterator):
diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py
index 1401345..a6feef6 100644
--- a/gerber/gerber_statements.py
+++ b/gerber/gerber_statements.py
@@ -802,13 +802,13 @@ class CoordStmt(Statement):
if self.function:
coord_str += 'Fn: %s ' % self.function
if self.x is not None:
- coord_str += 'X: %f ' % self.x
+ coord_str += 'X: %g ' % self.x
if self.y is not None:
- coord_str += 'Y: %f ' % self.y
+ coord_str += 'Y: %g ' % self.y
if self.i is not None:
- coord_str += 'I: %f ' % self.i
+ coord_str += 'I: %g ' % self.i
if self.j is not None:
- coord_str += 'J: %f ' % self.j
+ coord_str += 'J: %g ' % self.j
if self.op:
if self.op == 'D01':
op = 'Lights On'
@@ -829,7 +829,7 @@ class ApertureStmt(Statement):
def __init__(self, d, deprecated=None):
Statement.__init__(self, "APERTURE")
self.d = int(d)
- self.deprecated = True if deprecated is not None else False
+ self.deprecated = True if deprecated is not None and deprecated is not False else False
def to_gerber(self, settings=None):
if self.deprecated:
diff --git a/gerber/primitives.py b/gerber/primitives.py
index 1663a53..ffdbea7 100644
--- a/gerber/primitives.py
+++ b/gerber/primitives.py
@@ -200,10 +200,10 @@ class Arc(Primitive):
if theta1 <= math.pi * 1.5 and (theta0 >= math.pi * 1.5 or theta0 < theta1):
points.append((self.center[0], self.center[1] - self.radius ))
x, y = zip(*points)
- min_x = min(x)
- max_x = max(x)
- min_y = min(y)
- max_y = max(y)
+ min_x = min(x) - self.aperture.radius
+ max_x = max(x) + self.aperture.radius
+ min_y = min(y) - self.aperture.radius
+ max_y = max(y) + self.aperture.radius
return ((min_x, max_x), (min_y, max_y))
diff --git a/gerber/render/svgwrite_backend.py b/gerber/render/svgwrite_backend.py
index 9e6a5e4..ae7c377 100644
--- a/gerber/render/svgwrite_backend.py
+++ b/gerber/render/svgwrite_backend.py
@@ -89,7 +89,7 @@ class GerberSvgContext(GerberContext):
direction = '-' if arc.direction == 'clockwise' else '+'
arc_path.push_arc(end, 0, radius, large_arc, direction, True)
self.dwg.add(arc_path)
-
+
def _render_region(self, region, color):
points = [tuple(map(mul, point, self.scale)) for point in region.points]
region_path = self.dwg.path(d='M %f, %f' % points[0],
diff --git a/gerber/tests/test_excellon.py b/gerber/tests/test_excellon.py
index 70e4560..de45b44 100644
--- a/gerber/tests/test_excellon.py
+++ b/gerber/tests/test_excellon.py
@@ -2,7 +2,8 @@
# -*- coding: utf-8 -*-
# Author: Hamilton Kibbe <ham@hamiltonkib.be>
-from ..excellon import read, detect_excellon_format, ExcellonFile
+from ..cam import FileSettings
+from ..excellon import read, detect_excellon_format, ExcellonFile, ExcellonParser
from tests import *
import os
@@ -25,3 +26,112 @@ def test_read_settings():
ncdrill = read(NCDRILL_FILE)
assert_equal(ncdrill.settings['format'], (2, 4))
assert_equal(ncdrill.settings['zeros'], 'trailing')
+
+def test_bounds():
+ ncdrill = read(NCDRILL_FILE)
+ xbound, ybound = ncdrill.bounds
+ assert_array_almost_equal(xbound, (0.1300, 2.1430))
+ assert_array_almost_equal(ybound, (0.3946, 1.7164))
+
+def test_report():
+ ncdrill = read(NCDRILL_FILE)
+
+def test_parser_hole_count():
+ settings = FileSettings(**detect_excellon_format(NCDRILL_FILE))
+ p = ExcellonParser(settings)
+ p.parse(NCDRILL_FILE)
+ assert_equal(p.hole_count, 36)
+
+def test_parser_hole_sizes():
+ settings = FileSettings(**detect_excellon_format(NCDRILL_FILE))
+ p = ExcellonParser(settings)
+ p.parse(NCDRILL_FILE)
+ assert_equal(p.hole_sizes, [0.0236, 0.0354, 0.04, 0.126, 0.128])
+
+def test_parse_whitespace():
+ p = ExcellonParser(FileSettings())
+ assert_equal(p._parse(' '), None)
+
+def test_parse_comment():
+ p = ExcellonParser(FileSettings())
+ p._parse(';A comment')
+ assert_equal(p.statements[0].comment, 'A comment')
+
+def test_parse_format_comment():
+ p = ExcellonParser(FileSettings())
+ p._parse('; FILE_FORMAT=9:9 ')
+ assert_equal(p.format, (9, 9))
+
+def test_parse_header():
+ p = ExcellonParser(FileSettings())
+ p._parse('M48 ')
+ assert_equal(p.state, 'HEADER')
+ p._parse('M95 ')
+ assert_equal(p.state, 'DRILL')
+
+def test_parse_rout():
+ p = ExcellonParser(FileSettings())
+ p._parse('G00 ')
+ assert_equal(p.state, 'ROUT')
+ p._parse('G05 ')
+ assert_equal(p.state, 'DRILL')
+
+def test_parse_version():
+ p = ExcellonParser(FileSettings())
+ p._parse('VER,1 ')
+ assert_equal(p.statements[0].version, 1)
+ p._parse('VER,2 ')
+ assert_equal(p.statements[1].version, 2)
+
+def test_parse_format():
+ p = ExcellonParser(FileSettings())
+ p._parse('FMAT,1 ')
+ assert_equal(p.statements[0].format, 1)
+ p._parse('FMAT,2 ')
+ assert_equal(p.statements[1].format, 2)
+
+def test_parse_units():
+ settings = FileSettings(units='inch', zeros='trailing')
+ p = ExcellonParser(settings)
+ p._parse(';METRIC,LZ')
+ assert_equal(p.units, 'inch')
+ assert_equal(p.zeros, 'trailing')
+ p._parse('METRIC,LZ')
+ assert_equal(p.units, 'metric')
+ assert_equal(p.zeros, 'leading')
+
+def test_parse_incremental_mode():
+ settings = FileSettings(units='inch', zeros='trailing')
+ p = ExcellonParser(settings)
+ assert_equal(p.notation, 'absolute')
+ p._parse('ICI,ON ')
+ assert_equal(p.notation, 'incremental')
+ p._parse('ICI,OFF ')
+ assert_equal(p.notation, 'absolute')
+
+def test_parse_absolute_mode():
+ settings = FileSettings(units='inch', zeros='trailing')
+ p = ExcellonParser(settings)
+ assert_equal(p.notation, 'absolute')
+ p._parse('ICI,ON ')
+ assert_equal(p.notation, 'incremental')
+ p._parse('G90 ')
+ assert_equal(p.notation, 'absolute')
+
+def test_parse_repeat_hole():
+ p = ExcellonParser(FileSettings())
+ p._parse('R03X1.5Y1.5')
+ assert_equal(p.statements[0].count, 3)
+
+def test_parse_incremental_position():
+ p = ExcellonParser(FileSettings(notation='incremental'))
+ p._parse('X01Y01')
+ p._parse('X01Y01')
+ assert_equal(p.pos, [2.,2.])
+
+def test_parse_unknown():
+ p = ExcellonParser(FileSettings())
+ p._parse('Not A Valid Statement')
+ assert_equal(p.statements[0].stmt, 'Not A Valid Statement')
+
+
diff --git a/gerber/tests/test_excellon_statements.py b/gerber/tests/test_excellon_statements.py
index 2e508ff..35bd045 100644
--- a/gerber/tests/test_excellon_statements.py
+++ b/gerber/tests/test_excellon_statements.py
@@ -7,17 +7,36 @@ from .tests import assert_equal, assert_raises
from ..excellon_statements import *
from ..cam import FileSettings
+def test_excellon_statement_implementation():
+ stmt = ExcellonStatement()
+ assert_raises(NotImplementedError, stmt.from_excellon, None)
+ assert_raises(NotImplementedError, stmt.to_excellon)
def test_excellontool_factory():
- """ Test ExcellonTool factory method
+ """ Test ExcellonTool factory methods
"""
- exc_line = 'T8F00S00C0.12500'
+ exc_line = 'T8F01B02S00003H04Z05C0.12500'
settings = FileSettings(format=(2, 5), zero_suppression='trailing',
units='inch', notation='absolute')
tool = ExcellonTool.from_excellon(exc_line, settings)
+ assert_equal(tool.number, 8)
assert_equal(tool.diameter, 0.125)
- assert_equal(tool.feed_rate, 0)
- assert_equal(tool.rpm, 0)
+ assert_equal(tool.feed_rate, 1)
+ assert_equal(tool.retract_rate,2)
+ assert_equal(tool.rpm, 3)
+ assert_equal(tool.max_hit_count, 4)
+ assert_equal(tool.depth_offset, 5)
+
+ stmt = {'number': 8, 'feed_rate': 1, 'retract_rate': 2, 'rpm': 3,
+ 'diameter': 0.125, 'max_hit_count': 4, 'depth_offset': 5}
+ tool = ExcellonTool.from_dict(settings, stmt)
+ assert_equal(tool.number, 8)
+ assert_equal(tool.diameter, 0.125)
+ assert_equal(tool.feed_rate, 1)
+ assert_equal(tool.retract_rate,2)
+ assert_equal(tool.rpm, 3)
+ assert_equal(tool.max_hit_count, 4)
+ assert_equal(tool.depth_offset, 5)
def test_excellontool_dump():
@@ -25,7 +44,8 @@ def test_excellontool_dump():
"""
exc_lines = ['T01F0S0C0.01200', 'T02F0S0C0.01500', 'T03F0S0C0.01968',
'T04F0S0C0.02800', 'T05F0S0C0.03300', 'T06F0S0C0.03800',
- 'T07F0S0C0.04300', 'T08F0S0C0.12500', 'T09F0S0C0.13000', ]
+ 'T07F0S0C0.04300', 'T08F0S0C0.12500', 'T09F0S0C0.13000',
+ 'T08B01F02H03S00003C0.12500Z04', 'T01F0S300.999C0.01200']
settings = FileSettings(format=(2, 5), zero_suppression='trailing',
units='inch', notation='absolute')
for line in exc_lines:
@@ -44,6 +64,19 @@ def test_excellontool_order():
assert_equal(tool1.feed_rate, tool2.feed_rate)
assert_equal(tool1.rpm, tool2.rpm)
+def test_excellontool_conversion():
+ tool = ExcellonTool.from_dict(FileSettings(), {'number': 8, 'diameter': 25.4})
+ tool.to_inch()
+ assert_equal(tool.diameter, 1.)
+ tool = ExcellonTool.from_dict(FileSettings(), {'number': 8, 'diameter': 1})
+ tool.to_metric()
+ assert_equal(tool.diameter, 25.4)
+
+def test_excellontool_repr():
+ tool = ExcellonTool.from_dict(FileSettings(), {'number': 8, 'diameter': 0.125})
+ assert_equal(str(tool), '<ExcellonTool 08: 0.125in. dia.>')
+ tool = ExcellonTool.from_dict(FileSettings(units='metric'), {'number': 8, 'diameter': 0.125})
+ assert_equal(str(tool), '<ExcellonTool 08: 0.125mm dia.>')
def test_toolselection_factory():
""" Test ToolSelectionStmt factory method
@@ -93,22 +126,49 @@ def test_coordinatestmt_factory():
assert_equal(stmt.y, 0.4639)
assert_equal(stmt.to_excellon(settings), "X9660Y4639")
-
-
def test_coordinatestmt_dump():
""" Test CoordinateStmt to_excellon()
"""
lines = ['X278207Y65293', 'X243795', 'Y82528', 'Y86028',
'X251295Y81528', 'X2525Y78', 'X255Y575', 'Y52',
'X2675', 'Y575', 'X2425', 'Y52', 'X23', ]
-
settings = FileSettings(format=(2, 4), zero_suppression='leading',
units='inch', notation='absolute')
-
for line in lines:
stmt = CoordinateStmt.from_excellon(line, settings)
assert_equal(stmt.to_excellon(settings), line)
+def test_coordinatestmt_conversion():
+ stmt = CoordinateStmt.from_excellon('X254Y254', FileSettings())
+ stmt.to_inch()
+ assert_equal(stmt.x, 1.)
+ assert_equal(stmt.y, 1.)
+ stmt = CoordinateStmt.from_excellon('X01Y01', FileSettings())
+ stmt.to_metric()
+ assert_equal(stmt.x, 25.4)
+ assert_equal(stmt.y, 25.4)
+
+def test_coordinatestmt_string():
+ settings = FileSettings(format=(2, 4), zero_suppression='leading',
+ units='inch', notation='absolute')
+ stmt = CoordinateStmt.from_excellon('X9660Y4639', settings)
+ assert_equal(str(stmt), '<Coordinate Statement: X: 0.966 Y: 0.4639 >')
+
+
+def test_repeathole_stmt_factory():
+ stmt = RepeatHoleStmt.from_excellon('R0004X015Y32', FileSettings(zeros='leading'))
+ assert_equal(stmt.count, 4)
+ assert_equal(stmt.xdelta, 1.5)
+ assert_equal(stmt.ydelta, 32)
+
+def test_repeatholestmt_dump():
+ line = 'R4X015Y32'
+ stmt = RepeatHoleStmt.from_excellon(line, FileSettings())
+ assert_equal(stmt.to_excellon(FileSettings()), line)
+
+def test_repeathole_str():
+ stmt = RepeatHoleStmt.from_excellon('R4X015Y32', FileSettings())
+ assert_equal(str(stmt), '<Repeat Hole: 4 times>')
def test_commentstmt_factory():
""" Test CommentStmt factory method
@@ -134,6 +194,35 @@ def test_commentstmt_dump():
stmt = CommentStmt.from_excellon(line)
assert_equal(stmt.to_excellon(), line)
+def test_header_begin_stmt():
+ stmt = HeaderBeginStmt()
+ assert_equal(stmt.to_excellon(None), 'M48')
+
+def test_header_end_stmt():
+ stmt = HeaderEndStmt()
+ assert_equal(stmt.to_excellon(None), 'M95')
+
+def test_rewindstop_stmt():
+ stmt = RewindStopStmt()
+ assert_equal(stmt.to_excellon(None), '%')
+
+def test_endofprogramstmt_factory():
+ stmt = EndOfProgramStmt.from_excellon('M30X01Y02', FileSettings())
+ assert_equal(stmt.x, 1.)
+ assert_equal(stmt.y, 2.)
+ stmt = EndOfProgramStmt.from_excellon('M30X01', FileSettings())
+ assert_equal(stmt.x, 1.)
+ assert_equal(stmt.y, None)
+ stmt = EndOfProgramStmt.from_excellon('M30Y02', FileSettings())
+ assert_equal(stmt.x, None)
+ assert_equal(stmt.y, 2.)
+
+def test_endofprogramStmt_dump():
+ lines = ['M30X01Y02',]
+ for line in lines:
+ stmt = EndOfProgramStmt.from_excellon(line, FileSettings())
+ assert_equal(stmt.to_excellon(FileSettings()), line)
+
def test_unitstmt_factory():
""" Test UnitStmt factory method
@@ -295,3 +384,25 @@ def test_measmodestmt_validation():
"""
assert_raises(ValueError, MeasuringModeStmt.from_excellon, 'M70')
assert_raises(ValueError, MeasuringModeStmt, 'millimeters')
+
+
+def test_routemode_stmt():
+ stmt = RouteModeStmt()
+ assert_equal(stmt.to_excellon(FileSettings()), 'G00')
+
+def test_drillmode_stmt():
+ stmt = DrillModeStmt()
+ assert_equal(stmt.to_excellon(FileSettings()), 'G05')
+
+def test_absolutemode_stmt():
+ stmt = AbsoluteModeStmt()
+ assert_equal(stmt.to_excellon(FileSettings()), 'G90')
+
+def test_unknownstmt():
+ stmt = UnknownStmt('TEST')
+ assert_equal(stmt.stmt, 'TEST')
+ assert_equal(str(stmt), '<Unknown Statement: TEST>')
+
+def test_unknownstmt_dump():
+ stmt = UnknownStmt('TEST')
+ assert_equal(stmt.to_excellon(FileSettings()), 'TEST')
diff --git a/gerber/tests/test_gerber_statements.py b/gerber/tests/test_gerber_statements.py
index 0875b57..c6040c0 100644
--- a/gerber/tests/test_gerber_statements.py
+++ b/gerber/tests/test_gerber_statements.py
@@ -394,6 +394,10 @@ def test_comment_stmt_dump():
stmt = CommentStmt('A comment')
assert_equal(stmt.to_gerber(), 'G04A comment*')
+def test_comment_stmt_string():
+ stmt = CommentStmt('A comment')
+ assert_equal(str(stmt), '<Comment: A comment>')
+
def test_eofstmt():
""" Test EofStmt
"""
@@ -406,6 +410,9 @@ def test_eofstmt_dump():
stmt = EofStmt()
assert_equal(stmt.to_gerber(), 'M02*')
+def test_eofstmt_string():
+ assert_equal(str(EofStmt()), '<EOF Statement>')
+
def test_quadmodestmt_factory():
""" Test QuadrantModeStmt.from_gerber()
"""
@@ -572,8 +579,6 @@ def test_MIParamStmt_string():
mi = MIParamStmt.from_dict(stmt)
assert_equal(str(mi), '<Image Mirror: A=1 B=0>')
-
-
def test_coordstmt_ctor():
cs = CoordStmt('G04', 0.0, 0.1, 0.2, 0.3, 'D01', FileSettings())
assert_equal(cs.function, 'G04')
@@ -583,5 +588,67 @@ def test_coordstmt_ctor():
assert_equal(cs.j, 0.3)
assert_equal(cs.op, 'D01')
+def test_coordstmt_factory():
+ stmt = {'function': 'G04', 'x': '0', 'y': '001', 'i': '002', 'j': '003', 'op': 'D01'}
+ cs = CoordStmt.from_dict(stmt, FileSettings())
+ assert_equal(cs.function, 'G04')
+ assert_equal(cs.x, 0.0)
+ assert_equal(cs.y, 0.1)
+ assert_equal(cs.i, 0.2)
+ assert_equal(cs.j, 0.3)
+ assert_equal(cs.op, 'D01')
-
+def test_coordstmt_dump():
+ cs = CoordStmt('G04', 0.0, 0.1, 0.2, 0.3, 'D01', FileSettings())
+ assert_equal(cs.to_gerber(FileSettings()), 'G04X0Y001I002J003D01*')
+
+def test_coordstmt_conversion():
+ cs = CoordStmt('G71', 25.4, 25.4, 25.4, 25.4, 'D01', FileSettings())
+ cs.to_inch()
+ assert_equal(cs.x, 1.)
+ assert_equal(cs.y, 1.)
+ assert_equal(cs.i, 1.)
+ assert_equal(cs.j, 1.)
+ assert_equal(cs.function, 'G70')
+
+ cs = CoordStmt('G70', 1., 1., 1., 1., 'D01', FileSettings())
+ cs.to_metric()
+ assert_equal(cs.x, 25.4)
+ assert_equal(cs.y, 25.4)
+ assert_equal(cs.i, 25.4)
+ assert_equal(cs.j, 25.4)
+ assert_equal(cs.function, 'G71')
+
+def test_coordstmt_string():
+ cs = CoordStmt('G04', 0, 1, 2, 3, 'D01', FileSettings())
+ assert_equal(str(cs), '<Coordinate Statement: Fn: G04 X: 0 Y: 1 I: 2 J: 3 Op: Lights On>')
+ cs = CoordStmt('G04', None, None, None, None, 'D02', FileSettings())
+ assert_equal(str(cs), '<Coordinate Statement: Fn: G04 Op: Lights Off>')
+ cs = CoordStmt('G04', None, None, None, None, 'D03', FileSettings())
+ assert_equal(str(cs), '<Coordinate Statement: Fn: G04 Op: Flash>')
+ cs = CoordStmt('G04', None, None, None, None, 'TEST', FileSettings())
+ assert_equal(str(cs), '<Coordinate Statement: Fn: G04 Op: TEST>')
+
+def test_aperturestmt_ctor():
+ ast = ApertureStmt(3, False)
+ assert_equal(ast.d, 3)
+ assert_equal(ast.deprecated, False)
+ ast = ApertureStmt(4, True)
+ assert_equal(ast.d, 4)
+ assert_equal(ast.deprecated, True)
+ ast = ApertureStmt(4, 1)
+ assert_equal(ast.d, 4)
+ assert_equal(ast.deprecated, True)
+ ast = ApertureStmt(3)
+ assert_equal(ast.d, 3)
+ assert_equal(ast.deprecated, False)
+
+def test_aperturestmt_dump():
+ ast = ApertureStmt(3, False)
+ assert_equal(ast.to_gerber(), 'D3*')
+ ast = ApertureStmt(3, True)
+ assert_equal(ast.to_gerber(), 'G54D3*')
+ assert_equal(str(ast), '<Aperture: 3>')
+
+
+ \ No newline at end of file
diff --git a/gerber/tests/test_primitives.py b/gerber/tests/test_primitives.py
index 877823d..f8b8620 100644
--- a/gerber/tests/test_primitives.py
+++ b/gerber/tests/test_primitives.py
@@ -73,20 +73,21 @@ def test_arc_sweep_angle():
((1, 0), (-1, 0), (0, 0), 'counterclockwise', math.radians(180)),]
for start, end, center, direction, sweep in cases:
- a = Arc(start, end, center, direction, 0)
+ c = Circle((0,0), 1)
+ a = Arc(start, end, center, direction, c)
assert_equal(a.sweep_angle, sweep)
-# Need to update bounds calculation using aperture
-#def test_arc_bounds():
-# """ Test Arc primitive bounding box calculation
-# """
-# cases = [((1, 0), (0, 1), (0, 0), 'clockwise', ((-1, 1), (-1, 1))),
-# ((1, 0), (0, 1), (0, 0), 'counterclockwise', ((0, 1), (0, 1))),
-# #TODO: ADD MORE TEST CASES HERE
-# ]
-# for start, end, center, direction, bounds in cases:
-# a = Arc(start, end, center, direction, 0)
-# assert_equal(a.bounding_box, bounds)
+def test_arc_bounds():
+ """ Test Arc primitive bounding box calculation
+ """
+ cases = [((1, 0), (0, 1), (0, 0), 'clockwise', ((-1.5, 1.5), (-1.5, 1.5))),
+ ((1, 0), (0, 1), (0, 0), 'counterclockwise', ((-0.5, 1.5), (-0.5, 1.5))),
+ #TODO: ADD MORE TEST CASES HERE
+ ]
+ for start, end, center, direction, bounds in cases:
+ c = Circle((0,0), 1)
+ a = Arc(start, end, center, direction, c)
+ assert_equal(a.bounding_box, bounds)
def test_circle_radius():