diff --git a/gerbonara/tests/conftest.py b/gerbonara/tests/conftest.py deleted file mode 100644 index 40cf756..0000000 --- a/gerbonara/tests/conftest.py +++ /dev/null @@ -1,89 +0,0 @@ - -import os -from pathlib import Path -import tqdm -import multiprocessing.pool -import subprocess -from itertools import chain - -import pytest - -from .image_support import ImageDifference, run_cargo_cmd, bulk_populate_kicad_fp_export_cache, KICAD_CONTAINER - -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' Calculated difference: {diff}', - f' Histogram: {diff.histogram}', ] - - -# store report in node object so tmp_gbr can determine if the test failed. -@pytest.hookimpl(tryfirst=True, hookwrapper=True) -def pytest_runtest_makereport(item, call): - outcome = yield - rep = outcome.get_result() - setattr(item, f'rep_{rep.when}', rep) - - -fail_dir = Path('gerbonara_test_failures') -def pytest_sessionstart(session): - if 'PYTEST_XDIST_WORKER' in os.environ: # only run this on the controller - return - - for f in chain(fail_dir.glob('*.gbr'), fail_dir.glob('*.png')): - f.unlink() - - try: - run_cargo_cmd('resvg', '--help', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - except FileNotFoundError: - pytest.exit('resvg binary not found, aborting test.', 2) - - -def pytest_configure(config): - os.nice(20) - # Resvg can sometimes consume a lot of memory. Make sure we don't kill the user's session. - if (oom_adj := Path('/proc/self/oom_adj')).is_file(): - oom_adj.write_text('15\n') - - if 'PYTEST_XDIST_WORKER' in os.environ: # only run this on the controller - return - - if (lib_dir := os.environ.get('KICAD_FOOTPRINTS')): - lib_dir = Path(lib_dir).expanduser() - if not lib_dir.is_dir(): - raise ValueError(f'Path "{lib_dir}" given by KICAD_FOOTPRINTS environment variable does not exist or is not a directory.') - - print('Updating podman image') - subprocess.run(['podman', 'pull', KICAD_CONTAINER], check=True) - - print('Checking and bulk re-building KiCad footprint library cache') - with multiprocessing.pool.ThreadPool() as pool: # use thread pool here since we're only monitoring podman processes - lib_dirs = list(lib_dir.glob('*.pretty')) - res = list(tqdm.tqdm(pool.imap(lambda path: bulk_populate_kicad_fp_export_cache(path), lib_dirs), total=len(lib_dirs))) - - -def pytest_addoption(parser): - parser.addoption('--kicad-symbol-library', nargs='*', help='Run symbol library tests on given symbol libraries. May be given multiple times.') - parser.addoption('--kicad-footprint-files', nargs='*', help='Run footprint library tests on given footprint files. May be given multiple times.') - - -def pytest_generate_tests(metafunc): - if 'kicad_library_file' in metafunc.fixturenames: - if not (library_files := metafunc.config.getoption('symbol_library', None)): - if (lib_dir := os.environ.get('KICAD_SYMBOLS')): - lib_dir = Path(lib_dir).expanduser() - library_files = list(lib_dir.glob('*.kicad_sym')) - else: - raise ValueError('Either --kicad-symbol-library command line parameter or KICAD_SYMBOLS environment variable must be given to run kicad symbol tests.') - metafunc.parametrize('kicad_library_file', library_files, ids=list(map(str, library_files))) - - if 'kicad_mod_file' in metafunc.fixturenames: - if not (mod_files := metafunc.config.getoption('footprint_files', None)): - if (lib_dir := os.environ.get('KICAD_FOOTPRINTS')): - lib_dir = Path(lib_dir).expanduser() - mod_files = list(lib_dir.glob('*.pretty/*.kicad_mod')) - else: - raise ValueError('Either --kicad-footprint-files command line parameter or KICAD_FOOTPRINTS environment variable must be given to run kicad footprint tests.') - metafunc.parametrize('kicad_mod_file', mod_files, ids=list(map(str, mod_files))) diff --git a/gerbonara/tests/image_support.py b/gerbonara/tests/image_support.py deleted file mode 100644 index 5f6db04..0000000 --- a/gerbonara/tests/image_support.py +++ /dev/null @@ -1,360 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright 2022 Jan Sebastian Götte -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Based on https://github.com/tracespace/tracespace -# - -import subprocess -from pathlib import Path -import tempfile -import textwrap -import os -import sys -import stat -import random -import statistics -from functools import total_ordering -import shutil -import bs4 -from contextlib import contextmanager -import hashlib - -import tqdm -import numpy as np -from PIL import Image - -cachedir = Path(__file__).parent / 'image_cache' -cachedir.mkdir(exist_ok=True) - -KICAD_CONTAINER = os.environ.get('KICAD_CONTAINER', 'registry.hub.docker.com/kicad/kicad:nightly') - -@total_ordering -class ImageDifference: - def __init__(self, value, histogram): - self.value = value - self.histogram = histogram - - 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)) - -@total_ordering -class Histogram: - def __init__(self, value, size): - self.value, self.size = value, size - - def __eq__(self, other): - other = np.array(other) - other[other == None] = self.value[other == None] - return (self.value == other).all() - - def __lt__(self, other): - other = np.array(other) - other[other == None] = self.value[other == None] - return (self.value <= other).all() - - def __getitem__(self, index): - return self.value[index] - - def __str__(self): - return f'{list(self.value)} size={self.size}' - - -def run_cargo_cmd(cmd, args, **kwargs): - if cmd.upper() in os.environ: - return subprocess.run([os.environ[cmd.upper()], *args], **kwargs) - - try: - return subprocess.run([cmd, *args], **kwargs) - - except FileNotFoundError: - return subprocess.run([str(Path.home() / '.cargo' / 'bin' / cmd), *args], **kwargs) - -def svg_to_png(in_svg, out_png, dpi=100, bg=None): - params = f'{dpi}{bg}'.encode() - digest = hashlib.blake2b(Path(in_svg).read_bytes() + params).hexdigest() - cachefile = cachedir / f'{digest}.png' - - if not cachefile.is_file(): - bg = 'black' if bg is None else bg - run_cargo_cmd('resvg', ['--background', bg, '--dpi', str(dpi), in_svg, cachefile], check=True, stdout=subprocess.DEVNULL) - - shutil.copy(cachefile, out_png) - -to_gerbv_svg_units = lambda val, unit='mm': val*72 if unit == 'inch' else val/25.4*72 - -def gerbv_export(in_gbr, out_svg, export_format='svg', origin=(0, 0), size=(6, 6), fg='#ffffff', bg='#000000', override_unit_spec=None): - params = f'{origin}{size}{fg}{bg}'.encode() - digest = hashlib.blake2b(Path(in_gbr).read_bytes() + params).hexdigest() - cachefile = cachedir / f'{digest}.svg' - - if not cachefile.is_file(): - print(f'Building cache for {Path(in_gbr).name}') - # NOTE: gerbv seems to always export 'clear' polarity apertures as white, irrespective of --foreground, --background - # and project file color settings. - # TODO: File issue upstream. - with tempfile.NamedTemporaryFile('w') as f: - if override_unit_spec: - units, zeros, digits = override_unit_spec - print(f'{Path(in_gbr).name}: overriding excellon unit spec to {units=} {zeros=} {digits=}') - units = 0 if units == 'inch' else 1 - zeros = {None: 0, 'leading': 1, 'trailing': 2}[zeros] - unit_spec = textwrap.dedent(f'''(cons 'attribs (list - (list 'autodetect 'Boolean 0) - (list 'zero_suppression 'Enum {zeros}) - (list 'units 'Enum {units}) - (list 'digits 'Integer {digits}) - ))''') - else: - unit_spec = '' - - r, g, b = int(fg[1:3], 16), int(fg[3:5], 16), int(fg[5:], 16) - color = f"(cons 'color #({r*257} {g*257} {b*257}))" - f.write(f'''(gerbv-file-version! "2.0A")(define-layer! 0 (cons 'filename "{in_gbr}"){unit_spec}{color})''') - f.flush() - if override_unit_spec: - shutil.copy(f.name, '/tmp/foo.gbv') - - x, y = origin - w, h = size - cmd = ['gerbv', '-x', export_format, - '--border=0', - f'--origin={x:.6f}x{y:.6f}', f'--window_inch={w:.6f}x{h:.6f}', - f'--background={bg}', - f'--foreground={fg}', - '-o', str(cachefile), '-p', f.name] - subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - else: - print(f'Re-using cache for {Path(in_gbr).name}') - shutil.copy(cachefile, out_svg) - -def kicad_fp_export(mod_file, out_svg): - mod_file = Path(mod_file) - if mod_file.suffix.lower() != '.kicad_mod': - raise ValueError("KiCad footprint file must have .kicad_mod extension for kicad-cli to do it's thing") - - params = f'(noparams)'.encode() - digest = hashlib.blake2b(mod_file.read_bytes() + params).hexdigest() - cachefile = cachedir / f'{digest}.svg' - - if not cachefile.is_file(): - print(f'Building cache for {mod_file.name}') - - with tempfile.TemporaryDirectory() as tmpdir: - os.chmod(tmpdir, 0o1777) - pretty_dir = mod_file.parent - fp_name = mod_file.name[:-len('.kicad_mod')] - cmd = ['podman', 'run', - '--rm', # Clean up volumes after exit - '--userns=keep-id', # To allow container to read from bind mount - '--mount', f'type=bind,src={pretty_dir},dst=/{pretty_dir.name}', - '--mount', f'type=bind,src={tmpdir},dst=/out', - KICAD_CONTAINER, - 'kicad-cli', 'fp', 'export', 'svg', '--output', '/out', '--footprint', fp_name, f'/{pretty_dir.name}'] - subprocess.run(cmd, check=True) #, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - out_file = Path(tmpdir) / f'{fp_name}.svg' - shutil.copy(out_file, cachefile) - else: - print(f'Re-using cache for {mod_file.name}') - shutil.copy(cachefile, out_svg) - - -def bulk_populate_kicad_fp_export_cache(pretty_dir): - def cachefile(mod_file): - params = f'(noparams)'.encode() - digest = hashlib.blake2b(mod_file.read_bytes() + params).hexdigest() - return cachedir / f'{digest}.svg' - - mod_files = list(pretty_dir.glob('*.kicad_mod')) - hit_rate = statistics.mean([int(cachefile(fn).is_file()) - for fn in random.sample(mod_files, min(len(mod_files), 50))]) - - if hit_rate < 0.9: - #tqdm.tqdm.write(f'Modfile cache is out of date (hit rate {hit_rate*100:.0f}%), re-building entire cache in bulk') - - with tempfile.TemporaryDirectory() as tmpdir: - os.chmod(tmpdir, 0o1777) - cmd = ['podman', 'run', - '--rm', # Clean up volumes after exit - '--userns=keep-id', # To allow container to read from bind mount - '--mount', f'type=bind,src={pretty_dir},dst=/{pretty_dir.name}', - '--mount', f'type=bind,src={tmpdir},dst=/out', - KICAD_CONTAINER, - 'kicad-cli', 'fp', 'export', 'svg', '--output', '/out', f'/{pretty_dir.name}'] - - try: - subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL) - - except subprocess.CalledProcessError as e: - print('Error running command with command line:', ' '.join(e.cmd), file=sys.stderr) - raise e - - for fn in mod_files: - out_file = Path(tmpdir) / fn.with_suffix('.svg').name - if not out_file.is_file(): - tqdm.tqdm.write(f'Output file {out_file} is missing while bulk re-building cache for {pretty_dir}.') - else: - shutil.copy(out_file, cachefile(fn)) - -@contextmanager -def svg_soup(filename): - with open(filename, 'r') as f: - soup = bs4.BeautifulSoup(f.read(), 'xml') - - yield soup - - with open(filename, 'w') as f: - f.write(str(soup)) - -def cleanup_gerbv_svg(soup): - width = soup.svg["width"] - height = soup.svg["height"] - width = width[:-2] if width.endswith('pt') else width - height = height[:-2] if height.endswith('pt') else height - soup.svg['width'] = f'{float(width)/72*25.4:.4f}mm' - soup.svg['height'] = f'{float(height)/72*25.4:.4f}mm' - for group in soup.find_all('g'): - # gerbv uses Cairo's SVG canvas. Cairo's SVG canvas is kind of broken. It has no support for unit - # handling at all, which means the output files just end up being in pixels at 72 dpi. Further, it - # seems gerbv's aperture macro rendering interacts poorly with Cairo's SVG export. gerbv renders - # aperture macros into a new surface, which for some reason gets clipped by Cairo to the given - # canvas size. This is just wrong, so we just nuke the clip path from these SVG groups here. - # - # Apart from being graphically broken, this additionally causes very bad rendering performance. - del group['clip-path'] - -def gerber_difference(reference, actual, diff_out=None, svg_transform=None, size=(10,10), ref_unit_spec=None): - with tempfile.NamedTemporaryFile(suffix='.svg') as act_svg,\ - tempfile.NamedTemporaryFile(suffix='.svg') as ref_svg: - - gerbv_export(reference, ref_svg.name, size=size, export_format='svg', override_unit_spec=ref_unit_spec) - gerbv_export(actual, act_svg.name, size=size, export_format='svg') - - with svg_soup(ref_svg.name) as soup: - if svg_transform is not None: - svg = soup.svg - children = list(svg.children) - g = soup.new_tag('g', attrs={'transform': svg_transform}) - for c in children: - g.append(c.extract()) - svg.append(g) - - cleanup_gerbv_svg(soup) - - with svg_soup(act_svg.name) as soup: - cleanup_gerbv_svg(soup) - - return svg_difference(ref_svg.name, act_svg.name, diff_out=diff_out) - -def gerber_difference_merge(ref1, ref2, actual, diff_out=None, composite_out=None, svg_transform1=None, svg_transform2=None, size=(10,10)): - with tempfile.NamedTemporaryFile(suffix='.svg') as act_svg,\ - tempfile.NamedTemporaryFile(suffix='.svg') as ref1_svg,\ - tempfile.NamedTemporaryFile(suffix='.svg') as ref2_svg: - - gerbv_export(ref1, ref1_svg.name, size=size, export_format='svg') - gerbv_export(ref2, ref2_svg.name, size=size, export_format='svg') - gerbv_export(actual, act_svg.name, size=size, export_format='svg') - for var in ['ref1_svg', 'ref2_svg', 'act_svg']: - print(f'=== {var} ===') - print(Path(locals()[var].name).read_text().splitlines()[1]) - - with svg_soup(ref1_svg.name) as soup1: - if svg_transform1 is not None: - svg = soup1.svg - children = list(svg.children) - g = soup1.new_tag('g', attrs={'transform': svg_transform1}) - for c in children: - g.append(c.extract()) - svg.append(g) - cleanup_gerbv_svg(soup1) - - with svg_soup(ref2_svg.name) as soup2: - if svg_transform2 is not None: - svg = soup2.svg - children = list(svg.children) - g = soup2.new_tag('g', attrs={'transform': svg_transform2}) - for c in children: - g.append(c.extract()) - svg.append(g) - cleanup_gerbv_svg(soup2) - - defs1 = soup1.find('defs') - if not defs1: - defs1 = soup1.new_tag('defs') - soup1.find('svg').insert(0, defs1) - - defs2 = soup2.find('defs') - if defs2: - defs2 = defs2.extract() - # explicitly convert .contents into list here and below because else bs4 stumbles over itself - # iterating because we modify the tree in the loop body. - for c in list(defs2.contents): - if hasattr(c, 'attrs'): - c['id'] = 'gn-merge-b-' + c.attrs.get('id', str(id(c))) - defs1.append(c) - - for use in soup2.find_all('use', recursive=True): - if (href := use.get('xlink:href', '')).startswith('#'): - use['xlink:href'] = f'#gn-merge-b-{href[1:]}' - - svg1 = soup1.find('svg') - for c in list(soup2.find('svg').contents): - if hasattr(c, 'attrs'): - c['id'] = 'gn-merge-b-' + c.attrs.get('id', str(id(c))) - svg1.append(c) - - if composite_out: - shutil.copyfile(ref1_svg.name, composite_out) - - with svg_soup(act_svg.name) as soup: - cleanup_gerbv_svg(soup) - - return svg_difference(ref1_svg.name, act_svg.name, diff_out=diff_out) - -def svg_difference(reference, actual, diff_out=None, background=None, dpi=100): - with tempfile.NamedTemporaryFile(suffix='-ref.png') as ref_png,\ - tempfile.NamedTemporaryFile(suffix='-act.png') as act_png: - - svg_to_png(reference, ref_png.name, bg=background, dpi=dpi) - svg_to_png(actual, act_png.name, bg=background, dpi=dpi) - - return image_difference(ref_png.name, act_png.name, diff_out=diff_out) - -def image_difference(reference, actual, diff_out=None): - ref = np.array(Image.open(reference)).astype(float) - out = np.array(Image.open(actual)).astype(float) - - ref, out = ref.mean(axis=2), out.mean(axis=2) # convert to grayscale - # TODO blur images here before comparison to mitigate aliasing issue - delta = np.abs(out - ref).astype(float) / 255 - if diff_out: - Image.fromarray((delta*255).astype(np.uint8), mode='L').save(diff_out) - - hist, _bins = np.histogram(delta, bins=10, range=(0, 1)) - return (ImageDifference(delta.mean(), hist), - ImageDifference(delta.max(), hist), - Histogram(hist, out.size)) - - diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..900a415 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,69 @@ +[project] +name = "gerbonara" +version = "1.5.0" +description = "Tools to handle Gerber and Excellon files in Python" +readme = "README.md" +license = "Apache-2.0" +requires-python = ">=3.12" +dependencies = ["click", "rtree", "quart"] + +authors = [ + { name = "jaseg" }, + { name = "XenGi" }, +] + +maintainers = [ + { name = "Gerbonara maintainers", email = "gerbonara@jaseg.de" }, +] + +keywords = ["gerber", "excellon", "pcb", "RS274x", "EDA"] + +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Intended Audience :: Manufacturing", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: Apache Software License", + "Topic :: Artistic Software", + "Topic :: Multimedia :: Graphics", + "Topic :: Printing", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", + "Topic :: Scientific/Engineering :: Image Processing", + "Topic :: Utilities", +] + +[project.urls] +Homepage = "https://jaseg.de/projects/gerbonara/" +Documentation = "https://gerbolyze.gitlab.io/gerbonara/" +Source = "https://git.jaseg.de/gerbonara.git" +Tracker = "https://gitlab.com/gerbolyze/gerbonara/issues" + +[project.scripts] +gerbonara = "gerbonara.cli:cli" + +[dependency-groups] +dev = [ + "pytest", + "pytest-xdist", + "numpy", + "scipy", + "tqdm", + "beautifulsoup4", + "pillow" + ] + +[build-system] +requires = ["uv-build"] +build-backend = "uv_build" + +[tool.pytest] +testpaths = ["tests"] +norecursedirs = ["*"] +kicad_symbols_tag = "9.0.6" +kicad_footprints_tag = "9.0.6" +# Tag to use for container for footprint svg export +# For a list of available tags, see https://hub.docker.com/r/kicad/kicad/tags +kicad_container_tag = "9.0.6-full" diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 06d3c26..0000000 --- a/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -[pytest] -testpaths = gerbonara/tests -norecursedirs=* diff --git a/setup.py b/setup.py deleted file mode 100644 index c702896..0000000 --- a/setup.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 - -from pathlib import Path -from setuptools import setup, find_packages -import subprocess - -def version(): - try: - res = subprocess.run(['git', 'describe', '--tags', '--match', 'v*'], capture_output=True, check=True, text=True) - version, _, _rest = res.stdout.strip()[1:].partition('-') - return version - except: - subprocess.run(['git', 'describe', '--tags', '--match', 'v*']) - raise - -setup( - name='gerbonara', - version=version(), - author='jaseg, XenGi', - author_email='gerbonara@jaseg.de', - description='Tools to handle Gerber and Excellon files in Python', - long_description=Path('README.md').read_text(), - long_description_content_type='text/markdown', - url='https://gitlab.com/gerbolyze/gerbonara', - project_urls={ - 'Documentation': 'https://gerbolyze.gitlab.io/gerbonara/', - # 'Funding': 'https://donate.pypi.org', - # 'Say Thanks!': 'http://saythanks.io/to/example', - 'Source': 'https://gitlab.com/gerbolyze/gerbonara', - 'Tracker': 'https://gitlab.com/gerbolyze/gerbonara/issues', - }, - packages=find_packages(exclude=['tests']), - include_package_data=True, - install_requires=['click', 'rtree', 'quart'], - entry_points={ - 'console_scripts': [ - 'gerbonara = gerbonara.cli:cli', - ], - }, - classifiers=[ - 'Development Status :: 4 - Beta', - #'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'Intended Audience :: Manufacturing', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: Apache Software License', - 'Natural Language :: English', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Topic :: Artistic Software', - 'Topic :: Multimedia :: Graphics', - 'Topic :: Printing', - 'Topic :: Scientific/Engineering', - 'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)', - 'Topic :: Scientific/Engineering :: Image Processing', - 'Topic :: Utilities', - 'Typing :: Typed', - ], - keywords='gerber excellon pcb', - python_requires='>=3.10', -) diff --git a/gerbonara/NOTES b/src/gerbonara/NOTES similarity index 100% rename from gerbonara/NOTES rename to src/gerbonara/NOTES diff --git a/gerbonara/__init__.py b/src/gerbonara/__init__.py similarity index 93% rename from gerbonara/__init__.py rename to src/gerbonara/__init__.py index edbaa73..a78cef1 100644 --- a/gerbonara/__init__.py +++ b/src/gerbonara/__init__.py @@ -30,5 +30,6 @@ from .excellon import ExcellonFile from .ipc356 import Netlist from .layers import LayerStack from .utils import MM, Inch +from importlib.metadata import version -__version__ = '1.5.0' +__version__ = version('gerbonara') diff --git a/gerbonara/__main__.py b/src/gerbonara/__main__.py similarity index 100% rename from gerbonara/__main__.py rename to src/gerbonara/__main__.py diff --git a/gerbonara/aperture_macros/__init__.py b/src/gerbonara/aperture_macros/__init__.py similarity index 100% rename from gerbonara/aperture_macros/__init__.py rename to src/gerbonara/aperture_macros/__init__.py diff --git a/gerbonara/aperture_macros/expression.py b/src/gerbonara/aperture_macros/expression.py similarity index 100% rename from gerbonara/aperture_macros/expression.py rename to src/gerbonara/aperture_macros/expression.py diff --git a/gerbonara/aperture_macros/parse.py b/src/gerbonara/aperture_macros/parse.py similarity index 100% rename from gerbonara/aperture_macros/parse.py rename to src/gerbonara/aperture_macros/parse.py diff --git a/gerbonara/aperture_macros/primitive.py b/src/gerbonara/aperture_macros/primitive.py similarity index 100% rename from gerbonara/aperture_macros/primitive.py rename to src/gerbonara/aperture_macros/primitive.py diff --git a/gerbonara/apertures.py b/src/gerbonara/apertures.py similarity index 100% rename from gerbonara/apertures.py rename to src/gerbonara/apertures.py diff --git a/gerbonara/cad/__init__.py b/src/gerbonara/cad/__init__.py similarity index 100% rename from gerbonara/cad/__init__.py rename to src/gerbonara/cad/__init__.py diff --git a/gerbonara/cad/breakout.py b/src/gerbonara/cad/breakout.py similarity index 100% rename from gerbonara/cad/breakout.py rename to src/gerbonara/cad/breakout.py diff --git a/gerbonara/cad/data/__init__.py b/src/gerbonara/cad/data/__init__.py similarity index 100% rename from gerbonara/cad/data/__init__.py rename to src/gerbonara/cad/data/__init__.py diff --git a/gerbonara/cad/data/center-pad-spikes.kicad_mod b/src/gerbonara/cad/data/center-pad-spikes.kicad_mod similarity index 100% rename from gerbonara/cad/data/center-pad-spikes.kicad_mod rename to src/gerbonara/cad/data/center-pad-spikes.kicad_mod diff --git a/gerbonara/cad/data/pad-between-spiked.kicad_mod b/src/gerbonara/cad/data/pad-between-spiked.kicad_mod similarity index 100% rename from gerbonara/cad/data/pad-between-spiked.kicad_mod rename to src/gerbonara/cad/data/pad-between-spiked.kicad_mod diff --git a/gerbonara/cad/data/tht-0.8.kicad_mod b/src/gerbonara/cad/data/tht-0.8.kicad_mod similarity index 100% rename from gerbonara/cad/data/tht-0.8.kicad_mod rename to src/gerbonara/cad/data/tht-0.8.kicad_mod diff --git a/gerbonara/cad/kicad/__init__.py b/src/gerbonara/cad/kicad/__init__.py similarity index 100% rename from gerbonara/cad/kicad/__init__.py rename to src/gerbonara/cad/kicad/__init__.py diff --git a/gerbonara/cad/kicad/base_types.py b/src/gerbonara/cad/kicad/base_types.py similarity index 100% rename from gerbonara/cad/kicad/base_types.py rename to src/gerbonara/cad/kicad/base_types.py diff --git a/gerbonara/cad/kicad/footprints.py b/src/gerbonara/cad/kicad/footprints.py similarity index 100% rename from gerbonara/cad/kicad/footprints.py rename to src/gerbonara/cad/kicad/footprints.py diff --git a/gerbonara/cad/kicad/graphical_primitives.py b/src/gerbonara/cad/kicad/graphical_primitives.py similarity index 100% rename from gerbonara/cad/kicad/graphical_primitives.py rename to src/gerbonara/cad/kicad/graphical_primitives.py diff --git a/gerbonara/cad/kicad/layer_colors.py b/src/gerbonara/cad/kicad/layer_colors.py similarity index 100% rename from gerbonara/cad/kicad/layer_colors.py rename to src/gerbonara/cad/kicad/layer_colors.py diff --git a/gerbonara/cad/kicad/pcb.py b/src/gerbonara/cad/kicad/pcb.py similarity index 100% rename from gerbonara/cad/kicad/pcb.py rename to src/gerbonara/cad/kicad/pcb.py diff --git a/gerbonara/cad/kicad/primitives.py b/src/gerbonara/cad/kicad/primitives.py similarity index 100% rename from gerbonara/cad/kicad/primitives.py rename to src/gerbonara/cad/kicad/primitives.py diff --git a/gerbonara/cad/kicad/schematic.py b/src/gerbonara/cad/kicad/schematic.py similarity index 100% rename from gerbonara/cad/kicad/schematic.py rename to src/gerbonara/cad/kicad/schematic.py diff --git a/gerbonara/cad/kicad/schematic_colors.py b/src/gerbonara/cad/kicad/schematic_colors.py similarity index 100% rename from gerbonara/cad/kicad/schematic_colors.py rename to src/gerbonara/cad/kicad/schematic_colors.py diff --git a/gerbonara/cad/kicad/sexp.py b/src/gerbonara/cad/kicad/sexp.py similarity index 100% rename from gerbonara/cad/kicad/sexp.py rename to src/gerbonara/cad/kicad/sexp.py diff --git a/gerbonara/cad/kicad/sexp_mapper.py b/src/gerbonara/cad/kicad/sexp_mapper.py similarity index 100% rename from gerbonara/cad/kicad/sexp_mapper.py rename to src/gerbonara/cad/kicad/sexp_mapper.py diff --git a/gerbonara/cad/kicad/symbols.py b/src/gerbonara/cad/kicad/symbols.py similarity index 100% rename from gerbonara/cad/kicad/symbols.py rename to src/gerbonara/cad/kicad/symbols.py diff --git a/gerbonara/cad/kicad/tmtheme.py b/src/gerbonara/cad/kicad/tmtheme.py similarity index 100% rename from gerbonara/cad/kicad/tmtheme.py rename to src/gerbonara/cad/kicad/tmtheme.py diff --git a/gerbonara/cad/primitives.py b/src/gerbonara/cad/primitives.py similarity index 100% rename from gerbonara/cad/primitives.py rename to src/gerbonara/cad/primitives.py diff --git a/gerbonara/cad/protoboard.py b/src/gerbonara/cad/protoboard.py similarity index 100% rename from gerbonara/cad/protoboard.py rename to src/gerbonara/cad/protoboard.py diff --git a/gerbonara/cad/protoserve.py b/src/gerbonara/cad/protoserve.py similarity index 100% rename from gerbonara/cad/protoserve.py rename to src/gerbonara/cad/protoserve.py diff --git a/gerbonara/cad/protoserve_data/__init__.py b/src/gerbonara/cad/protoserve_data/__init__.py similarity index 100% rename from gerbonara/cad/protoserve_data/__init__.py rename to src/gerbonara/cad/protoserve_data/__init__.py diff --git a/gerbonara/cad/protoserve_data/protoserve.html b/src/gerbonara/cad/protoserve_data/protoserve.html similarity index 100% rename from gerbonara/cad/protoserve_data/protoserve.html rename to src/gerbonara/cad/protoserve_data/protoserve.html diff --git a/gerbonara/cam.py b/src/gerbonara/cam.py similarity index 100% rename from gerbonara/cam.py rename to src/gerbonara/cam.py diff --git a/gerbonara/cli.py b/src/gerbonara/cli.py similarity index 100% rename from gerbonara/cli.py rename to src/gerbonara/cli.py diff --git a/gerbonara/data/__init__.py b/src/gerbonara/data/__init__.py similarity index 100% rename from gerbonara/data/__init__.py rename to src/gerbonara/data/__init__.py diff --git a/gerbonara/data/newstroke_font.cpp b/src/gerbonara/data/newstroke_font.cpp similarity index 100% rename from gerbonara/data/newstroke_font.cpp rename to src/gerbonara/data/newstroke_font.cpp diff --git a/gerbonara/excellon.py b/src/gerbonara/excellon.py similarity index 100% rename from gerbonara/excellon.py rename to src/gerbonara/excellon.py diff --git a/gerbonara/graphic_objects.py b/src/gerbonara/graphic_objects.py similarity index 100% rename from gerbonara/graphic_objects.py rename to src/gerbonara/graphic_objects.py diff --git a/gerbonara/graphic_primitives.py b/src/gerbonara/graphic_primitives.py similarity index 100% rename from gerbonara/graphic_primitives.py rename to src/gerbonara/graphic_primitives.py diff --git a/gerbonara/ipc356.py b/src/gerbonara/ipc356.py similarity index 100% rename from gerbonara/ipc356.py rename to src/gerbonara/ipc356.py diff --git a/gerbonara/layer_rules.py b/src/gerbonara/layer_rules.py similarity index 100% rename from gerbonara/layer_rules.py rename to src/gerbonara/layer_rules.py diff --git a/gerbonara/layers.py b/src/gerbonara/layers.py similarity index 100% rename from gerbonara/layers.py rename to src/gerbonara/layers.py diff --git a/gerbonara/newstroke.py b/src/gerbonara/newstroke.py similarity index 100% rename from gerbonara/newstroke.py rename to src/gerbonara/newstroke.py diff --git a/gerbonara/rs274x.py b/src/gerbonara/rs274x.py similarity index 100% rename from gerbonara/rs274x.py rename to src/gerbonara/rs274x.py diff --git a/gerbonara/utils.py b/src/gerbonara/utils.py similarity index 100% rename from gerbonara/utils.py rename to src/gerbonara/utils.py diff --git a/gerbonara/tests/.gitignore b/tests/.gitignore similarity index 100% rename from gerbonara/tests/.gitignore rename to tests/.gitignore diff --git a/gerbonara/tests/__init__.py b/tests/__init__.py similarity index 100% rename from gerbonara/tests/__init__.py rename to tests/__init__.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..fbf4b0a --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,129 @@ + +import os +from pathlib import Path +import tqdm +import multiprocessing.pool +import subprocess +from itertools import chain + +import pytest + +from .image_support import ImageDifference, run_cargo_cmd, ImageSupport + + +@pytest.fixture +def kicad_container(request): + return request.config.kicad_container + + +@pytest.fixture() +def kicad_footprints_libdir(request): + return request.config.kicad_footprints_libdir + + +@pytest.fixture() +def kicad_symbols_libdir(request): + return request.config.kicad_symbols_libdir + + +@pytest.fixture() +def img_support(request): + return request.config.image_support + + +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' Calculated difference: {diff}', + f' Histogram: {diff.histogram}', ] + + +# store report in node object so tmp_gbr can determine if the test failed. +@pytest.hookimpl(tryfirst=True, hookwrapper=True) +def pytest_runtest_makereport(item, call): + outcome = yield + rep = outcome.get_result() + setattr(item, f'rep_{rep.when}', rep) + + +fail_dir = Path('gerbonara_test_failures') +def pytest_sessionstart(session): + if 'PYTEST_XDIST_WORKER' in os.environ: # only run this on the controller + return + + for f in chain(fail_dir.glob('*.gbr'), fail_dir.glob('*.png')): + f.unlink() + + try: + run_cargo_cmd('resvg', '--help', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except FileNotFoundError: + pytest.exit('resvg binary not found, aborting test.', 2) + + +def _update_repo_cache(lib_dir, repo_url, tag): + if not lib_dir.is_dir(): + print(f'Checking out KiCad footprint repo tag {tag}') + subprocess.run(['git', '-c', 'advice.detachedHead=false', 'clone', '--branch', tag, '--depth', '1', repo_url, str(lib_dir)], check=True) + else: + print(f'Found cached KiCad footprint checkout, updating to {tag}') + subprocess.run(['git', '-C', str(lib_dir), 'fetch', '--depth', '1', 'origin', tag], check=True) + subprocess.run(['git', '-c', 'advice.detachedHead=false', '-C', str(lib_dir), 'reset', '--hard', tag], check=True) + subprocess.run(['git', '-C', str(lib_dir), 'clean', '--force', '-d', '-x'], check=True) + + +def pytest_addoption(parser): + parser.addini('kicad_footprints_tag', 'git tag or branch for KiCad footprint library repo used as testdata', default='main') + parser.addini('kicad_symbols_tag', 'git tag or branch for KiCad symbol library repo used as testdata', default='main') + parser.addini('kicad_container_tag', 'docker hub tag for the KiCad container to use for exporting footprint images', default='main') + + +def pytest_configure(config): + os.nice(20) + # Resvg can sometimes consume a lot of memory. Make sure we don't kill the user's session. + if (oom_adj := Path('/proc/self/oom_adj')).is_file(): + oom_adj.write_text('15\n') + + if 'PYTEST_XDIST_WORKER' in os.environ: # only run this on the controller + return + + if (lib_dir := os.environ.get('KICAD_FOOTPRINTS')): + config.kicad_footprints_libdir = Path(lib_dir).expanduser() + else: + config.kicad_footprints_libdir = config.cache.mkdir('kicad-footprints') / 'repo' + + if (lib_dir := os.environ.get('KICAD_SYMBOLS')): + config.kicad_symbols_libdir = Path(lib_dir).expanduser() + else: + config.kicad_symbols_libdir = config.cache.mkdir('kicad-symbols') / 'repo' + + # Update cached library repos unless they are overridden from outside. + if not os.environ.get('KICAD_FOOTPRINTS'): + tag = config.getini('kicad_footprints_tag') + _update_repo_cache(config.kicad_footprints_libdir, 'https://gitlab.com/kicad/libraries/kicad-footprints', tag) + + if not os.environ.get('KICAD_SYMBOLS'): + tag = config.getini('kicad_symbols_tag') + _update_repo_cache(config.kicad_symbols_libdir, 'https://gitlab.com/kicad/libraries/kicad-symbols', tag) + + print('Updating podman image') + tag = config.getini("kicad_container_tag") + config.kicad_container = os.environ.get('KICAD_CONTAINER', f'registry.hub.docker.com/kicad/kicad:{tag}') + subprocess.run(['podman', 'pull', config.kicad_container], check=True) + + config.image_support = ImageSupport(config.cache.mkdir('image_cache'), config.kicad_container) + print('Checking KiCad footprint library render cache') + with multiprocessing.pool.ThreadPool() as pool: # use thread pool here since we're only monitoring podman processes + lib_dirs = list(config.kicad_footprints_libdir.glob('*.pretty')) + res = list(tqdm.tqdm(pool.imap(lambda path: config.image_support.bulk_populate_kicad_fp_export_cache(path), lib_dirs), total=len(lib_dirs))) + + +def pytest_generate_tests(metafunc): + if 'kicad_library_file' in metafunc.fixturenames: + library_files = list(kicad_symbols_libdir(metafunc.config).glob('*.kicad_sym')) + metafunc.parametrize('kicad_library_file', library_files, ids=list(map(str, library_files))) + + if 'kicad_mod_file' in metafunc.fixturenames: + mod_files = list(kicad_footprints_libdir(metafunc.config).glob('*.pretty/*.kicad_mod')) + metafunc.parametrize('kicad_mod_file', mod_files, ids=list(map(str, mod_files))) diff --git a/gerbonara/tests/golden/example_am_exposure_modifier.png b/tests/golden/example_am_exposure_modifier.png similarity index 100% rename from gerbonara/tests/golden/example_am_exposure_modifier.png rename to tests/golden/example_am_exposure_modifier.png diff --git a/gerbonara/tests/golden/example_coincident_hole.png b/tests/golden/example_coincident_hole.png similarity index 100% rename from gerbonara/tests/golden/example_coincident_hole.png rename to tests/golden/example_coincident_hole.png diff --git a/gerbonara/tests/golden/example_cutin.png b/tests/golden/example_cutin.png similarity index 100% rename from gerbonara/tests/golden/example_cutin.png rename to tests/golden/example_cutin.png diff --git a/gerbonara/tests/golden/example_cutin_multiple.png b/tests/golden/example_cutin_multiple.png similarity index 100% rename from gerbonara/tests/golden/example_cutin_multiple.png rename to tests/golden/example_cutin_multiple.png diff --git a/gerbonara/tests/golden/example_flash_circle.png b/tests/golden/example_flash_circle.png similarity index 100% rename from gerbonara/tests/golden/example_flash_circle.png rename to tests/golden/example_flash_circle.png diff --git a/gerbonara/tests/golden/example_flash_obround.png b/tests/golden/example_flash_obround.png similarity index 100% rename from gerbonara/tests/golden/example_flash_obround.png rename to tests/golden/example_flash_obround.png diff --git a/gerbonara/tests/golden/example_flash_polygon.png b/tests/golden/example_flash_polygon.png similarity index 100% rename from gerbonara/tests/golden/example_flash_polygon.png rename to tests/golden/example_flash_polygon.png diff --git a/gerbonara/tests/golden/example_flash_rectangle.png b/tests/golden/example_flash_rectangle.png similarity index 100% rename from gerbonara/tests/golden/example_flash_rectangle.png rename to tests/golden/example_flash_rectangle.png diff --git a/gerbonara/tests/golden/example_fully_coincident.png b/tests/golden/example_fully_coincident.png similarity index 100% rename from gerbonara/tests/golden/example_fully_coincident.png rename to tests/golden/example_fully_coincident.png diff --git a/gerbonara/tests/golden/example_holes_dont_clear.png b/tests/golden/example_holes_dont_clear.png similarity index 100% rename from gerbonara/tests/golden/example_holes_dont_clear.png rename to tests/golden/example_holes_dont_clear.png diff --git a/gerbonara/tests/golden/example_level_holes.png b/tests/golden/example_level_holes.png similarity index 100% rename from gerbonara/tests/golden/example_level_holes.png rename to tests/golden/example_level_holes.png diff --git a/gerbonara/tests/golden/example_not_overlapping_contour.png b/tests/golden/example_not_overlapping_contour.png similarity index 100% rename from gerbonara/tests/golden/example_not_overlapping_contour.png rename to tests/golden/example_not_overlapping_contour.png diff --git a/gerbonara/tests/golden/example_not_overlapping_touching.png b/tests/golden/example_not_overlapping_touching.png similarity index 100% rename from gerbonara/tests/golden/example_not_overlapping_touching.png rename to tests/golden/example_not_overlapping_touching.png diff --git a/gerbonara/tests/golden/example_overlapping_contour.png b/tests/golden/example_overlapping_contour.png similarity index 100% rename from gerbonara/tests/golden/example_overlapping_contour.png rename to tests/golden/example_overlapping_contour.png diff --git a/gerbonara/tests/golden/example_overlapping_touching.png b/tests/golden/example_overlapping_touching.png similarity index 100% rename from gerbonara/tests/golden/example_overlapping_touching.png rename to tests/golden/example_overlapping_touching.png diff --git a/gerbonara/tests/golden/example_simple_contour.png b/tests/golden/example_simple_contour.png similarity index 100% rename from gerbonara/tests/golden/example_simple_contour.png rename to tests/golden/example_simple_contour.png diff --git a/gerbonara/tests/golden/example_single_contour.png b/tests/golden/example_single_contour.png similarity index 100% rename from gerbonara/tests/golden/example_single_contour.png rename to tests/golden/example_single_contour.png diff --git a/gerbonara/tests/golden/example_single_contour_3.png b/tests/golden/example_single_contour_3.png similarity index 100% rename from gerbonara/tests/golden/example_single_contour_3.png rename to tests/golden/example_single_contour_3.png diff --git a/gerbonara/tests/golden/example_two_square_boxes.gbr b/tests/golden/example_two_square_boxes.gbr similarity index 100% rename from gerbonara/tests/golden/example_two_square_boxes.gbr rename to tests/golden/example_two_square_boxes.gbr diff --git a/gerbonara/tests/golden/example_two_square_boxes.png b/tests/golden/example_two_square_boxes.png similarity index 100% rename from gerbonara/tests/golden/example_two_square_boxes.png rename to tests/golden/example_two_square_boxes.png diff --git a/tests/image_support.py b/tests/image_support.py new file mode 100644 index 0000000..589fb44 --- /dev/null +++ b/tests/image_support.py @@ -0,0 +1,372 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2022 Jan Sebastian Götte +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Based on https://github.com/tracespace/tracespace +# + +import subprocess +from pathlib import Path +import tempfile +import textwrap +import os +import sys +import stat +import random +import statistics +from functools import total_ordering +import shutil +import bs4 +from contextlib import contextmanager +import hashlib + +import tqdm +import numpy as np +from PIL import Image + +@total_ordering +class ImageDifference: + def __init__(self, value, histogram): + self.value = value + self.histogram = histogram + + 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)) + +@total_ordering +class Histogram: + def __init__(self, value, size): + self.value, self.size = value, size + + def __eq__(self, other): + other = np.array(other) + other[other == None] = self.value[other == None] + return (self.value == other).all() + + def __lt__(self, other): + other = np.array(other) + other[other == None] = self.value[other == None] + return (self.value <= other).all() + + def __getitem__(self, index): + return self.value[index] + + def __str__(self): + return f'{list(self.value)} size={self.size}' + + +def run_cargo_cmd(cmd, args, **kwargs): + if cmd.upper() in os.environ: + return subprocess.run([os.environ[cmd.upper()], *args], **kwargs) + + try: + return subprocess.run([cmd, *args], **kwargs) + + except FileNotFoundError: + return subprocess.run([str(Path.home() / '.cargo' / 'bin' / cmd), *args], **kwargs) + + +to_gerbv_svg_units = lambda val, unit='mm': val*72 if unit == 'inch' else val/25.4*72 + + +@contextmanager +def svg_soup(filename): + with open(filename, 'r') as f: + soup = bs4.BeautifulSoup(f.read(), 'xml') + + yield soup + + with open(filename, 'w') as f: + f.write(str(soup)) + + +class ImageSupport: + def __init__(self, cachedir, kicad_container): + self.cache_root = cachedir + self.kicad_container = kicad_container + + + def cachedir(self, scope, filename, suffix): + return self.cache_root / scope / f'{filename}.{suffix}' + + + def svg_to_png(self, in_svg, out_png, dpi=100, bg=None): + params = f'{dpi}{bg}'.encode() + digest = hashlib.blake2b(Path(in_svg).read_bytes() + params).hexdigest() + cachefile = self.cachedir('svg_render', digest, 'png') + + if not cachefile.is_file(): + bg = 'black' if bg is None else bg + run_cargo_cmd('resvg', ['--background', bg, '--dpi', str(dpi), in_svg, cachefile], check=True, stdout=subprocess.DEVNULL) + + shutil.copy(cachefile, out_png) + + + def gerbv_export(self, in_gbr, out_svg, export_format='svg', origin=(0, 0), size=(6, 6), fg='#ffffff', bg='#000000', override_unit_spec=None): + params = f'{origin}{size}{fg}{bg}'.encode() + digest = hashlib.blake2b(Path(in_gbr).read_bytes() + params).hexdigest() + cachefile = self.cachedir('gerbv-export', digest, 'svg') + + if not cachefile.is_file(): + print(f'Building cache for {Path(in_gbr).name}') + # NOTE: gerbv seems to always export 'clear' polarity apertures as white, irrespective of --foreground, --background + # and project file color settings. + # TODO: File issue upstream. + with tempfile.NamedTemporaryFile('w') as f: + if override_unit_spec: + units, zeros, digits = override_unit_spec + print(f'{Path(in_gbr).name}: overriding excellon unit spec to {units=} {zeros=} {digits=}') + units = 0 if units == 'inch' else 1 + zeros = {None: 0, 'leading': 1, 'trailing': 2}[zeros] + unit_spec = textwrap.dedent(f'''(cons 'attribs (list + (list 'autodetect 'Boolean 0) + (list 'zero_suppression 'Enum {zeros}) + (list 'units 'Enum {units}) + (list 'digits 'Integer {digits}) + ))''') + else: + unit_spec = '' + + r, g, b = int(fg[1:3], 16), int(fg[3:5], 16), int(fg[5:], 16) + color = f"(cons 'color #({r*257} {g*257} {b*257}))" + f.write(f'''(gerbv-file-version! "2.0A")(define-layer! 0 (cons 'filename "{in_gbr}"){unit_spec}{color})''') + f.flush() + if override_unit_spec: + shutil.copy(f.name, '/tmp/foo.gbv') + + x, y = origin + w, h = size + cmd = ['gerbv', '-x', export_format, + '--border=0', + f'--origin={x:.6f}x{y:.6f}', f'--window_inch={w:.6f}x{h:.6f}', + f'--background={bg}', + f'--foreground={fg}', + '-o', str(cachefile), '-p', f.name] + subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + else: + print(f'Re-using cache for {Path(in_gbr).name}') + shutil.copy(cachefile, out_svg) + + + def kicad_fp_export(self, mod_file, out_svg): + mod_file = Path(mod_file) + if mod_file.suffix.lower() != '.kicad_mod': + raise ValueError("KiCad footprint file must have .kicad_mod extension for kicad-cli to do it's thing") + + params = f'(noparams)'.encode() + digest = hashlib.blake2b(mod_file.read_bytes() + params).hexdigest() + cachefile = self.cachedir('kicad-footprint-export', digest, 'svg') + + if not cachefile.is_file(): + print(f'Building cache for {mod_file.name}') + + with tempfile.TemporaryDirectory() as tmpdir: + os.chmod(tmpdir, 0o1777) + pretty_dir = mod_file.parent + fp_name = mod_file.name[:-len('.kicad_mod')] + cmd = ['podman', 'run', + '--rm', # Clean up volumes after exit + '--userns=keep-id', # To allow container to read from bind mount + '--mount', f'type=bind,src={pretty_dir},dst=/{pretty_dir.name}', + '--mount', f'type=bind,src={tmpdir},dst=/out', + self.kicad_container, + 'kicad-cli', 'fp', 'export', 'svg', '--output', '/out', '--footprint', fp_name, f'/{pretty_dir.name}'] + subprocess.run(cmd, check=True) #, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + out_file = Path(tmpdir) / f'{fp_name}.svg' + shutil.copy(out_file, cachefile) + else: + print(f'Re-using cache for {mod_file.name}') + shutil.copy(cachefile, out_svg) + + + def bulk_populate_kicad_fp_export_cache(self, pretty_dir): + def cachefile(mod_file): + params = f'(noparams)'.encode() + digest = hashlib.blake2b(mod_file.read_bytes() + params).hexdigest() + return self.cachedir('kicad-footprint-export', digest, 'svg') + + mod_files = list(pretty_dir.glob('*.kicad_mod')) + hit_rate = statistics.mean([int(cachefile(fn).is_file()) + for fn in random.sample(mod_files, min(len(mod_files), 50))]) + + if hit_rate < 0.9: + #tqdm.tqdm.write(f'Modfile cache is out of date (hit rate {hit_rate*100:.0f}%), re-building entire cache in bulk') + + with tempfile.TemporaryDirectory() as tmpdir: + os.chmod(tmpdir, 0o1777) + cmd = ['podman', 'run', + '--rm', # Clean up volumes after exit + '--userns=keep-id', # To allow container to read from bind mount + '--mount', f'type=bind,src={pretty_dir},dst=/{pretty_dir.name}', + '--mount', f'type=bind,src={tmpdir},dst=/out', + self.kicad_container, + 'kicad-cli', 'fp', 'export', 'svg', '--output', '/out', f'/{pretty_dir.name}'] + print(f'Running "{" ".join(cmd)}"') + + try: + subprocess.run(cmd, check=True) #, stdout=subprocess.DEVNULL) + + except subprocess.CalledProcessError as e: + print('Error running command with command line:', ' '.join(e.cmd), file=sys.stderr) + raise e + + for fn in mod_files: + out_file = Path(tmpdir) / fn.with_suffix('.svg').name + if not out_file.is_file(): + tqdm.tqdm.write(f'Output file {out_file} is missing while bulk re-building cache for {pretty_dir}.') + else: + shutil.copy(out_file, cachefile(fn)) + + + def cleanup_gerbv_svg(self, soup): + width = soup.svg["width"] + height = soup.svg["height"] + width = width[:-2] if width.endswith('pt') else width + height = height[:-2] if height.endswith('pt') else height + soup.svg['width'] = f'{float(width)/72*25.4:.4f}mm' + soup.svg['height'] = f'{float(height)/72*25.4:.4f}mm' + for group in soup.find_all('g'): + # gerbv uses Cairo's SVG canvas. Cairo's SVG canvas is kind of broken. It has no support for unit + # handling at all, which means the output files just end up being in pixels at 72 dpi. Further, it + # seems gerbv's aperture macro rendering interacts poorly with Cairo's SVG export. gerbv renders + # aperture macros into a new surface, which for some reason gets clipped by Cairo to the given + # canvas size. This is just wrong, so we just nuke the clip path from these SVG groups here. + # + # Apart from being graphically broken, this additionally causes very bad rendering performance. + del group['clip-path'] + + def gerber_difference(self, reference, actual, diff_out=None, svg_transform=None, size=(10,10), ref_unit_spec=None): + with tempfile.NamedTemporaryFile(suffix='.svg') as act_svg,\ + tempfile.NamedTemporaryFile(suffix='.svg') as ref_svg: + + self.gerbv_export(reference, ref_svg.name, size=size, export_format='svg', override_unit_spec=ref_unit_spec) + self.gerbv_export(actual, act_svg.name, size=size, export_format='svg') + + with svg_soup(ref_svg.name) as soup: + if svg_transform is not None: + svg = soup.svg + children = list(svg.children) + g = soup.new_tag('g', attrs={'transform': svg_transform}) + for c in children: + g.append(c.extract()) + svg.append(g) + + self.cleanup_gerbv_svg(soup) + + with svg_soup(act_svg.name) as soup: + self.cleanup_gerbv_svg(soup) + + return self.svg_difference(ref_svg.name, act_svg.name, diff_out=diff_out) + + def gerber_difference_merge(self, ref1, ref2, actual, diff_out=None, composite_out=None, svg_transform1=None, svg_transform2=None, size=(10,10)): + with tempfile.NamedTemporaryFile(suffix='.svg') as act_svg,\ + tempfile.NamedTemporaryFile(suffix='.svg') as ref1_svg,\ + tempfile.NamedTemporaryFile(suffix='.svg') as ref2_svg: + + self.gerbv_export(ref1, ref1_svg.name, size=size, export_format='svg') + self.gerbv_export(ref2, ref2_svg.name, size=size, export_format='svg') + self.gerbv_export(actual, act_svg.name, size=size, export_format='svg') + for var in ['ref1_svg', 'ref2_svg', 'act_svg']: + print(f'=== {var} ===') + print(Path(locals()[var].name).read_text().splitlines()[1]) + + with svg_soup(ref1_svg.name) as soup1: + if svg_transform1 is not None: + svg = soup1.svg + children = list(svg.children) + g = soup1.new_tag('g', attrs={'transform': svg_transform1}) + for c in children: + g.append(c.extract()) + svg.append(g) + self.cleanup_gerbv_svg(soup1) + + with svg_soup(ref2_svg.name) as soup2: + if svg_transform2 is not None: + svg = soup2.svg + children = list(svg.children) + g = soup2.new_tag('g', attrs={'transform': svg_transform2}) + for c in children: + g.append(c.extract()) + svg.append(g) + self.cleanup_gerbv_svg(soup2) + + defs1 = soup1.find('defs') + if not defs1: + defs1 = soup1.new_tag('defs') + soup1.find('svg').insert(0, defs1) + + defs2 = soup2.find('defs') + if defs2: + defs2 = defs2.extract() + # explicitly convert .contents into list here and below because else bs4 stumbles over itself + # iterating because we modify the tree in the loop body. + for c in list(defs2.contents): + if hasattr(c, 'attrs'): + c['id'] = 'gn-merge-b-' + c.attrs.get('id', str(id(c))) + defs1.append(c) + + for use in soup2.find_all('use', recursive=True): + if (href := use.get('xlink:href', '')).startswith('#'): + use['xlink:href'] = f'#gn-merge-b-{href[1:]}' + + svg1 = soup1.find('svg') + for c in list(soup2.find('svg').contents): + if hasattr(c, 'attrs'): + c['id'] = 'gn-merge-b-' + c.attrs.get('id', str(id(c))) + svg1.append(c) + + if composite_out: + shutil.copyfile(ref1_svg.name, composite_out) + + with svg_soup(act_svg.name) as soup: + self.cleanup_gerbv_svg(soup) + + return self.svg_difference(ref1_svg.name, act_svg.name, diff_out=diff_out) + + def svg_difference(self, reference, actual, diff_out=None, background=None, dpi=100): + with tempfile.NamedTemporaryFile(suffix='-ref.png') as ref_png,\ + tempfile.NamedTemporaryFile(suffix='-act.png') as act_png: + + svg_to_png(reference, ref_png.name, bg=background, dpi=dpi) + svg_to_png(actual, act_png.name, bg=background, dpi=dpi) + + return self.image_difference(ref_png.name, act_png.name, diff_out=diff_out) + + def image_difference(self, reference, actual, diff_out=None): + ref = np.array(Image.open(reference)).astype(float) + out = np.array(Image.open(actual)).astype(float) + + ref, out = ref.mean(axis=2), out.mean(axis=2) # convert to grayscale + # TODO blur images here before comparison to mitigate aliasing issue + delta = np.abs(out - ref).astype(float) / 255 + if diff_out: + Image.fromarray((delta*255).astype(np.uint8), mode='L').save(diff_out) + + hist, _bins = np.histogram(delta, bins=10, range=(0, 1)) + return (ImageDifference(delta.mean(), hist), + ImageDifference(delta.max(), hist), + Histogram(hist, out.size)) + + diff --git a/gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.Apr b/tests/resources/Target3001/IRNASIoTbank1.2.Apr similarity index 100% rename from gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.Apr rename to tests/resources/Target3001/IRNASIoTbank1.2.Apr diff --git a/gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.Bot b/tests/resources/Target3001/IRNASIoTbank1.2.Bot similarity index 100% rename from gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.Bot rename to tests/resources/Target3001/IRNASIoTbank1.2.Bot diff --git a/gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.Drill b/tests/resources/Target3001/IRNASIoTbank1.2.Drill similarity index 100% rename from gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.Drill rename to tests/resources/Target3001/IRNASIoTbank1.2.Drill diff --git a/gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.Info b/tests/resources/Target3001/IRNASIoTbank1.2.Info similarity index 100% rename from gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.Info rename to tests/resources/Target3001/IRNASIoTbank1.2.Info diff --git a/gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.Outline b/tests/resources/Target3001/IRNASIoTbank1.2.Outline similarity index 100% rename from gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.Outline rename to tests/resources/Target3001/IRNASIoTbank1.2.Outline diff --git a/gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.PasteBot b/tests/resources/Target3001/IRNASIoTbank1.2.PasteBot similarity index 100% rename from gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.PasteBot rename to tests/resources/Target3001/IRNASIoTbank1.2.PasteBot diff --git a/gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.PasteTop b/tests/resources/Target3001/IRNASIoTbank1.2.PasteTop similarity index 100% rename from gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.PasteTop rename to tests/resources/Target3001/IRNASIoTbank1.2.PasteTop diff --git a/gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.PosiBot b/tests/resources/Target3001/IRNASIoTbank1.2.PosiBot similarity index 100% rename from gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.PosiBot rename to tests/resources/Target3001/IRNASIoTbank1.2.PosiBot diff --git a/gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.PosiTop b/tests/resources/Target3001/IRNASIoTbank1.2.PosiTop similarity index 100% rename from gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.PosiTop rename to tests/resources/Target3001/IRNASIoTbank1.2.PosiTop diff --git a/gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.StopBot b/tests/resources/Target3001/IRNASIoTbank1.2.StopBot similarity index 100% rename from gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.StopBot rename to tests/resources/Target3001/IRNASIoTbank1.2.StopBot diff --git a/gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.StopTop b/tests/resources/Target3001/IRNASIoTbank1.2.StopTop similarity index 100% rename from gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.StopTop rename to tests/resources/Target3001/IRNASIoTbank1.2.StopTop diff --git a/gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.Tool b/tests/resources/Target3001/IRNASIoTbank1.2.Tool similarity index 100% rename from gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.Tool rename to tests/resources/Target3001/IRNASIoTbank1.2.Tool diff --git a/gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.Top b/tests/resources/Target3001/IRNASIoTbank1.2.Top similarity index 100% rename from gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.Top rename to tests/resources/Target3001/IRNASIoTbank1.2.Top diff --git a/gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.Whl b/tests/resources/Target3001/IRNASIoTbank1.2.Whl similarity index 100% rename from gerbonara/tests/resources/Target3001/IRNASIoTbank1.2.Whl rename to tests/resources/Target3001/IRNASIoTbank1.2.Whl diff --git a/gerbonara/tests/resources/Target3001/LICENSE b/tests/resources/Target3001/LICENSE similarity index 100% rename from gerbonara/tests/resources/Target3001/LICENSE rename to tests/resources/Target3001/LICENSE diff --git a/gerbonara/tests/resources/Target3001/README b/tests/resources/Target3001/README similarity index 100% rename from gerbonara/tests/resources/Target3001/README rename to tests/resources/Target3001/README diff --git a/gerbonara/tests/resources/allegro-2/MINNOWMAX_REVA2_PUBLIC_BOTTOMSIDE.pdf b/tests/resources/allegro-2/MINNOWMAX_REVA2_PUBLIC_BOTTOMSIDE.pdf similarity index 100% rename from gerbonara/tests/resources/allegro-2/MINNOWMAX_REVA2_PUBLIC_BOTTOMSIDE.pdf rename to tests/resources/allegro-2/MINNOWMAX_REVA2_PUBLIC_BOTTOMSIDE.pdf diff --git a/gerbonara/tests/resources/allegro-2/MINNOWMAX_REVA2_PUBLIC_TOPSIDE.pdf b/tests/resources/allegro-2/MINNOWMAX_REVA2_PUBLIC_TOPSIDE.pdf similarity index 100% rename from gerbonara/tests/resources/allegro-2/MINNOWMAX_REVA2_PUBLIC_TOPSIDE.pdf rename to tests/resources/allegro-2/MINNOWMAX_REVA2_PUBLIC_TOPSIDE.pdf diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_DRILL/MinnowMax_RevA1_NCDRILL.drl b/tests/resources/allegro-2/MinnowMax_RevA1_DRILL/MinnowMax_RevA1_NCDRILL.drl similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_DRILL/MinnowMax_RevA1_NCDRILL.drl rename to tests/resources/allegro-2/MinnowMax_RevA1_DRILL/MinnowMax_RevA1_NCDRILL.drl diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_DRILL/MinnowMax_RevA1_NCROUTE.rou b/tests/resources/allegro-2/MinnowMax_RevA1_DRILL/MinnowMax_RevA1_NCROUTE.rou similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_DRILL/MinnowMax_RevA1_NCROUTE.rou rename to tests/resources/allegro-2/MinnowMax_RevA1_DRILL/MinnowMax_RevA1_NCROUTE.rou diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_DRILL/nc_param.txt b/tests/resources/allegro-2/MinnowMax_RevA1_DRILL/nc_param.txt similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_DRILL/nc_param.txt rename to tests/resources/allegro-2/MinnowMax_RevA1_DRILL/nc_param.txt diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_DRILL/ncdrill.log b/tests/resources/allegro-2/MinnowMax_RevA1_DRILL/ncdrill.log similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_DRILL/ncdrill.log rename to tests/resources/allegro-2/MinnowMax_RevA1_DRILL/ncdrill.log diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_DRILL/ncroute.log b/tests/resources/allegro-2/MinnowMax_RevA1_DRILL/ncroute.log similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_DRILL/ncroute.log rename to tests/resources/allegro-2/MinnowMax_RevA1_DRILL/ncroute.log diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_assy.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_assy.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_assy.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_assy.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_bslk.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_bslk.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_bslk.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_bslk.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_fab.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_fab.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_fab.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_fab.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr10_GAF.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr10_GAF.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr10_GAF.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr10_GAF.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr1_GAF.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr1_GAF.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr1_GAF.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr1_GAF.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr2.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr2.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr2.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr2.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr3.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr3.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr3.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr3.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr4.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr4.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr4.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr4.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr5.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr5.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr5.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr5.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr6.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr6.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr6.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr6.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr7.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr7.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr7.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr7.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr8.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr8.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr8.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr8.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr9.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr9.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr9.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_lyr9.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_smc_GAF.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_smc_GAF.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_smc_GAF.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_smc_GAF.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_sms_GAF.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_sms_GAF.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_sms_GAF.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_sms_GAF.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_spc.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_spc.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_spc.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_spc.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_sps.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_sps.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_sps.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_sps.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_tslk_GAF.art b/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_tslk_GAF.art similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_tslk_GAF.art rename to tests/resources/allegro-2/MinnowMax_RevA1_GAF_Gerber/MinnowMax_tslk_GAF.art diff --git a/gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_IPC356A.ipc b/tests/resources/allegro-2/MinnowMax_RevA1_IPC356A.ipc similarity index 100% rename from gerbonara/tests/resources/allegro-2/MinnowMax_RevA1_IPC356A.ipc rename to tests/resources/allegro-2/MinnowMax_RevA1_IPC356A.ipc diff --git a/gerbonara/tests/resources/allegro-2/README b/tests/resources/allegro-2/README similarity index 100% rename from gerbonara/tests/resources/allegro-2/README rename to tests/resources/allegro-2/README diff --git a/gerbonara/tests/resources/allegro-2/license.md b/tests/resources/allegro-2/license.md similarity index 100% rename from gerbonara/tests/resources/allegro-2/license.md rename to tests/resources/allegro-2/license.md diff --git a/gerbonara/tests/resources/allegro/08_057494d-ipc356.ipc b/tests/resources/allegro/08_057494d-ipc356.ipc similarity index 100% rename from gerbonara/tests/resources/allegro/08_057494d-ipc356.ipc rename to tests/resources/allegro/08_057494d-ipc356.ipc diff --git a/gerbonara/tests/resources/allegro/08_057494d.rou b/tests/resources/allegro/08_057494d.rou similarity index 100% rename from gerbonara/tests/resources/allegro/08_057494d.rou rename to tests/resources/allegro/08_057494d.rou diff --git a/gerbonara/tests/resources/allegro/README b/tests/resources/allegro/README similarity index 100% rename from gerbonara/tests/resources/allegro/README rename to tests/resources/allegro/README diff --git a/gerbonara/tests/resources/allegro/Read_Me.1 b/tests/resources/allegro/Read_Me.1 similarity index 100% rename from gerbonara/tests/resources/allegro/Read_Me.1 rename to tests/resources/allegro/Read_Me.1 diff --git a/gerbonara/tests/resources/allegro/art_param.txt b/tests/resources/allegro/art_param.txt similarity index 100% rename from gerbonara/tests/resources/allegro/art_param.txt rename to tests/resources/allegro/art_param.txt diff --git a/gerbonara/tests/resources/allegro/assy1.art b/tests/resources/allegro/assy1.art similarity index 100% rename from gerbonara/tests/resources/allegro/assy1.art rename to tests/resources/allegro/assy1.art diff --git a/gerbonara/tests/resources/allegro/assy2.art b/tests/resources/allegro/assy2.art similarity index 100% rename from gerbonara/tests/resources/allegro/assy2.art rename to tests/resources/allegro/assy2.art diff --git a/gerbonara/tests/resources/allegro/fab1.art b/tests/resources/allegro/fab1.art similarity index 100% rename from gerbonara/tests/resources/allegro/fab1.art rename to tests/resources/allegro/fab1.art diff --git a/gerbonara/tests/resources/allegro/l1_primary.art b/tests/resources/allegro/l1_primary.art similarity index 100% rename from gerbonara/tests/resources/allegro/l1_primary.art rename to tests/resources/allegro/l1_primary.art diff --git a/gerbonara/tests/resources/allegro/l2_gnd.art b/tests/resources/allegro/l2_gnd.art similarity index 100% rename from gerbonara/tests/resources/allegro/l2_gnd.art rename to tests/resources/allegro/l2_gnd.art diff --git a/gerbonara/tests/resources/allegro/l3_vcc.art b/tests/resources/allegro/l3_vcc.art similarity index 100% rename from gerbonara/tests/resources/allegro/l3_vcc.art rename to tests/resources/allegro/l3_vcc.art diff --git a/gerbonara/tests/resources/allegro/l4_secondary.art b/tests/resources/allegro/l4_secondary.art similarity index 100% rename from gerbonara/tests/resources/allegro/l4_secondary.art rename to tests/resources/allegro/l4_secondary.art diff --git a/gerbonara/tests/resources/allegro/mask_prm.art b/tests/resources/allegro/mask_prm.art similarity index 100% rename from gerbonara/tests/resources/allegro/mask_prm.art rename to tests/resources/allegro/mask_prm.art diff --git a/gerbonara/tests/resources/allegro/mask_sec.art b/tests/resources/allegro/mask_sec.art similarity index 100% rename from gerbonara/tests/resources/allegro/mask_sec.art rename to tests/resources/allegro/mask_sec.art diff --git a/gerbonara/tests/resources/allegro/nc_param.txt b/tests/resources/allegro/nc_param.txt similarity index 100% rename from gerbonara/tests/resources/allegro/nc_param.txt rename to tests/resources/allegro/nc_param.txt diff --git a/gerbonara/tests/resources/allegro/ncdrill-1-4.drl b/tests/resources/allegro/ncdrill-1-4.drl similarity index 100% rename from gerbonara/tests/resources/allegro/ncdrill-1-4.drl rename to tests/resources/allegro/ncdrill-1-4.drl diff --git a/gerbonara/tests/resources/allegro/ncdrill.log b/tests/resources/allegro/ncdrill.log similarity index 100% rename from gerbonara/tests/resources/allegro/ncdrill.log rename to tests/resources/allegro/ncdrill.log diff --git a/gerbonara/tests/resources/allegro/netlist.err b/tests/resources/allegro/netlist.err similarity index 100% rename from gerbonara/tests/resources/allegro/netlist.err rename to tests/resources/allegro/netlist.err diff --git a/gerbonara/tests/resources/allegro/paste_prm.art b/tests/resources/allegro/paste_prm.art similarity index 100% rename from gerbonara/tests/resources/allegro/paste_prm.art rename to tests/resources/allegro/paste_prm.art diff --git a/gerbonara/tests/resources/allegro/paste_sec.art b/tests/resources/allegro/paste_sec.art similarity index 100% rename from gerbonara/tests/resources/allegro/paste_sec.art rename to tests/resources/allegro/paste_sec.art diff --git a/gerbonara/tests/resources/allegro/photo.log b/tests/resources/allegro/photo.log similarity index 100% rename from gerbonara/tests/resources/allegro/photo.log rename to tests/resources/allegro/photo.log diff --git a/gerbonara/tests/resources/allegro/silk_prm.art b/tests/resources/allegro/silk_prm.art similarity index 100% rename from gerbonara/tests/resources/allegro/silk_prm.art rename to tests/resources/allegro/silk_prm.art diff --git a/gerbonara/tests/resources/allegro/silk_sec.art b/tests/resources/allegro/silk_sec.art similarity index 100% rename from gerbonara/tests/resources/allegro/silk_sec.art rename to tests/resources/allegro/silk_sec.art diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2-macro.APR_LIB b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2-macro.APR_LIB similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2-macro.APR_LIB rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2-macro.APR_LIB diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.EXTREP b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.EXTREP similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.EXTREP rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.EXTREP diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G1 b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G1 similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G1 rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G1 diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G10 b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G10 similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G10 rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G10 diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G11 b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G11 similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G11 rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G11 diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G12 b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G12 similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G12 rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G12 diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G2 b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G2 similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G2 rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G2 diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G3 b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G3 similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G3 rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G3 diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G4 b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G4 similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G4 rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G4 diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G5 b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G5 similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G5 rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G5 diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G6 b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G6 similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G6 rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G6 diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G7 b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G7 similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G7 rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G7 diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G8 b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G8 similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G8 rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G8 diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G9 b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G9 similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G9 rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.G9 diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GBL b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GBL similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GBL rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GBL diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GBO b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GBO similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GBO rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GBO diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GBP b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GBP similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GBP rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GBP diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GBS b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GBS similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GBS rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GBS diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GM1 b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GM1 similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GM1 rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GM1 diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GM14 b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GM14 similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GM14 rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GM14 diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GM15 b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GM15 similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GM15 rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GM15 diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GPB b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GPB similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GPB rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GPB diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GPT b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GPT similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GPT rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GPT diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GTL b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GTL similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GTL rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GTL diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GTO b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GTO similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GTO rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GTO diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GTP b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GTP similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GTP rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GTP diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GTS b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GTS similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GTS rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.GTS diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.REP b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.REP similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.REP rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.REP diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.RUL b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.RUL similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.RUL rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.RUL diff --git a/gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.apr b/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.apr similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.apr rename to tests/resources/altium-composite-drill/Gerber/LimeSDR-QPCIe_1v2.apr diff --git a/gerbonara/tests/resources/altium-composite-drill/LICENSE b/tests/resources/altium-composite-drill/LICENSE similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/LICENSE rename to tests/resources/altium-composite-drill/LICENSE diff --git a/gerbonara/tests/resources/altium-composite-drill/NC Drill/LimeSDR-QPCIe_1v2-RoundHoles.TXT b/tests/resources/altium-composite-drill/NC Drill/LimeSDR-QPCIe_1v2-RoundHoles.TXT similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/NC Drill/LimeSDR-QPCIe_1v2-RoundHoles.TXT rename to tests/resources/altium-composite-drill/NC Drill/LimeSDR-QPCIe_1v2-RoundHoles.TXT diff --git a/gerbonara/tests/resources/altium-composite-drill/NC Drill/LimeSDR-QPCIe_1v2-SlotHoles.TXT b/tests/resources/altium-composite-drill/NC Drill/LimeSDR-QPCIe_1v2-SlotHoles.TXT similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/NC Drill/LimeSDR-QPCIe_1v2-SlotHoles.TXT rename to tests/resources/altium-composite-drill/NC Drill/LimeSDR-QPCIe_1v2-SlotHoles.TXT diff --git a/gerbonara/tests/resources/altium-composite-drill/NC Drill/LimeSDR-QPCIe_1v2.DRR b/tests/resources/altium-composite-drill/NC Drill/LimeSDR-QPCIe_1v2.DRR similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/NC Drill/LimeSDR-QPCIe_1v2.DRR rename to tests/resources/altium-composite-drill/NC Drill/LimeSDR-QPCIe_1v2.DRR diff --git a/gerbonara/tests/resources/altium-composite-drill/NC Drill/LimeSDR-QPCIe_1v2.LDP b/tests/resources/altium-composite-drill/NC Drill/LimeSDR-QPCIe_1v2.LDP similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/NC Drill/LimeSDR-QPCIe_1v2.LDP rename to tests/resources/altium-composite-drill/NC Drill/LimeSDR-QPCIe_1v2.LDP diff --git a/gerbonara/tests/resources/altium-composite-drill/README b/tests/resources/altium-composite-drill/README similarity index 100% rename from gerbonara/tests/resources/altium-composite-drill/README rename to tests/resources/altium-composite-drill/README diff --git a/gerbonara/tests/resources/altium-old-composite-drill.txt b/tests/resources/altium-old-composite-drill.txt similarity index 100% rename from gerbonara/tests/resources/altium-old-composite-drill.txt rename to tests/resources/altium-old-composite-drill.txt diff --git a/gerbonara/tests/resources/board_outline.GKO b/tests/resources/board_outline.GKO similarity index 100% rename from gerbonara/tests/resources/board_outline.GKO rename to tests/resources/board_outline.GKO diff --git a/gerbonara/tests/resources/bottom_copper.GBL b/tests/resources/bottom_copper.GBL similarity index 100% rename from gerbonara/tests/resources/bottom_copper.GBL rename to tests/resources/bottom_copper.GBL diff --git a/gerbonara/tests/resources/bottom_mask.GBS b/tests/resources/bottom_mask.GBS similarity index 100% rename from gerbonara/tests/resources/bottom_mask.GBS rename to tests/resources/bottom_mask.GBS diff --git a/gerbonara/tests/resources/bottom_silk.GBO b/tests/resources/bottom_silk.GBO similarity index 100% rename from gerbonara/tests/resources/bottom_silk.GBO rename to tests/resources/bottom_silk.GBO diff --git a/gerbonara/tests/resources/diptrace/LICENSE.txt b/tests/resources/diptrace/LICENSE.txt similarity index 100% rename from gerbonara/tests/resources/diptrace/LICENSE.txt rename to tests/resources/diptrace/LICENSE.txt diff --git a/gerbonara/tests/resources/diptrace/README b/tests/resources/diptrace/README similarity index 100% rename from gerbonara/tests/resources/diptrace/README rename to tests/resources/diptrace/README diff --git a/gerbonara/tests/resources/diptrace/keyboard.drl b/tests/resources/diptrace/keyboard.drl similarity index 100% rename from gerbonara/tests/resources/diptrace/keyboard.drl rename to tests/resources/diptrace/keyboard.drl diff --git a/gerbonara/tests/resources/diptrace/keyboard_BoardOutline.gbr b/tests/resources/diptrace/keyboard_BoardOutline.gbr similarity index 100% rename from gerbonara/tests/resources/diptrace/keyboard_BoardOutline.gbr rename to tests/resources/diptrace/keyboard_BoardOutline.gbr diff --git a/gerbonara/tests/resources/diptrace/keyboard_Bottom.gbr b/tests/resources/diptrace/keyboard_Bottom.gbr similarity index 100% rename from gerbonara/tests/resources/diptrace/keyboard_Bottom.gbr rename to tests/resources/diptrace/keyboard_Bottom.gbr diff --git a/gerbonara/tests/resources/diptrace/keyboard_BottomMask.gbr b/tests/resources/diptrace/keyboard_BottomMask.gbr similarity index 100% rename from gerbonara/tests/resources/diptrace/keyboard_BottomMask.gbr rename to tests/resources/diptrace/keyboard_BottomMask.gbr diff --git a/gerbonara/tests/resources/diptrace/keyboard_BottomSilk.gbr b/tests/resources/diptrace/keyboard_BottomSilk.gbr similarity index 100% rename from gerbonara/tests/resources/diptrace/keyboard_BottomSilk.gbr rename to tests/resources/diptrace/keyboard_BottomSilk.gbr diff --git a/gerbonara/tests/resources/diptrace/mainboard.drl b/tests/resources/diptrace/mainboard.drl similarity index 100% rename from gerbonara/tests/resources/diptrace/mainboard.drl rename to tests/resources/diptrace/mainboard.drl diff --git a/gerbonara/tests/resources/diptrace/mainboard_BoardOutline.gbr b/tests/resources/diptrace/mainboard_BoardOutline.gbr similarity index 100% rename from gerbonara/tests/resources/diptrace/mainboard_BoardOutline.gbr rename to tests/resources/diptrace/mainboard_BoardOutline.gbr diff --git a/gerbonara/tests/resources/diptrace/mainboard_Bottom.gbr b/tests/resources/diptrace/mainboard_Bottom.gbr similarity index 100% rename from gerbonara/tests/resources/diptrace/mainboard_Bottom.gbr rename to tests/resources/diptrace/mainboard_Bottom.gbr diff --git a/gerbonara/tests/resources/diptrace/mainboard_BottomMask.gbr b/tests/resources/diptrace/mainboard_BottomMask.gbr similarity index 100% rename from gerbonara/tests/resources/diptrace/mainboard_BottomMask.gbr rename to tests/resources/diptrace/mainboard_BottomMask.gbr diff --git a/gerbonara/tests/resources/diptrace/mainboard_Top.gbr b/tests/resources/diptrace/mainboard_Top.gbr similarity index 100% rename from gerbonara/tests/resources/diptrace/mainboard_Top.gbr rename to tests/resources/diptrace/mainboard_Top.gbr diff --git a/gerbonara/tests/resources/diptrace/mainboard_TopMask.gbr b/tests/resources/diptrace/mainboard_TopMask.gbr similarity index 100% rename from gerbonara/tests/resources/diptrace/mainboard_TopMask.gbr rename to tests/resources/diptrace/mainboard_TopMask.gbr diff --git a/gerbonara/tests/resources/diptrace/mainboard_TopSilk.gbr b/tests/resources/diptrace/mainboard_TopSilk.gbr similarity index 100% rename from gerbonara/tests/resources/diptrace/mainboard_TopSilk.gbr rename to tests/resources/diptrace/mainboard_TopSilk.gbr diff --git a/gerbonara/tests/resources/diptrace/panel.drl b/tests/resources/diptrace/panel.drl similarity index 100% rename from gerbonara/tests/resources/diptrace/panel.drl rename to tests/resources/diptrace/panel.drl diff --git a/gerbonara/tests/resources/diptrace/panel_BoardOutline.gbr b/tests/resources/diptrace/panel_BoardOutline.gbr similarity index 100% rename from gerbonara/tests/resources/diptrace/panel_BoardOutline.gbr rename to tests/resources/diptrace/panel_BoardOutline.gbr diff --git a/gerbonara/tests/resources/diptrace/panel_Bottom.gbr b/tests/resources/diptrace/panel_Bottom.gbr similarity index 100% rename from gerbonara/tests/resources/diptrace/panel_Bottom.gbr rename to tests/resources/diptrace/panel_Bottom.gbr diff --git a/gerbonara/tests/resources/diptrace/panel_BottomMask.gbr b/tests/resources/diptrace/panel_BottomMask.gbr similarity index 100% rename from gerbonara/tests/resources/diptrace/panel_BottomMask.gbr rename to tests/resources/diptrace/panel_BottomMask.gbr diff --git a/gerbonara/tests/resources/diptrace/panel_BottomSilk.gbr b/tests/resources/diptrace/panel_BottomSilk.gbr similarity index 100% rename from gerbonara/tests/resources/diptrace/panel_BottomSilk.gbr rename to tests/resources/diptrace/panel_BottomSilk.gbr diff --git a/gerbonara/tests/resources/eagle-newer/LICENSE b/tests/resources/eagle-newer/LICENSE similarity index 100% rename from gerbonara/tests/resources/eagle-newer/LICENSE rename to tests/resources/eagle-newer/LICENSE diff --git a/gerbonara/tests/resources/eagle-newer/README b/tests/resources/eagle-newer/README similarity index 100% rename from gerbonara/tests/resources/eagle-newer/README rename to tests/resources/eagle-newer/README diff --git a/gerbonara/tests/resources/eagle-newer/copper_bottom.gbr b/tests/resources/eagle-newer/copper_bottom.gbr similarity index 100% rename from gerbonara/tests/resources/eagle-newer/copper_bottom.gbr rename to tests/resources/eagle-newer/copper_bottom.gbr diff --git a/gerbonara/tests/resources/eagle-newer/copper_top.gbr b/tests/resources/eagle-newer/copper_top.gbr similarity index 100% rename from gerbonara/tests/resources/eagle-newer/copper_top.gbr rename to tests/resources/eagle-newer/copper_top.gbr diff --git a/gerbonara/tests/resources/eagle-newer/drills.xln b/tests/resources/eagle-newer/drills.xln similarity index 100% rename from gerbonara/tests/resources/eagle-newer/drills.xln rename to tests/resources/eagle-newer/drills.xln diff --git a/gerbonara/tests/resources/eagle-newer/gerber_job.gbrjob b/tests/resources/eagle-newer/gerber_job.gbrjob similarity index 100% rename from gerbonara/tests/resources/eagle-newer/gerber_job.gbrjob rename to tests/resources/eagle-newer/gerber_job.gbrjob diff --git a/gerbonara/tests/resources/eagle-newer/profile.gbr b/tests/resources/eagle-newer/profile.gbr similarity index 100% rename from gerbonara/tests/resources/eagle-newer/profile.gbr rename to tests/resources/eagle-newer/profile.gbr diff --git a/gerbonara/tests/resources/eagle-newer/silkscreen_bottom.gbr b/tests/resources/eagle-newer/silkscreen_bottom.gbr similarity index 100% rename from gerbonara/tests/resources/eagle-newer/silkscreen_bottom.gbr rename to tests/resources/eagle-newer/silkscreen_bottom.gbr diff --git a/gerbonara/tests/resources/eagle-newer/silkscreen_top.gbr b/tests/resources/eagle-newer/silkscreen_top.gbr similarity index 100% rename from gerbonara/tests/resources/eagle-newer/silkscreen_top.gbr rename to tests/resources/eagle-newer/silkscreen_top.gbr diff --git a/gerbonara/tests/resources/eagle-newer/soldermask_bottom.gbr b/tests/resources/eagle-newer/soldermask_bottom.gbr similarity index 100% rename from gerbonara/tests/resources/eagle-newer/soldermask_bottom.gbr rename to tests/resources/eagle-newer/soldermask_bottom.gbr diff --git a/gerbonara/tests/resources/eagle-newer/soldermask_top.gbr b/tests/resources/eagle-newer/soldermask_top.gbr similarity index 100% rename from gerbonara/tests/resources/eagle-newer/soldermask_top.gbr rename to tests/resources/eagle-newer/soldermask_top.gbr diff --git a/gerbonara/tests/resources/eagle-newer/solderpaste_bottom.gbr b/tests/resources/eagle-newer/solderpaste_bottom.gbr similarity index 100% rename from gerbonara/tests/resources/eagle-newer/solderpaste_bottom.gbr rename to tests/resources/eagle-newer/solderpaste_bottom.gbr diff --git a/gerbonara/tests/resources/eagle-newer/solderpaste_top.gbr b/tests/resources/eagle-newer/solderpaste_top.gbr similarity index 100% rename from gerbonara/tests/resources/eagle-newer/solderpaste_top.gbr rename to tests/resources/eagle-newer/solderpaste_top.gbr diff --git a/gerbonara/tests/resources/eagle_files/copper_bottom_l4.gbr b/tests/resources/eagle_files/copper_bottom_l4.gbr similarity index 100% rename from gerbonara/tests/resources/eagle_files/copper_bottom_l4.gbr rename to tests/resources/eagle_files/copper_bottom_l4.gbr diff --git a/gerbonara/tests/resources/eagle_files/copper_inner_l2.gbr b/tests/resources/eagle_files/copper_inner_l2.gbr similarity index 100% rename from gerbonara/tests/resources/eagle_files/copper_inner_l2.gbr rename to tests/resources/eagle_files/copper_inner_l2.gbr diff --git a/gerbonara/tests/resources/eagle_files/copper_inner_l3.gbr b/tests/resources/eagle_files/copper_inner_l3.gbr similarity index 100% rename from gerbonara/tests/resources/eagle_files/copper_inner_l3.gbr rename to tests/resources/eagle_files/copper_inner_l3.gbr diff --git a/gerbonara/tests/resources/eagle_files/copper_top_l1.gbr b/tests/resources/eagle_files/copper_top_l1.gbr similarity index 100% rename from gerbonara/tests/resources/eagle_files/copper_top_l1.gbr rename to tests/resources/eagle_files/copper_top_l1.gbr diff --git a/gerbonara/tests/resources/eagle_files/profile.gbr b/tests/resources/eagle_files/profile.gbr similarity index 100% rename from gerbonara/tests/resources/eagle_files/profile.gbr rename to tests/resources/eagle_files/profile.gbr diff --git a/gerbonara/tests/resources/eagle_files/silkscreen_bottom.gbr b/tests/resources/eagle_files/silkscreen_bottom.gbr similarity index 100% rename from gerbonara/tests/resources/eagle_files/silkscreen_bottom.gbr rename to tests/resources/eagle_files/silkscreen_bottom.gbr diff --git a/gerbonara/tests/resources/eagle_files/silkscreen_top.gbr b/tests/resources/eagle_files/silkscreen_top.gbr similarity index 100% rename from gerbonara/tests/resources/eagle_files/silkscreen_top.gbr rename to tests/resources/eagle_files/silkscreen_top.gbr diff --git a/gerbonara/tests/resources/eagle_files/soldermask_bottom.gbr b/tests/resources/eagle_files/soldermask_bottom.gbr similarity index 100% rename from gerbonara/tests/resources/eagle_files/soldermask_bottom.gbr rename to tests/resources/eagle_files/soldermask_bottom.gbr diff --git a/gerbonara/tests/resources/eagle_files/soldermask_top.gbr b/tests/resources/eagle_files/soldermask_top.gbr similarity index 100% rename from gerbonara/tests/resources/eagle_files/soldermask_top.gbr rename to tests/resources/eagle_files/soldermask_top.gbr diff --git a/gerbonara/tests/resources/eagle_files/solderpaste_bottom.gbr b/tests/resources/eagle_files/solderpaste_bottom.gbr similarity index 100% rename from gerbonara/tests/resources/eagle_files/solderpaste_bottom.gbr rename to tests/resources/eagle_files/solderpaste_bottom.gbr diff --git a/gerbonara/tests/resources/eagle_files/solderpaste_top.gbr b/tests/resources/eagle_files/solderpaste_top.gbr similarity index 100% rename from gerbonara/tests/resources/eagle_files/solderpaste_top.gbr rename to tests/resources/eagle_files/solderpaste_top.gbr diff --git a/gerbonara/tests/resources/easyeda/Gerber_BoardOutline.GKO b/tests/resources/easyeda/Gerber_BoardOutline.GKO similarity index 100% rename from gerbonara/tests/resources/easyeda/Gerber_BoardOutline.GKO rename to tests/resources/easyeda/Gerber_BoardOutline.GKO diff --git a/gerbonara/tests/resources/easyeda/Gerber_BottomLayer.GBL b/tests/resources/easyeda/Gerber_BottomLayer.GBL similarity index 100% rename from gerbonara/tests/resources/easyeda/Gerber_BottomLayer.GBL rename to tests/resources/easyeda/Gerber_BottomLayer.GBL diff --git a/gerbonara/tests/resources/easyeda/Gerber_BottomSolderMaskLayer.GBS b/tests/resources/easyeda/Gerber_BottomSolderMaskLayer.GBS similarity index 100% rename from gerbonara/tests/resources/easyeda/Gerber_BottomSolderMaskLayer.GBS rename to tests/resources/easyeda/Gerber_BottomSolderMaskLayer.GBS diff --git a/gerbonara/tests/resources/easyeda/Gerber_Drill_NPTH.DRL b/tests/resources/easyeda/Gerber_Drill_NPTH.DRL similarity index 100% rename from gerbonara/tests/resources/easyeda/Gerber_Drill_NPTH.DRL rename to tests/resources/easyeda/Gerber_Drill_NPTH.DRL diff --git a/gerbonara/tests/resources/easyeda/Gerber_Drill_PTH.DRL b/tests/resources/easyeda/Gerber_Drill_PTH.DRL similarity index 100% rename from gerbonara/tests/resources/easyeda/Gerber_Drill_PTH.DRL rename to tests/resources/easyeda/Gerber_Drill_PTH.DRL diff --git a/gerbonara/tests/resources/easyeda/Gerber_TopLayer.GTL b/tests/resources/easyeda/Gerber_TopLayer.GTL similarity index 100% rename from gerbonara/tests/resources/easyeda/Gerber_TopLayer.GTL rename to tests/resources/easyeda/Gerber_TopLayer.GTL diff --git a/gerbonara/tests/resources/easyeda/Gerber_TopPasteMaskLayer.GTP b/tests/resources/easyeda/Gerber_TopPasteMaskLayer.GTP similarity index 100% rename from gerbonara/tests/resources/easyeda/Gerber_TopPasteMaskLayer.GTP rename to tests/resources/easyeda/Gerber_TopPasteMaskLayer.GTP diff --git a/gerbonara/tests/resources/easyeda/Gerber_TopPasteMaskLayer.bottom.svg b/tests/resources/easyeda/Gerber_TopPasteMaskLayer.bottom.svg similarity index 100% rename from gerbonara/tests/resources/easyeda/Gerber_TopPasteMaskLayer.bottom.svg rename to tests/resources/easyeda/Gerber_TopPasteMaskLayer.bottom.svg diff --git a/gerbonara/tests/resources/easyeda/Gerber_TopPasteMaskLayer.gtp.top.solderpaste.svg b/tests/resources/easyeda/Gerber_TopPasteMaskLayer.gtp.top.solderpaste.svg similarity index 100% rename from gerbonara/tests/resources/easyeda/Gerber_TopPasteMaskLayer.gtp.top.solderpaste.svg rename to tests/resources/easyeda/Gerber_TopPasteMaskLayer.gtp.top.solderpaste.svg diff --git a/gerbonara/tests/resources/easyeda/Gerber_TopPasteMaskLayer.gtp.top.solderpaste_2.svg b/tests/resources/easyeda/Gerber_TopPasteMaskLayer.gtp.top.solderpaste_2.svg similarity index 100% rename from gerbonara/tests/resources/easyeda/Gerber_TopPasteMaskLayer.gtp.top.solderpaste_2.svg rename to tests/resources/easyeda/Gerber_TopPasteMaskLayer.gtp.top.solderpaste_2.svg diff --git a/gerbonara/tests/resources/easyeda/Gerber_TopPasteMaskLayer.top.svg b/tests/resources/easyeda/Gerber_TopPasteMaskLayer.top.svg similarity index 100% rename from gerbonara/tests/resources/easyeda/Gerber_TopPasteMaskLayer.top.svg rename to tests/resources/easyeda/Gerber_TopPasteMaskLayer.top.svg diff --git a/gerbonara/tests/resources/easyeda/Gerber_TopSilkLayer.GTO b/tests/resources/easyeda/Gerber_TopSilkLayer.GTO similarity index 100% rename from gerbonara/tests/resources/easyeda/Gerber_TopSilkLayer.GTO rename to tests/resources/easyeda/Gerber_TopSilkLayer.GTO diff --git a/gerbonara/tests/resources/easyeda/Gerber_TopSolderMaskLayer.GTS b/tests/resources/easyeda/Gerber_TopSolderMaskLayer.GTS similarity index 100% rename from gerbonara/tests/resources/easyeda/Gerber_TopSolderMaskLayer.GTS rename to tests/resources/easyeda/Gerber_TopSolderMaskLayer.GTS diff --git a/gerbonara/tests/resources/easyeda/How-to-order-PCB.txt b/tests/resources/easyeda/How-to-order-PCB.txt similarity index 100% rename from gerbonara/tests/resources/easyeda/How-to-order-PCB.txt rename to tests/resources/easyeda/How-to-order-PCB.txt diff --git a/gerbonara/tests/resources/easyeda/README b/tests/resources/easyeda/README similarity index 100% rename from gerbonara/tests/resources/easyeda/README rename to tests/resources/easyeda/README diff --git a/gerbonara/tests/resources/example_am_exposure_modifier.gbr b/tests/resources/example_am_exposure_modifier.gbr similarity index 100% rename from gerbonara/tests/resources/example_am_exposure_modifier.gbr rename to tests/resources/example_am_exposure_modifier.gbr diff --git a/gerbonara/tests/resources/example_coincident_hole.gbr b/tests/resources/example_coincident_hole.gbr similarity index 100% rename from gerbonara/tests/resources/example_coincident_hole.gbr rename to tests/resources/example_coincident_hole.gbr diff --git a/gerbonara/tests/resources/example_cutin.gbr b/tests/resources/example_cutin.gbr similarity index 100% rename from gerbonara/tests/resources/example_cutin.gbr rename to tests/resources/example_cutin.gbr diff --git a/gerbonara/tests/resources/example_cutin_multiple.gbr b/tests/resources/example_cutin_multiple.gbr similarity index 100% rename from gerbonara/tests/resources/example_cutin_multiple.gbr rename to tests/resources/example_cutin_multiple.gbr diff --git a/gerbonara/tests/resources/example_flash_circle.gbr b/tests/resources/example_flash_circle.gbr similarity index 100% rename from gerbonara/tests/resources/example_flash_circle.gbr rename to tests/resources/example_flash_circle.gbr diff --git a/gerbonara/tests/resources/example_flash_obround.gbr b/tests/resources/example_flash_obround.gbr similarity index 100% rename from gerbonara/tests/resources/example_flash_obround.gbr rename to tests/resources/example_flash_obround.gbr diff --git a/gerbonara/tests/resources/example_flash_polygon.gbr b/tests/resources/example_flash_polygon.gbr similarity index 100% rename from gerbonara/tests/resources/example_flash_polygon.gbr rename to tests/resources/example_flash_polygon.gbr diff --git a/gerbonara/tests/resources/example_flash_rectangle.gbr b/tests/resources/example_flash_rectangle.gbr similarity index 100% rename from gerbonara/tests/resources/example_flash_rectangle.gbr rename to tests/resources/example_flash_rectangle.gbr diff --git a/gerbonara/tests/resources/example_fully_coincident.gbr b/tests/resources/example_fully_coincident.gbr similarity index 100% rename from gerbonara/tests/resources/example_fully_coincident.gbr rename to tests/resources/example_fully_coincident.gbr diff --git a/gerbonara/tests/resources/example_guess_by_content.g0 b/tests/resources/example_guess_by_content.g0 similarity index 100% rename from gerbonara/tests/resources/example_guess_by_content.g0 rename to tests/resources/example_guess_by_content.g0 diff --git a/gerbonara/tests/resources/example_holes_dont_clear.gbr b/tests/resources/example_holes_dont_clear.gbr similarity index 100% rename from gerbonara/tests/resources/example_holes_dont_clear.gbr rename to tests/resources/example_holes_dont_clear.gbr diff --git a/gerbonara/tests/resources/example_level_holes.gbr b/tests/resources/example_level_holes.gbr similarity index 100% rename from gerbonara/tests/resources/example_level_holes.gbr rename to tests/resources/example_level_holes.gbr diff --git a/gerbonara/tests/resources/example_not_overlapping_contour.gbr b/tests/resources/example_not_overlapping_contour.gbr similarity index 100% rename from gerbonara/tests/resources/example_not_overlapping_contour.gbr rename to tests/resources/example_not_overlapping_contour.gbr diff --git a/gerbonara/tests/resources/example_not_overlapping_touching.gbr b/tests/resources/example_not_overlapping_touching.gbr similarity index 100% rename from gerbonara/tests/resources/example_not_overlapping_touching.gbr rename to tests/resources/example_not_overlapping_touching.gbr diff --git a/gerbonara/tests/resources/example_outline_with_arcs.gbr b/tests/resources/example_outline_with_arcs.gbr similarity index 100% rename from gerbonara/tests/resources/example_outline_with_arcs.gbr rename to tests/resources/example_outline_with_arcs.gbr diff --git a/gerbonara/tests/resources/example_overlapping_contour.gbr b/tests/resources/example_overlapping_contour.gbr similarity index 100% rename from gerbonara/tests/resources/example_overlapping_contour.gbr rename to tests/resources/example_overlapping_contour.gbr diff --git a/gerbonara/tests/resources/example_overlapping_touching.gbr b/tests/resources/example_overlapping_touching.gbr similarity index 100% rename from gerbonara/tests/resources/example_overlapping_touching.gbr rename to tests/resources/example_overlapping_touching.gbr diff --git a/gerbonara/tests/resources/example_simple_contour.gbr b/tests/resources/example_simple_contour.gbr similarity index 100% rename from gerbonara/tests/resources/example_simple_contour.gbr rename to tests/resources/example_simple_contour.gbr diff --git a/gerbonara/tests/resources/example_single_contour_1.gbr b/tests/resources/example_single_contour_1.gbr similarity index 100% rename from gerbonara/tests/resources/example_single_contour_1.gbr rename to tests/resources/example_single_contour_1.gbr diff --git a/gerbonara/tests/resources/example_single_contour_2.gbr b/tests/resources/example_single_contour_2.gbr similarity index 100% rename from gerbonara/tests/resources/example_single_contour_2.gbr rename to tests/resources/example_single_contour_2.gbr diff --git a/gerbonara/tests/resources/example_single_contour_3.gbr b/tests/resources/example_single_contour_3.gbr similarity index 100% rename from gerbonara/tests/resources/example_single_contour_3.gbr rename to tests/resources/example_single_contour_3.gbr diff --git a/gerbonara/tests/resources/example_two_square_boxes.gbr b/tests/resources/example_two_square_boxes.gbr similarity index 100% rename from gerbonara/tests/resources/example_two_square_boxes.gbr rename to tests/resources/example_two_square_boxes.gbr diff --git a/gerbonara/tests/resources/fab-3000/2340080a_p2.tgz b/tests/resources/fab-3000/2340080a_p2.tgz similarity index 100% rename from gerbonara/tests/resources/fab-3000/2340080a_p2.tgz rename to tests/resources/fab-3000/2340080a_p2.tgz diff --git a/gerbonara/tests/resources/fab-3000/LICENSE b/tests/resources/fab-3000/LICENSE similarity index 100% rename from gerbonara/tests/resources/fab-3000/LICENSE rename to tests/resources/fab-3000/LICENSE diff --git a/gerbonara/tests/resources/fab-3000/SOURCE b/tests/resources/fab-3000/SOURCE similarity index 100% rename from gerbonara/tests/resources/fab-3000/SOURCE rename to tests/resources/fab-3000/SOURCE diff --git a/gerbonara/tests/resources/fab-3000/bl b/tests/resources/fab-3000/bl similarity index 100% rename from gerbonara/tests/resources/fab-3000/bl rename to tests/resources/fab-3000/bl diff --git a/gerbonara/tests/resources/fab-3000/bo b/tests/resources/fab-3000/bo similarity index 100% rename from gerbonara/tests/resources/fab-3000/bo rename to tests/resources/fab-3000/bo diff --git a/gerbonara/tests/resources/fab-3000/bs b/tests/resources/fab-3000/bs similarity index 100% rename from gerbonara/tests/resources/fab-3000/bs rename to tests/resources/fab-3000/bs diff --git a/gerbonara/tests/resources/fab-3000/drl b/tests/resources/fab-3000/drl similarity index 100% rename from gerbonara/tests/resources/fab-3000/drl rename to tests/resources/fab-3000/drl diff --git a/gerbonara/tests/resources/fab-3000/ko b/tests/resources/fab-3000/ko similarity index 100% rename from gerbonara/tests/resources/fab-3000/ko rename to tests/resources/fab-3000/ko diff --git a/gerbonara/tests/resources/fab-3000/tl b/tests/resources/fab-3000/tl similarity index 100% rename from gerbonara/tests/resources/fab-3000/tl rename to tests/resources/fab-3000/tl diff --git a/gerbonara/tests/resources/fab-3000/to b/tests/resources/fab-3000/to similarity index 100% rename from gerbonara/tests/resources/fab-3000/to rename to tests/resources/fab-3000/to diff --git a/gerbonara/tests/resources/fab-3000/ts b/tests/resources/fab-3000/ts similarity index 100% rename from gerbonara/tests/resources/fab-3000/ts rename to tests/resources/fab-3000/ts diff --git a/gerbonara/tests/resources/fritzing/LICENSE b/tests/resources/fritzing/LICENSE similarity index 100% rename from gerbonara/tests/resources/fritzing/LICENSE rename to tests/resources/fritzing/LICENSE diff --git a/gerbonara/tests/resources/fritzing/README b/tests/resources/fritzing/README similarity index 100% rename from gerbonara/tests/resources/fritzing/README rename to tests/resources/fritzing/README diff --git a/gerbonara/tests/resources/fritzing/combined.GKO b/tests/resources/fritzing/combined.GKO similarity index 100% rename from gerbonara/tests/resources/fritzing/combined.GKO rename to tests/resources/fritzing/combined.GKO diff --git a/gerbonara/tests/resources/fritzing/combined.gbl b/tests/resources/fritzing/combined.gbl similarity index 100% rename from gerbonara/tests/resources/fritzing/combined.gbl rename to tests/resources/fritzing/combined.gbl diff --git a/gerbonara/tests/resources/fritzing/combined.gbo b/tests/resources/fritzing/combined.gbo similarity index 100% rename from gerbonara/tests/resources/fritzing/combined.gbo rename to tests/resources/fritzing/combined.gbo diff --git a/gerbonara/tests/resources/fritzing/combined.gbs b/tests/resources/fritzing/combined.gbs similarity index 100% rename from gerbonara/tests/resources/fritzing/combined.gbs rename to tests/resources/fritzing/combined.gbs diff --git a/gerbonara/tests/resources/fritzing/combined.gm1 b/tests/resources/fritzing/combined.gm1 similarity index 100% rename from gerbonara/tests/resources/fritzing/combined.gm1 rename to tests/resources/fritzing/combined.gm1 diff --git a/gerbonara/tests/resources/fritzing/combined.gtl b/tests/resources/fritzing/combined.gtl similarity index 100% rename from gerbonara/tests/resources/fritzing/combined.gtl rename to tests/resources/fritzing/combined.gtl diff --git a/gerbonara/tests/resources/fritzing/combined.gto b/tests/resources/fritzing/combined.gto similarity index 100% rename from gerbonara/tests/resources/fritzing/combined.gto rename to tests/resources/fritzing/combined.gto diff --git a/gerbonara/tests/resources/fritzing/combined.gts b/tests/resources/fritzing/combined.gts similarity index 100% rename from gerbonara/tests/resources/fritzing/combined.gts rename to tests/resources/fritzing/combined.gts diff --git a/gerbonara/tests/resources/fritzing/combined.txt b/tests/resources/fritzing/combined.txt similarity index 100% rename from gerbonara/tests/resources/fritzing/combined.txt rename to tests/resources/fritzing/combined.txt diff --git a/gerbonara/tests/resources/fritzing/gyro_328p_6050_2021_panelize.gerberset b/tests/resources/fritzing/gyro_328p_6050_2021_panelize.gerberset similarity index 100% rename from gerbonara/tests/resources/fritzing/gyro_328p_6050_2021_panelize.gerberset rename to tests/resources/fritzing/gyro_328p_6050_2021_panelize.gerberset diff --git a/gerbonara/tests/resources/fusion360/README b/tests/resources/fusion360/README similarity index 100% rename from gerbonara/tests/resources/fusion360/README rename to tests/resources/fusion360/README diff --git a/gerbonara/tests/resources/fusion360/copper_bottom.gbr b/tests/resources/fusion360/copper_bottom.gbr similarity index 100% rename from gerbonara/tests/resources/fusion360/copper_bottom.gbr rename to tests/resources/fusion360/copper_bottom.gbr diff --git a/gerbonara/tests/resources/fusion360/copper_top.gbr b/tests/resources/fusion360/copper_top.gbr similarity index 100% rename from gerbonara/tests/resources/fusion360/copper_top.gbr rename to tests/resources/fusion360/copper_top.gbr diff --git a/gerbonara/tests/resources/fusion360/gerber_job.gbrjob b/tests/resources/fusion360/gerber_job.gbrjob similarity index 100% rename from gerbonara/tests/resources/fusion360/gerber_job.gbrjob rename to tests/resources/fusion360/gerber_job.gbrjob diff --git a/gerbonara/tests/resources/fusion360/profile.gbr b/tests/resources/fusion360/profile.gbr similarity index 100% rename from gerbonara/tests/resources/fusion360/profile.gbr rename to tests/resources/fusion360/profile.gbr diff --git a/gerbonara/tests/resources/fusion360/silkscreen_bottom.gbr b/tests/resources/fusion360/silkscreen_bottom.gbr similarity index 100% rename from gerbonara/tests/resources/fusion360/silkscreen_bottom.gbr rename to tests/resources/fusion360/silkscreen_bottom.gbr diff --git a/gerbonara/tests/resources/fusion360/silkscreen_top.gbr b/tests/resources/fusion360/silkscreen_top.gbr similarity index 100% rename from gerbonara/tests/resources/fusion360/silkscreen_top.gbr rename to tests/resources/fusion360/silkscreen_top.gbr diff --git a/gerbonara/tests/resources/fusion360/soldermask_bottom.gbr b/tests/resources/fusion360/soldermask_bottom.gbr similarity index 100% rename from gerbonara/tests/resources/fusion360/soldermask_bottom.gbr rename to tests/resources/fusion360/soldermask_bottom.gbr diff --git a/gerbonara/tests/resources/fusion360/soldermask_top.gbr b/tests/resources/fusion360/soldermask_top.gbr similarity index 100% rename from gerbonara/tests/resources/fusion360/soldermask_top.gbr rename to tests/resources/fusion360/soldermask_top.gbr diff --git a/gerbonara/tests/resources/fusion360/solderpaste_bottom.gbr b/tests/resources/fusion360/solderpaste_bottom.gbr similarity index 100% rename from gerbonara/tests/resources/fusion360/solderpaste_bottom.gbr rename to tests/resources/fusion360/solderpaste_bottom.gbr diff --git a/gerbonara/tests/resources/fusion360/solderpaste_top.gbr b/tests/resources/fusion360/solderpaste_top.gbr similarity index 100% rename from gerbonara/tests/resources/fusion360/solderpaste_top.gbr rename to tests/resources/fusion360/solderpaste_top.gbr diff --git a/gerbonara/tests/resources/geda/LICENSE b/tests/resources/geda/LICENSE similarity index 100% rename from gerbonara/tests/resources/geda/LICENSE rename to tests/resources/geda/LICENSE diff --git a/gerbonara/tests/resources/geda/README b/tests/resources/geda/README similarity index 100% rename from gerbonara/tests/resources/geda/README rename to tests/resources/geda/README diff --git a/gerbonara/tests/resources/geda/controller.bottom.gbr b/tests/resources/geda/controller.bottom.gbr similarity index 100% rename from gerbonara/tests/resources/geda/controller.bottom.gbr rename to tests/resources/geda/controller.bottom.gbr diff --git a/gerbonara/tests/resources/geda/controller.bottommask.gbr b/tests/resources/geda/controller.bottommask.gbr similarity index 100% rename from gerbonara/tests/resources/geda/controller.bottommask.gbr rename to tests/resources/geda/controller.bottommask.gbr diff --git a/gerbonara/tests/resources/geda/controller.fab.gbr b/tests/resources/geda/controller.fab.gbr similarity index 100% rename from gerbonara/tests/resources/geda/controller.fab.gbr rename to tests/resources/geda/controller.fab.gbr diff --git a/gerbonara/tests/resources/geda/controller.group3.gbr b/tests/resources/geda/controller.group3.gbr similarity index 100% rename from gerbonara/tests/resources/geda/controller.group3.gbr rename to tests/resources/geda/controller.group3.gbr diff --git a/gerbonara/tests/resources/geda/controller.plated-drill.cnc b/tests/resources/geda/controller.plated-drill.cnc similarity index 100% rename from gerbonara/tests/resources/geda/controller.plated-drill.cnc rename to tests/resources/geda/controller.plated-drill.cnc diff --git a/gerbonara/tests/resources/geda/controller.top.gbr b/tests/resources/geda/controller.top.gbr similarity index 100% rename from gerbonara/tests/resources/geda/controller.top.gbr rename to tests/resources/geda/controller.top.gbr diff --git a/gerbonara/tests/resources/geda/controller.topmask.gbr b/tests/resources/geda/controller.topmask.gbr similarity index 100% rename from gerbonara/tests/resources/geda/controller.topmask.gbr rename to tests/resources/geda/controller.topmask.gbr diff --git a/gerbonara/tests/resources/geda/controller.topsilk.gbr b/tests/resources/geda/controller.topsilk.gbr similarity index 100% rename from gerbonara/tests/resources/geda/controller.topsilk.gbr rename to tests/resources/geda/controller.topsilk.gbr diff --git a/gerbonara/tests/resources/geda/controller.unplated-drill.cnc b/tests/resources/geda/controller.unplated-drill.cnc similarity index 100% rename from gerbonara/tests/resources/geda/controller.unplated-drill.cnc rename to tests/resources/geda/controller.unplated-drill.cnc diff --git a/gerbonara/tests/resources/geda/driver.bottom.gbr b/tests/resources/geda/driver.bottom.gbr similarity index 100% rename from gerbonara/tests/resources/geda/driver.bottom.gbr rename to tests/resources/geda/driver.bottom.gbr diff --git a/gerbonara/tests/resources/geda/driver.bottommask.gbr b/tests/resources/geda/driver.bottommask.gbr similarity index 100% rename from gerbonara/tests/resources/geda/driver.bottommask.gbr rename to tests/resources/geda/driver.bottommask.gbr diff --git a/gerbonara/tests/resources/geda/driver.fab.gbr b/tests/resources/geda/driver.fab.gbr similarity index 100% rename from gerbonara/tests/resources/geda/driver.fab.gbr rename to tests/resources/geda/driver.fab.gbr diff --git a/gerbonara/tests/resources/geda/driver.group5.gbr b/tests/resources/geda/driver.group5.gbr similarity index 100% rename from gerbonara/tests/resources/geda/driver.group5.gbr rename to tests/resources/geda/driver.group5.gbr diff --git a/gerbonara/tests/resources/geda/driver.plated-drill.cnc b/tests/resources/geda/driver.plated-drill.cnc similarity index 100% rename from gerbonara/tests/resources/geda/driver.plated-drill.cnc rename to tests/resources/geda/driver.plated-drill.cnc diff --git a/gerbonara/tests/resources/geda/driver.top.gbr b/tests/resources/geda/driver.top.gbr similarity index 100% rename from gerbonara/tests/resources/geda/driver.top.gbr rename to tests/resources/geda/driver.top.gbr diff --git a/gerbonara/tests/resources/geda/driver.topmask.gbr b/tests/resources/geda/driver.topmask.gbr similarity index 100% rename from gerbonara/tests/resources/geda/driver.topmask.gbr rename to tests/resources/geda/driver.topmask.gbr diff --git a/gerbonara/tests/resources/geda/driver.topsilk.gbr b/tests/resources/geda/driver.topsilk.gbr similarity index 100% rename from gerbonara/tests/resources/geda/driver.topsilk.gbr rename to tests/resources/geda/driver.topsilk.gbr diff --git a/gerbonara/tests/resources/geda/driver.unplated-drill.cnc b/tests/resources/geda/driver.unplated-drill.cnc similarity index 100% rename from gerbonara/tests/resources/geda/driver.unplated-drill.cnc rename to tests/resources/geda/driver.unplated-drill.cnc diff --git a/gerbonara/tests/resources/gerbv.gbr b/tests/resources/gerbv.gbr similarity index 100% rename from gerbonara/tests/resources/gerbv.gbr rename to tests/resources/gerbv.gbr diff --git a/gerbonara/tests/resources/ipc-d-356.ipc b/tests/resources/ipc-d-356.ipc similarity index 100% rename from gerbonara/tests/resources/ipc-d-356.ipc rename to tests/resources/ipc-d-356.ipc diff --git a/gerbonara/tests/resources/kicad-older/README b/tests/resources/kicad-older/README similarity index 100% rename from gerbonara/tests/resources/kicad-older/README rename to tests/resources/kicad-older/README diff --git a/gerbonara/tests/resources/kicad-older/chibi_2024-B.Cu.gbr b/tests/resources/kicad-older/chibi_2024-B.Cu.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-older/chibi_2024-B.Cu.gbr rename to tests/resources/kicad-older/chibi_2024-B.Cu.gbr diff --git a/gerbonara/tests/resources/kicad-older/chibi_2024-B.Mask.gbr b/tests/resources/kicad-older/chibi_2024-B.Mask.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-older/chibi_2024-B.Mask.gbr rename to tests/resources/kicad-older/chibi_2024-B.Mask.gbr diff --git a/gerbonara/tests/resources/kicad-older/chibi_2024-B.Paste.gbr b/tests/resources/kicad-older/chibi_2024-B.Paste.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-older/chibi_2024-B.Paste.gbr rename to tests/resources/kicad-older/chibi_2024-B.Paste.gbr diff --git a/gerbonara/tests/resources/kicad-older/chibi_2024-B.SilkS.gbr b/tests/resources/kicad-older/chibi_2024-B.SilkS.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-older/chibi_2024-B.SilkS.gbr rename to tests/resources/kicad-older/chibi_2024-B.SilkS.gbr diff --git a/gerbonara/tests/resources/kicad-older/chibi_2024-Edge.Cuts.gbr b/tests/resources/kicad-older/chibi_2024-Edge.Cuts.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-older/chibi_2024-Edge.Cuts.gbr rename to tests/resources/kicad-older/chibi_2024-Edge.Cuts.gbr diff --git a/gerbonara/tests/resources/kicad-older/chibi_2024-F.Cu.gbr b/tests/resources/kicad-older/chibi_2024-F.Cu.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-older/chibi_2024-F.Cu.gbr rename to tests/resources/kicad-older/chibi_2024-F.Cu.gbr diff --git a/gerbonara/tests/resources/kicad-older/chibi_2024-F.Mask.gbr b/tests/resources/kicad-older/chibi_2024-F.Mask.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-older/chibi_2024-F.Mask.gbr rename to tests/resources/kicad-older/chibi_2024-F.Mask.gbr diff --git a/gerbonara/tests/resources/kicad-older/chibi_2024-F.Paste.gbr b/tests/resources/kicad-older/chibi_2024-F.Paste.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-older/chibi_2024-F.Paste.gbr rename to tests/resources/kicad-older/chibi_2024-F.Paste.gbr diff --git a/gerbonara/tests/resources/kicad-older/chibi_2024-F.SilkS.gbr b/tests/resources/kicad-older/chibi_2024-F.SilkS.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-older/chibi_2024-F.SilkS.gbr rename to tests/resources/kicad-older/chibi_2024-F.SilkS.gbr diff --git a/gerbonara/tests/resources/kicad-older/chibi_2024.drl b/tests/resources/kicad-older/chibi_2024.drl similarity index 100% rename from gerbonara/tests/resources/kicad-older/chibi_2024.drl rename to tests/resources/kicad-older/chibi_2024.drl diff --git a/gerbonara/tests/resources/kicad-x2-tests/SOURCE b/tests/resources/kicad-x2-tests/SOURCE similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/SOURCE rename to tests/resources/kicad-x2-tests/SOURCE diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-B_Cu.gbr b/tests/resources/kicad-x2-tests/nox2ap/Flashpads-B_Cu.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-B_Cu.gbr rename to tests/resources/kicad-x2-tests/nox2ap/Flashpads-B_Cu.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-B_Mask.gbr b/tests/resources/kicad-x2-tests/nox2ap/Flashpads-B_Mask.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-B_Mask.gbr rename to tests/resources/kicad-x2-tests/nox2ap/Flashpads-B_Mask.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-B_Paste.gbr b/tests/resources/kicad-x2-tests/nox2ap/Flashpads-B_Paste.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-B_Paste.gbr rename to tests/resources/kicad-x2-tests/nox2ap/Flashpads-B_Paste.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-B_Silkscreen.gbr b/tests/resources/kicad-x2-tests/nox2ap/Flashpads-B_Silkscreen.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-B_Silkscreen.gbr rename to tests/resources/kicad-x2-tests/nox2ap/Flashpads-B_Silkscreen.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-Edge_Cuts.gbr b/tests/resources/kicad-x2-tests/nox2ap/Flashpads-Edge_Cuts.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-Edge_Cuts.gbr rename to tests/resources/kicad-x2-tests/nox2ap/Flashpads-Edge_Cuts.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-F_Cu.gbr b/tests/resources/kicad-x2-tests/nox2ap/Flashpads-F_Cu.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-F_Cu.gbr rename to tests/resources/kicad-x2-tests/nox2ap/Flashpads-F_Cu.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-F_Mask.gbr b/tests/resources/kicad-x2-tests/nox2ap/Flashpads-F_Mask.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-F_Mask.gbr rename to tests/resources/kicad-x2-tests/nox2ap/Flashpads-F_Mask.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-F_Paste.gbr b/tests/resources/kicad-x2-tests/nox2ap/Flashpads-F_Paste.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-F_Paste.gbr rename to tests/resources/kicad-x2-tests/nox2ap/Flashpads-F_Paste.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-F_Silkscreen.gbr b/tests/resources/kicad-x2-tests/nox2ap/Flashpads-F_Silkscreen.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-F_Silkscreen.gbr rename to tests/resources/kicad-x2-tests/nox2ap/Flashpads-F_Silkscreen.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-NPTH.drl b/tests/resources/kicad-x2-tests/nox2ap/Flashpads-NPTH.drl similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-NPTH.drl rename to tests/resources/kicad-x2-tests/nox2ap/Flashpads-NPTH.drl diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-PTH.drl b/tests/resources/kicad-x2-tests/nox2ap/Flashpads-PTH.drl similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2ap/Flashpads-PTH.drl rename to tests/resources/kicad-x2-tests/nox2ap/Flashpads-PTH.drl diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-B_Cu.gbr b/tests/resources/kicad-x2-tests/nox2noap/Flashpads-B_Cu.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-B_Cu.gbr rename to tests/resources/kicad-x2-tests/nox2noap/Flashpads-B_Cu.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-B_Mask.gbr b/tests/resources/kicad-x2-tests/nox2noap/Flashpads-B_Mask.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-B_Mask.gbr rename to tests/resources/kicad-x2-tests/nox2noap/Flashpads-B_Mask.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-B_Paste.gbr b/tests/resources/kicad-x2-tests/nox2noap/Flashpads-B_Paste.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-B_Paste.gbr rename to tests/resources/kicad-x2-tests/nox2noap/Flashpads-B_Paste.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-B_Silkscreen.gbr b/tests/resources/kicad-x2-tests/nox2noap/Flashpads-B_Silkscreen.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-B_Silkscreen.gbr rename to tests/resources/kicad-x2-tests/nox2noap/Flashpads-B_Silkscreen.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-Edge_Cuts.gbr b/tests/resources/kicad-x2-tests/nox2noap/Flashpads-Edge_Cuts.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-Edge_Cuts.gbr rename to tests/resources/kicad-x2-tests/nox2noap/Flashpads-Edge_Cuts.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-F_Cu.gbr b/tests/resources/kicad-x2-tests/nox2noap/Flashpads-F_Cu.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-F_Cu.gbr rename to tests/resources/kicad-x2-tests/nox2noap/Flashpads-F_Cu.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-F_Mask.gbr b/tests/resources/kicad-x2-tests/nox2noap/Flashpads-F_Mask.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-F_Mask.gbr rename to tests/resources/kicad-x2-tests/nox2noap/Flashpads-F_Mask.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-F_Paste.gbr b/tests/resources/kicad-x2-tests/nox2noap/Flashpads-F_Paste.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-F_Paste.gbr rename to tests/resources/kicad-x2-tests/nox2noap/Flashpads-F_Paste.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-F_Silkscreen.gbr b/tests/resources/kicad-x2-tests/nox2noap/Flashpads-F_Silkscreen.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-F_Silkscreen.gbr rename to tests/resources/kicad-x2-tests/nox2noap/Flashpads-F_Silkscreen.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-NPTH.drl b/tests/resources/kicad-x2-tests/nox2noap/Flashpads-NPTH.drl similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-NPTH.drl rename to tests/resources/kicad-x2-tests/nox2noap/Flashpads-NPTH.drl diff --git a/gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-PTH.drl b/tests/resources/kicad-x2-tests/nox2noap/Flashpads-PTH.drl similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/nox2noap/Flashpads-PTH.drl rename to tests/resources/kicad-x2-tests/nox2noap/Flashpads-PTH.drl diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-B_Cu.gbr b/tests/resources/kicad-x2-tests/x2ap/Flashpads-B_Cu.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-B_Cu.gbr rename to tests/resources/kicad-x2-tests/x2ap/Flashpads-B_Cu.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-B_Mask.gbr b/tests/resources/kicad-x2-tests/x2ap/Flashpads-B_Mask.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-B_Mask.gbr rename to tests/resources/kicad-x2-tests/x2ap/Flashpads-B_Mask.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-B_Paste.gbr b/tests/resources/kicad-x2-tests/x2ap/Flashpads-B_Paste.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-B_Paste.gbr rename to tests/resources/kicad-x2-tests/x2ap/Flashpads-B_Paste.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-B_Silkscreen.gbr b/tests/resources/kicad-x2-tests/x2ap/Flashpads-B_Silkscreen.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-B_Silkscreen.gbr rename to tests/resources/kicad-x2-tests/x2ap/Flashpads-B_Silkscreen.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-Edge_Cuts.gbr b/tests/resources/kicad-x2-tests/x2ap/Flashpads-Edge_Cuts.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-Edge_Cuts.gbr rename to tests/resources/kicad-x2-tests/x2ap/Flashpads-Edge_Cuts.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-F_Cu.gbr b/tests/resources/kicad-x2-tests/x2ap/Flashpads-F_Cu.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-F_Cu.gbr rename to tests/resources/kicad-x2-tests/x2ap/Flashpads-F_Cu.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-F_Mask.gbr b/tests/resources/kicad-x2-tests/x2ap/Flashpads-F_Mask.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-F_Mask.gbr rename to tests/resources/kicad-x2-tests/x2ap/Flashpads-F_Mask.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-F_Paste.gbr b/tests/resources/kicad-x2-tests/x2ap/Flashpads-F_Paste.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-F_Paste.gbr rename to tests/resources/kicad-x2-tests/x2ap/Flashpads-F_Paste.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-F_Silkscreen.gbr b/tests/resources/kicad-x2-tests/x2ap/Flashpads-F_Silkscreen.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-F_Silkscreen.gbr rename to tests/resources/kicad-x2-tests/x2ap/Flashpads-F_Silkscreen.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-NPTH.drl b/tests/resources/kicad-x2-tests/x2ap/Flashpads-NPTH.drl similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-NPTH.drl rename to tests/resources/kicad-x2-tests/x2ap/Flashpads-NPTH.drl diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-PTH.drl b/tests/resources/kicad-x2-tests/x2ap/Flashpads-PTH.drl similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2ap/Flashpads-PTH.drl rename to tests/resources/kicad-x2-tests/x2ap/Flashpads-PTH.drl diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-B_Cu.gbr b/tests/resources/kicad-x2-tests/x2noap/Flashpads-B_Cu.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-B_Cu.gbr rename to tests/resources/kicad-x2-tests/x2noap/Flashpads-B_Cu.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-B_Mask.gbr b/tests/resources/kicad-x2-tests/x2noap/Flashpads-B_Mask.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-B_Mask.gbr rename to tests/resources/kicad-x2-tests/x2noap/Flashpads-B_Mask.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-B_Paste.gbr b/tests/resources/kicad-x2-tests/x2noap/Flashpads-B_Paste.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-B_Paste.gbr rename to tests/resources/kicad-x2-tests/x2noap/Flashpads-B_Paste.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-B_Silkscreen.gbr b/tests/resources/kicad-x2-tests/x2noap/Flashpads-B_Silkscreen.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-B_Silkscreen.gbr rename to tests/resources/kicad-x2-tests/x2noap/Flashpads-B_Silkscreen.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-Edge_Cuts.gbr b/tests/resources/kicad-x2-tests/x2noap/Flashpads-Edge_Cuts.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-Edge_Cuts.gbr rename to tests/resources/kicad-x2-tests/x2noap/Flashpads-Edge_Cuts.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-F_Cu.gbr b/tests/resources/kicad-x2-tests/x2noap/Flashpads-F_Cu.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-F_Cu.gbr rename to tests/resources/kicad-x2-tests/x2noap/Flashpads-F_Cu.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-F_Mask.gbr b/tests/resources/kicad-x2-tests/x2noap/Flashpads-F_Mask.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-F_Mask.gbr rename to tests/resources/kicad-x2-tests/x2noap/Flashpads-F_Mask.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-F_Paste.gbr b/tests/resources/kicad-x2-tests/x2noap/Flashpads-F_Paste.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-F_Paste.gbr rename to tests/resources/kicad-x2-tests/x2noap/Flashpads-F_Paste.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-F_Silkscreen.gbr b/tests/resources/kicad-x2-tests/x2noap/Flashpads-F_Silkscreen.gbr similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-F_Silkscreen.gbr rename to tests/resources/kicad-x2-tests/x2noap/Flashpads-F_Silkscreen.gbr diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-NPTH.drl b/tests/resources/kicad-x2-tests/x2noap/Flashpads-NPTH.drl similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-NPTH.drl rename to tests/resources/kicad-x2-tests/x2noap/Flashpads-NPTH.drl diff --git a/gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-PTH.drl b/tests/resources/kicad-x2-tests/x2noap/Flashpads-PTH.drl similarity index 100% rename from gerbonara/tests/resources/kicad-x2-tests/x2noap/Flashpads-PTH.drl rename to tests/resources/kicad-x2-tests/x2noap/Flashpads-PTH.drl diff --git a/gerbonara/tests/resources/multiline_read.ger b/tests/resources/multiline_read.ger similarity index 100% rename from gerbonara/tests/resources/multiline_read.ger rename to tests/resources/multiline_read.ger diff --git a/gerbonara/tests/resources/ncdrill.DRD b/tests/resources/ncdrill.DRD similarity index 100% rename from gerbonara/tests/resources/ncdrill.DRD rename to tests/resources/ncdrill.DRD diff --git a/gerbonara/tests/resources/open_outline_altium.gbr b/tests/resources/open_outline_altium.gbr similarity index 100% rename from gerbonara/tests/resources/open_outline_altium.gbr rename to tests/resources/open_outline_altium.gbr diff --git a/gerbonara/tests/resources/orcad/Assembly.art b/tests/resources/orcad/Assembly.art similarity index 100% rename from gerbonara/tests/resources/orcad/Assembly.art rename to tests/resources/orcad/Assembly.art diff --git a/gerbonara/tests/resources/orcad/BOTTOM.art b/tests/resources/orcad/BOTTOM.art similarity index 100% rename from gerbonara/tests/resources/orcad/BOTTOM.art rename to tests/resources/orcad/BOTTOM.art diff --git a/gerbonara/tests/resources/orcad/GND2.art b/tests/resources/orcad/GND2.art similarity index 100% rename from gerbonara/tests/resources/orcad/GND2.art rename to tests/resources/orcad/GND2.art diff --git a/gerbonara/tests/resources/orcad/LAYER_1.art b/tests/resources/orcad/LAYER_1.art similarity index 100% rename from gerbonara/tests/resources/orcad/LAYER_1.art rename to tests/resources/orcad/LAYER_1.art diff --git a/gerbonara/tests/resources/orcad/LAYER_2.art b/tests/resources/orcad/LAYER_2.art similarity index 100% rename from gerbonara/tests/resources/orcad/LAYER_2.art rename to tests/resources/orcad/LAYER_2.art diff --git a/gerbonara/tests/resources/orcad/PWR.art b/tests/resources/orcad/PWR.art similarity index 100% rename from gerbonara/tests/resources/orcad/PWR.art rename to tests/resources/orcad/PWR.art diff --git a/gerbonara/tests/resources/orcad/README b/tests/resources/orcad/README similarity index 100% rename from gerbonara/tests/resources/orcad/README rename to tests/resources/orcad/README diff --git a/gerbonara/tests/resources/orcad/Solder_Mask_Bottom.art b/tests/resources/orcad/Solder_Mask_Bottom.art similarity index 100% rename from gerbonara/tests/resources/orcad/Solder_Mask_Bottom.art rename to tests/resources/orcad/Solder_Mask_Bottom.art diff --git a/gerbonara/tests/resources/orcad/Solder_Mask_Top.art b/tests/resources/orcad/Solder_Mask_Top.art similarity index 100% rename from gerbonara/tests/resources/orcad/Solder_Mask_Top.art rename to tests/resources/orcad/Solder_Mask_Top.art diff --git a/gerbonara/tests/resources/orcad/TOP.art b/tests/resources/orcad/TOP.art similarity index 100% rename from gerbonara/tests/resources/orcad/TOP.art rename to tests/resources/orcad/TOP.art diff --git a/gerbonara/tests/resources/orcad/arena_12-12_v6_L1-L6.drl b/tests/resources/orcad/arena_12-12_v6_L1-L6.drl similarity index 100% rename from gerbonara/tests/resources/orcad/arena_12-12_v6_L1-L6.drl rename to tests/resources/orcad/arena_12-12_v6_L1-L6.drl diff --git a/gerbonara/tests/resources/orcad/silk_screen_bottom.art b/tests/resources/orcad/silk_screen_bottom.art similarity index 100% rename from gerbonara/tests/resources/orcad/silk_screen_bottom.art rename to tests/resources/orcad/silk_screen_bottom.art diff --git a/gerbonara/tests/resources/orcad/silk_screen_top.art b/tests/resources/orcad/silk_screen_top.art similarity index 100% rename from gerbonara/tests/resources/orcad/silk_screen_top.art rename to tests/resources/orcad/silk_screen_top.art diff --git a/gerbonara/tests/resources/p-cad/LICENSE b/tests/resources/p-cad/LICENSE similarity index 100% rename from gerbonara/tests/resources/p-cad/LICENSE rename to tests/resources/p-cad/LICENSE diff --git a/gerbonara/tests/resources/p-cad/README b/tests/resources/p-cad/README similarity index 100% rename from gerbonara/tests/resources/p-cad/README rename to tests/resources/p-cad/README diff --git a/gerbonara/tests/resources/p-cad/SOURCE b/tests/resources/p-cad/SOURCE similarity index 100% rename from gerbonara/tests/resources/p-cad/SOURCE rename to tests/resources/p-cad/SOURCE diff --git a/gerbonara/tests/resources/p-cad/ZXINET.DRL b/tests/resources/p-cad/ZXINET.DRL similarity index 100% rename from gerbonara/tests/resources/p-cad/ZXINET.DRL rename to tests/resources/p-cad/ZXINET.DRL diff --git a/gerbonara/tests/resources/p-cad/ZXINET.GBL b/tests/resources/p-cad/ZXINET.GBL similarity index 100% rename from gerbonara/tests/resources/p-cad/ZXINET.GBL rename to tests/resources/p-cad/ZXINET.GBL diff --git a/gerbonara/tests/resources/p-cad/ZXINET.GBO b/tests/resources/p-cad/ZXINET.GBO similarity index 100% rename from gerbonara/tests/resources/p-cad/ZXINET.GBO rename to tests/resources/p-cad/ZXINET.GBO diff --git a/gerbonara/tests/resources/p-cad/ZXINET.GBS b/tests/resources/p-cad/ZXINET.GBS similarity index 100% rename from gerbonara/tests/resources/p-cad/ZXINET.GBS rename to tests/resources/p-cad/ZXINET.GBS diff --git a/gerbonara/tests/resources/p-cad/ZXINET.GKO b/tests/resources/p-cad/ZXINET.GKO similarity index 100% rename from gerbonara/tests/resources/p-cad/ZXINET.GKO rename to tests/resources/p-cad/ZXINET.GKO diff --git a/gerbonara/tests/resources/p-cad/ZXINET.GTL b/tests/resources/p-cad/ZXINET.GTL similarity index 100% rename from gerbonara/tests/resources/p-cad/ZXINET.GTL rename to tests/resources/p-cad/ZXINET.GTL diff --git a/gerbonara/tests/resources/p-cad/ZXINET.GTO b/tests/resources/p-cad/ZXINET.GTO similarity index 100% rename from gerbonara/tests/resources/p-cad/ZXINET.GTO rename to tests/resources/p-cad/ZXINET.GTO diff --git a/gerbonara/tests/resources/p-cad/ZXINET.GTS b/tests/resources/p-cad/ZXINET.GTS similarity index 100% rename from gerbonara/tests/resources/p-cad/ZXINET.GTS rename to tests/resources/p-cad/ZXINET.GTS diff --git a/gerbonara/tests/resources/p-cad/ZXINET.gvp b/tests/resources/p-cad/ZXINET.gvp similarity index 100% rename from gerbonara/tests/resources/p-cad/ZXINET.gvp rename to tests/resources/p-cad/ZXINET.gvp diff --git a/gerbonara/tests/resources/p-cad/ZXINET.zip b/tests/resources/p-cad/ZXINET.zip similarity index 100% rename from gerbonara/tests/resources/p-cad/ZXINET.zip rename to tests/resources/p-cad/ZXINET.zip diff --git a/gerbonara/tests/resources/pads/Bottom.pho b/tests/resources/pads/Bottom.pho similarity index 100% rename from gerbonara/tests/resources/pads/Bottom.pho rename to tests/resources/pads/Bottom.pho diff --git a/gerbonara/tests/resources/pads/Drill.drl b/tests/resources/pads/Drill.drl similarity index 100% rename from gerbonara/tests/resources/pads/Drill.drl rename to tests/resources/pads/Drill.drl diff --git a/gerbonara/tests/resources/pads/LICENSE b/tests/resources/pads/LICENSE similarity index 100% rename from gerbonara/tests/resources/pads/LICENSE rename to tests/resources/pads/LICENSE diff --git a/gerbonara/tests/resources/pads/Layer2.pho b/tests/resources/pads/Layer2.pho similarity index 100% rename from gerbonara/tests/resources/pads/Layer2.pho rename to tests/resources/pads/Layer2.pho diff --git a/gerbonara/tests/resources/pads/Layer3.pho b/tests/resources/pads/Layer3.pho similarity index 100% rename from gerbonara/tests/resources/pads/Layer3.pho rename to tests/resources/pads/Layer3.pho diff --git a/gerbonara/tests/resources/pads/README b/tests/resources/pads/README similarity index 100% rename from gerbonara/tests/resources/pads/README rename to tests/resources/pads/README diff --git a/gerbonara/tests/resources/pads/SMB.pho b/tests/resources/pads/SMB.pho similarity index 100% rename from gerbonara/tests/resources/pads/SMB.pho rename to tests/resources/pads/SMB.pho diff --git a/gerbonara/tests/resources/pads/SMT.pho b/tests/resources/pads/SMT.pho similarity index 100% rename from gerbonara/tests/resources/pads/SMT.pho rename to tests/resources/pads/SMT.pho diff --git a/gerbonara/tests/resources/pads/SSB.pho b/tests/resources/pads/SSB.pho similarity index 100% rename from gerbonara/tests/resources/pads/SSB.pho rename to tests/resources/pads/SSB.pho diff --git a/gerbonara/tests/resources/pads/SST.pho b/tests/resources/pads/SST.pho similarity index 100% rename from gerbonara/tests/resources/pads/SST.pho rename to tests/resources/pads/SST.pho diff --git a/gerbonara/tests/resources/pads/Top.pho b/tests/resources/pads/Top.pho similarity index 100% rename from gerbonara/tests/resources/pads/Top.pho rename to tests/resources/pads/Top.pho diff --git a/gerbonara/tests/resources/pcb-rnd/LICENSE b/tests/resources/pcb-rnd/LICENSE similarity index 100% rename from gerbonara/tests/resources/pcb-rnd/LICENSE rename to tests/resources/pcb-rnd/LICENSE diff --git a/gerbonara/tests/resources/pcb-rnd/README b/tests/resources/pcb-rnd/README similarity index 100% rename from gerbonara/tests/resources/pcb-rnd/README rename to tests/resources/pcb-rnd/README diff --git a/gerbonara/tests/resources/pcb-rnd/power-art.asb b/tests/resources/pcb-rnd/power-art.asb similarity index 100% rename from gerbonara/tests/resources/pcb-rnd/power-art.asb rename to tests/resources/pcb-rnd/power-art.asb diff --git a/gerbonara/tests/resources/pcb-rnd/power-art.ast b/tests/resources/pcb-rnd/power-art.ast similarity index 100% rename from gerbonara/tests/resources/pcb-rnd/power-art.ast rename to tests/resources/pcb-rnd/power-art.ast diff --git a/gerbonara/tests/resources/pcb-rnd/power-art.fab b/tests/resources/pcb-rnd/power-art.fab similarity index 100% rename from gerbonara/tests/resources/pcb-rnd/power-art.fab rename to tests/resources/pcb-rnd/power-art.fab diff --git a/gerbonara/tests/resources/pcb-rnd/power-art.gbl b/tests/resources/pcb-rnd/power-art.gbl similarity index 100% rename from gerbonara/tests/resources/pcb-rnd/power-art.gbl rename to tests/resources/pcb-rnd/power-art.gbl diff --git a/gerbonara/tests/resources/pcb-rnd/power-art.gbo b/tests/resources/pcb-rnd/power-art.gbo similarity index 100% rename from gerbonara/tests/resources/pcb-rnd/power-art.gbo rename to tests/resources/pcb-rnd/power-art.gbo diff --git a/gerbonara/tests/resources/pcb-rnd/power-art.gbp b/tests/resources/pcb-rnd/power-art.gbp similarity index 100% rename from gerbonara/tests/resources/pcb-rnd/power-art.gbp rename to tests/resources/pcb-rnd/power-art.gbp diff --git a/gerbonara/tests/resources/pcb-rnd/power-art.gbs b/tests/resources/pcb-rnd/power-art.gbs similarity index 100% rename from gerbonara/tests/resources/pcb-rnd/power-art.gbs rename to tests/resources/pcb-rnd/power-art.gbs diff --git a/gerbonara/tests/resources/pcb-rnd/power-art.gko b/tests/resources/pcb-rnd/power-art.gko similarity index 100% rename from gerbonara/tests/resources/pcb-rnd/power-art.gko rename to tests/resources/pcb-rnd/power-art.gko diff --git a/gerbonara/tests/resources/pcb-rnd/power-art.gtl b/tests/resources/pcb-rnd/power-art.gtl similarity index 100% rename from gerbonara/tests/resources/pcb-rnd/power-art.gtl rename to tests/resources/pcb-rnd/power-art.gtl diff --git a/gerbonara/tests/resources/pcb-rnd/power-art.gto b/tests/resources/pcb-rnd/power-art.gto similarity index 100% rename from gerbonara/tests/resources/pcb-rnd/power-art.gto rename to tests/resources/pcb-rnd/power-art.gto diff --git a/gerbonara/tests/resources/pcb-rnd/power-art.gtp b/tests/resources/pcb-rnd/power-art.gtp similarity index 100% rename from gerbonara/tests/resources/pcb-rnd/power-art.gtp rename to tests/resources/pcb-rnd/power-art.gtp diff --git a/gerbonara/tests/resources/pcb-rnd/power-art.gts b/tests/resources/pcb-rnd/power-art.gts similarity index 100% rename from gerbonara/tests/resources/pcb-rnd/power-art.gts rename to tests/resources/pcb-rnd/power-art.gts diff --git a/gerbonara/tests/resources/pcb-rnd/power-art.lht b/tests/resources/pcb-rnd/power-art.lht similarity index 100% rename from gerbonara/tests/resources/pcb-rnd/power-art.lht rename to tests/resources/pcb-rnd/power-art.lht diff --git a/gerbonara/tests/resources/pcb-rnd/power-art.xln b/tests/resources/pcb-rnd/power-art.xln similarity index 100% rename from gerbonara/tests/resources/pcb-rnd/power-art.xln rename to tests/resources/pcb-rnd/power-art.xln diff --git a/gerbonara/tests/resources/siemens-2/Gerber/BoardOutlline.gdo b/tests/resources/siemens-2/Gerber/BoardOutlline.gdo similarity index 100% rename from gerbonara/tests/resources/siemens-2/Gerber/BoardOutlline.gdo rename to tests/resources/siemens-2/Gerber/BoardOutlline.gdo diff --git a/gerbonara/tests/resources/siemens-2/Gerber/DrillDrawingThrough.gdo b/tests/resources/siemens-2/Gerber/DrillDrawingThrough.gdo similarity index 100% rename from gerbonara/tests/resources/siemens-2/Gerber/DrillDrawingThrough.gdo rename to tests/resources/siemens-2/Gerber/DrillDrawingThrough.gdo diff --git a/gerbonara/tests/resources/siemens-2/Gerber/EtchLayerBottom.gdo b/tests/resources/siemens-2/Gerber/EtchLayerBottom.gdo similarity index 100% rename from gerbonara/tests/resources/siemens-2/Gerber/EtchLayerBottom.gdo rename to tests/resources/siemens-2/Gerber/EtchLayerBottom.gdo diff --git a/gerbonara/tests/resources/siemens-2/Gerber/EtchLayerTop.gdo b/tests/resources/siemens-2/Gerber/EtchLayerTop.gdo similarity index 100% rename from gerbonara/tests/resources/siemens-2/Gerber/EtchLayerTop.gdo rename to tests/resources/siemens-2/Gerber/EtchLayerTop.gdo diff --git a/gerbonara/tests/resources/siemens-2/Gerber/GerberPlot.gpf b/tests/resources/siemens-2/Gerber/GerberPlot.gpf similarity index 100% rename from gerbonara/tests/resources/siemens-2/Gerber/GerberPlot.gpf rename to tests/resources/siemens-2/Gerber/GerberPlot.gpf diff --git a/gerbonara/tests/resources/siemens-2/Gerber/PCB.dsn b/tests/resources/siemens-2/Gerber/PCB.dsn similarity index 100% rename from gerbonara/tests/resources/siemens-2/Gerber/PCB.dsn rename to tests/resources/siemens-2/Gerber/PCB.dsn diff --git a/gerbonara/tests/resources/siemens-2/Gerber/SolderPasteBottom.gdo b/tests/resources/siemens-2/Gerber/SolderPasteBottom.gdo similarity index 100% rename from gerbonara/tests/resources/siemens-2/Gerber/SolderPasteBottom.gdo rename to tests/resources/siemens-2/Gerber/SolderPasteBottom.gdo diff --git a/gerbonara/tests/resources/siemens-2/Gerber/SolderPasteTop.gdo b/tests/resources/siemens-2/Gerber/SolderPasteTop.gdo similarity index 100% rename from gerbonara/tests/resources/siemens-2/Gerber/SolderPasteTop.gdo rename to tests/resources/siemens-2/Gerber/SolderPasteTop.gdo diff --git a/gerbonara/tests/resources/siemens-2/Gerber/SoldermaskBottom.gdo b/tests/resources/siemens-2/Gerber/SoldermaskBottom.gdo similarity index 100% rename from gerbonara/tests/resources/siemens-2/Gerber/SoldermaskBottom.gdo rename to tests/resources/siemens-2/Gerber/SoldermaskBottom.gdo diff --git a/gerbonara/tests/resources/siemens-2/Gerber/SoldermaskTop.gdo b/tests/resources/siemens-2/Gerber/SoldermaskTop.gdo similarity index 100% rename from gerbonara/tests/resources/siemens-2/Gerber/SoldermaskTop.gdo rename to tests/resources/siemens-2/Gerber/SoldermaskTop.gdo diff --git a/gerbonara/tests/resources/siemens-2/LICENSE b/tests/resources/siemens-2/LICENSE similarity index 100% rename from gerbonara/tests/resources/siemens-2/LICENSE rename to tests/resources/siemens-2/LICENSE diff --git a/gerbonara/tests/resources/siemens-2/NCDrill/ContourPlated.ncd b/tests/resources/siemens-2/NCDrill/ContourPlated.ncd similarity index 100% rename from gerbonara/tests/resources/siemens-2/NCDrill/ContourPlated.ncd rename to tests/resources/siemens-2/NCDrill/ContourPlated.ncd diff --git a/gerbonara/tests/resources/siemens-2/NCDrill/ThruHoleNonPlated.ncd b/tests/resources/siemens-2/NCDrill/ThruHoleNonPlated.ncd similarity index 100% rename from gerbonara/tests/resources/siemens-2/NCDrill/ThruHoleNonPlated.ncd rename to tests/resources/siemens-2/NCDrill/ThruHoleNonPlated.ncd diff --git a/gerbonara/tests/resources/siemens-2/NCDrill/ThruHolePlated.ncd b/tests/resources/siemens-2/NCDrill/ThruHolePlated.ncd similarity index 100% rename from gerbonara/tests/resources/siemens-2/NCDrill/ThruHolePlated.ncd rename to tests/resources/siemens-2/NCDrill/ThruHolePlated.ncd diff --git a/gerbonara/tests/resources/siemens-2/README b/tests/resources/siemens-2/README similarity index 100% rename from gerbonara/tests/resources/siemens-2/README rename to tests/resources/siemens-2/README diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_ContourPlated.ncd b/tests/resources/siemens/80101_0125_F200_ContourPlated.ncd similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_ContourPlated.ncd rename to tests/resources/siemens/80101_0125_F200_ContourPlated.ncd diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_DrillDrawingThrough.gdo b/tests/resources/siemens/80101_0125_F200_DrillDrawingThrough.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_DrillDrawingThrough.gdo rename to tests/resources/siemens/80101_0125_F200_DrillDrawingThrough.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_L01_Top.gdo b/tests/resources/siemens/80101_0125_F200_L01_Top.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_L01_Top.gdo rename to tests/resources/siemens/80101_0125_F200_L01_Top.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_L02.gdo b/tests/resources/siemens/80101_0125_F200_L02.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_L02.gdo rename to tests/resources/siemens/80101_0125_F200_L02.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_L03.gdo b/tests/resources/siemens/80101_0125_F200_L03.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_L03.gdo rename to tests/resources/siemens/80101_0125_F200_L03.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_L04.gdo b/tests/resources/siemens/80101_0125_F200_L04.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_L04.gdo rename to tests/resources/siemens/80101_0125_F200_L04.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_L05.gdo b/tests/resources/siemens/80101_0125_F200_L05.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_L05.gdo rename to tests/resources/siemens/80101_0125_F200_L05.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_L06.gdo b/tests/resources/siemens/80101_0125_F200_L06.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_L06.gdo rename to tests/resources/siemens/80101_0125_F200_L06.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_L07.gdo b/tests/resources/siemens/80101_0125_F200_L07.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_L07.gdo rename to tests/resources/siemens/80101_0125_F200_L07.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_L08.gdo b/tests/resources/siemens/80101_0125_F200_L08.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_L08.gdo rename to tests/resources/siemens/80101_0125_F200_L08.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_L09.gdo b/tests/resources/siemens/80101_0125_F200_L09.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_L09.gdo rename to tests/resources/siemens/80101_0125_F200_L09.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_L10.gdo b/tests/resources/siemens/80101_0125_F200_L10.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_L10.gdo rename to tests/resources/siemens/80101_0125_F200_L10.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_L11.gdo b/tests/resources/siemens/80101_0125_F200_L11.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_L11.gdo rename to tests/resources/siemens/80101_0125_F200_L11.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_L12_Bottom.gdo b/tests/resources/siemens/80101_0125_F200_L12_Bottom.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_L12_Bottom.gdo rename to tests/resources/siemens/80101_0125_F200_L12_Bottom.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_SilkscreenBottom.gdo b/tests/resources/siemens/80101_0125_F200_SilkscreenBottom.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_SilkscreenBottom.gdo rename to tests/resources/siemens/80101_0125_F200_SilkscreenBottom.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_SilkscreenTop.gdo b/tests/resources/siemens/80101_0125_F200_SilkscreenTop.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_SilkscreenTop.gdo rename to tests/resources/siemens/80101_0125_F200_SilkscreenTop.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_SolderPasteBottom.gdo b/tests/resources/siemens/80101_0125_F200_SolderPasteBottom.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_SolderPasteBottom.gdo rename to tests/resources/siemens/80101_0125_F200_SolderPasteBottom.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_SolderPasteTop.gdo b/tests/resources/siemens/80101_0125_F200_SolderPasteTop.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_SolderPasteTop.gdo rename to tests/resources/siemens/80101_0125_F200_SolderPasteTop.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_SoldermaskBottom.gdo b/tests/resources/siemens/80101_0125_F200_SoldermaskBottom.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_SoldermaskBottom.gdo rename to tests/resources/siemens/80101_0125_F200_SoldermaskBottom.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_SoldermaskTop.gdo b/tests/resources/siemens/80101_0125_F200_SoldermaskTop.gdo similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_SoldermaskTop.gdo rename to tests/resources/siemens/80101_0125_F200_SoldermaskTop.gdo diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_ThruHoleNonPlated.ncd b/tests/resources/siemens/80101_0125_F200_ThruHoleNonPlated.ncd similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_ThruHoleNonPlated.ncd rename to tests/resources/siemens/80101_0125_F200_ThruHoleNonPlated.ncd diff --git a/gerbonara/tests/resources/siemens/80101_0125_F200_ThruHolePlated.ncd b/tests/resources/siemens/80101_0125_F200_ThruHolePlated.ncd similarity index 100% rename from gerbonara/tests/resources/siemens/80101_0125_F200_ThruHolePlated.ncd rename to tests/resources/siemens/80101_0125_F200_ThruHolePlated.ncd diff --git a/gerbonara/tests/resources/siemens/LICENSE b/tests/resources/siemens/LICENSE similarity index 100% rename from gerbonara/tests/resources/siemens/LICENSE rename to tests/resources/siemens/LICENSE diff --git a/gerbonara/tests/resources/siemens/README b/tests/resources/siemens/README similarity index 100% rename from gerbonara/tests/resources/siemens/README rename to tests/resources/siemens/README diff --git a/gerbonara/tests/resources/test_fine_lines_x.gbr b/tests/resources/test_fine_lines_x.gbr similarity index 100% rename from gerbonara/tests/resources/test_fine_lines_x.gbr rename to tests/resources/test_fine_lines_x.gbr diff --git a/gerbonara/tests/resources/test_fine_lines_y.gbr b/tests/resources/test_fine_lines_y.gbr similarity index 100% rename from gerbonara/tests/resources/test_fine_lines_y.gbr rename to tests/resources/test_fine_lines_y.gbr diff --git a/gerbonara/tests/resources/test_syntax_error.exc b/tests/resources/test_syntax_error.exc similarity index 100% rename from gerbonara/tests/resources/test_syntax_error.exc rename to tests/resources/test_syntax_error.exc diff --git a/gerbonara/tests/resources/test_syntax_error.gbr b/tests/resources/test_syntax_error.gbr similarity index 100% rename from gerbonara/tests/resources/test_syntax_error.gbr rename to tests/resources/test_syntax_error.gbr diff --git a/gerbonara/tests/resources/top_copper.GTL b/tests/resources/top_copper.GTL similarity index 100% rename from gerbonara/tests/resources/top_copper.GTL rename to tests/resources/top_copper.GTL diff --git a/gerbonara/tests/resources/top_mask.GTS b/tests/resources/top_mask.GTS similarity index 100% rename from gerbonara/tests/resources/top_mask.GTS rename to tests/resources/top_mask.GTS diff --git a/gerbonara/tests/resources/top_silk.GTO b/tests/resources/top_silk.GTO similarity index 100% rename from gerbonara/tests/resources/top_silk.GTO rename to tests/resources/top_silk.GTO diff --git a/gerbonara/tests/resources/upverter/LICENSE b/tests/resources/upverter/LICENSE similarity index 100% rename from gerbonara/tests/resources/upverter/LICENSE rename to tests/resources/upverter/LICENSE diff --git a/gerbonara/tests/resources/upverter/README b/tests/resources/upverter/README similarity index 100% rename from gerbonara/tests/resources/upverter/README rename to tests/resources/upverter/README diff --git a/gerbonara/tests/resources/upverter/design_export.drl b/tests/resources/upverter/design_export.drl similarity index 100% rename from gerbonara/tests/resources/upverter/design_export.drl rename to tests/resources/upverter/design_export.drl diff --git a/gerbonara/tests/resources/upverter/design_export.gbl b/tests/resources/upverter/design_export.gbl similarity index 100% rename from gerbonara/tests/resources/upverter/design_export.gbl rename to tests/resources/upverter/design_export.gbl diff --git a/gerbonara/tests/resources/upverter/design_export.gbo b/tests/resources/upverter/design_export.gbo similarity index 100% rename from gerbonara/tests/resources/upverter/design_export.gbo rename to tests/resources/upverter/design_export.gbo diff --git a/gerbonara/tests/resources/upverter/design_export.gbp b/tests/resources/upverter/design_export.gbp similarity index 100% rename from gerbonara/tests/resources/upverter/design_export.gbp rename to tests/resources/upverter/design_export.gbp diff --git a/gerbonara/tests/resources/upverter/design_export.gbs b/tests/resources/upverter/design_export.gbs similarity index 100% rename from gerbonara/tests/resources/upverter/design_export.gbs rename to tests/resources/upverter/design_export.gbs diff --git a/gerbonara/tests/resources/upverter/design_export.gko b/tests/resources/upverter/design_export.gko similarity index 100% rename from gerbonara/tests/resources/upverter/design_export.gko rename to tests/resources/upverter/design_export.gko diff --git a/gerbonara/tests/resources/upverter/design_export.gtl b/tests/resources/upverter/design_export.gtl similarity index 100% rename from gerbonara/tests/resources/upverter/design_export.gtl rename to tests/resources/upverter/design_export.gtl diff --git a/gerbonara/tests/resources/upverter/design_export.gto b/tests/resources/upverter/design_export.gto similarity index 100% rename from gerbonara/tests/resources/upverter/design_export.gto rename to tests/resources/upverter/design_export.gto diff --git a/gerbonara/tests/resources/upverter/design_export.gtp b/tests/resources/upverter/design_export.gtp similarity index 100% rename from gerbonara/tests/resources/upverter/design_export.gtp rename to tests/resources/upverter/design_export.gtp diff --git a/gerbonara/tests/resources/upverter/design_export.gts b/tests/resources/upverter/design_export.gts similarity index 100% rename from gerbonara/tests/resources/upverter/design_export.gts rename to tests/resources/upverter/design_export.gts diff --git a/gerbonara/tests/resources/upverter/design_export.xln b/tests/resources/upverter/design_export.xln similarity index 100% rename from gerbonara/tests/resources/upverter/design_export.xln rename to tests/resources/upverter/design_export.xln diff --git a/gerbonara/tests/resources/upverter/layers.cfg b/tests/resources/upverter/layers.cfg similarity index 100% rename from gerbonara/tests/resources/upverter/layers.cfg rename to tests/resources/upverter/layers.cfg diff --git a/gerbonara/tests/resources/zuken-emulated/Drill/8seg_Driver__routed_Drill_thru_nplt.fdl b/tests/resources/zuken-emulated/Drill/8seg_Driver__routed_Drill_thru_nplt.fdl similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/Drill/8seg_Driver__routed_Drill_thru_nplt.fdl rename to tests/resources/zuken-emulated/Drill/8seg_Driver__routed_Drill_thru_nplt.fdl diff --git a/gerbonara/tests/resources/zuken-emulated/Drill/8seg_Driver__routed_Drill_thru_nplt.fdr b/tests/resources/zuken-emulated/Drill/8seg_Driver__routed_Drill_thru_nplt.fdr similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/Drill/8seg_Driver__routed_Drill_thru_nplt.fdr rename to tests/resources/zuken-emulated/Drill/8seg_Driver__routed_Drill_thru_nplt.fdr diff --git a/gerbonara/tests/resources/zuken-emulated/Drill/8seg_Driver__routed_Drill_thru_plt.fdr/8seg_Driver__routed_Drill_thru_plt.fdl b/tests/resources/zuken-emulated/Drill/8seg_Driver__routed_Drill_thru_plt.fdr/8seg_Driver__routed_Drill_thru_plt.fdl similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/Drill/8seg_Driver__routed_Drill_thru_plt.fdr/8seg_Driver__routed_Drill_thru_plt.fdl rename to tests/resources/zuken-emulated/Drill/8seg_Driver__routed_Drill_thru_plt.fdr/8seg_Driver__routed_Drill_thru_plt.fdl diff --git a/gerbonara/tests/resources/zuken-emulated/Drill/8seg_Driver__routed_Drill_thru_plt.fdr/8seg_Driver__routed_Drill_thru_plt.fdr b/tests/resources/zuken-emulated/Drill/8seg_Driver__routed_Drill_thru_plt.fdr/8seg_Driver__routed_Drill_thru_plt.fdr similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/Drill/8seg_Driver__routed_Drill_thru_plt.fdr/8seg_Driver__routed_Drill_thru_plt.fdr rename to tests/resources/zuken-emulated/Drill/8seg_Driver__routed_Drill_thru_plt.fdr/8seg_Driver__routed_Drill_thru_plt.fdr diff --git a/gerbonara/tests/resources/zuken-emulated/Gerber/Conductive-1.fph b/tests/resources/zuken-emulated/Gerber/Conductive-1.fph similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/Gerber/Conductive-1.fph rename to tests/resources/zuken-emulated/Gerber/Conductive-1.fph diff --git a/gerbonara/tests/resources/zuken-emulated/Gerber/Conductive-2.fph b/tests/resources/zuken-emulated/Gerber/Conductive-2.fph similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/Gerber/Conductive-2.fph rename to tests/resources/zuken-emulated/Gerber/Conductive-2.fph diff --git a/gerbonara/tests/resources/zuken-emulated/Gerber/MetalMask-A.fph b/tests/resources/zuken-emulated/Gerber/MetalMask-A.fph similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/Gerber/MetalMask-A.fph rename to tests/resources/zuken-emulated/Gerber/MetalMask-A.fph diff --git a/gerbonara/tests/resources/zuken-emulated/Gerber/MetalMask-B.fph b/tests/resources/zuken-emulated/Gerber/MetalMask-B.fph similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/Gerber/MetalMask-B.fph rename to tests/resources/zuken-emulated/Gerber/MetalMask-B.fph diff --git a/gerbonara/tests/resources/zuken-emulated/Gerber/Resist-A.fph b/tests/resources/zuken-emulated/Gerber/Resist-A.fph similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/Gerber/Resist-A.fph rename to tests/resources/zuken-emulated/Gerber/Resist-A.fph diff --git a/gerbonara/tests/resources/zuken-emulated/Gerber/Resist-B.fph b/tests/resources/zuken-emulated/Gerber/Resist-B.fph similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/Gerber/Resist-B.fph rename to tests/resources/zuken-emulated/Gerber/Resist-B.fph diff --git a/gerbonara/tests/resources/zuken-emulated/Gerber/Symbol-A.fph b/tests/resources/zuken-emulated/Gerber/Symbol-A.fph similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/Gerber/Symbol-A.fph rename to tests/resources/zuken-emulated/Gerber/Symbol-A.fph diff --git a/gerbonara/tests/resources/zuken-emulated/Gerber/Symbol-B.fph b/tests/resources/zuken-emulated/Gerber/Symbol-B.fph similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/Gerber/Symbol-B.fph rename to tests/resources/zuken-emulated/Gerber/Symbol-B.fph diff --git a/gerbonara/tests/resources/zuken-emulated/LICENSE b/tests/resources/zuken-emulated/LICENSE similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/LICENSE rename to tests/resources/zuken-emulated/LICENSE diff --git a/gerbonara/tests/resources/zuken-emulated/README b/tests/resources/zuken-emulated/README similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/README rename to tests/resources/zuken-emulated/README diff --git a/gerbonara/tests/resources/zuken-emulated/orig/driver-B_Cu b/tests/resources/zuken-emulated/orig/driver-B_Cu similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/orig/driver-B_Cu rename to tests/resources/zuken-emulated/orig/driver-B_Cu diff --git a/gerbonara/tests/resources/zuken-emulated/orig/driver-B_Mask b/tests/resources/zuken-emulated/orig/driver-B_Mask similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/orig/driver-B_Mask rename to tests/resources/zuken-emulated/orig/driver-B_Mask diff --git a/gerbonara/tests/resources/zuken-emulated/orig/driver-B_Paste b/tests/resources/zuken-emulated/orig/driver-B_Paste similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/orig/driver-B_Paste rename to tests/resources/zuken-emulated/orig/driver-B_Paste diff --git a/gerbonara/tests/resources/zuken-emulated/orig/driver-B_SilkS b/tests/resources/zuken-emulated/orig/driver-B_SilkS similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/orig/driver-B_SilkS rename to tests/resources/zuken-emulated/orig/driver-B_SilkS diff --git a/gerbonara/tests/resources/zuken-emulated/orig/driver-Edge_Cuts b/tests/resources/zuken-emulated/orig/driver-Edge_Cuts similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/orig/driver-Edge_Cuts rename to tests/resources/zuken-emulated/orig/driver-Edge_Cuts diff --git a/gerbonara/tests/resources/zuken-emulated/orig/driver-F_Cu b/tests/resources/zuken-emulated/orig/driver-F_Cu similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/orig/driver-F_Cu rename to tests/resources/zuken-emulated/orig/driver-F_Cu diff --git a/gerbonara/tests/resources/zuken-emulated/orig/driver-F_Mask b/tests/resources/zuken-emulated/orig/driver-F_Mask similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/orig/driver-F_Mask rename to tests/resources/zuken-emulated/orig/driver-F_Mask diff --git a/gerbonara/tests/resources/zuken-emulated/orig/driver-F_Paste b/tests/resources/zuken-emulated/orig/driver-F_Paste similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/orig/driver-F_Paste rename to tests/resources/zuken-emulated/orig/driver-F_Paste diff --git a/gerbonara/tests/resources/zuken-emulated/orig/driver-F_SilkS b/tests/resources/zuken-emulated/orig/driver-F_SilkS similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/orig/driver-F_SilkS rename to tests/resources/zuken-emulated/orig/driver-F_SilkS diff --git a/gerbonara/tests/resources/zuken-emulated/orig/driver-NPTH b/tests/resources/zuken-emulated/orig/driver-NPTH similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/orig/driver-NPTH rename to tests/resources/zuken-emulated/orig/driver-NPTH diff --git a/gerbonara/tests/resources/zuken-emulated/orig/driver-PTH b/tests/resources/zuken-emulated/orig/driver-PTH similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/orig/driver-PTH rename to tests/resources/zuken-emulated/orig/driver-PTH diff --git a/gerbonara/tests/resources/zuken-emulated/scripts/drill_log_sample b/tests/resources/zuken-emulated/scripts/drill_log_sample similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/scripts/drill_log_sample rename to tests/resources/zuken-emulated/scripts/drill_log_sample diff --git a/gerbonara/tests/resources/zuken-emulated/scripts/prepare.sh b/tests/resources/zuken-emulated/scripts/prepare.sh similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/scripts/prepare.sh rename to tests/resources/zuken-emulated/scripts/prepare.sh diff --git a/gerbonara/tests/resources/zuken-emulated/scripts/zukenka_excellon.py b/tests/resources/zuken-emulated/scripts/zukenka_excellon.py similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/scripts/zukenka_excellon.py rename to tests/resources/zuken-emulated/scripts/zukenka_excellon.py diff --git a/gerbonara/tests/resources/zuken-emulated/scripts/zukenka_gerber.py b/tests/resources/zuken-emulated/scripts/zukenka_gerber.py similarity index 100% rename from gerbonara/tests/resources/zuken-emulated/scripts/zukenka_gerber.py rename to tests/resources/zuken-emulated/scripts/zukenka_gerber.py diff --git a/gerbonara/tests/test_cli.py b/tests/test_cli.py similarity index 99% rename from gerbonara/tests/test_cli.py rename to tests/test_cli.py index 9c1bd5d..2ce93f0 100644 --- a/gerbonara/tests/test_cli.py +++ b/tests/test_cli.py @@ -28,8 +28,8 @@ from click.testing import CliRunner from bs4 import BeautifulSoup from .utils import * -from .. import cli -from ..utils import MM +from gerbonara import cli +from gerbonara.utils import MM @pytest.fixture() diff --git a/gerbonara/tests/test_excellon.py b/tests/test_excellon.py similarity index 92% rename from gerbonara/tests/test_excellon.py rename to tests/test_excellon.py index 4dcedd6..b90ba0d 100644 --- a/gerbonara/tests/test_excellon.py +++ b/tests/test_excellon.py @@ -21,14 +21,14 @@ import math import pytest from scipy.spatial import KDTree -from ..excellon import ExcellonFile -from ..rs274x import GerberFile -from ..cam import FileSettings -from ..graphic_objects import Flash +from gerbonara.excellon import ExcellonFile +from gerbonara.rs274x import GerberFile +from gerbonara.cam import FileSettings +from gerbonara.graphic_objects import Flash from .image_support import * from .utils import * -from ..utils import Inch, MM +from gerbonara.utils import Inch, MM REFERENCE_FILES = { 'easyeda/Gerber_Drill_NPTH.DRL': (('inch', 'leading', 4), None), @@ -63,7 +63,7 @@ REFERENCE_FILES = { @filter_syntax_warnings @pytest.mark.parametrize('reference', list(REFERENCE_FILES.items()), indirect=True) -def test_round_trip(reference, tmpfile): +def test_round_trip(reference, tmpfile, img_support): reference, (unit_spec, _) = reference tmp = tmpfile('Output excellon', '.drl') @@ -75,14 +75,14 @@ def test_round_trip(reference, tmpfile): # due to its use of bare coordinates for routed slots. Thus, we skip this test (for now). return - mean, _max, hist = gerber_difference(reference, tmp, diff_out=tmpfile('Difference', '.png'), ref_unit_spec=unit_spec) + mean, _max, hist = img_support.gerber_difference(reference, tmp, diff_out=tmpfile('Difference', '.png'), ref_unit_spec=unit_spec) assert mean < 5e-5 assert hist[9] == 0 assert hist[3:].sum() < 5e-5*hist.size @filter_syntax_warnings @pytest.mark.parametrize('reference', list(REFERENCE_FILES.items()), indirect=True) -def test_first_level_idempotence_svg(reference, tmpfile): +def test_first_level_idempotence_svg(reference, tmpfile, img_support): reference, (unit_spec, _) = reference tmp = tmpfile('Output excellon', '.drl') ref_svg = tmpfile('Reference SVG render', '.svg') @@ -95,7 +95,7 @@ def test_first_level_idempotence_svg(reference, tmpfile): ref_svg.write_text(str(a.to_svg(fg='black', bg='white'))) out_svg.write_text(str(b.to_svg(fg='black', bg='white'))) - mean, _max, hist = svg_difference(ref_svg, out_svg, diff_out=tmpfile('Difference', '.png'), background='white') + mean, _max, hist = img_support.svg_difference(ref_svg, out_svg, diff_out=tmpfile('Difference', '.png'), background='white') assert mean < 5e-5 assert hist[9] == 0 assert hist[3:].sum() < 5e-5*hist.size diff --git a/gerbonara/tests/test_ipc356.py b/tests/test_ipc356.py similarity index 98% rename from gerbonara/tests/test_ipc356.py rename to tests/test_ipc356.py index a074e78..5bfaefa 100644 --- a/gerbonara/tests/test_ipc356.py +++ b/tests/test_ipc356.py @@ -19,11 +19,11 @@ import pytest -from ..ipc356 import * -from ..cam import FileSettings +from gerbonara.ipc356 import * +from gerbonara.cam import FileSettings from .utils import * -from ..utils import Inch, MM +from gerbonara.utils import Inch, MM REFERENCE_FILES = [ diff --git a/gerbonara/tests/test_kicad_footprints.py b/tests/test_kicad_footprints.py similarity index 93% rename from gerbonara/tests/test_kicad_footprints.py rename to tests/test_kicad_footprints.py index 1685a12..0007d5f 100644 --- a/gerbonara/tests/test_kicad_footprints.py +++ b/tests/test_kicad_footprints.py @@ -7,15 +7,15 @@ import re import bs4 from .utils import tmpfile, print_on_error -from .image_support import kicad_fp_export, svg_difference, svg_soup, svg_to_png, run_cargo_cmd +from .image_support import run_cargo_cmd -from .. import graphic_objects as go -from ..utils import MM, arc_bounds, sum_bounds -from ..layers import LayerStack -from ..cad.kicad.sexp import build_sexp, Atom -from ..cad.kicad.sexp_mapper import sexp -from ..cad.kicad.footprints import Footprint, FootprintInstance, LAYER_MAP_G2K -from ..cad.kicad.layer_colors import KICAD_LAYER_COLORS, KICAD_DRILL_COLORS +from gerbonara import graphic_objects as go +from gerbonara.utils import MM, arc_bounds, sum_bounds +from gerbonara.layers import LayerStack +from gerbonara.cad.kicad.sexp import build_sexp, Atom +from gerbonara.cad.kicad.sexp_mapper import sexp +from gerbonara.cad.kicad.footprints import Footprint, FootprintInstance, LAYER_MAP_G2K +from gerbonara.cad.kicad.layer_colors import KICAD_LAYER_COLORS, KICAD_DRILL_COLORS def test_parse(kicad_mod_file): @@ -176,7 +176,7 @@ def _parse_path_d(path): yield max_x, max_y last_x, last_y = ax, ay -def test_render(kicad_mod_file, tmpfile, print_on_error): +def test_render(kicad_mod_file, tmpfile, print_on_error, img_support): # These files have a large, mask-only pad that has a large solder mask margin set. Kicad doesn't render the margin # at all, which I think it should. We render things exactly as I'd expect. if kicad_mod_file.name in ['Fiducial_classic_big_CopperBottom_Type2.kicad_mod', @@ -227,7 +227,7 @@ def test_render(kicad_mod_file, tmpfile, print_on_error): print_on_error('Input footprint:', kicad_mod_file) ref_svg = tmpfile('Reference render', '.svg') - kicad_fp_export(kicad_mod_file, ref_svg) + img_support.kicad_fp_export(kicad_mod_file, ref_svg) # KiCad's bounding box calculation for SVG output looks broken, and the resulting files have viewports that are too # large. We align our output and KiCad's output using the footprint's courtyard layer. @@ -311,9 +311,9 @@ def test_render(kicad_mod_file, tmpfile, print_on_error): root.append(soup.find('g', id='l-bottom-fabrication').extract()) root.append(soup.find('g', id='l-top-fabrication').extract()) - svg_to_png(ref_svg, tmpfile('Reference render', '.png'), bg=None, dpi=600) - svg_to_png(out_svg, tmpfile('Output render', '.png'), bg=None, dpi=600) - mean, _max, hist = svg_difference(ref_svg, out_svg, dpi=600, diff_out=tmpfile('Difference', '.png')) + img_support.svg_to_png(ref_svg, tmpfile('Reference render', '.png'), bg=None, dpi=600) + img_support.svg_to_png(out_svg, tmpfile('Output render', '.png'), bg=None, dpi=600) + mean, _max, hist = img_support.svg_difference(ref_svg, out_svg, dpi=600, diff_out=tmpfile('Difference', '.png')) # compensate for circular pads aliasing badly aliasing_artifacts = 1e-3 * len(fp.sexp.pads)/10 diff --git a/gerbonara/tests/test_kicad_sexpr.py b/tests/test_kicad_sexpr.py similarity index 95% rename from gerbonara/tests/test_kicad_sexpr.py rename to tests/test_kicad_sexpr.py index b2c60b1..0bc3653 100644 --- a/gerbonara/tests/test_kicad_sexpr.py +++ b/tests/test_kicad_sexpr.py @@ -1,5 +1,5 @@ -from ..cad.kicad.sexp import parse_sexp, build_sexp +from gerbonara.cad.kicad.sexp import parse_sexp, build_sexp def test_sexp_round_trip(): test_sexp = '''(()() (foo) (23)\t(foo 23) (foo 23 bar baz) (foo bar baz) ("foo bar") (" foo " bar) (23 " baz ") diff --git a/gerbonara/tests/test_kicad_symbols.py b/tests/test_kicad_symbols.py similarity index 96% rename from gerbonara/tests/test_kicad_symbols.py rename to tests/test_kicad_symbols.py index d97cc6f..5c9eca1 100644 --- a/gerbonara/tests/test_kicad_symbols.py +++ b/tests/test_kicad_symbols.py @@ -2,9 +2,9 @@ from itertools import zip_longest import re -from ..cad.kicad.sexp import build_sexp -from ..cad.kicad.sexp_mapper import sexp -from ..cad.kicad.symbols import Library +from gerbonara.cad.kicad.sexp import build_sexp +from gerbonara.cad.kicad.sexp_mapper import sexp +from gerbonara.cad.kicad.symbols import Library from .utils import tmpfile diff --git a/gerbonara/tests/test_layers.py b/tests/test_layers.py similarity index 99% rename from gerbonara/tests/test_layers.py rename to tests/test_layers.py index 293afb2..b0535ac 100644 --- a/gerbonara/tests/test_layers.py +++ b/tests/test_layers.py @@ -21,9 +21,9 @@ from pathlib import Path import pytest from .utils import * -from ..layers import LayerStack -from ..rs274x import GerberFile -from ..excellon import ExcellonFile +from gerbonara.layers import LayerStack +from gerbonara.rs274x import GerberFile +from gerbonara.excellon import ExcellonFile # hand-classified REFERENCE_DIRS = { diff --git a/gerbonara/tests/test_rs274x.py b/tests/test_rs274x.py similarity index 93% rename from gerbonara/tests/test_rs274x.py rename to tests/test_rs274x.py index 8f0da70..2613f9a 100644 --- a/gerbonara/tests/test_rs274x.py +++ b/tests/test_rs274x.py @@ -23,8 +23,8 @@ import math from PIL import Image import pytest -from ..rs274x import GerberFile -from ..cam import FileSettings +from gerbonara.rs274x import GerberFile +from gerbonara.cam import FileSettings from .image_support import * from .utils import * @@ -332,12 +332,12 @@ HAS_ZERO_SIZE_APERTURES = [ @filter_syntax_warnings @pytest.mark.parametrize('reference', REFERENCE_FILES, indirect=True) -def test_round_trip(reference, tmpfile): +def test_round_trip(reference, tmpfile, img_support): tmp_gbr = tmpfile('Output gerber', '.gbr') GerberFile.open(reference).save(tmp_gbr) - mean, _max, hist = gerber_difference(reference, tmp_gbr, diff_out=tmpfile('Difference', '.png')) + mean, _max, hist = img_support.gerber_difference(reference, tmp_gbr, diff_out=tmpfile('Difference', '.png')) assert mean < 5e-5 assert hist[9] == 0 assert hist[3:].sum() < 5e-5*hist.size @@ -368,7 +368,7 @@ TEST_OFFSETS = [(0, 0), (100, 0), (0, 100), (2, 0), (10, 100)] @filter_syntax_warnings @pytest.mark.parametrize('reference', MIN_REFERENCE_FILES, indirect=True) @pytest.mark.parametrize('angle', TEST_ANGLES) -def test_rotation(reference, angle, tmpfile): +def test_rotation(reference, angle, tmpfile, img_support): if 'flash_rectangle' in str(reference) and angle == 1024: # gerbv's rendering of this is broken, the hole is missing. pytest.skip() @@ -380,7 +380,7 @@ def test_rotation(reference, angle, tmpfile): f.save(tmp_gbr) cx, cy = 0, to_gerbv_svg_units(10, unit='inch') - mean, _max, hist = gerber_difference(reference, tmp_gbr, diff_out=tmpfile('Difference', '.png'), + mean, _max, hist = img_support.gerber_difference(reference, tmp_gbr, diff_out=tmpfile('Difference', '.png'), svg_transform=f'rotate({angle} {cx} {cy})') assert mean < 1e-3 # relax mean criterion compared to above. assert hist[9] == 0 @@ -389,7 +389,7 @@ def test_rotation(reference, angle, tmpfile): @pytest.mark.parametrize('reference', MIN_REFERENCE_FILES, indirect=True) @pytest.mark.parametrize('angle', TEST_ANGLES) @pytest.mark.parametrize('center', [(0, 0), (10, 0), (0, -10), (10, 20)]) -def test_rotation_center(reference, angle, center, tmpfile): +def test_rotation_center(reference, angle, center, tmpfile, img_support): if 'flash_rectangle' in str(reference) and angle in (30, 1024): # gerbv's rendering of this is broken, the hole is missing. pytest.skip() @@ -403,7 +403,7 @@ def test_rotation_center(reference, angle, center, tmpfile): # calculate circle center in SVG coordinates size = (10, 10) # inches cx, cy = to_gerbv_svg_units(center[0]), to_gerbv_svg_units(size[1], 'inch')-to_gerbv_svg_units(center[1], 'mm') - mean, _max, hist = gerber_difference(reference, tmp_gbr, diff_out=tmpfile('Difference', '.png'), + mean, _max, hist = img_support.gerber_difference(reference, tmp_gbr, diff_out=tmpfile('Difference', '.png'), svg_transform=f'rotate({angle} {cx} {cy})', size=size) assert mean < 1e-3 @@ -413,7 +413,7 @@ def test_rotation_center(reference, angle, center, tmpfile): @filter_syntax_warnings @pytest.mark.parametrize('reference', MIN_REFERENCE_FILES, indirect=True) @pytest.mark.parametrize('offset', TEST_OFFSETS) -def test_offset(reference, offset, tmpfile): +def test_offset(reference, offset, tmpfile, img_support): tmp_gbr = tmpfile('Output gerber', '.gbr') f = GerberFile.open(reference) @@ -422,7 +422,7 @@ def test_offset(reference, offset, tmpfile): # flip y offset since svg's y axis is flipped compared to that of gerber dx, dy = to_gerbv_svg_units(offset[0]), -to_gerbv_svg_units(offset[1]) - mean, _max, hist = gerber_difference(reference, tmp_gbr, diff_out=tmpfile('Difference', '.png'), + mean, _max, hist = img_support.gerber_difference(reference, tmp_gbr, diff_out=tmpfile('Difference', '.png'), svg_transform=f'translate({dx} {dy})') assert mean < 1e-4 assert hist[9] == 0 @@ -432,7 +432,7 @@ def test_offset(reference, offset, tmpfile): @pytest.mark.parametrize('angle', TEST_ANGLES) @pytest.mark.parametrize('center', [(0, 0), (10, 0), (0, -10), (10, 20)]) @pytest.mark.parametrize('offset', [(0, 0), (100, 0), (0, 100), (100, 10)]) -def test_combined(reference, angle, center, offset, tmpfile): +def test_combined(reference, angle, center, offset, tmpfile, img_support): if 'flash_rectangle' in str(reference) and angle in (30, 1024): # gerbv's rendering of this is broken, the hole is missing. pytest.skip() @@ -447,7 +447,7 @@ def test_combined(reference, angle, center, offset, tmpfile): size = (10, 10) # inches cx, cy = to_gerbv_svg_units(center[0]), to_gerbv_svg_units(size[1], 'inch')-to_gerbv_svg_units(center[1], 'mm') dx, dy = to_gerbv_svg_units(offset[0]), -to_gerbv_svg_units(offset[1]) - mean, _max, hist = gerber_difference(reference, tmp_gbr, diff_out=tmpfile('Difference', '.png'), + mean, _max, hist = img_support.gerber_difference(reference, tmp_gbr, diff_out=tmpfile('Difference', '.png'), svg_transform=f'translate({dx} {dy}) rotate({angle} {cx} {cy})', size=size) assert mean < 1e-3 @@ -464,7 +464,7 @@ def test_combined(reference, angle, center, offset, tmpfile): 'eagle_files/copper_bottom_l4.gbr', ]) @pytest.mark.parametrize('angle', [0, 10, 90]) @pytest.mark.parametrize('offset', [(0, 0, 0, 0), (100, 0, 0, 0), (0, 0, 0, 100), (100, 0, 0, 100)]) -def test_compositing(file_a, file_b, angle, offset, tmpfile, print_on_error): +def test_compositing(file_a, file_b, angle, offset, tmpfile, print_on_error, img_support): # TODO bottom_silk.GBO renders incorrectly with gerbv: the outline does not exist in svg. In GUI, the logo only # renders at very high magnification. Skip, and once we have our own SVG export maybe use that instead. Or just use @@ -497,7 +497,7 @@ def test_compositing(file_a, file_b, angle, offset, tmpfile, print_on_error): # note that we have to specify cx, cy even if we rotate around the origin since gerber's origin lies at (x=0 # y=+document size) in SVG's coordinate space because svg's y axis is flipped compared to gerber's. cx, cy = 0, to_gerbv_svg_units(size[1], 'inch') - mean, _max, hist = gerber_difference_merge(ref_a, ref_b, tmp_gbr, + mean, _max, hist = img_support.gerber_difference_merge(ref_a, ref_b, tmp_gbr, composite_out=tmpfile('Composite', '.svg'), diff_out=tmpfile('Difference', '.png'), svg_transform1=f'translate({ax} {ay}) rotate({angle} {cx} {cy})', svg_transform2=f'translate({bx} {by})', @@ -508,7 +508,7 @@ def test_compositing(file_a, file_b, angle, offset, tmpfile, print_on_error): @filter_syntax_warnings @pytest.mark.parametrize('reference', REFERENCE_FILES, indirect=True) -def test_svg_export_gerber(reference, tmpfile): +def test_svg_export_gerber(reference, tmpfile, img_support): if reference.name in ('silkscreen_bottom.gbr', 'silkscreen_top.gbr', 'top_silk.GTO'): # Some weird svg rendering artifact. Might be caused by mismatching svg units between gerbv and us. Result looks # fine though. @@ -531,19 +531,19 @@ def test_svg_export_gerber(reference, tmpfile): # using resvg for both allows an apples-to-apples comparison of both results. ref_svg = tmpfile('Reference export', '.svg') ref_png = tmpfile('Reference render', '.png') - gerbv_export(reference, ref_svg, origin=bounds[0], size=bounds[1], fg='#000000', bg='#ffffff') + img_support.gerbv_export(reference, ref_svg, origin=bounds[0], size=bounds[1], fg='#000000', bg='#ffffff') with svg_soup(ref_svg) as soup: - cleanup_gerbv_svg(soup) - svg_to_png(ref_svg, ref_png, dpi=300, bg='white') + img_support.cleanup_gerbv_svg(soup) + img_support.svg_to_png(ref_svg, ref_png, dpi=300, bg='white') out_png = tmpfile('Output render', '.png') - svg_to_png(out_svg, out_png, dpi=300, bg='white') + img_support.svg_to_png(out_svg, out_png, dpi=300, bg='white') if reference.name in HAS_ZERO_SIZE_APERTURES: # gerbv does not render these correctly. return - mean, _max, hist = image_difference(ref_png, out_png, diff_out=tmpfile('Difference', '.png')) + mean, _max, hist = img_support.image_difference(ref_png, out_png, diff_out=tmpfile('Difference', '.png')) assert hist[9] < 1 if 'Minnow' in reference.name or 'LimeSDR' in reference.name or '80101_0125_F200' in reference.name: # This is a dense design with lots of traces, leading to lots of aliasing artifacts. @@ -555,7 +555,7 @@ def test_svg_export_gerber(reference, tmpfile): @filter_syntax_warnings @pytest.mark.parametrize('reference', REFERENCE_FILES, indirect=True) -def test_bounding_box(reference, tmpfile): +def test_bounding_box(reference, tmpfile, img_support): if reference.name == 'MinnowMax_assy.art': # This leads to worst-case performance in resvg, this testcase takes over 1h to finish. So skip. pytest.skip() @@ -583,7 +583,7 @@ def test_bounding_box(reference, tmpfile): f.write(str(grb.to_svg(margin=margin, arg_unit='inch', fg='white', bg='black'))) out_png = tmpfile('Render', '.png') - svg_to_png(out_svg, out_png, dpi=dpi) + img_support.svg_to_png(out_svg, out_png, dpi=dpi) img = np.array(Image.open(out_png)) img = img[:, :, :3].mean(axis=2) # drop alpha and convert to grayscale diff --git a/gerbonara/tests/test_utils.py b/tests/test_utils.py similarity index 97% rename from gerbonara/tests/test_utils.py rename to tests/test_utils.py index bc3fedb..156c2ea 100644 --- a/gerbonara/tests/test_utils.py +++ b/tests/test_utils.py @@ -22,8 +22,8 @@ import random import pytest -from ..cam import FileSettings -from ..utils import convex_hull, point_in_polygon, setup_svg, Tag +from gerbonara.cam import FileSettings +from gerbonara.utils import convex_hull, point_in_polygon, setup_svg, Tag from .utils import * diff --git a/gerbonara/tests/utils.py b/tests/utils.py similarity index 100% rename from gerbonara/tests/utils.py rename to tests/utils.py diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..22b74c4 --- /dev/null +++ b/uv.lock @@ -0,0 +1,616 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "aiofiles" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + +[[package]] +name = "flask" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, +] + +[[package]] +name = "gerbonara" +version = "1.5.0" +source = { editable = "." } +dependencies = [ + { name = "click" }, + { name = "quart" }, + { name = "rtree" }, +] + +[package.dev-dependencies] +dev = [ + { name = "beautifulsoup4" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "pytest" }, + { name = "pytest-xdist" }, + { name = "scipy" }, + { name = "tqdm" }, +] + +[package.metadata] +requires-dist = [ + { name = "click" }, + { name = "quart" }, + { name = "rtree" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "beautifulsoup4" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "pytest" }, + { name = "pytest-xdist" }, + { name = "scipy" }, + { name = "tqdm" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "h2" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" }, +] + +[[package]] +name = "hpack" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" }, +] + +[[package]] +name = "hypercorn" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, + { name = "h2" }, + { name = "priority" }, + { name = "wsproto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/01/39f41a014b83dd5c795217362f2ca9071cf243e6a75bdcd6cd5b944658cc/hypercorn-0.18.0.tar.gz", hash = "sha256:d63267548939c46b0247dc8e5b45a9947590e35e64ee73a23c074aa3cf88e9da", size = 68420, upload-time = "2025-11-08T13:54:04.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/35/850277d1b17b206bd10874c8a9a3f52e059452fb49bb0d22cbb908f6038b/hypercorn-0.18.0-py3-none-any.whl", hash = "sha256:225e268f2c1c2f28f6d8f6db8f40cb8c992963610c5725e13ccfcddccb24b1cd", size = 61640, upload-time = "2025-11-08T13:54:03.202Z" }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a", size = 20582187, upload-time = "2025-10-15T16:18:11.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/7a/02420400b736f84317e759291b8edaeee9dc921f72b045475a9cbdb26b17/numpy-2.3.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ef1b5a3e808bc40827b5fa2c8196151a4c5abe110e1726949d7abddfe5c7ae11", size = 20957727, upload-time = "2025-10-15T16:15:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/18/90/a014805d627aa5750f6f0e878172afb6454552da929144b3c07fcae1bb13/numpy-2.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2f91f496a87235c6aaf6d3f3d89b17dba64996abadccb289f48456cff931ca9", size = 14187262, upload-time = "2025-10-15T16:15:47.761Z" }, + { url = "https://files.pythonhosted.org/packages/c7/e4/0a94b09abe89e500dc748e7515f21a13e30c5c3fe3396e6d4ac108c25fca/numpy-2.3.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f77e5b3d3da652b474cc80a14084927a5e86a5eccf54ca8ca5cbd697bf7f2667", size = 5115992, upload-time = "2025-10-15T16:15:50.144Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/db77c75b055c6157cbd4f9c92c4458daef0dd9cbe6d8d2fe7f803cb64c37/numpy-2.3.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ab1c5f5ee40d6e01cbe96de5863e39b215a4d24e7d007cad56c7184fdf4aeef", size = 6648672, upload-time = "2025-10-15T16:15:52.442Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e6/e31b0d713719610e406c0ea3ae0d90760465b086da8783e2fd835ad59027/numpy-2.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77b84453f3adcb994ddbd0d1c5d11db2d6bda1a2b7fd5ac5bd4649d6f5dc682e", size = 14284156, upload-time = "2025-10-15T16:15:54.351Z" }, + { url = "https://files.pythonhosted.org/packages/f9/58/30a85127bfee6f108282107caf8e06a1f0cc997cb6b52cdee699276fcce4/numpy-2.3.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4121c5beb58a7f9e6dfdee612cb24f4df5cd4db6e8261d7f4d7450a997a65d6a", size = 16641271, upload-time = "2025-10-15T16:15:56.67Z" }, + { url = "https://files.pythonhosted.org/packages/06/f2/2e06a0f2adf23e3ae29283ad96959267938d0efd20a2e25353b70065bfec/numpy-2.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:65611ecbb00ac9846efe04db15cbe6186f562f6bb7e5e05f077e53a599225d16", size = 16059531, upload-time = "2025-10-15T16:15:59.412Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e7/b106253c7c0d5dc352b9c8fab91afd76a93950998167fa3e5afe4ef3a18f/numpy-2.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dabc42f9c6577bcc13001b8810d300fe814b4cfbe8a92c873f269484594f9786", size = 18578983, upload-time = "2025-10-15T16:16:01.804Z" }, + { url = "https://files.pythonhosted.org/packages/73/e3/04ecc41e71462276ee867ccbef26a4448638eadecf1bc56772c9ed6d0255/numpy-2.3.4-cp312-cp312-win32.whl", hash = "sha256:a49d797192a8d950ca59ee2d0337a4d804f713bb5c3c50e8db26d49666e351dc", size = 6291380, upload-time = "2025-10-15T16:16:03.938Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a8/566578b10d8d0e9955b1b6cd5db4e9d4592dd0026a941ff7994cedda030a/numpy-2.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:985f1e46358f06c2a09921e8921e2c98168ed4ae12ccd6e5e87a4f1857923f32", size = 12787999, upload-time = "2025-10-15T16:16:05.801Z" }, + { url = "https://files.pythonhosted.org/packages/58/22/9c903a957d0a8071b607f5b1bff0761d6e608b9a965945411f867d515db1/numpy-2.3.4-cp312-cp312-win_arm64.whl", hash = "sha256:4635239814149e06e2cb9db3dd584b2fa64316c96f10656983b8026a82e6e4db", size = 10197412, upload-time = "2025-10-15T16:16:07.854Z" }, + { url = "https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966", size = 20950335, upload-time = "2025-10-15T16:16:10.304Z" }, + { url = "https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3", size = 14179878, upload-time = "2025-10-15T16:16:12.595Z" }, + { url = "https://files.pythonhosted.org/packages/ac/01/5a67cb785bda60f45415d09c2bc245433f1c68dd82eef9c9002c508b5a65/numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197", size = 5108673, upload-time = "2025-10-15T16:16:14.877Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cd/8428e23a9fcebd33988f4cb61208fda832800ca03781f471f3727a820704/numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e", size = 6641438, upload-time = "2025-10-15T16:16:16.805Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7", size = 14281290, upload-time = "2025-10-15T16:16:18.764Z" }, + { url = "https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953", size = 16636543, upload-time = "2025-10-15T16:16:21.072Z" }, + { url = "https://files.pythonhosted.org/packages/47/6a/8cfc486237e56ccfb0db234945552a557ca266f022d281a2f577b98e955c/numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37", size = 16056117, upload-time = "2025-10-15T16:16:23.369Z" }, + { url = "https://files.pythonhosted.org/packages/b1/0e/42cb5e69ea901e06ce24bfcc4b5664a56f950a70efdcf221f30d9615f3f3/numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd", size = 18577788, upload-time = "2025-10-15T16:16:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/86/92/41c3d5157d3177559ef0a35da50f0cda7fa071f4ba2306dd36818591a5bc/numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646", size = 6282620, upload-time = "2025-10-15T16:16:29.811Z" }, + { url = "https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d", size = 12784672, upload-time = "2025-10-15T16:16:31.589Z" }, + { url = "https://files.pythonhosted.org/packages/ad/df/5474fb2f74970ca8eb978093969b125a84cc3d30e47f82191f981f13a8a0/numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc", size = 10196702, upload-time = "2025-10-15T16:16:33.902Z" }, + { url = "https://files.pythonhosted.org/packages/11/83/66ac031464ec1767ea3ed48ce40f615eb441072945e98693bec0bcd056cc/numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879", size = 21049003, upload-time = "2025-10-15T16:16:36.101Z" }, + { url = "https://files.pythonhosted.org/packages/5f/99/5b14e0e686e61371659a1d5bebd04596b1d72227ce36eed121bb0aeab798/numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562", size = 14302980, upload-time = "2025-10-15T16:16:39.124Z" }, + { url = "https://files.pythonhosted.org/packages/2c/44/e9486649cd087d9fc6920e3fc3ac2aba10838d10804b1e179fb7cbc4e634/numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a", size = 5231472, upload-time = "2025-10-15T16:16:41.168Z" }, + { url = "https://files.pythonhosted.org/packages/3e/51/902b24fa8887e5fe2063fd61b1895a476d0bbf46811ab0c7fdf4bd127345/numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6", size = 6739342, upload-time = "2025-10-15T16:16:43.777Z" }, + { url = "https://files.pythonhosted.org/packages/34/f1/4de9586d05b1962acdcdb1dc4af6646361a643f8c864cef7c852bf509740/numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7", size = 14354338, upload-time = "2025-10-15T16:16:46.081Z" }, + { url = "https://files.pythonhosted.org/packages/1f/06/1c16103b425de7969d5a76bdf5ada0804b476fed05d5f9e17b777f1cbefd/numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0", size = 16702392, upload-time = "2025-10-15T16:16:48.455Z" }, + { url = "https://files.pythonhosted.org/packages/34/b2/65f4dc1b89b5322093572b6e55161bb42e3e0487067af73627f795cc9d47/numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f", size = 16134998, upload-time = "2025-10-15T16:16:51.114Z" }, + { url = "https://files.pythonhosted.org/packages/d4/11/94ec578896cdb973aaf56425d6c7f2aff4186a5c00fac15ff2ec46998b46/numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64", size = 18651574, upload-time = "2025-10-15T16:16:53.429Z" }, + { url = "https://files.pythonhosted.org/packages/62/b7/7efa763ab33dbccf56dade36938a77345ce8e8192d6b39e470ca25ff3cd0/numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb", size = 6413135, upload-time = "2025-10-15T16:16:55.992Z" }, + { url = "https://files.pythonhosted.org/packages/43/70/aba4c38e8400abcc2f345e13d972fb36c26409b3e644366db7649015f291/numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c", size = 12928582, upload-time = "2025-10-15T16:16:57.943Z" }, + { url = "https://files.pythonhosted.org/packages/67/63/871fad5f0073fc00fbbdd7232962ea1ac40eeaae2bba66c76214f7954236/numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40", size = 10266691, upload-time = "2025-10-15T16:17:00.048Z" }, + { url = "https://files.pythonhosted.org/packages/72/71/ae6170143c115732470ae3a2d01512870dd16e0953f8a6dc89525696069b/numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e", size = 20955580, upload-time = "2025-10-15T16:17:02.509Z" }, + { url = "https://files.pythonhosted.org/packages/af/39/4be9222ffd6ca8a30eda033d5f753276a9c3426c397bb137d8e19dedd200/numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff", size = 14188056, upload-time = "2025-10-15T16:17:04.873Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3d/d85f6700d0a4aa4f9491030e1021c2b2b7421b2b38d01acd16734a2bfdc7/numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f", size = 5116555, upload-time = "2025-10-15T16:17:07.499Z" }, + { url = "https://files.pythonhosted.org/packages/bf/04/82c1467d86f47eee8a19a464c92f90a9bb68ccf14a54c5224d7031241ffb/numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b", size = 6643581, upload-time = "2025-10-15T16:17:09.774Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d3/c79841741b837e293f48bd7db89d0ac7a4f2503b382b78a790ef1dc778a5/numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7", size = 14299186, upload-time = "2025-10-15T16:17:11.937Z" }, + { url = "https://files.pythonhosted.org/packages/e8/7e/4a14a769741fbf237eec5a12a2cbc7a4c4e061852b6533bcb9e9a796c908/numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2", size = 16638601, upload-time = "2025-10-15T16:17:14.391Z" }, + { url = "https://files.pythonhosted.org/packages/93/87/1c1de269f002ff0a41173fe01dcc925f4ecff59264cd8f96cf3b60d12c9b/numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52", size = 16074219, upload-time = "2025-10-15T16:17:17.058Z" }, + { url = "https://files.pythonhosted.org/packages/cd/28/18f72ee77408e40a76d691001ae599e712ca2a47ddd2c4f695b16c65f077/numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26", size = 18576702, upload-time = "2025-10-15T16:17:19.379Z" }, + { url = "https://files.pythonhosted.org/packages/c3/76/95650169b465ececa8cf4b2e8f6df255d4bf662775e797ade2025cc51ae6/numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc", size = 6337136, upload-time = "2025-10-15T16:17:22.886Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/a231a5c43ede5d6f77ba4a91e915a87dea4aeea76560ba4d2bf185c683f0/numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9", size = 12920542, upload-time = "2025-10-15T16:17:24.783Z" }, + { url = "https://files.pythonhosted.org/packages/0d/0c/ae9434a888f717c5ed2ff2393b3f344f0ff6f1c793519fa0c540461dc530/numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868", size = 10480213, upload-time = "2025-10-15T16:17:26.935Z" }, + { url = "https://files.pythonhosted.org/packages/83/4b/c4a5f0841f92536f6b9592694a5b5f68c9ab37b775ff342649eadf9055d3/numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec", size = 21052280, upload-time = "2025-10-15T16:17:29.638Z" }, + { url = "https://files.pythonhosted.org/packages/3e/80/90308845fc93b984d2cc96d83e2324ce8ad1fd6efea81b324cba4b673854/numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3", size = 14302930, upload-time = "2025-10-15T16:17:32.384Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4e/07439f22f2a3b247cec4d63a713faae55e1141a36e77fb212881f7cda3fb/numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365", size = 5231504, upload-time = "2025-10-15T16:17:34.515Z" }, + { url = "https://files.pythonhosted.org/packages/ab/de/1e11f2547e2fe3d00482b19721855348b94ada8359aef5d40dd57bfae9df/numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252", size = 6739405, upload-time = "2025-10-15T16:17:36.128Z" }, + { url = "https://files.pythonhosted.org/packages/3b/40/8cd57393a26cebe2e923005db5134a946c62fa56a1087dc7c478f3e30837/numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e", size = 14354866, upload-time = "2025-10-15T16:17:38.884Z" }, + { url = "https://files.pythonhosted.org/packages/93/39/5b3510f023f96874ee6fea2e40dfa99313a00bf3ab779f3c92978f34aace/numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0", size = 16703296, upload-time = "2025-10-15T16:17:41.564Z" }, + { url = "https://files.pythonhosted.org/packages/41/0d/19bb163617c8045209c1996c4e427bccbc4bbff1e2c711f39203c8ddbb4a/numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0", size = 16136046, upload-time = "2025-10-15T16:17:43.901Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c1/6dba12fdf68b02a21ac411c9df19afa66bed2540f467150ca64d246b463d/numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f", size = 18652691, upload-time = "2025-10-15T16:17:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/f8/73/f85056701dbbbb910c51d846c58d29fd46b30eecd2b6ba760fc8b8a1641b/numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d", size = 6485782, upload-time = "2025-10-15T16:17:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/17/90/28fa6f9865181cb817c2471ee65678afa8a7e2a1fb16141473d5fa6bacc3/numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6", size = 13113301, upload-time = "2025-10-15T16:17:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/54/23/08c002201a8e7e1f9afba93b97deceb813252d9cfd0d3351caed123dcf97/numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29", size = 10547532, upload-time = "2025-10-15T16:17:53.48Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pillow" +version = "12.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377, upload-time = "2025-10-15T18:22:05.993Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343, upload-time = "2025-10-15T18:22:07.718Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981, upload-time = "2025-10-15T18:22:09.287Z" }, + { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399, upload-time = "2025-10-15T18:22:10.872Z" }, + { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740, upload-time = "2025-10-15T18:22:12.769Z" }, + { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201, upload-time = "2025-10-15T18:22:14.813Z" }, + { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334, upload-time = "2025-10-15T18:22:16.375Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162, upload-time = "2025-10-15T18:22:17.996Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769, upload-time = "2025-10-15T18:22:19.923Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107, upload-time = "2025-10-15T18:22:21.644Z" }, + { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643", size = 4045493, upload-time = "2025-10-15T18:22:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4", size = 4120461, upload-time = "2025-10-15T18:22:27.286Z" }, + { url = "https://files.pythonhosted.org/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399", size = 3576912, upload-time = "2025-10-15T18:22:28.751Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5", size = 5249132, upload-time = "2025-10-15T18:22:30.641Z" }, + { url = "https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b", size = 4650099, upload-time = "2025-10-15T18:22:32.73Z" }, + { url = "https://files.pythonhosted.org/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3", size = 6230808, upload-time = "2025-10-15T18:22:34.337Z" }, + { url = "https://files.pythonhosted.org/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07", size = 8037804, upload-time = "2025-10-15T18:22:36.402Z" }, + { url = "https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e", size = 6345553, upload-time = "2025-10-15T18:22:38.066Z" }, + { url = "https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344", size = 7037729, upload-time = "2025-10-15T18:22:39.769Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27", size = 6459789, upload-time = "2025-10-15T18:22:41.437Z" }, + { url = "https://files.pythonhosted.org/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79", size = 7130917, upload-time = "2025-10-15T18:22:43.152Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098", size = 6302391, upload-time = "2025-10-15T18:22:44.753Z" }, + { url = "https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905", size = 7007477, upload-time = "2025-10-15T18:22:46.838Z" }, + { url = "https://files.pythonhosted.org/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a", size = 2435918, upload-time = "2025-10-15T18:22:48.399Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3", size = 5251406, upload-time = "2025-10-15T18:22:49.905Z" }, + { url = "https://files.pythonhosted.org/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced", size = 4653218, upload-time = "2025-10-15T18:22:51.587Z" }, + { url = "https://files.pythonhosted.org/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b", size = 6266564, upload-time = "2025-10-15T18:22:53.215Z" }, + { url = "https://files.pythonhosted.org/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d", size = 8069260, upload-time = "2025-10-15T18:22:54.933Z" }, + { url = "https://files.pythonhosted.org/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a", size = 6379248, upload-time = "2025-10-15T18:22:56.605Z" }, + { url = "https://files.pythonhosted.org/packages/84/b0/d525ef47d71590f1621510327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe", size = 7066043, upload-time = "2025-10-15T18:22:58.53Z" }, + { url = "https://files.pythonhosted.org/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee", size = 6490915, upload-time = "2025-10-15T18:23:00.582Z" }, + { url = "https://files.pythonhosted.org/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef", size = 7157998, upload-time = "2025-10-15T18:23:02.627Z" }, + { url = "https://files.pythonhosted.org/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9", size = 6306201, upload-time = "2025-10-15T18:23:04.709Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b", size = 7013165, upload-time = "2025-10-15T18:23:06.46Z" }, + { url = "https://files.pythonhosted.org/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47", size = 2437834, upload-time = "2025-10-15T18:23:08.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9", size = 4045531, upload-time = "2025-10-15T18:23:10.121Z" }, + { url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2", size = 4120554, upload-time = "2025-10-15T18:23:12.14Z" }, + { url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a", size = 3576812, upload-time = "2025-10-15T18:23:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b", size = 5252689, upload-time = "2025-10-15T18:23:15.562Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad", size = 4650186, upload-time = "2025-10-15T18:23:17.379Z" }, + { url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01", size = 6230308, upload-time = "2025-10-15T18:23:18.971Z" }, + { url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c", size = 8039222, upload-time = "2025-10-15T18:23:20.909Z" }, + { url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e", size = 6346657, upload-time = "2025-10-15T18:23:23.077Z" }, + { url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e", size = 7038482, upload-time = "2025-10-15T18:23:25.005Z" }, + { url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9", size = 6461416, upload-time = "2025-10-15T18:23:27.009Z" }, + { url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab", size = 7131584, upload-time = "2025-10-15T18:23:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b", size = 6400621, upload-time = "2025-10-15T18:23:32.06Z" }, + { url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b", size = 7142916, upload-time = "2025-10-15T18:23:34.71Z" }, + { url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0", size = 2523836, upload-time = "2025-10-15T18:23:36.967Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6", size = 5255092, upload-time = "2025-10-15T18:23:38.573Z" }, + { url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6", size = 4653158, upload-time = "2025-10-15T18:23:40.238Z" }, + { url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1", size = 6267882, upload-time = "2025-10-15T18:23:42.434Z" }, + { url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e", size = 8071001, upload-time = "2025-10-15T18:23:44.29Z" }, + { url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca", size = 6380146, upload-time = "2025-10-15T18:23:46.065Z" }, + { url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925", size = 7067344, upload-time = "2025-10-15T18:23:47.898Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8", size = 6491864, upload-time = "2025-10-15T18:23:49.607Z" }, + { url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4", size = 7158911, upload-time = "2025-10-15T18:23:51.351Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52", size = 6408045, upload-time = "2025-10-15T18:23:53.177Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a", size = 7148282, upload-time = "2025-10-15T18:23:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "priority" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/3c/eb7c35f4dcede96fca1842dac5f4f5d15511aa4b52f3a961219e68ae9204/priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0", size = 24792, upload-time = "2021-06-27T10:15:05.487Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/5f/82c8074f7e84978129347c2c6ec8b6c59f3584ff1a20bc3c940a3e061790/priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa", size = 8946, upload-time = "2021-06-27T10:15:03.856Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668, upload-time = "2025-11-12T13:05:07.379Z" }, +] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + +[[package]] +name = "quart" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "blinker" }, + { name = "click" }, + { name = "flask" }, + { name = "hypercorn" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/9d/12e1143a5bd2ccc05c293a6f5ae1df8fd94a8fc1440ecc6c344b2b30ce13/quart-0.20.0.tar.gz", hash = "sha256:08793c206ff832483586f5ae47018c7e40bdd75d886fee3fabbdaa70c2cf505d", size = 63874, upload-time = "2024-12-23T13:53:05.664Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/e9/cc28f21f52913adf333f653b9e0a3bf9cb223f5083a26422968ba73edd8d/quart-0.20.0-py3-none-any.whl", hash = "sha256:003c08f551746710acb757de49d9b768986fd431517d0eb127380b656b98b8f1", size = 77960, upload-time = "2024-12-23T13:53:02.842Z" }, +] + +[[package]] +name = "rtree" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/09/7302695875a019514de9a5dd17b8320e7a19d6e7bc8f85dcfb79a4ce2da3/rtree-1.4.1.tar.gz", hash = "sha256:c6b1b3550881e57ebe530cc6cffefc87cd9bf49c30b37b894065a9f810875e46", size = 52425, upload-time = "2025-08-13T19:32:01.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/d9/108cd989a4c0954e60b3cdc86fd2826407702b5375f6dfdab2802e5fed98/rtree-1.4.1-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:d672184298527522d4914d8ae53bf76982b86ca420b0acde9298a7a87d81d4a4", size = 468484, upload-time = "2025-08-13T19:31:50.593Z" }, + { url = "https://files.pythonhosted.org/packages/f3/cf/2710b6fd6b07ea0aef317b29f335790ba6adf06a28ac236078ed9bd8a91d/rtree-1.4.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a7e48d805e12011c2cf739a29d6a60ae852fb1de9fc84220bbcef67e6e595d7d", size = 436325, upload-time = "2025-08-13T19:31:52.367Z" }, + { url = "https://files.pythonhosted.org/packages/55/e1/4d075268a46e68db3cac51846eb6a3ab96ed481c585c5a1ad411b3c23aad/rtree-1.4.1-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efa8c4496e31e9ad58ff6c7df89abceac7022d906cb64a3e18e4fceae6b77f65", size = 459789, upload-time = "2025-08-13T19:31:53.926Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/e5d44be90525cd28503e7f836d077ae6663ec0687a13ba7810b4114b3668/rtree-1.4.1-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12de4578f1b3381a93a655846900be4e3d5f4cd5e306b8b00aa77c1121dc7e8c", size = 507644, upload-time = "2025-08-13T19:31:55.164Z" }, + { url = "https://files.pythonhosted.org/packages/fd/85/b8684f769a142163b52859a38a486493b05bafb4f2fb71d4f945de28ebf9/rtree-1.4.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b558edda52eca3e6d1ee629042192c65e6b7f2c150d6d6cd207ce82f85be3967", size = 1454478, upload-time = "2025-08-13T19:31:56.808Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a4/c2292b95246b9165cc43a0c3757e80995d58bc9b43da5cb47ad6e3535213/rtree-1.4.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f155bc8d6bac9dcd383481dee8c130947a4866db1d16cb6dff442329a038a0dc", size = 1555140, upload-time = "2025-08-13T19:31:58.031Z" }, + { url = "https://files.pythonhosted.org/packages/74/25/5282c8270bfcd620d3e73beb35b40ac4ab00f0a898d98ebeb41ef0989ec8/rtree-1.4.1-py3-none-win_amd64.whl", hash = "sha256:efe125f416fd27150197ab8521158662943a40f87acab8028a1aac4ad667a489", size = 389358, upload-time = "2025-08-13T19:31:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/3f/50/0a9e7e7afe7339bd5e36911f0ceb15fed51945836ed803ae5afd661057fd/rtree-1.4.1-py3-none-win_arm64.whl", hash = "sha256:3d46f55729b28138e897ffef32f7ce93ac335cb67f9120125ad3742a220800f0", size = 355253, upload-time = "2025-08-13T19:32:00.296Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883, upload-time = "2025-10-28T17:38:54.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/41/5bf55c3f386b1643812f3a5674edf74b26184378ef0f3e7c7a09a7e2ca7f/scipy-1.16.3-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81fc5827606858cf71446a5e98715ba0e11f0dbc83d71c7409d05486592a45d6", size = 36659043, upload-time = "2025-10-28T17:32:40.285Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0f/65582071948cfc45d43e9870bf7ca5f0e0684e165d7c9ef4e50d783073eb/scipy-1.16.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:c97176013d404c7346bf57874eaac5187d969293bf40497140b0a2b2b7482e07", size = 28898986, upload-time = "2025-10-28T17:32:45.325Z" }, + { url = "https://files.pythonhosted.org/packages/96/5e/36bf3f0ac298187d1ceadde9051177d6a4fe4d507e8f59067dc9dd39e650/scipy-1.16.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2b71d93c8a9936046866acebc915e2af2e292b883ed6e2cbe5c34beb094b82d9", size = 20889814, upload-time = "2025-10-28T17:32:49.277Z" }, + { url = "https://files.pythonhosted.org/packages/80/35/178d9d0c35394d5d5211bbff7ac4f2986c5488b59506fef9e1de13ea28d3/scipy-1.16.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3d4a07a8e785d80289dfe66b7c27d8634a773020742ec7187b85ccc4b0e7b686", size = 23565795, upload-time = "2025-10-28T17:32:53.337Z" }, + { url = "https://files.pythonhosted.org/packages/fa/46/d1146ff536d034d02f83c8afc3c4bab2eddb634624d6529a8512f3afc9da/scipy-1.16.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0553371015692a898e1aa858fed67a3576c34edefa6b7ebdb4e9dde49ce5c203", size = 33349476, upload-time = "2025-10-28T17:32:58.353Z" }, + { url = "https://files.pythonhosted.org/packages/79/2e/415119c9ab3e62249e18c2b082c07aff907a273741b3f8160414b0e9193c/scipy-1.16.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72d1717fd3b5e6ec747327ce9bda32d5463f472c9dce9f54499e81fbd50245a1", size = 35676692, upload-time = "2025-10-28T17:33:03.88Z" }, + { url = "https://files.pythonhosted.org/packages/27/82/df26e44da78bf8d2aeaf7566082260cfa15955a5a6e96e6a29935b64132f/scipy-1.16.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fb2472e72e24d1530debe6ae078db70fb1605350c88a3d14bc401d6306dbffe", size = 36019345, upload-time = "2025-10-28T17:33:09.773Z" }, + { url = "https://files.pythonhosted.org/packages/82/31/006cbb4b648ba379a95c87262c2855cd0d09453e500937f78b30f02fa1cd/scipy-1.16.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5192722cffe15f9329a3948c4b1db789fbb1f05c97899187dcf009b283aea70", size = 38678975, upload-time = "2025-10-28T17:33:15.809Z" }, + { url = "https://files.pythonhosted.org/packages/c2/7f/acbd28c97e990b421af7d6d6cd416358c9c293fc958b8529e0bd5d2a2a19/scipy-1.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:56edc65510d1331dae01ef9b658d428e33ed48b4f77b1d51caf479a0253f96dc", size = 38555926, upload-time = "2025-10-28T17:33:21.388Z" }, + { url = "https://files.pythonhosted.org/packages/ce/69/c5c7807fd007dad4f48e0a5f2153038dc96e8725d3345b9ee31b2b7bed46/scipy-1.16.3-cp312-cp312-win_arm64.whl", hash = "sha256:a8a26c78ef223d3e30920ef759e25625a0ecdd0d60e5a8818b7513c3e5384cf2", size = 25463014, upload-time = "2025-10-28T17:33:25.975Z" }, + { url = "https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:d2ec56337675e61b312179a1ad124f5f570c00f920cc75e1000025451b88241c", size = 36617856, upload-time = "2025-10-28T17:33:31.375Z" }, + { url = "https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:16b8bc35a4cc24db80a0ec836a9286d0e31b2503cb2fd7ff7fb0e0374a97081d", size = 28874306, upload-time = "2025-10-28T17:33:36.516Z" }, + { url = "https://files.pythonhosted.org/packages/15/65/3a9400efd0228a176e6ec3454b1fa998fbbb5a8defa1672c3f65706987db/scipy-1.16.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:5803c5fadd29de0cf27fa08ccbfe7a9e5d741bf63e4ab1085437266f12460ff9", size = 20865371, upload-time = "2025-10-28T17:33:42.094Z" }, + { url = "https://files.pythonhosted.org/packages/33/d7/eda09adf009a9fb81827194d4dd02d2e4bc752cef16737cc4ef065234031/scipy-1.16.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b81c27fc41954319a943d43b20e07c40bdcd3ff7cf013f4fb86286faefe546c4", size = 23524877, upload-time = "2025-10-28T17:33:48.483Z" }, + { url = "https://files.pythonhosted.org/packages/7d/6b/3f911e1ebc364cb81320223a3422aab7d26c9c7973109a9cd0f27c64c6c0/scipy-1.16.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0c3b4dd3d9b08dbce0f3440032c52e9e2ab9f96ade2d3943313dfe51a7056959", size = 33342103, upload-time = "2025-10-28T17:33:56.495Z" }, + { url = "https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7dc1360c06535ea6116a2220f760ae572db9f661aba2d88074fe30ec2aa1ff88", size = 35697297, upload-time = "2025-10-28T17:34:04.722Z" }, + { url = "https://files.pythonhosted.org/packages/04/e1/6496dadbc80d8d896ff72511ecfe2316b50313bfc3ebf07a3f580f08bd8c/scipy-1.16.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:663b8d66a8748051c3ee9c96465fb417509315b99c71550fda2591d7dd634234", size = 36021756, upload-time = "2025-10-28T17:34:13.482Z" }, + { url = "https://files.pythonhosted.org/packages/fe/bd/a8c7799e0136b987bda3e1b23d155bcb31aec68a4a472554df5f0937eef7/scipy-1.16.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eab43fae33a0c39006a88096cd7b4f4ef545ea0447d250d5ac18202d40b6611d", size = 38696566, upload-time = "2025-10-28T17:34:22.384Z" }, + { url = "https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl", hash = "sha256:062246acacbe9f8210de8e751b16fc37458213f124bef161a5a02c7a39284304", size = 38529877, upload-time = "2025-10-28T17:35:51.076Z" }, + { url = "https://files.pythonhosted.org/packages/7f/14/9d9fbcaa1260a94f4bb5b64ba9213ceb5d03cd88841fe9fd1ffd47a45b73/scipy-1.16.3-cp313-cp313-win_arm64.whl", hash = "sha256:50a3dbf286dbc7d84f176f9a1574c705f277cb6565069f88f60db9eafdbe3ee2", size = 25455366, upload-time = "2025-10-28T17:35:59.014Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a3/9ec205bd49f42d45d77f1730dbad9ccf146244c1647605cf834b3a8c4f36/scipy-1.16.3-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:fb4b29f4cf8cc5a8d628bc8d8e26d12d7278cd1f219f22698a378c3d67db5e4b", size = 37027931, upload-time = "2025-10-28T17:34:31.451Z" }, + { url = "https://files.pythonhosted.org/packages/25/06/ca9fd1f3a4589cbd825b1447e5db3a8ebb969c1eaf22c8579bd286f51b6d/scipy-1.16.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:8d09d72dc92742988b0e7750bddb8060b0c7079606c0d24a8cc8e9c9c11f9079", size = 29400081, upload-time = "2025-10-28T17:34:39.087Z" }, + { url = "https://files.pythonhosted.org/packages/6a/56/933e68210d92657d93fb0e381683bc0e53a965048d7358ff5fbf9e6a1b17/scipy-1.16.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:03192a35e661470197556de24e7cb1330d84b35b94ead65c46ad6f16f6b28f2a", size = 21391244, upload-time = "2025-10-28T17:34:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/a8/7e/779845db03dc1418e215726329674b40576879b91814568757ff0014ad65/scipy-1.16.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:57d01cb6f85e34f0946b33caa66e892aae072b64b034183f3d87c4025802a119", size = 23929753, upload-time = "2025-10-28T17:34:51.793Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4b/f756cf8161d5365dcdef9e5f460ab226c068211030a175d2fc7f3f41ca64/scipy-1.16.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:96491a6a54e995f00a28a3c3badfff58fd093bf26cd5fb34a2188c8c756a3a2c", size = 33496912, upload-time = "2025-10-28T17:34:59.8Z" }, + { url = "https://files.pythonhosted.org/packages/09/b5/222b1e49a58668f23839ca1542a6322bb095ab8d6590d4f71723869a6c2c/scipy-1.16.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cd13e354df9938598af2be05822c323e97132d5e6306b83a3b4ee6724c6e522e", size = 35802371, upload-time = "2025-10-28T17:35:08.173Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8d/5964ef68bb31829bde27611f8c9deeac13764589fe74a75390242b64ca44/scipy-1.16.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63d3cdacb8a824a295191a723ee5e4ea7768ca5ca5f2838532d9f2e2b3ce2135", size = 36190477, upload-time = "2025-10-28T17:35:16.7Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f2/b31d75cb9b5fa4dd39a0a931ee9b33e7f6f36f23be5ef560bf72e0f92f32/scipy-1.16.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e7efa2681ea410b10dde31a52b18b0154d66f2485328830e45fdf183af5aefc6", size = 38796678, upload-time = "2025-10-28T17:35:26.354Z" }, + { url = "https://files.pythonhosted.org/packages/b4/1e/b3723d8ff64ab548c38d87055483714fefe6ee20e0189b62352b5e015bb1/scipy-1.16.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2d1ae2cf0c350e7705168ff2429962a89ad90c2d49d1dd300686d8b2a5af22fc", size = 38640178, upload-time = "2025-10-28T17:35:35.304Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f3/d854ff38789aca9b0cc23008d607ced9de4f7ab14fa1ca4329f86b3758ca/scipy-1.16.3-cp313-cp313t-win_arm64.whl", hash = "sha256:0c623a54f7b79dd88ef56da19bc2873afec9673a48f3b85b18e4d402bdd29a5a", size = 25803246, upload-time = "2025-10-28T17:35:42.155Z" }, + { url = "https://files.pythonhosted.org/packages/99/f6/99b10fd70f2d864c1e29a28bbcaa0c6340f9d8518396542d9ea3b4aaae15/scipy-1.16.3-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:875555ce62743e1d54f06cdf22c1e0bc47b91130ac40fe5d783b6dfa114beeb6", size = 36606469, upload-time = "2025-10-28T17:36:08.741Z" }, + { url = "https://files.pythonhosted.org/packages/4d/74/043b54f2319f48ea940dd025779fa28ee360e6b95acb7cd188fad4391c6b/scipy-1.16.3-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:bb61878c18a470021fb515a843dc7a76961a8daceaaaa8bad1332f1bf4b54657", size = 28872043, upload-time = "2025-10-28T17:36:16.599Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e1/24b7e50cc1c4ee6ffbcb1f27fe9f4c8b40e7911675f6d2d20955f41c6348/scipy-1.16.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2622206f5559784fa5c4b53a950c3c7c1cf3e84ca1b9c4b6c03f062f289ca26", size = 20862952, upload-time = "2025-10-28T17:36:22.966Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3a/3e8c01a4d742b730df368e063787c6808597ccb38636ed821d10b39ca51b/scipy-1.16.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7f68154688c515cdb541a31ef8eb66d8cd1050605be9dcd74199cbd22ac739bc", size = 23508512, upload-time = "2025-10-28T17:36:29.731Z" }, + { url = "https://files.pythonhosted.org/packages/1f/60/c45a12b98ad591536bfe5330cb3cfe1850d7570259303563b1721564d458/scipy-1.16.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3c820ddb80029fe9f43d61b81d8b488d3ef8ca010d15122b152db77dc94c22", size = 33413639, upload-time = "2025-10-28T17:36:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/71/bc/35957d88645476307e4839712642896689df442f3e53b0fa016ecf8a3357/scipy-1.16.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d3837938ae715fc0fe3c39c0202de3a8853aff22ca66781ddc2ade7554b7e2cc", size = 35704729, upload-time = "2025-10-28T17:36:46.547Z" }, + { url = "https://files.pythonhosted.org/packages/3b/15/89105e659041b1ca11c386e9995aefacd513a78493656e57789f9d9eab61/scipy-1.16.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aadd23f98f9cb069b3bd64ddc900c4d277778242e961751f77a8cb5c4b946fb0", size = 36086251, upload-time = "2025-10-28T17:36:55.161Z" }, + { url = "https://files.pythonhosted.org/packages/1a/87/c0ea673ac9c6cc50b3da2196d860273bc7389aa69b64efa8493bdd25b093/scipy-1.16.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b7c5f1bda1354d6a19bc6af73a649f8285ca63ac6b52e64e658a5a11d4d69800", size = 38716681, upload-time = "2025-10-28T17:37:04.1Z" }, + { url = "https://files.pythonhosted.org/packages/91/06/837893227b043fb9b0d13e4bd7586982d8136cb249ffb3492930dab905b8/scipy-1.16.3-cp314-cp314-win_amd64.whl", hash = "sha256:e5d42a9472e7579e473879a1990327830493a7047506d58d73fc429b84c1d49d", size = 39358423, upload-time = "2025-10-28T17:38:20.005Z" }, + { url = "https://files.pythonhosted.org/packages/95/03/28bce0355e4d34a7c034727505a02d19548549e190bedd13a721e35380b7/scipy-1.16.3-cp314-cp314-win_arm64.whl", hash = "sha256:6020470b9d00245926f2d5bb93b119ca0340f0d564eb6fbaad843eaebf9d690f", size = 26135027, upload-time = "2025-10-28T17:38:24.966Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6f/69f1e2b682efe9de8fe9f91040f0cd32f13cfccba690512ba4c582b0bc29/scipy-1.16.3-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:e1d27cbcb4602680a49d787d90664fa4974063ac9d4134813332a8c53dbe667c", size = 37028379, upload-time = "2025-10-28T17:37:14.061Z" }, + { url = "https://files.pythonhosted.org/packages/7c/2d/e826f31624a5ebbab1cd93d30fd74349914753076ed0593e1d56a98c4fb4/scipy-1.16.3-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:9b9c9c07b6d56a35777a1b4cc8966118fb16cfd8daf6743867d17d36cfad2d40", size = 29400052, upload-time = "2025-10-28T17:37:21.709Z" }, + { url = "https://files.pythonhosted.org/packages/69/27/d24feb80155f41fd1f156bf144e7e049b4e2b9dd06261a242905e3bc7a03/scipy-1.16.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:3a4c460301fb2cffb7f88528f30b3127742cff583603aa7dc964a52c463b385d", size = 21391183, upload-time = "2025-10-28T17:37:29.559Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d3/1b229e433074c5738a24277eca520a2319aac7465eea7310ea6ae0e98ae2/scipy-1.16.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f667a4542cc8917af1db06366d3f78a5c8e83badd56409f94d1eac8d8d9133fa", size = 23930174, upload-time = "2025-10-28T17:37:36.306Z" }, + { url = "https://files.pythonhosted.org/packages/16/9d/d9e148b0ec680c0f042581a2be79a28a7ab66c0c4946697f9e7553ead337/scipy-1.16.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f379b54b77a597aa7ee5e697df0d66903e41b9c85a6dd7946159e356319158e8", size = 33497852, upload-time = "2025-10-28T17:37:42.228Z" }, + { url = "https://files.pythonhosted.org/packages/2f/22/4e5f7561e4f98b7bea63cf3fd7934bff1e3182e9f1626b089a679914d5c8/scipy-1.16.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4aff59800a3b7f786b70bfd6ab551001cb553244988d7d6b8299cb1ea653b353", size = 35798595, upload-time = "2025-10-28T17:37:48.102Z" }, + { url = "https://files.pythonhosted.org/packages/83/42/6644d714c179429fc7196857866f219fef25238319b650bb32dde7bf7a48/scipy-1.16.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:da7763f55885045036fabcebd80144b757d3db06ab0861415d1c3b7c69042146", size = 36186269, upload-time = "2025-10-28T17:37:53.72Z" }, + { url = "https://files.pythonhosted.org/packages/ac/70/64b4d7ca92f9cf2e6fc6aaa2eecf80bb9b6b985043a9583f32f8177ea122/scipy-1.16.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ffa6eea95283b2b8079b821dc11f50a17d0571c92b43e2b5b12764dc5f9b285d", size = 38802779, upload-time = "2025-10-28T17:37:59.393Z" }, + { url = "https://files.pythonhosted.org/packages/61/82/8d0e39f62764cce5ffd5284131e109f07cf8955aef9ab8ed4e3aa5e30539/scipy-1.16.3-cp314-cp314t-win_amd64.whl", hash = "sha256:d9f48cafc7ce94cf9b15c6bffdc443a81a27bf7075cf2dcd5c8b40f85d10c4e7", size = 39471128, upload-time = "2025-10-28T17:38:05.259Z" }, + { url = "https://files.pythonhosted.org/packages/64/47/a494741db7280eae6dc033510c319e34d42dd41b7ac0c7ead39354d1a2b5/scipy-1.16.3-cp314-cp314t-win_arm64.whl", hash = "sha256:21d9d6b197227a12dcbf9633320a4e34c6b0e51c57268df255a0942983bac562", size = 26464127, upload-time = "2025-10-28T17:38:11.34Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, +] + +[[package]] +name = "wsproto" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/8d/48e227460422d3f78f52618d8ef7d7a0474c6fcdaddf7f2d1aa25854ea75/wsproto-1.3.1.tar.gz", hash = "sha256:81529992325c28f0d9b86ca66fc973da96eb80ab53410249ce2e502749c7723c", size = 50083, upload-time = "2025-11-12T07:50:48.408Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/da/539c2d24b13025e54a86ce3215eb9b6297b023937a087db9ef2a436cc7b4/wsproto-1.3.1-py3-none-any.whl", hash = "sha256:297ce79322989c0d286cc158681641cd18bc7632dfb38cf4054696a89179b993", size = 24402, upload-time = "2025-11-12T07:50:47.178Z" }, +]