summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHamilton Kibbe <hamilton.kibbe@gmail.com>2015-10-10 16:51:21 -0400
committerHamilton Kibbe <hamilton.kibbe@gmail.com>2015-10-10 16:51:21 -0400
commitdd63b169f177389602e17bc6ced53bd0f1ba0de3 (patch)
tree12be6d968c97be78c3910b2c84b048211e88c7e2
parentb81c9d4bf96845ced3495eb158ec3a3c9e4dce3d (diff)
downloadgerbonara-dd63b169f177389602e17bc6ced53bd0f1ba0de3.tar.gz
gerbonara-dd63b169f177389602e17bc6ced53bd0f1ba0de3.tar.bz2
gerbonara-dd63b169f177389602e17bc6ced53bd0f1ba0de3.zip
Allow files to be read from strings per #37
Adds a loads() method to the top level module which generates a GerberFile or ExcellonFile from a string
-rw-r--r--examples/cairo_example.py3
-rw-r--r--gerber/__init__.py2
-rw-r--r--gerber/common.py36
-rwxr-xr-xgerber/excellon.py50
-rw-r--r--gerber/render/cairo_backend.py6
-rw-r--r--gerber/render/render.py1
-rw-r--r--gerber/rs274x.py24
-rw-r--r--gerber/tests/test_common.py13
-rw-r--r--gerber/tests/test_excellon.py38
-rw-r--r--gerber/utils.py20
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'