diff options
-rw-r--r-- | examples/cairo_example.py | 3 | ||||
-rw-r--r-- | gerber/__init__.py | 2 | ||||
-rw-r--r-- | gerber/common.py | 36 | ||||
-rwxr-xr-x | gerber/excellon.py | 50 | ||||
-rw-r--r-- | gerber/render/cairo_backend.py | 6 | ||||
-rw-r--r-- | gerber/render/render.py | 1 | ||||
-rw-r--r-- | gerber/rs274x.py | 24 | ||||
-rw-r--r-- | gerber/tests/test_common.py | 13 | ||||
-rw-r--r-- | gerber/tests/test_excellon.py | 38 | ||||
-rw-r--r-- | gerber/utils.py | 20 |
10 files changed, 140 insertions, 53 deletions
diff --git a/examples/cairo_example.py b/examples/cairo_example.py index 49c4d0c..a312e89 100644 --- a/examples/cairo_example.py +++ b/examples/cairo_example.py @@ -30,9 +30,6 @@ from gerber.render import GerberCairoContext GERBER_FOLDER = os.path.abspath(os.path.join(os.path.dirname(__file__), 'gerbers')) - - - # Open the gerber files copper = read(os.path.join(GERBER_FOLDER, 'copper.GTL')) mask = read(os.path.join(GERBER_FOLDER, 'soldermask.GTS')) diff --git a/gerber/__init__.py b/gerber/__init__.py index 1a11159..b5a9014 100644 --- a/gerber/__init__.py +++ b/gerber/__init__.py @@ -23,4 +23,4 @@ gerber-tools provides utilities for working with Gerber (RS-274X) and Excellon files in python. """ -from .common import read
\ No newline at end of file +from .common import read, loads
\ No newline at end of file diff --git a/gerber/common.py b/gerber/common.py index 78da2cd..50ba728 100644 --- a/gerber/common.py +++ b/gerber/common.py @@ -15,6 +15,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from . import rs274x +from . import excellon +from .utils import detect_file_format + def read(filename): """ Read a gerber or excellon file and return a representative object. @@ -30,10 +34,9 @@ def read(filename): CncFile object representing the file, either GerberFile or ExcellonFile. Returns None if file is not an Excellon or Gerber file. """ - from . import rs274x - from . import excellon - from .utils import detect_file_format - fmt = detect_file_format(filename) + with open(filename, 'r') as f: + data = f.read() + fmt = detect_file_format(data) if fmt == 'rs274x': return rs274x.read(filename) elif fmt == 'excellon': @@ -41,3 +44,28 @@ def read(filename): else: raise TypeError('Unable to detect file format') +def loads(data): + """ Read gerber or excellon file contents from a string and return a + representative object. + + Parameters + ---------- + data : string + gerber or excellon file contents as a string. + + Returns + ------- + file : CncFile subclass + CncFile object representing the file, either GerberFile or + ExcellonFile. Returns None if file is not an Excellon or Gerber file. + """ + + fmt = detect_file_format(data) + if fmt == 'rs274x': + return rs274x.loads(data) + elif fmt == 'excellon': + return excellon.loads(data) + else: + raise TypeError('Unable to detect file format') + + diff --git a/gerber/excellon.py b/gerber/excellon.py index d89b349..ba8573d 100755 --- a/gerber/excellon.py +++ b/gerber/excellon.py @@ -25,6 +25,7 @@ This module provides Excellon file classes and parsing utilities import math
import operator
+from cStringIO import StringIO
from .excellon_statements import *
from .cam import CamFile, FileSettings
@@ -46,9 +47,28 @@ def read(filename): """
# File object should use settings from source file by default.
- settings = FileSettings(**detect_excellon_format(filename))
+ with open(filename, 'r') as f:
+ data = f.read()
+ settings = FileSettings(**detect_excellon_format(data))
return ExcellonParser(settings).parse(filename)
+def loads(data):
+ """ Read data from string and return an ExcellonFile
+ Parameters
+ ----------
+ data : string
+ string containing Excellon file contents
+
+ Returns
+ -------
+ file : :class:`gerber.excellon.ExcellonFile`
+ An ExcellonFile created from the specified file.
+
+ """
+ # File object should use settings from source file by default.
+ settings = FileSettings(**detect_excellon_format(data))
+ return ExcellonParser(settings).parse_raw(data)
+
class DrillHit(object):
def __init__(self, tool, position):
@@ -302,9 +322,12 @@ class ExcellonParser(object): def parse(self, filename):
with open(filename, 'r') as f:
- for line in f:
- self._parse(line.strip())
-
+ data = f.read()
+ return self.parse_raw(data, filename)
+
+ def parse_raw(self, data, filename=None):
+ for line in StringIO(data):
+ self._parse(line.strip())
for stmt in self.statements:
stmt.units = self.units
return ExcellonFile(self.statements, self.tools, self.hits,
@@ -428,14 +451,13 @@ class ExcellonParser(object): zeros=self.zeros, notation=self.notation)
-def detect_excellon_format(filename):
+def detect_excellon_format(data=None, filename=None):
""" Detect excellon file decimal format and zero-suppression settings.
Parameters
----------
- filename : string
- Name of the file to parse. This does not check if the file is actually
- an Excellon file, so do that before calling this.
+ data : string
+ String containing contents of Excellon file.
Returns
-------
@@ -449,10 +471,16 @@ def detect_excellon_format(filename): 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:
+ with open(filename, 'r') as f:
+ data = f.read()
# Check for obvious clues:
p = ExcellonParser()
- p.parse(filename)
+ p.parse_raw(data)
# Get zero_suppression from a unit statement
zero_statements = [stmt.zeros for stmt in p.statements
@@ -485,8 +513,8 @@ def detect_excellon_format(filename): settings = FileSettings(zeros=zeros, format=fmt)
try:
p = ExcellonParser(settings)
- p.parse(filename)
- size = tuple([t[1] - t[0] for t in p.bounds])
+ p.parse_raw(data)
+ size = tuple([t[0] - t[1] for t in p.bounds])
hole_area = 0.0
for hit in p.hits:
tool = hit.tool
diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index a97e552..345f331 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -172,3 +172,9 @@ class GerberCairoContext(GerberContext): else:
self.surface.write_to_png(filename)
+
+ def dump_svg_str(self):
+ self.surface.finish()
+ self.surface_buffer.flush()
+ return self.surface_buffer.read()
+
\ No newline at end of file diff --git a/gerber/render/render.py b/gerber/render/render.py index 124e743..8f49796 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -181,3 +181,4 @@ class GerberContext(object): def _render_test_record(self, primitive, color): pass + diff --git a/gerber/rs274x.py b/gerber/rs274x.py index 1df3646..000f7a1 100644 --- a/gerber/rs274x.py +++ b/gerber/rs274x.py @@ -21,6 +21,7 @@ import copy import json import re +from cStringIO import StringIO from .gerber_statements import * from .primitives import * @@ -43,6 +44,9 @@ def read(filename): return GerberParser().parse(filename) +def loads(data): + return GerberParser().parse_raw(data) + class GerberFile(CamFile): """ A class representing a single gerber file @@ -75,7 +79,6 @@ class GerberFile(CamFile): def __init__(self, statements, settings, primitives, filename=None): super(GerberFile, self).__init__(statements, settings, primitives, filename) - @property def comments(self): return [comment.comment for comment in self.statements @@ -205,12 +208,14 @@ class GerberParser(object): self.quadrant_mode = 'multi-quadrant' self.step_and_repeat = (1, 1, 0, 0) - def parse(self, filename): - fp = open(filename, "r") - data = fp.readlines() + with open(filename, "r") as fp: + data = fp.read() + return self.parse_raw(data, filename=None) - for stmt in self._parse(data): + def parse_raw(self, data, filename=None): + lines = [line for line in StringIO(data)] + for stmt in self._parse(lines): self.evaluate(stmt) self.statements.append(stmt) @@ -225,10 +230,10 @@ class GerberParser(object): return json.dumps(stmts) def dump_str(self): - s = "" + string = "" for stmt in self.statements: - s += str(stmt) + "\n" - return s + string += str(stmt) + "\n" + return string def _parse(self, data): oldline = '' @@ -404,7 +409,6 @@ class GerberParser(object): else: raise Exception("Invalid statement to evaluate") - def _define_aperture(self, d, shape, modifiers): aperture = None if shape == 'C': @@ -490,7 +494,7 @@ class GerberParser(object): self.current_region = [Arc(start, end, center, self.direction, self.apertures[self.aperture], level_polarity=self.level_polarity, units=self.settings.units),] else: self.current_region.append(Arc(start, end, center, self.direction, self.apertures[self.aperture], level_polarity=self.level_polarity, units=self.settings.units)) - + elif self.op == "D02": pass diff --git a/gerber/tests/test_common.py b/gerber/tests/test_common.py index 76e3991..0ba4b68 100644 --- a/gerber/tests/test_common.py +++ b/gerber/tests/test_common.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Author: Hamilton Kibbe <ham@hamiltonkib.be> -from ..common import read +from ..common import read, loads from ..excellon import ExcellonFile from ..rs274x import GerberFile from .tests import * @@ -23,9 +23,20 @@ def test_file_type_detection(): assert_true(isinstance(ncdrill, ExcellonFile)) assert_true(isinstance(top_copper, GerberFile)) + +def test_load_from_string(): + with open(NCDRILL_FILE, 'r') as f: + ncdrill = loads(f.read()) + with open(TOP_COPPER_FILE, 'r') as f: + top_copper = loads(f.read()) + assert_true(isinstance(ncdrill, ExcellonFile)) + assert_true(isinstance(top_copper, GerberFile)) + + def test_file_type_validation(): """ Test file format validation """ assert_raises(TypeError, read, 'LICENSE') + diff --git a/gerber/tests/test_excellon.py b/gerber/tests/test_excellon.py index 006277d..b821649 100644 --- a/gerber/tests/test_excellon.py +++ b/gerber/tests/test_excellon.py @@ -11,41 +11,51 @@ from .tests import * NCDRILL_FILE = os.path.join(os.path.dirname(__file__), - 'resources/ncdrill.DRD') + 'resources/ncdrill.DRD') def test_format_detection(): """ Test file type detection """ - settings = detect_excellon_format(NCDRILL_FILE) + with open(NCDRILL_FILE) as f: + data = f.read() + settings = detect_excellon_format(data) assert_equal(settings['format'], (2, 4)) assert_equal(settings['zeros'], 'trailing') + settings = detect_excellon_format(filename=NCDRILL_FILE) + assert_equal(settings['format'], (2, 4)) + assert_equal(settings['zeros'], 'trailing') + + def test_read(): ncdrill = read(NCDRILL_FILE) assert(isinstance(ncdrill, ExcellonFile)) + def test_write(): ncdrill = read(NCDRILL_FILE) ncdrill.write('test.ncd') with open(NCDRILL_FILE) as src: - srclines = src.readlines() - + srclines = src.readlines() with open('test.ncd') as res: - for idx, line in enumerate(res): - assert_equal(line.strip(), srclines[idx].strip()) + for idx, line in enumerate(res): + assert_equal(line.strip(), srclines[idx].strip()) os.remove('test.ncd') + 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) @@ -57,9 +67,7 @@ def test_conversion(): ncdrill_inch = copy.deepcopy(ncdrill) ncdrill.to_metric() assert_equal(ncdrill.settings.units, 'metric') - inch_primitives = ncdrill_inch.primitives - for tool in iter(ncdrill_inch.tools.values()): tool.to_metric() for primitive in inch_primitives: @@ -80,26 +88,31 @@ def test_parser_hole_count(): 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 ') @@ -107,6 +120,7 @@ def test_parse_header(): p._parse('M95 ') assert_equal(p.state, 'DRILL') + def test_parse_rout(): p = ExcellonParser(FileSettings()) p._parse('G00 ') @@ -114,6 +128,7 @@ def test_parse_rout(): p._parse('G05 ') assert_equal(p.state, 'DRILL') + def test_parse_version(): p = ExcellonParser(FileSettings()) p._parse('VER,1 ') @@ -121,6 +136,7 @@ def test_parse_version(): p._parse('VER,2 ') assert_equal(p.statements[1].version, 2) + def test_parse_format(): p = ExcellonParser(FileSettings()) p._parse('FMAT,1 ') @@ -128,6 +144,7 @@ def test_parse_format(): p._parse('FMAT,2 ') assert_equal(p.statements[1].format, 2) + def test_parse_units(): settings = FileSettings(units='inch', zeros='trailing') p = ExcellonParser(settings) @@ -138,6 +155,7 @@ def test_parse_units(): assert_equal(p.units, 'metric') assert_equal(p.zeros, 'leading') + def test_parse_incremental_mode(): settings = FileSettings(units='inch', zeros='trailing') p = ExcellonParser(settings) @@ -147,6 +165,7 @@ def test_parse_incremental_mode(): p._parse('ICI,OFF ') assert_equal(p.notation, 'absolute') + def test_parse_absolute_mode(): settings = FileSettings(units='inch', zeros='trailing') p = ExcellonParser(settings) @@ -156,18 +175,21 @@ def test_parse_absolute_mode(): p._parse('G90 ') assert_equal(p.notation, 'absolute') + def test_parse_repeat_hole(): p = ExcellonParser(FileSettings()) p.active_tool = ExcellonTool(FileSettings(), number=8) 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') diff --git a/gerber/utils.py b/gerber/utils.py index df26516..1c0af52 100644 --- a/gerber/utils.py +++ b/gerber/utils.py @@ -201,30 +201,20 @@ def decimal_string(value, precision=6, padding=False): return int(floatstr) -def detect_file_format(filename): +def detect_file_format(data): """ Determine format of a file Parameters ---------- - filename : string - Filename of the file to read. + data : string + string containing file data. Returns ------- format : string - File format. either 'excellon' or 'rs274x' + File format. 'excellon' or 'rs274x' or 'unknown' """ - - # Read the first 20 lines (if possible) - lines = [] - with open(filename, 'r') as f: - try: - for i in range(20): - lines.append(f.readline()) - except StopIteration: - pass - - # Look for + lines = data.split('\n') for line in lines: if 'M48' in line: return 'excellon' |