summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md120
-rw-r--r--gerber/render/cairo_backend.py31
-rw-r--r--gerber/rs274x.py11
-rw-r--r--gerber/tests/test_cairo_backend.py40
4 files changed, 123 insertions, 79 deletions
diff --git a/README.md b/README.md
index d7b359e..f741f80 100644
--- a/README.md
+++ b/README.md
@@ -1,60 +1,60 @@
-pcb-tools
-============
-[![Travis CI Build Status](https://travis-ci.org/curtacircuitos/pcb-tools.svg?branch=master)](https://travis-ci.org/curtacircuitos/pcb-tools)
-[![Coverage Status](https://coveralls.io/repos/curtacircuitos/pcb-tools/badge.png?branch=master)](https://coveralls.io/r/curtacircuitos/pcb-tools?branch=master)
-[![Documentation Status](https://readthedocs.org/projects/pcb-tools/badge/?version=latest)](https://readthedocs.org/projects/pcb-tools/?badge=latest)
-
-Tools to handle Gerber and Excellon files in Python.
-
-Usage Example:
----------------
- import gerber
- from gerber.render import GerberCairoContext
-
- # Read gerber and Excellon files
- top_copper = gerber.read('example.GTL')
- nc_drill = gerber.read('example.txt')
-
- # Rendering context
- ctx = GerberCairoContext()
-
- # Create SVG image
- top_copper.render(ctx)
- nc_drill.render(ctx, 'composite.svg')
-
-
-Rendering Examples:
--------------------
-###Top Composite rendering
-![Composite Top Image](examples/cairo_example.png)
-![Composite Bottom Image](examples/cairo_bottom.png)
-
-Source code for this example can be found [here](examples/cairo_example.py).
-
-
-Install from source:
-```
-$ git clone https://github.com/curtacircuitos/pcb-tools.git
-$ cd pcb-tools
-$ pip install -r requirements.txt
-$ python setup.py install
-```
-
-Documentation:
---------------
-[PCB Tools Documentation](http://pcb-tools.readthedocs.org/en/latest/)
-
-
-Development and Testing:
-------------------------
-
-Dependencies for developing and testing pcb-tools are listed in test-requirements.txt. Use of a virtual environment is strongly recommended.
-
- $ virtualenv venv
- $ source venv/bin/activate
- (venv)$ pip install -r test-requirements.txt
- (venv)$ pip install -e .
-
-We use nose to run pcb-tools's suite of unittests and doctests.
-
- (venv)$ nosetests
+pcb-tools
+============
+[![Travis CI Build Status](https://travis-ci.org/curtacircuitos/pcb-tools.svg?branch=master)](https://travis-ci.org/curtacircuitos/pcb-tools)
+[![Coverage Status](https://coveralls.io/repos/curtacircuitos/pcb-tools/badge.png?branch=master)](https://coveralls.io/r/curtacircuitos/pcb-tools?branch=master)
+[![Documentation Status](https://readthedocs.org/projects/pcb-tools/badge/?version=latest)](https://readthedocs.org/projects/pcb-tools/?badge=latest)
+
+Tools to handle Gerber and Excellon files in Python.
+
+Usage Example:
+---------------
+ import gerber
+ from gerber.render import GerberCairoContext
+
+ # Read gerber and Excellon files
+ top_copper = gerber.read('example.GTL')
+ nc_drill = gerber.read('example.txt')
+
+ # Rendering context
+ ctx = GerberCairoContext()
+
+ # Create SVG image
+ top_copper.render(ctx)
+ nc_drill.render(ctx, 'composite.svg')
+
+
+Rendering Examples:
+-------------------
+### Top Composite rendering
+![Composite Top Image](examples/cairo_example.png)
+![Composite Bottom Image](examples/cairo_bottom.png)
+
+Source code for this example can be found [here](examples/cairo_example.py).
+
+
+Install from source:
+```
+$ git clone https://github.com/curtacircuitos/pcb-tools.git
+$ cd pcb-tools
+$ pip install -r requirements.txt
+$ python setup.py install
+```
+
+Documentation:
+--------------
+[PCB Tools Documentation](http://pcb-tools.readthedocs.org/en/latest/)
+
+
+Development and Testing:
+------------------------
+
+Dependencies for developing and testing pcb-tools are listed in test-requirements.txt. Use of a virtual environment is strongly recommended.
+
+ $ virtualenv venv
+ $ source venv/bin/activate
+ (venv)$ pip install -r test-requirements.txt
+ (venv)$ pip install -e .
+
+We use nose to run pcb-tools's suite of unittests and doctests.
+
+ (venv)$ nosetests
diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py
index 76be60a..2e9b143 100644
--- a/gerber/render/cairo_backend.py
+++ b/gerber/render/cairo_backend.py
@@ -77,7 +77,7 @@ class GerberCairoContext(GerberContext):
self.output_ctx = cairo.Context(self.surface)
def render_layer(self, layer, filename=None, settings=None, bgsettings=None,
- verbose=False):
+ verbose=False, bounds=None):
if settings is None:
settings = THEMES['default'].get(layer.layer_class, RenderSettings())
if bgsettings is None:
@@ -87,7 +87,10 @@ class GerberCairoContext(GerberContext):
if verbose:
print('[Render]: Rendering Background.')
self.clear()
- self.set_bounds(layer.bounds)
+ if bounds is not None:
+ self.set_bounds(bounds)
+ else:
+ self.set_bounds(layer.bounds)
self._paint_background(bgsettings)
if verbose:
print('[Render]: Rendering {} Layer.'.format(layer.layer_class))
@@ -139,7 +142,7 @@ class GerberCairoContext(GerberContext):
if is_svg:
self.surface.finish()
self.surface_buffer.flush()
- with open(filename, "w") as f:
+ with open(filename, "wb") as f:
self.surface_buffer.seek(0)
f.write(self.surface_buffer.read())
f.flush()
@@ -249,10 +252,10 @@ class GerberCairoContext(GerberContext):
mask.ctx.set_line_cap(cairo.LINE_CAP_ROUND if isinstance(arc.aperture, Circle) else cairo.LINE_CAP_SQUARE)
mask.ctx.move_to(*start) # You actually have to do this...
if arc.direction == 'counterclockwise':
- mask.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2)
+ mask.ctx.arc(center[0], center[1], radius, angle1, angle2)
else:
- mask.ctx.arc_negative(*center, radius=radius,
- angle1=angle1, angle2=angle2)
+ mask.ctx.arc_negative(center[0], center[1], radius,
+ angle1, angle2)
mask.ctx.move_to(*end) # ...lame
mask.ctx.stroke()
@@ -288,11 +291,11 @@ class GerberCairoContext(GerberContext):
angle1 = prim.start_angle
angle2 = prim.end_angle
if prim.direction == 'counterclockwise':
- mask.ctx.arc(*center, radius=radius,
- angle1=angle1, angle2=angle2)
+ mask.ctx.arc(center[0], center[1], radius,
+ angle1, angle2)
else:
- mask.ctx.arc_negative(*center, radius=radius,
- angle1=angle1, angle2=angle2)
+ mask.ctx.arc_negative(center[0], center[1], radius,
+ angle1, angle2)
mask.ctx.fill()
self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0])
@@ -357,7 +360,7 @@ class GerberCairoContext(GerberContext):
with self._clip_primitive(rectangle):
with self._new_mask() as mask:
mask.ctx.set_line_width(0)
- mask.ctx.rectangle(*lower_left, width=width, height=height)
+ mask.ctx.rectangle(lower_left[0], lower_left[1], width, height)
mask.ctx.fill()
center = self.scale_point(rectangle.position)
@@ -415,7 +418,7 @@ class GerberCairoContext(GerberContext):
width, height = tuple([abs(coord) for coord in
self.scale_point((rectangle.width,
rectangle.height))])
- mask.ctx.rectangle(*lower_left, width=width, height=height)
+ mask.ctx.rectangle(lower_left[0], lower_left[1], width, height)
mask.ctx.fill()
center = self.scale_point(obround.position)
@@ -565,7 +568,7 @@ class GerberCairoContext(GerberContext):
def _flatten(self, color=None, alpha=None):
color = color if color is not None else self.color
alpha = alpha if alpha is not None else self.alpha
- self.output_ctx.set_source_rgba(*color, alpha=alpha)
+ self.output_ctx.set_source_rgba(color[0], color[1], color[2], alpha)
self.output_ctx.mask_surface(self.active_layer)
self.ctx = None
self.active_layer = None
@@ -576,7 +579,7 @@ class GerberCairoContext(GerberContext):
alpha = settings.alpha if settings is not None else 1.0
if not self.has_bg:
self.has_bg = True
- self.output_ctx.set_source_rgba(*color, alpha=alpha)
+ self.output_ctx.set_source_rgba(color[0], color[1], color[2], alpha)
self.output_ctx.paint()
def _clip_primitive(self, primitive):
diff --git a/gerber/rs274x.py b/gerber/rs274x.py
index 813b246..afdf45f 100644
--- a/gerber/rs274x.py
+++ b/gerber/rs274x.py
@@ -765,14 +765,17 @@ class GerberParser(object):
# Calculate the radius error
sqdist_start = sq_distance(start, test_center)
sqdist_end = sq_distance(end, test_center)
+ sqdist_diff = abs(sqdist_start - sqdist_end)
# Take the option with the lowest radius error from the set of
# options with a valid sweep angle
- if ((abs(sqdist_start - sqdist_end) < sqdist_diff_min)
- and (sweep_angle >= 0)
- and (sweep_angle <= math.pi / 2.0)):
+ # In some rare cases, the sweep angle is numerically (10**-14) above pi/2
+ # So it is safer to compare the angles with some tolerance
+ is_lowest_radius_error = sqdist_diff < sqdist_diff_min
+ is_valid_sweep_angle = sweep_angle >= 0 and sweep_angle <= math.pi / 2.0 + 1e-6
+ if is_lowest_radius_error and is_valid_sweep_angle:
center = test_center
- sqdist_diff_min = abs(sqdist_start - sqdist_end)
+ sqdist_diff_min = sqdist_diff
return center
else:
return (start[0] + offsets[0], start[1] + offsets[1])
diff --git a/gerber/tests/test_cairo_backend.py b/gerber/tests/test_cairo_backend.py
index 9195b93..d5ce4ed 100644
--- a/gerber/tests/test_cairo_backend.py
+++ b/gerber/tests/test_cairo_backend.py
@@ -3,7 +3,8 @@
# Author: Garret Fick <garret@ficksworkshop.com>
import os
-
+import shutil
+import tempfile
from ..render.cairo_backend import GerberCairoContext
from ..rs274x import read
@@ -136,6 +137,11 @@ def _DISABLED_test_render_am_exposure_modifier():
_test_render('resources/example_am_exposure_modifier.gbr', 'golden/example_am_exposure_modifier.png')
+def test_render_svg_simple_contour():
+ """Example of rendering to an SVG file"""
+ _test_simple_render_svg('resources/example_simple_contour.gbr')
+
+
def _resolve_path(path):
return os.path.join(os.path.dirname(__file__),
path)
@@ -187,3 +193,35 @@ def _test_render(gerber_path, png_expected_path, create_output_path = None):
assert_true(equal)
return gerber
+
+
+def _test_simple_render_svg(gerber_path):
+ """Render the gerber file as SVG
+
+ Note: verifies only the header, not the full content.
+
+ Parameters
+ ----------
+ gerber_path : string
+ Path to Gerber file to open
+ """
+
+ gerber_path = _resolve_path(gerber_path)
+ gerber = read(gerber_path)
+
+ # Create SVG image to the memory stream
+ ctx = GerberCairoContext()
+ gerber.render(ctx)
+
+ temp_dir = tempfile.mkdtemp()
+ svg_temp_path = os.path.join(temp_dir, 'output.svg')
+
+ assert_false(os.path.exists(svg_temp_path))
+ ctx.dump(svg_temp_path)
+ assert_true(os.path.exists(svg_temp_path))
+
+ with open(svg_temp_path, 'r') as expected_file:
+ expected_bytes = expected_file.read()
+ assert_equal(expected_bytes[:38], '<?xml version="1.0" encoding="UTF-8"?>')
+
+ shutil.rmtree(temp_dir)