Fix some failing tests
This commit is contained in:
parent
3fb26e6940
commit
d8ff4a468b
9 changed files with 178 additions and 133 deletions
|
|
@ -76,6 +76,8 @@ class ApertureMacro:
|
|||
primitive = ap.PRIMITIVE_CLASSES[int(primitive)](unit=unit, args=args)
|
||||
macro.primitives.append(primitive)
|
||||
|
||||
return macro
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if self._name is not None:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
import math
|
||||
from dataclasses import dataclass, replace, astuple
|
||||
from dataclasses import dataclass, replace, astuple, InitVar
|
||||
|
||||
from .aperture_macros.parse import GenericMacros
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ class Aperture:
|
|||
return {'hole_dia': self.hole_rect_h, 'hole_rect_h': self.hole_dia}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@dataclass
|
||||
class CircleAperture(Aperture):
|
||||
gerber_shape_code = 'C'
|
||||
human_readable_shape = 'circle'
|
||||
|
|
@ -96,7 +96,7 @@ class CircleAperture(Aperture):
|
|||
return strip_right(self.diameter, self.hole_dia, self.hole_rect_h)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@dataclass
|
||||
class RectangleAperture(Aperture):
|
||||
gerber_shape_code = 'R'
|
||||
human_readable_shape = 'rect'
|
||||
|
|
@ -134,7 +134,7 @@ class RectangleAperture(Aperture):
|
|||
return strip_right(self.w, self.h, self.hole_dia, self.hole_rect_h)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@dataclass
|
||||
class ObroundAperture(Aperture):
|
||||
gerber_shape_code = 'O'
|
||||
human_readable_shape = 'obround'
|
||||
|
|
@ -170,7 +170,7 @@ class ObroundAperture(Aperture):
|
|||
return strip_right(self.w, self.h, self.hole_dia, self.hole_rect_h)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@dataclass
|
||||
class PolygonAperture(Aperture):
|
||||
gerber_shape_code = 'P'
|
||||
diameter : float
|
||||
|
|
@ -187,7 +187,6 @@ class PolygonAperture(Aperture):
|
|||
flash = _flash_hole
|
||||
|
||||
def _rotated(self):
|
||||
self.rotation %= (2*math.pi / self.n_vertices)
|
||||
return self
|
||||
|
||||
def to_macro(self):
|
||||
|
|
@ -195,22 +194,22 @@ class PolygonAperture(Aperture):
|
|||
|
||||
@property
|
||||
def params(self):
|
||||
rotation = self.rotation % (2*math.pi / self.n_vertices) if self.rotation is not None else None
|
||||
if self.hole_dia is not None:
|
||||
return self.diameter, self.n_vertices, self.rotation, self.hole_dia
|
||||
elif self.rotation:
|
||||
return self.diameter, self.n_vertices, self.rotation
|
||||
return self.diameter, self.n_vertices, rotation, self.hole_dia
|
||||
elif rotation is not None and not math.isclose(rotation, 0):
|
||||
return self.diameter, self.n_vertices, rotation
|
||||
else:
|
||||
return self.diameter, self.n_vertices
|
||||
|
||||
|
||||
@dataclass
|
||||
class ApertureMacroInstance(Aperture):
|
||||
params : [float]
|
||||
macro : object
|
||||
parameters : [float]
|
||||
rotation : float = 0
|
||||
|
||||
def __init__(self, macro, *parameters):
|
||||
self.params = parameters
|
||||
def __post__init__(self, macro):
|
||||
self._primitives = macro.to_graphic_primitives(parameters)
|
||||
self.macro = macro
|
||||
|
||||
@property
|
||||
def gerber_shape_code(self):
|
||||
|
|
@ -227,7 +226,7 @@ class ApertureMacroInstance(Aperture):
|
|||
return self.to_macro()
|
||||
|
||||
def to_macro(self):
|
||||
return type(self)(self.macro.rotated(self.rotation), self.params)
|
||||
return replace(self, macro=macro.rotated(self.rotation))
|
||||
|
||||
def __eq__(self, other):
|
||||
return hasattr(other, 'macro') and self.macro == other.macro and \
|
||||
|
|
@ -236,6 +235,6 @@ class ApertureMacroInstance(Aperture):
|
|||
|
||||
@property
|
||||
def params(self):
|
||||
return astuple(self)[:-1]
|
||||
return tuple(self.parameters)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class FormatSpecStmt(ParamStmt):
|
|||
|
||||
def to_gerber(self, settings):
|
||||
zeros = 'T' if settings.zeros == 'trailing' else 'L' # default to leading if "None" is specified
|
||||
notation = 'A' if settings.notation == 'absolute' else 'I'
|
||||
notation = 'I' if settings.notation == 'incremental' else 'A' # default to absolute
|
||||
number_format = str(settings.number_format[0]) + str(settings.number_format[1])
|
||||
|
||||
return f'%FS{zeros}{notation}X{number_format}Y{number_format}*%'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
|
||||
from dataclasses import dataclass, KW_ONLY
|
||||
import math
|
||||
from dataclasses import dataclass, KW_ONLY, astuple
|
||||
|
||||
from . import graphic_primitives as gp
|
||||
from .gerber_statements import *
|
||||
|
|
@ -160,57 +161,45 @@ class Slot(GerberObject):
|
|||
yield gp.Line(*self.p1, *self.p2, self.width, polarity_dark=self.polarity_dark)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Arc(GerberObject):
|
||||
x : float
|
||||
y : float
|
||||
r : float
|
||||
angle1 : float # radians!
|
||||
angle2 : float # radians!
|
||||
x1 : float
|
||||
y1 : float
|
||||
x2 : float
|
||||
y2 : float
|
||||
cx : float
|
||||
cy : float
|
||||
flipped : bool
|
||||
aperture : object
|
||||
|
||||
@classmethod
|
||||
def from_coords(kls, start, end, center_delta, aperture, flipped=False, polarity_dark=True):
|
||||
x0, y0 = start
|
||||
x1, y1 = end
|
||||
dx, dy = center_delta
|
||||
cx, cy = x0+dx, y0+dy
|
||||
angle1 = math.atan2(y0-cy, x0-cx)
|
||||
angle2 = math.atan2(y1-cy, x1-cx)
|
||||
aperture = self.aperture
|
||||
if flipped:
|
||||
angle1, angle2 = angle2, angle1
|
||||
r = math.sqrt(dx**2 + dy**2)
|
||||
# r should be approximately (depending on coordinate resolution) equal for center->start and center->end
|
||||
return kls(cx, cy, r, angle1, angle2, polarity_dark=polarity_dark)
|
||||
|
||||
def with_offset(self, dx, dy):
|
||||
return replace(self, x=self.x+dx, y=self.y+dy)
|
||||
|
||||
@property
|
||||
def p1(self):
|
||||
return self.x + self.r*sin(self.angle1), self.y + self.r*cos(self.angle1)
|
||||
return self.x1, self.y1
|
||||
|
||||
@property
|
||||
def p2(self):
|
||||
return self.x + self.r*sin(self.angle2), self.y + self.r*cos(self.angle2)
|
||||
return self.x2, self.y2
|
||||
|
||||
@property
|
||||
def center(self):
|
||||
return (self.x, self.y)
|
||||
return self.x1 + self.cx, self.y1 + self.cy
|
||||
|
||||
def rotate(self, rotation, cx=None, cy=None):
|
||||
self.x, self.y = gp.rotate_point(self.x, self.y, rotation, cx, cy)
|
||||
self.angle1 = (self.angle1+rotation) % (2*math.pi)
|
||||
self.angle2 = (self.angle2+rotation) % (2*math.pi)
|
||||
cx, cy = gp.rotate_point(*self.center, rotation, cx, cy)
|
||||
self.x1, self.y1 = gp.rotate_point(self.x1, self.y1, rotation, cx, cy)
|
||||
self.x2, self.y2 = gp.rotate_point(self.x2, self.y2, rotation, cx, cy)
|
||||
self.cx, self.cy = cx - self.x1, cy - self.y1
|
||||
|
||||
def to_primitives(self):
|
||||
yield gp.Arc(self.x, self.y, self.r, self.angle1, self.angle2, self.aperture.equivalent_width, polarity_dark=self.polarity_dark)
|
||||
yield gp.Arc(*astuple(self)[:7], width=self.aperture.equivalent_width, polarity_dark=self.polarity_dark)
|
||||
|
||||
def to_statements(self, gs):
|
||||
yield from gs.set_aperture(self.aperture)
|
||||
yield from gs.set_interpolation_mode(CircularCCWModeStmt)
|
||||
yield from gs.set_current_point(self.p1)
|
||||
x2, y2 = self.p2
|
||||
yield InterpolateStmt(x2, y2, self.x-x2, self.y-y2)
|
||||
yield InterpolateStmt(self.x2, self.y2, self.cx, self.cy)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -95,11 +95,13 @@ class Line(GraphicPrimitive):
|
|||
|
||||
@dataclass
|
||||
class Arc(GraphicPrimitive):
|
||||
x : float
|
||||
y : float
|
||||
r : float
|
||||
angle1 : float # radians!
|
||||
angle2 : float # radians!
|
||||
x1 : float
|
||||
y1 : float
|
||||
x2 : float
|
||||
y2 : float
|
||||
cx : float
|
||||
cy : float
|
||||
flipped : bool
|
||||
width : float
|
||||
|
||||
# FIXME bounds
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ class GerberFile(CamFile):
|
|||
|
||||
yield ApertureDefStmt(number, aperture)
|
||||
|
||||
aperture_map[aperture] = number
|
||||
aperture_map[id(aperture)] = number
|
||||
|
||||
gs = GraphicsState(aperture_map=aperture_map)
|
||||
for primitive in self.objects:
|
||||
|
|
@ -296,34 +296,64 @@ class GraphicsState:
|
|||
a, b, c, d = self._mat
|
||||
|
||||
if not relative:
|
||||
return (a*x + b*y + self.image_offset[0]), (c*x + d*y + self.image_offset[1])
|
||||
rx, ry = (a*x + b*y + self.image_offset[0]), (c*x + d*y + self.image_offset[1])
|
||||
print(f'map {x},{y} to {rx},{ry}')
|
||||
return rx, ry
|
||||
else:
|
||||
# Apply mirroring, scale and rotation, but do not apply offset
|
||||
return (a*x + b*y), (c*x + d*y)
|
||||
rx, ry = (a*x + b*y), (c*x + d*y)
|
||||
print(f'map {x},{y} to {rx},{ry}')
|
||||
return rx, ry
|
||||
|
||||
def flash(self, x, y):
|
||||
return gp.Flash(self.aperture, *self.map_coord(x, y), polarity_dark=self.polarity_dark)
|
||||
self.update_point(x, y)
|
||||
return go.Flash(*self.map_coord(*self.point), self.aperture, polarity_dark=self.polarity_dark)
|
||||
|
||||
def interpolate(self, x, y, i=None, j=None, aperture=True):
|
||||
if self.point is None:
|
||||
warnings.warn('D01 interpolation without preceding D02 move.', SyntaxWarning)
|
||||
self.point = (0, 0)
|
||||
old_point = self.map_coord(*self.update_point(x, y))
|
||||
|
||||
if self.interpolation_mode == LinearModeStmt:
|
||||
if i is not None or j is not None:
|
||||
raise SyntaxError("i/j coordinates given for linear D01 operation (which doesn't take i/j)")
|
||||
|
||||
return self._create_line(x, y, aperture)
|
||||
return self._create_line(old_point, self.map_coord(*self.point), aperture)
|
||||
|
||||
else:
|
||||
return self._create_arc(x, y, i, j, aperture)
|
||||
if i is None and j is None:
|
||||
warnings.warn('Linear segment implied during arc interpolation mode through D01 w/o I, J values', SyntaxWarning)
|
||||
return self._create_line(old_point, self.map_coord(*self.point), aperture)
|
||||
|
||||
def _create_line(self, x, y, aperture=True):
|
||||
old_point, self.point = self.point, self.map_coord(x, y)
|
||||
return go.Line(*old_point, *self.point, self.aperture if aperture else None, polarity_dark=self.polarity_dark)
|
||||
else:
|
||||
if i is None:
|
||||
warnings.warn('Arc is missing I value', SyntaxWarning)
|
||||
i = 0
|
||||
if j is None:
|
||||
warnings.warn('Arc is missing J value', SyntaxWarning)
|
||||
j = 0
|
||||
return self._create_arc(old_point, self.map_coord(*self.point), (i, j), aperture)
|
||||
|
||||
def _create_arc(self, x, y, i, j, aperture=True):
|
||||
old_point, self.point = self.point, self.map_coord(x, y)
|
||||
def _create_line(self, old_point, new_point, aperture=True):
|
||||
return go.Line(*old_point, *new_point, self.aperture if aperture else None, polarity_dark=self.polarity_dark)
|
||||
|
||||
def _create_arc(self, old_point, new_point, control_point, aperture=True):
|
||||
direction = 'ccw' if self.interpolation_mode == CircularCCWModeStmt else 'cw'
|
||||
return go.Arc.from_coords(old_point, self.point, *self.map_coord(i, j, relative=True),
|
||||
return go.Arc(*old_point, *new_point,* self.map_coord(*control_point, relative=True),
|
||||
flipped=(direction == 'cw'), aperture=(self.aperture if aperture else None), polarity_dark=self.polarity_dark)
|
||||
|
||||
def update_point(self, x, y):
|
||||
print(f'update_point {x=} {y=}')
|
||||
old_point = self.point
|
||||
if x is None:
|
||||
x = self.point[0]
|
||||
if y is None:
|
||||
y = self.point[1]
|
||||
if self.point != (x, y):
|
||||
self.point = (x, y)
|
||||
return old_point
|
||||
|
||||
# Helpers for gerber generation
|
||||
def set_polarity(self, polarity_dark):
|
||||
if self.polarity_dark != polarity_dark:
|
||||
|
|
@ -333,7 +363,7 @@ class GraphicsState:
|
|||
def set_aperture(self, aperture):
|
||||
if self.aperture != aperture:
|
||||
self.aperture = aperture
|
||||
yield ApertureStmt(self.aperture_map[aperture])
|
||||
yield ApertureStmt(self.aperture_map[id(aperture)])
|
||||
|
||||
def set_current_point(self, point):
|
||||
if self.point != point:
|
||||
|
|
@ -342,7 +372,7 @@ class GraphicsState:
|
|||
|
||||
def set_interpolation_mode(self, mode):
|
||||
if self.interpolation_mode != mode:
|
||||
gs.interpolation_mode = mode
|
||||
self.interpolation_mode = mode
|
||||
yield mode()
|
||||
|
||||
|
||||
|
|
@ -438,6 +468,7 @@ class GerberParser:
|
|||
# multiple statements from one line.
|
||||
if line.strip() and self.eof_found:
|
||||
warnings.warn('Data found in gerber file after EOF.', SyntaxWarning)
|
||||
print('line', line)
|
||||
|
||||
for name, le_regex in self.STATEMENT_REGEXES.items():
|
||||
if (match := le_regex.match(line)):
|
||||
|
|
@ -504,7 +535,7 @@ class GerberParser:
|
|||
raise SyntaxError("i/j coordinates given for D02/D03 operation (which doesn't take i/j)")
|
||||
|
||||
if op in ('D2', 'D02'):
|
||||
self.graphics_state.point = (x, y)
|
||||
self.graphics_state.update_point(x, y)
|
||||
if self.current_region:
|
||||
# Start a new region for every outline. As gerber has no concept of fill rules or winding numbers,
|
||||
# it does not make a graphical difference, and it makes the implementation slightly easier.
|
||||
|
|
@ -529,7 +560,7 @@ class GerberParser:
|
|||
|
||||
def _parse_aperture_definition(self, match):
|
||||
# number, shape, modifiers
|
||||
modifiers = [ float(val) for val in match['modifiers'].split(',') ]
|
||||
modifiers = [ float(val) for val in match['modifiers'].split('X') ] if match['modifiers'].strip() else []
|
||||
|
||||
aperture_classes = {
|
||||
'C': apertures.CircleAperture,
|
||||
|
|
@ -542,7 +573,7 @@ class GerberParser:
|
|||
new_aperture = kls(*modifiers)
|
||||
|
||||
elif (macro := self.aperture_macros.get(match['shape'])):
|
||||
new_aperture = apertures.ApertureMacroInstance(match['shape'], macro, modifiers)
|
||||
new_aperture = apertures.ApertureMacroInstance(macro, modifiers)
|
||||
|
||||
else:
|
||||
raise ValueError(f'Aperture shape "{match["shape"]}" is unknown')
|
||||
|
|
@ -556,7 +587,7 @@ class GerberParser:
|
|||
def _parse_format_spec(self, match):
|
||||
# This is a common problem in Eagle files, so just suppress it
|
||||
self.file_settings.zeros = {'L': 'leading', 'T': 'trailing'}.get(match['zero'], 'leading')
|
||||
self.file_settings.notation = 'absolute' if match['notation'] == 'A' else 'incremental'
|
||||
self.file_settings.notation = 'incremental' if match['notation'] == 'I' else 'absolute'
|
||||
|
||||
if match['x'] != match['y']:
|
||||
raise SyntaxError(f'FS specifies different coordinate formats for X and Y ({match["x"]} != {match["y"]})')
|
||||
|
|
@ -616,8 +647,9 @@ class GerberParser:
|
|||
self.graphics_state.output_axes = match['axes']
|
||||
|
||||
def _parse_image_polarity(self, match):
|
||||
warnings.warn('Deprecated IP (image polarity) statement found. This deprecated since rev. I4 (Oct 2013).',
|
||||
DeprecationWarning)
|
||||
# Do not warn, this is still common.
|
||||
# warnings.warn('Deprecated IP (image polarity) statement found. This deprecated since rev. I4 (Oct 2013).',
|
||||
# DeprecationWarning)
|
||||
self.graphics_state.image_polarity = dict(POS='positive', NEG='negative')[match['polarity']]
|
||||
|
||||
def _parse_image_rotation(self, match):
|
||||
|
|
@ -657,9 +689,9 @@ class GerberParser:
|
|||
DeprecationWarning)
|
||||
self.target.comments.append('Replaced deprecated {match["mode"]} unit mode statement with MO statement')
|
||||
|
||||
def _parse_old_unit(self, match):
|
||||
def _parse_old_notation(self, match):
|
||||
# FIXME make sure we always have FS at end of processing.
|
||||
self.settings.notation = 'absolute' if match['mode'] == 'G90' else 'incremental'
|
||||
self.file_settings.notation = 'absolute' if match['mode'] == 'G90' else 'incremental'
|
||||
warnings.warn(f'Deprecated {match["mode"]} notation mode statement found. This deprecated since 2012.',
|
||||
DeprecationWarning)
|
||||
self.target.comments.append('Replaced deprecated {match["mode"]} notation mode statement with FS statement')
|
||||
|
|
|
|||
|
|
@ -3,14 +3,13 @@ import pytest
|
|||
|
||||
from .image_support import ImageDifference
|
||||
|
||||
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
|
||||
def pytest_assertrepr_compare(op, left, right):
|
||||
if isinstance(left, ImageDifference) or isinstance(right, ImageDifference):
|
||||
diff = left if isinstance(left, ImageDifference) else right
|
||||
return [
|
||||
f'Image difference assertion failed.',
|
||||
f' Reference: {diff.ref_path}',
|
||||
f' Actual: {diff.out_path}',
|
||||
f' Actual: {diff.act_path}',
|
||||
f' Calculated difference: {diff}', ]
|
||||
|
||||
# store report in node object so tmp_gbr can determine if the test failed.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,28 @@
|
|||
import subprocess
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import os
|
||||
from functools import total_ordering
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
class ImageDifference:
|
||||
def __init__(self, value, ref_path, act_path):
|
||||
self.value, self.ref_path, self.act_path = value, ref_path, act_path
|
||||
|
||||
def __float__(self):
|
||||
return float(self.value)
|
||||
|
||||
def __eq__(self, other):
|
||||
return float(self) == float(other)
|
||||
|
||||
def __lt__(self, other):
|
||||
return float(self) < float(other)
|
||||
|
||||
def __str__(self):
|
||||
return str(float(self))
|
||||
|
||||
class ImageDifference(float):
|
||||
def __init__(self, value, ref_path, out_path):
|
||||
super().__init__(value)
|
||||
self.ref_path, self.out_path = ref_path, out_path
|
||||
|
||||
def run_cargo_cmd(cmd, args, **kwargs):
|
||||
if cmd.upper() in os.environ:
|
||||
|
|
@ -22,16 +37,18 @@ def run_cargo_cmd(cmd, args, **kwargs):
|
|||
def svg_to_png(in_svg, out_png):
|
||||
run_cargo_cmd('resvg', [in_svg, out_png], check=True, stdout=subprocess.DEVNULL)
|
||||
|
||||
def gbr_to_svg(in_gbr, out_svg):
|
||||
def gbr_to_svg(in_gbr, out_svg, origin=(0, 0), size=(10, 10)):
|
||||
x, y = origin
|
||||
w, h = size
|
||||
cmd = ['gerbv', '-x', 'svg',
|
||||
'--border=0',
|
||||
#f'--origin={origin_x:.6f}x{origin_y:.6f}', f'--window_inch={width:.6f}x{height:.6f}',
|
||||
f'--origin={x:.6f}x{y:.6f}', f'--window_inch={w:.6f}x{h:.6f}',
|
||||
'--foreground=#ffffff',
|
||||
'-o', str(out_svg), str(in_gbr)]
|
||||
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
def gerber_difference(reference, actual):
|
||||
with tempfile.NamedTemporaryFile(suffix='.svg') as out_svg,\
|
||||
with tempfile.NamedTemporaryFile(suffix='.svg') as act_svg,\
|
||||
tempfile.NamedTemporaryFile(suffix='.svg') as ref_svg:
|
||||
|
||||
gbr_to_svg(reference, ref_svg.name)
|
||||
|
|
@ -58,6 +75,6 @@ def image_difference(reference, actual):
|
|||
|
||||
ref, out = ref.mean(axis=2), out.mean(axis=2) # convert to grayscale
|
||||
delta = np.abs(out - ref).astype(float) / 255
|
||||
return ImageDifference(delta.mean(), ref, out)
|
||||
return ImageDifference(delta.mean(), reference, actual)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
# Author: Hamilton Kibbe <ham@hamiltonkib.be>
|
||||
import os
|
||||
import re
|
||||
import pytest
|
||||
import functools
|
||||
import tempfile
|
||||
|
|
@ -18,8 +19,10 @@ from .image_support import gerber_difference
|
|||
fail_dir = Path('gerbonara_test_failures')
|
||||
@pytest.fixture(scope='session', autouse=True)
|
||||
def clear_failure_dir(request):
|
||||
if fail_dir.is_dir():
|
||||
shutil.rmtree(fail_dir)
|
||||
for f in fail_dir.glob('*.gbr'):
|
||||
f.unlink()
|
||||
|
||||
reference_path = lambda reference: Path(__file__).parent / 'resources' / reference
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_gbr(request):
|
||||
|
|
@ -30,61 +33,63 @@ def tmp_gbr(request):
|
|||
if request.node.rep_call.failed:
|
||||
module, _, test_name = request.node.nodeid.rpartition('::')
|
||||
_test, _, test_name = test_name.partition('_')
|
||||
test_name = test_name.replace('[', '_').replace(']', '_')
|
||||
test_name, _, _ext = test_name.partition('.')
|
||||
test_name = re.sub(r'[^\w\d]', '_', test_name)
|
||||
fail_dir.mkdir(exist_ok=True)
|
||||
perm_path = fail_dir / f'failure_{test_name}.gbr'
|
||||
shutil.copy(tmp_out_gbr.name, perm_path)
|
||||
print('Failing output saved to {perm_path}')
|
||||
print(f'Failing output saved to {perm_path}')
|
||||
print(f'Reference file is {reference_path(request.node.funcargs["reference"])}')
|
||||
|
||||
@pytest.mark.filterwarnings('ignore:Deprecated.*statement found.*:DeprecationWarning')
|
||||
@pytest.mark.filterwarnings('ignore::SyntaxWarning')
|
||||
@pytest.mark.parametrize('reference', [ l.strip() for l in '''
|
||||
board_outline.GKO
|
||||
example_outline_with_arcs.gbr
|
||||
'''
|
||||
#example_two_square_boxes.gbr
|
||||
#example_coincident_hole.gbr
|
||||
#example_cutin.gbr
|
||||
#example_cutin_multiple.gbr
|
||||
#example_flash_circle.gbr
|
||||
#example_flash_obround.gbr
|
||||
#example_flash_polygon.gbr
|
||||
#example_flash_rectangle.gbr
|
||||
#example_fully_coincident.gbr
|
||||
#example_guess_by_content.g0
|
||||
#example_holes_dont_clear.gbr
|
||||
#example_level_holes.gbr
|
||||
#example_not_overlapping_contour.gbr
|
||||
#example_not_overlapping_touching.gbr
|
||||
#example_overlapping_contour.gbr
|
||||
#example_overlapping_touching.gbr
|
||||
#example_simple_contour.gbr
|
||||
#example_single_contour_1.gbr
|
||||
#example_single_contour_2.gbr
|
||||
#example_single_contour_3.gbr
|
||||
#example_am_exposure_modifier.gbr
|
||||
#bottom_copper.GBL
|
||||
#bottom_mask.GBS
|
||||
#bottom_silk.GBO
|
||||
#eagle_files/copper_bottom_l4.gbr
|
||||
#eagle_files/copper_inner_l2.gbr
|
||||
#eagle_files/copper_inner_l3.gbr
|
||||
#eagle_files/copper_top_l1.gbr
|
||||
#eagle_files/profile.gbr
|
||||
#eagle_files/silkscreen_bottom.gbr
|
||||
#eagle_files/silkscreen_top.gbr
|
||||
#eagle_files/soldermask_bottom.gbr
|
||||
#eagle_files/soldermask_top.gbr
|
||||
#eagle_files/solderpaste_bottom.gbr
|
||||
#eagle_files/solderpaste_top.gbr
|
||||
#multiline_read.ger
|
||||
#test_fine_lines_x.gbr
|
||||
#test_fine_lines_y.gbr
|
||||
#top_copper.GTL
|
||||
#top_mask.GTS
|
||||
#top_silk.GTO
|
||||
'''
|
||||
example_two_square_boxes.gbr
|
||||
example_coincident_hole.gbr
|
||||
example_cutin.gbr
|
||||
example_cutin_multiple.gbr
|
||||
example_flash_circle.gbr
|
||||
example_flash_obround.gbr
|
||||
example_flash_polygon.gbr
|
||||
example_flash_rectangle.gbr
|
||||
example_fully_coincident.gbr
|
||||
example_guess_by_content.g0
|
||||
example_holes_dont_clear.gbr
|
||||
example_level_holes.gbr
|
||||
example_not_overlapping_contour.gbr
|
||||
example_not_overlapping_touching.gbr
|
||||
example_overlapping_contour.gbr
|
||||
example_overlapping_touching.gbr
|
||||
example_simple_contour.gbr
|
||||
example_single_contour_1.gbr
|
||||
example_single_contour_2.gbr
|
||||
example_single_contour_3.gbr
|
||||
example_am_exposure_modifier.gbr
|
||||
bottom_copper.GBL
|
||||
bottom_mask.GBS
|
||||
bottom_silk.GBO
|
||||
eagle_files/copper_bottom_l4.gbr
|
||||
eagle_files/copper_inner_l2.gbr
|
||||
eagle_files/copper_inner_l3.gbr
|
||||
eagle_files/copper_top_l1.gbr
|
||||
eagle_files/profile.gbr
|
||||
eagle_files/silkscreen_bottom.gbr
|
||||
eagle_files/silkscreen_top.gbr
|
||||
eagle_files/soldermask_bottom.gbr
|
||||
eagle_files/soldermask_top.gbr
|
||||
eagle_files/solderpaste_bottom.gbr
|
||||
eagle_files/solderpaste_top.gbr
|
||||
multiline_read.ger
|
||||
test_fine_lines_x.gbr
|
||||
test_fine_lines_y.gbr
|
||||
top_copper.GTL
|
||||
top_mask.GTS
|
||||
top_silk.GTO
|
||||
'''.splitlines() if l ])
|
||||
def test_round_trip(tmp_gbr, reference):
|
||||
ref = Path(__file__).parent / 'resources' / reference
|
||||
ref = reference_path(reference)
|
||||
GerberFile.open(ref).save(tmp_gbr)
|
||||
assert gerber_difference(ref, tmp_gbr) < 0.02
|
||||
assert gerber_difference(ref, tmp_gbr) < 1e-5
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue