From 965d3ce23b92f8aff1063debd6d3364de15791fe Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 24 Jul 2016 22:08:31 +0800 Subject: [PATCH] Add more tests for rendering to PNG. Start adding tests for rendering to Gerber format. Changed definition of no hole to use None instead of 0 so we can differentiate when writing to Gerber format. Makde polygon use hole diameter instead of hole radius to match other primitives --- gerber/gerber_statements.py | 5 +- gerber/primitives.py | 30 ++- gerber/render/rs274x_backend.py | 19 +- gerber/rs274x.py | 10 +- .../golden/example_am_exposure_modifier.png | Bin 0 -> 10091 bytes .../tests/golden/example_holes_dont_clear.png | Bin 0 -> 11552 bytes .../tests/golden/example_two_square_boxes.gbr | 16 ++ .../example_am_exposure_modifier.gbr | 16 ++ .../resources/example_holes_dont_clear.gbr | 13 ++ gerber/tests/test_cairo_backend.py | 17 +- gerber/tests/test_primitives.py | 18 +- gerber/tests/test_rs274x_backend.py | 185 ++++++++++++++++++ 12 files changed, 302 insertions(+), 27 deletions(-) create mode 100644 gerber/tests/golden/example_am_exposure_modifier.png create mode 100644 gerber/tests/golden/example_holes_dont_clear.png create mode 100644 gerber/tests/golden/example_two_square_boxes.gbr create mode 100644 gerber/tests/resources/example_am_exposure_modifier.gbr create mode 100644 gerber/tests/resources/example_holes_dont_clear.gbr create mode 100644 gerber/tests/test_rs274x_backend.py diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 3212c1c..fba2a3c 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -281,7 +281,10 @@ class ADParamStmt(ParamStmt): @classmethod def circle(cls, dcode, diameter, hole_diameter): '''Create a circular aperture definition statement''' - return cls('AD', dcode, 'C', ([diameter, hole_diameter],)) + + if hole_diameter != None: + return cls('AD', dcode, 'C', ([diameter, hole_diameter],)) + return cls('AD', dcode, 'C', ([diameter],)) @classmethod def obround(cls, dcode, width, height): diff --git a/gerber/primitives.py b/gerber/primitives.py index b8ee344..f259eff 100644 --- a/gerber/primitives.py +++ b/gerber/primitives.py @@ -370,7 +370,7 @@ class Arc(Primitive): class Circle(Primitive): """ """ - def __init__(self, position, diameter, hole_diameter = 0, **kwargs): + def __init__(self, position, diameter, hole_diameter = None, **kwargs): super(Circle, self).__init__(**kwargs) validate_coordinates(position) self.position = position @@ -388,7 +388,9 @@ class Circle(Primitive): @property def hole_radius(self): - return self.hole_diameter / 2. + if self.hole_diameter != None: + return self.hole_diameter / 2. + return None @property def bounding_box(self): @@ -486,8 +488,10 @@ class Rectangle(Primitive): @property def hole_radius(self): - """The radius of the hole. If there is no hole, returns 0""" - return self.hole_diameter / 2. + """The radius of the hole. If there is no hole, returns None""" + if self.hole_diameter != None: + return self.hole_diameter / 2. + return None @property def bounding_box(self): @@ -691,8 +695,10 @@ class Obround(Primitive): @property def hole_radius(self): - """The radius of the hole. If there is no hole, returns 0""" - return self.hole_diameter / 2. + """The radius of the hole. If there is no hole, returns None""" + if self.hole_diameter != None: + return self.hole_diameter / 2. + return None @property def orientation(self): @@ -740,14 +746,14 @@ class Polygon(Primitive): """ Polygon flash defined by a set number of sides. """ - def __init__(self, position, sides, radius, hole_radius, **kwargs): + def __init__(self, position, sides, radius, hole_diameter, **kwargs): super(Polygon, self).__init__(**kwargs) validate_coordinates(position) self.position = position self.sides = sides self.radius = radius - self.hole_radius = hole_radius - self._to_convert = ['position', 'radius'] + self.hole_diameter = hole_diameter + self._to_convert = ['position', 'radius', 'hole_diameter'] @property def flashed(self): @@ -756,6 +762,12 @@ class Polygon(Primitive): @property def diameter(self): return self.radius * 2 + + @property + def hole_radius(self): + if self.hole_diameter != None: + return self.hole_diameter / 2. + return None @property def bounding_box(self): diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py index 15e9154..5ab74f0 100644 --- a/gerber/render/rs274x_backend.py +++ b/gerber/render/rs274x_backend.py @@ -1,9 +1,17 @@ +"""Renders an in-memory Gerber file to statements which can be written to a string +""" +from copy import deepcopy +try: + from cStringIO import StringIO +except(ImportError): + from io import StringIO + from .render import GerberContext from ..am_statements import * from ..gerber_statements import * from ..primitives import AMGroup, Arc, Circle, Line, Obround, Outline, Polygon, Rectangle -from copy import deepcopy + class AMGroupContext(object): '''A special renderer to generate aperature macros from an AMGroup''' @@ -467,4 +475,13 @@ class Rs274xContext(GerberContext): def _render_inverted_layer(self): pass + + def dump(self): + """Write the rendered file to a StringIO steam""" + statements = map(lambda stmt: stmt.to_gerber(self.settings), self.statements) + stream = StringIO() + for statement in statements: + stream.write(statement + '\n') + + return stream \ No newline at end of file diff --git a/gerber/rs274x.py b/gerber/rs274x.py index e88bba7..f009232 100644 --- a/gerber/rs274x.py +++ b/gerber/rs274x.py @@ -486,7 +486,7 @@ class GerberParser(object): if len(modifiers[0]) >= 2: hole_diameter = modifiers[0][1] else: - hole_diameter = 0 + hole_diameter = None aperture = Circle(position=None, diameter=diameter, hole_diameter=hole_diameter, units=self.settings.units) elif shape == 'R': @@ -496,7 +496,7 @@ class GerberParser(object): if len(modifiers[0]) >= 3: hole_diameter = modifiers[0][2] else: - hole_diameter = 0 + hole_diameter = None aperture = Rectangle(position=None, width=width, height=height, hole_diameter=hole_diameter, units=self.settings.units) elif shape == 'O': @@ -506,7 +506,7 @@ class GerberParser(object): if len(modifiers[0]) >= 3: hole_diameter = modifiers[0][2] else: - hole_diameter = 0 + hole_diameter = None aperture = Obround(position=None, width=width, height=height, hole_diameter=hole_diameter, units=self.settings.units) elif shape == 'P': @@ -520,8 +520,8 @@ class GerberParser(object): if len(modifiers[0]) > 3: hole_diameter = modifiers[0][3] else: - hole_diameter = 0 - aperture = Polygon(position=None, sides=number_vertices, radius=outer_diameter/2.0, hole_radius=hole_diameter/2.0, rotation=rotation) + hole_diameter = None + aperture = Polygon(position=None, sides=number_vertices, radius=outer_diameter/2.0, hole_diameter=hole_diameter, rotation=rotation) else: aperture = self.macros[shape].build(modifiers) diff --git a/gerber/tests/golden/example_am_exposure_modifier.png b/gerber/tests/golden/example_am_exposure_modifier.png new file mode 100644 index 0000000000000000000000000000000000000000..dac951ff31fd7c189f48f8c1d046b13ecf5e47ca GIT binary patch literal 10091 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 MboFyt=akR{02P6IBme*a literal 0 HcmV?d00001 diff --git a/gerber/tests/golden/example_holes_dont_clear.png b/gerber/tests/golden/example_holes_dont_clear.png new file mode 100644 index 0000000000000000000000000000000000000000..7efb67bae406d83e5f1a1fa477e3a687e758950a GIT binary patch literal 11552 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 +import io +import os + +from ..render.rs274x_backend import Rs274xContext +from ..rs274x import read +from .tests import * + +def test_render_two_boxes(): + """Umaco exapmle of two boxes""" + _test_render('resources/example_two_square_boxes.gbr', 'golden/example_two_square_boxes.gbr') + + +def _test_render_single_quadrant(): + """Umaco exapmle of a single quadrant arc""" + + # TODO there is probably a bug here + _test_render('resources/example_single_quadrant.gbr', 'golden/example_single_quadrant.gbr') + + +def _test_render_simple_contour(): + """Umaco exapmle of a simple arrow-shaped contour""" + _test_render('resources/example_simple_contour.gbr', 'golden/example_simple_contour.gbr') + + +def _test_render_single_contour_1(): + """Umaco example of a single contour + + The resulting image for this test is used by other tests because they must generate the same output.""" + _test_render('resources/example_single_contour_1.gbr', 'golden/example_single_contour.gbr') + + +def _test_render_single_contour_2(): + """Umaco exapmle of a single contour, alternate contour end order + + The resulting image for this test is used by other tests because they must generate the same output.""" + _test_render('resources/example_single_contour_2.gbr', 'golden/example_single_contour.gbr') + + +def _test_render_single_contour_3(): + """Umaco exapmle of a single contour with extra line""" + _test_render('resources/example_single_contour_3.gbr', 'golden/example_single_contour_3.gbr') + + +def _test_render_not_overlapping_contour(): + """Umaco example of D02 staring a second contour""" + _test_render('resources/example_not_overlapping_contour.gbr', 'golden/example_not_overlapping_contour.gbr') + + +def _test_render_not_overlapping_touching(): + """Umaco example of D02 staring a second contour""" + _test_render('resources/example_not_overlapping_touching.gbr', 'golden/example_not_overlapping_touching.gbr') + + +def _test_render_overlapping_touching(): + """Umaco example of D02 staring a second contour""" + _test_render('resources/example_overlapping_touching.gbr', 'golden/example_overlapping_touching.gbr') + + +def _test_render_overlapping_contour(): + """Umaco example of D02 staring a second contour""" + _test_render('resources/example_overlapping_contour.gbr', 'golden/example_overlapping_contour.gbr') + + +def _DISABLED_test_render_level_holes(): + """Umaco example of using multiple levels to create multiple holes""" + + # TODO This is clearly rendering wrong. I'm temporarily checking this in because there are more + # rendering fixes in the related repository that may resolve these. + _test_render('resources/example_level_holes.gbr', 'golden/example_overlapping_contour.gbr') + + +def _DISABLED_test_render_cutin(): + """Umaco example of using a cutin""" + + # TODO This is clearly rendering wrong. + _test_render('resources/example_cutin.gbr', 'golden/example_cutin.gbr') + + +def _test_render_fully_coincident(): + """Umaco example of coincident lines rendering two contours""" + + _test_render('resources/example_fully_coincident.gbr', 'golden/example_fully_coincident.gbr') + + +def _test_render_coincident_hole(): + """Umaco example of coincident lines rendering a hole in the contour""" + + _test_render('resources/example_coincident_hole.gbr', 'golden/example_coincident_hole.gbr') + + +def _test_render_cutin_multiple(): + """Umaco example of a region with multiple cutins""" + + _test_render('resources/example_cutin_multiple.gbr', 'golden/example_cutin_multiple.gbr') + + +def _test_flash_circle(): + """Umaco example a simple circular flash with and without a hole""" + + _test_render('resources/example_flash_circle.gbr', 'golden/example_flash_circle.gbr') + + +def _test_flash_rectangle(): + """Umaco example a simple rectangular flash with and without a hole""" + + _test_render('resources/example_flash_rectangle.gbr', 'golden/example_flash_rectangle.gbr') + + +def _test_flash_obround(): + """Umaco example a simple obround flash with and without a hole""" + + _test_render('resources/example_flash_obround.gbr', 'golden/example_flash_obround.gbr') + + +def _test_flash_polygon(): + """Umaco example a simple polygon flash with and without a hole""" + + _test_render('resources/example_flash_polygon.gbr', 'golden/example_flash_polygon.gbr') + + +def _test_holes_dont_clear(): + """Umaco example that an aperture with a hole does not clear the area""" + + _test_render('resources/example_holes_dont_clear.gbr', 'golden/example_holes_dont_clear.gbr') + + +def _test_render_am_exposure_modifier(): + """Umaco example that an aperture macro with a hole does not clear the area""" + + _test_render('resources/example_am_exposure_modifier.gbr', 'golden/example_am_exposure_modifier.gbr') + + +def _resolve_path(path): + return os.path.join(os.path.dirname(__file__), + path) + + +def _test_render(gerber_path, png_expected_path, create_output_path = None): + """Render the gerber file and compare to the expected PNG output. + + Parameters + ---------- + gerber_path : string + Path to Gerber file to open + png_expected_path : string + Path to the PNG file to compare to + create_output : string|None + If not None, write the generated PNG to the specified path. + This is primarily to help with + """ + + gerber_path = _resolve_path(gerber_path) + png_expected_path = _resolve_path(png_expected_path) + if create_output_path: + create_output_path = _resolve_path(create_output_path) + + gerber = read(gerber_path) + + # Create GBR output from the input file + ctx = Rs274xContext(gerber.settings) + gerber.render(ctx) + + actual_contents = ctx.dump() + + # If we want to write the file bytes, do it now. This happens + if create_output_path: + with open(create_output_path, 'wb') as out_file: + out_file.write(actual_contents.getvalue()) + # Creating the output is dangerous - it could overwrite the expected result. + # So if we are creating the output, we make the test fail on purpose so you + # won't forget to disable this + assert_false(True, 'Test created the output %s. This needs to be disabled to make sure the test behaves correctly' % (create_output_path,)) + + # Read the expected PNG file + + with open(png_expected_path, 'r') as expected_file: + expected_contents = expected_file.read() + + assert_equal(expected_contents, actual_contents.getvalue()) + + return gerber