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
This commit is contained in:
parent
7cd6acf126
commit
965d3ce23b
12 changed files with 302 additions and 27 deletions
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
BIN
gerber/tests/golden/example_am_exposure_modifier.png
Normal file
BIN
gerber/tests/golden/example_am_exposure_modifier.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
BIN
gerber/tests/golden/example_holes_dont_clear.png
Normal file
BIN
gerber/tests/golden/example_holes_dont_clear.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
16
gerber/tests/golden/example_two_square_boxes.gbr
Normal file
16
gerber/tests/golden/example_two_square_boxes.gbr
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
%FSLAX25Y25*%
|
||||
%MOMM*%
|
||||
%ADD10C,0.01*%
|
||||
D10*
|
||||
%LPD*%
|
||||
G01X0Y0D02*
|
||||
X500000D01*
|
||||
Y500000D01*
|
||||
X0D01*
|
||||
Y0D01*
|
||||
X600000D02*
|
||||
X1100000D01*
|
||||
Y500000D01*
|
||||
X600000D01*
|
||||
Y0D01*
|
||||
M02*
|
||||
16
gerber/tests/resources/example_am_exposure_modifier.gbr
Normal file
16
gerber/tests/resources/example_am_exposure_modifier.gbr
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
G04 Umaco example for exposure modifier and clearing area*
|
||||
%FSLAX26Y26*%
|
||||
%MOIN*%
|
||||
%AMSQUAREWITHHOLE*
|
||||
21,0.1,1,1,0,0,0*
|
||||
1,0,0.5,0,0*%
|
||||
%ADD10SQUAREWITHHOLE*%
|
||||
%ADD11C,1*%
|
||||
G01*
|
||||
%LPD*%
|
||||
D11*
|
||||
X-1000000Y-250000D02*
|
||||
X1000000Y250000D01*
|
||||
D10*
|
||||
X0Y0D03*
|
||||
M02*
|
||||
13
gerber/tests/resources/example_holes_dont_clear.gbr
Normal file
13
gerber/tests/resources/example_holes_dont_clear.gbr
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
G04 Demonstrates that apertures with holes do not clear the area - only the aperture hole*
|
||||
%FSLAX26Y26*%
|
||||
%MOIN*%
|
||||
%ADD10C,1X0.5*%
|
||||
%ADD11C,0.1*%
|
||||
G01*
|
||||
%LPD*%
|
||||
D11*
|
||||
X-1000000Y-250000D02*
|
||||
X1000000Y250000D01*
|
||||
D10*
|
||||
X0Y0D03*
|
||||
M02*
|
||||
|
|
@ -6,7 +6,7 @@ import io
|
|||
import os
|
||||
|
||||
from ..render.cairo_backend import GerberCairoContext
|
||||
from ..rs274x import read, GerberFile
|
||||
from ..rs274x import read
|
||||
from .tests import *
|
||||
from nose.tools import assert_tuple_equal
|
||||
|
||||
|
|
@ -121,7 +121,20 @@ def test_flash_obround():
|
|||
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.png', 'golden/example_flash_polygon.png')
|
||||
_test_render('resources/example_flash_polygon.gbr', 'golden/example_flash_polygon.png')
|
||||
|
||||
|
||||
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.png')
|
||||
|
||||
|
||||
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.png')
|
||||
|
||||
|
||||
def _resolve_path(path):
|
||||
return os.path.join(os.path.dirname(__file__),
|
||||
|
|
|
|||
|
|
@ -256,18 +256,18 @@ def test_circle_conversion():
|
|||
c.to_metric() #shouldn't do antyhing
|
||||
assert_equal(c.position, (2.54, 25.4))
|
||||
assert_equal(c.diameter, 254.)
|
||||
assert_equal(c.hole_diameter, 0.)
|
||||
assert_equal(c.hole_diameter, None)
|
||||
|
||||
c.to_inch()
|
||||
assert_equal(c.position, (0.1, 1.))
|
||||
assert_equal(c.diameter, 10.)
|
||||
assert_equal(c.hole_diameter, 0)
|
||||
assert_equal(c.hole_diameter, None)
|
||||
|
||||
#no effect
|
||||
c.to_inch()
|
||||
assert_equal(c.position, (0.1, 1.))
|
||||
assert_equal(c.diameter, 10.)
|
||||
assert_equal(c.hole_diameter, 0)
|
||||
assert_equal(c.hole_diameter, None)
|
||||
|
||||
# Circle initially metric, with hole
|
||||
c = Circle((2.54, 25.4), 254.0, 127.0, units='metric')
|
||||
|
|
@ -294,18 +294,18 @@ def test_circle_conversion():
|
|||
c.to_inch()
|
||||
assert_equal(c.position, (0.1, 1.))
|
||||
assert_equal(c.diameter, 10.)
|
||||
assert_equal(c.hole_diameter, 0)
|
||||
assert_equal(c.hole_diameter, None)
|
||||
|
||||
c.to_metric()
|
||||
assert_equal(c.position, (2.54, 25.4))
|
||||
assert_equal(c.diameter, 254.)
|
||||
assert_equal(c.hole_diameter, 0)
|
||||
assert_equal(c.hole_diameter, None)
|
||||
|
||||
#no effect
|
||||
c.to_metric()
|
||||
assert_equal(c.position, (2.54, 25.4))
|
||||
assert_equal(c.diameter, 254.)
|
||||
assert_equal(c.hole_diameter, 0)
|
||||
assert_equal(c.hole_diameter, None)
|
||||
|
||||
c = Circle((0.1, 1.0), 10.0, 5.0, units='inch')
|
||||
#No effect
|
||||
|
|
@ -820,12 +820,12 @@ def test_polygon_ctor():
|
|||
test_cases = (((0,0), 3, 5, 0),
|
||||
((0, 0), 5, 6, 0),
|
||||
((1,1), 7, 7, 45))
|
||||
for pos, sides, radius, hole_radius in test_cases:
|
||||
p = Polygon(pos, sides, radius, hole_radius)
|
||||
for pos, sides, radius, hole_diameter in test_cases:
|
||||
p = Polygon(pos, sides, radius, hole_diameter)
|
||||
assert_equal(p.position, pos)
|
||||
assert_equal(p.sides, sides)
|
||||
assert_equal(p.radius, radius)
|
||||
assert_equal(p.hole_radius, hole_radius)
|
||||
assert_equal(p.hole_diameter, hole_diameter)
|
||||
|
||||
def test_polygon_bounds():
|
||||
""" Test polygon bounding box calculation
|
||||
|
|
|
|||
185
gerber/tests/test_rs274x_backend.py
Normal file
185
gerber/tests/test_rs274x_backend.py
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Author: Garret Fick <garret@ficksworkshop.com>
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue