[Tests] cleanup
This commit is contained in:
parent
d9adb4e1b9
commit
1cce6c1f70
23 changed files with 540 additions and 467 deletions
|
|
@ -3,14 +3,11 @@
|
||||||
Test fixtures
|
Test fixtures
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# ruff: noqa: ARG001
|
# ruff: noqa: ARG001
|
||||||
from typing import Any
|
from typing import Any
|
||||||
import numpy
|
import numpy
|
||||||
from numpy.typing import NDArray
|
|
||||||
|
|
||||||
import pytest # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
FixtureRequest = Any
|
FixtureRequest = Any
|
||||||
PRNG = numpy.random.RandomState(12345)
|
PRNG = numpy.random.RandomState(12345)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
import pytest
|
from numpy.testing import assert_allclose
|
||||||
import numpy
|
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from ..abstract import Abstract
|
from ..abstract import Abstract
|
||||||
from ..ports import Port
|
from ..ports import Port
|
||||||
from ..ref import Ref
|
from ..ref import Ref
|
||||||
|
|
||||||
def test_abstract_init():
|
|
||||||
|
def test_abstract_init() -> None:
|
||||||
ports = {"A": Port((0, 0), 0), "B": Port((10, 0), pi)}
|
ports = {"A": Port((0, 0), 0), "B": Port((10, 0), pi)}
|
||||||
abs_obj = Abstract("test", ports)
|
abs_obj = Abstract("test", ports)
|
||||||
assert abs_obj.name == "test"
|
assert abs_obj.name == "test"
|
||||||
assert len(abs_obj.ports) == 2
|
assert len(abs_obj.ports) == 2
|
||||||
assert abs_obj.ports["A"] is not ports["A"] # Should be deepcopied
|
assert abs_obj.ports["A"] is not ports["A"] # Should be deepcopied
|
||||||
|
|
||||||
def test_abstract_transform():
|
|
||||||
|
def test_abstract_transform() -> None:
|
||||||
abs_obj = Abstract("test", {"A": Port((10, 0), 0)})
|
abs_obj = Abstract("test", {"A": Port((10, 0), 0)})
|
||||||
# Rotate 90 deg around (0,0)
|
# Rotate 90 deg around (0,0)
|
||||||
abs_obj.rotate_around((0, 0), pi / 2)
|
abs_obj.rotate_around((0, 0), pi / 2)
|
||||||
|
|
@ -29,7 +29,8 @@ def test_abstract_transform():
|
||||||
assert_allclose(abs_obj.ports["A"].offset, [0, -10], atol=1e-10)
|
assert_allclose(abs_obj.ports["A"].offset, [0, -10], atol=1e-10)
|
||||||
assert_allclose(abs_obj.ports["A"].rotation, 3 * pi / 2, atol=1e-10)
|
assert_allclose(abs_obj.ports["A"].rotation, 3 * pi / 2, atol=1e-10)
|
||||||
|
|
||||||
def test_abstract_ref_transform():
|
|
||||||
|
def test_abstract_ref_transform() -> None:
|
||||||
abs_obj = Abstract("test", {"A": Port((10, 0), 0)})
|
abs_obj = Abstract("test", {"A": Port((10, 0), 0)})
|
||||||
ref = Ref(offset=(100, 100), rotation=pi / 2, mirrored=True)
|
ref = Ref(offset=(100, 100), rotation=pi / 2, mirrored=True)
|
||||||
|
|
||||||
|
|
@ -49,7 +50,8 @@ def test_abstract_ref_transform():
|
||||||
assert_allclose(abs_obj.ports["A"].offset, [100, 110], atol=1e-10)
|
assert_allclose(abs_obj.ports["A"].offset, [100, 110], atol=1e-10)
|
||||||
assert_allclose(abs_obj.ports["A"].rotation, pi / 2, atol=1e-10)
|
assert_allclose(abs_obj.ports["A"].rotation, pi / 2, atol=1e-10)
|
||||||
|
|
||||||
def test_abstract_undo_transform():
|
|
||||||
|
def test_abstract_undo_transform() -> None:
|
||||||
abs_obj = Abstract("test", {"A": Port((100, 110), pi / 2)})
|
abs_obj = Abstract("test", {"A": Port((100, 110), pi / 2)})
|
||||||
ref = Ref(offset=(100, 100), rotation=pi / 2, mirrored=True)
|
ref = Ref(offset=(100, 100), rotation=pi / 2, mirrored=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import pytest
|
import pytest
|
||||||
import numpy
|
from numpy.testing import assert_equal
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from ..builder import Pather
|
from ..builder import Pather
|
||||||
|
|
@ -8,15 +7,17 @@ from ..builder.tools import PathTool
|
||||||
from ..library import Library
|
from ..library import Library
|
||||||
from ..ports import Port
|
from ..ports import Port
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def advanced_pather():
|
def advanced_pather() -> tuple[Pather, PathTool, Library]:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
# Simple PathTool: 2um width on layer (1,0)
|
# Simple PathTool: 2um width on layer (1,0)
|
||||||
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
||||||
p = Pather(lib, tools=tool)
|
p = Pather(lib, tools=tool)
|
||||||
return p, tool, lib
|
return p, tool, lib
|
||||||
|
|
||||||
def test_path_into_straight(advanced_pather):
|
|
||||||
|
def test_path_into_straight(advanced_pather: tuple[Pather, PathTool, Library]) -> None:
|
||||||
p, tool, lib = advanced_pather
|
p, tool, lib = advanced_pather
|
||||||
# Facing ports
|
# Facing ports
|
||||||
p.ports["src"] = Port((0, 0), 0, ptype="wire") # Facing East (into device)
|
p.ports["src"] = Port((0, 0), 0, ptype="wire") # Facing East (into device)
|
||||||
|
|
@ -31,7 +32,8 @@ def test_path_into_straight(advanced_pather):
|
||||||
# Pather.path adds a Reference to the generated pattern
|
# Pather.path adds a Reference to the generated pattern
|
||||||
assert len(p.pattern.refs) == 1
|
assert len(p.pattern.refs) == 1
|
||||||
|
|
||||||
def test_path_into_bend(advanced_pather):
|
|
||||||
|
def test_path_into_bend(advanced_pather: tuple[Pather, PathTool, Library]) -> None:
|
||||||
p, tool, lib = advanced_pather
|
p, tool, lib = advanced_pather
|
||||||
# Source at (0,0) rot 0 (facing East). Forward is West (-x).
|
# Source at (0,0) rot 0 (facing East). Forward is West (-x).
|
||||||
p.ports["src"] = Port((0, 0), 0, ptype="wire")
|
p.ports["src"] = Port((0, 0), 0, ptype="wire")
|
||||||
|
|
@ -48,7 +50,8 @@ def test_path_into_bend(advanced_pather):
|
||||||
# Single bend should result in 2 segments (one for x move, one for y move)
|
# Single bend should result in 2 segments (one for x move, one for y move)
|
||||||
assert len(p.pattern.refs) == 2
|
assert len(p.pattern.refs) == 2
|
||||||
|
|
||||||
def test_path_into_sbend(advanced_pather):
|
|
||||||
|
def test_path_into_sbend(advanced_pather: tuple[Pather, PathTool, Library]) -> None:
|
||||||
p, tool, lib = advanced_pather
|
p, tool, lib = advanced_pather
|
||||||
# Facing but offset ports
|
# Facing but offset ports
|
||||||
p.ports["src"] = Port((0, 0), 0, ptype="wire") # Forward is West (-x)
|
p.ports["src"] = Port((0, 0), 0, ptype="wire") # Forward is West (-x)
|
||||||
|
|
@ -59,7 +62,8 @@ def test_path_into_sbend(advanced_pather):
|
||||||
assert "src" not in p.ports
|
assert "src" not in p.ports
|
||||||
assert "dst" not in p.ports
|
assert "dst" not in p.ports
|
||||||
|
|
||||||
def test_path_from(advanced_pather):
|
|
||||||
|
def test_path_from(advanced_pather: tuple[Pather, PathTool, Library]) -> None:
|
||||||
p, tool, lib = advanced_pather
|
p, tool, lib = advanced_pather
|
||||||
p.ports["src"] = Port((0, 0), 0, ptype="wire")
|
p.ports["src"] = Port((0, 0), 0, ptype="wire")
|
||||||
p.ports["dst"] = Port((-20, 0), pi, ptype="wire")
|
p.ports["dst"] = Port((-20, 0), pi, ptype="wire")
|
||||||
|
|
@ -69,7 +73,8 @@ def test_path_from(advanced_pather):
|
||||||
assert "src" not in p.ports
|
assert "src" not in p.ports
|
||||||
assert "dst" not in p.ports
|
assert "dst" not in p.ports
|
||||||
|
|
||||||
def test_path_into_thru(advanced_pather):
|
|
||||||
|
def test_path_into_thru(advanced_pather: tuple[Pather, PathTool, Library]) -> None:
|
||||||
p, tool, lib = advanced_pather
|
p, tool, lib = advanced_pather
|
||||||
p.ports["src"] = Port((0, 0), 0, ptype="wire")
|
p.ports["src"] = Port((0, 0), 0, ptype="wire")
|
||||||
p.ports["dst"] = Port((-20, 0), pi, ptype="wire")
|
p.ports["dst"] = Port((-20, 0), pi, ptype="wire")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import pytest
|
import pytest
|
||||||
import numpy
|
from numpy.testing import assert_allclose
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from ..builder import Pather
|
from ..builder import Pather
|
||||||
|
|
@ -8,17 +7,18 @@ from ..builder.tools import AutoTool
|
||||||
from ..library import Library
|
from ..library import Library
|
||||||
from ..pattern import Pattern
|
from ..pattern import Pattern
|
||||||
from ..ports import Port
|
from ..ports import Port
|
||||||
from ..abstract import Abstract
|
|
||||||
|
|
||||||
def make_straight(length, width=2, ptype="wire"):
|
|
||||||
|
def make_straight(length: float, width: float = 2, ptype: str = "wire") -> Pattern:
|
||||||
pat = Pattern()
|
pat = Pattern()
|
||||||
pat.rect((1, 0), xmin=0, xmax=length, yctr=0, ly=width)
|
pat.rect((1, 0), xmin=0, xmax=length, yctr=0, ly=width)
|
||||||
pat.ports["in"] = Port((0, 0), 0, ptype=ptype)
|
pat.ports["in"] = Port((0, 0), 0, ptype=ptype)
|
||||||
pat.ports["out"] = Port((length, 0), pi, ptype=ptype)
|
pat.ports["out"] = Port((length, 0), pi, ptype=ptype)
|
||||||
return pat
|
return pat
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def autotool_setup():
|
def autotool_setup() -> tuple[Pather, AutoTool, Library]:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
|
|
||||||
# Define a simple bend
|
# Define a simple bend
|
||||||
|
|
@ -27,7 +27,7 @@ def autotool_setup():
|
||||||
bend_pat.ports["in"] = Port((0, 0), 0, ptype="wire")
|
bend_pat.ports["in"] = Port((0, 0), 0, ptype="wire")
|
||||||
bend_pat.ports["out"] = Port((2, -2), pi / 2, ptype="wire")
|
bend_pat.ports["out"] = Port((2, -2), pi / 2, ptype="wire")
|
||||||
lib["bend"] = bend_pat
|
lib["bend"] = bend_pat
|
||||||
bend_abs = lib.abstract("bend")
|
lib.abstract("bend")
|
||||||
|
|
||||||
# Define a transition (e.g., via)
|
# Define a transition (e.g., via)
|
||||||
via_pat = Pattern()
|
via_pat = Pattern()
|
||||||
|
|
@ -37,14 +37,13 @@ def autotool_setup():
|
||||||
via_abs = lib.abstract("via")
|
via_abs = lib.abstract("via")
|
||||||
|
|
||||||
tool_m1 = AutoTool(
|
tool_m1 = AutoTool(
|
||||||
straights=[AutoTool.Straight(ptype="wire_m1", fn=lambda l: make_straight(l, ptype="wire_m1"),
|
straights=[
|
||||||
in_port_name="in", out_port_name="out")],
|
AutoTool.Straight(ptype="wire_m1", fn=lambda length: make_straight(length, ptype="wire_m1"), in_port_name="in", out_port_name="out")
|
||||||
|
],
|
||||||
bends=[],
|
bends=[],
|
||||||
sbends=[],
|
sbends=[],
|
||||||
transitions={
|
transitions={("wire_m2", "wire_m1"): AutoTool.Transition(via_abs, "m2", "m1")},
|
||||||
("wire_m2", "wire_m1"): AutoTool.Transition(via_abs, "m2", "m1")
|
default_out_ptype="wire_m1",
|
||||||
},
|
|
||||||
default_out_ptype="wire_m1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
p = Pather(lib, tools=tool_m1)
|
p = Pather(lib, tools=tool_m1)
|
||||||
|
|
@ -53,7 +52,8 @@ def autotool_setup():
|
||||||
|
|
||||||
return p, tool_m1, lib
|
return p, tool_m1, lib
|
||||||
|
|
||||||
def test_autotool_transition(autotool_setup):
|
|
||||||
|
def test_autotool_transition(autotool_setup: tuple[Pather, AutoTool, Library]) -> None:
|
||||||
p, tool, lib = autotool_setup
|
p, tool, lib = autotool_setup
|
||||||
|
|
||||||
# Route m1 from an m2 port. Should trigger via.
|
# Route m1 from an m2 port. Should trigger via.
|
||||||
|
|
@ -79,4 +79,3 @@ def test_autotool_transition(autotool_setup):
|
||||||
|
|
||||||
assert_allclose(p.ports["start"].offset, [10, 0], atol=1e-10)
|
assert_allclose(p.ports["start"].offset, [10, 0], atol=1e-10)
|
||||||
assert p.ports["start"].ptype == "wire_m1"
|
assert p.ports["start"].ptype == "wire_m1"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import pytest
|
|
||||||
import numpy
|
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
from numpy.testing import assert_equal, assert_allclose
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
|
|
@ -8,13 +6,15 @@ from ..library import Library
|
||||||
from ..pattern import Pattern
|
from ..pattern import Pattern
|
||||||
from ..ports import Port
|
from ..ports import Port
|
||||||
|
|
||||||
def test_builder_init():
|
|
||||||
|
def test_builder_init() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
b = Builder(lib, name="mypat")
|
b = Builder(lib, name="mypat")
|
||||||
assert b.pattern is lib["mypat"]
|
assert b.pattern is lib["mypat"]
|
||||||
assert b.library is lib
|
assert b.library is lib
|
||||||
|
|
||||||
def test_builder_place():
|
|
||||||
|
def test_builder_place() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
child = Pattern()
|
child = Pattern()
|
||||||
child.ports["A"] = Port((0, 0), 0)
|
child.ports["A"] = Port((0, 0), 0)
|
||||||
|
|
@ -27,7 +27,8 @@ def test_builder_place():
|
||||||
assert_equal(b.ports["child_A"].offset, [10, 20])
|
assert_equal(b.ports["child_A"].offset, [10, 20])
|
||||||
assert "child" in b.pattern.refs
|
assert "child" in b.pattern.refs
|
||||||
|
|
||||||
def test_builder_plug():
|
|
||||||
|
def test_builder_plug() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
|
|
||||||
wire = Pattern()
|
wire = Pattern()
|
||||||
|
|
@ -51,7 +52,8 @@ def test_builder_plug():
|
||||||
assert_equal(b.ports["start"].offset, [90, 100])
|
assert_equal(b.ports["start"].offset, [90, 100])
|
||||||
assert_allclose(b.ports["start"].rotation, 0, atol=1e-10)
|
assert_allclose(b.ports["start"].rotation, 0, atol=1e-10)
|
||||||
|
|
||||||
def test_builder_interface():
|
|
||||||
|
def test_builder_interface() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
source = Pattern()
|
source = Pattern()
|
||||||
source.ports["P1"] = Port((0, 0), 0)
|
source.ports["P1"] = Port((0, 0), 0)
|
||||||
|
|
@ -62,7 +64,8 @@ def test_builder_interface():
|
||||||
assert "P1" in b.ports
|
assert "P1" in b.ports
|
||||||
assert b.pattern is lib["iface"]
|
assert b.pattern is lib["iface"]
|
||||||
|
|
||||||
def test_builder_set_dead():
|
|
||||||
|
def test_builder_set_dead() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
lib["sub"] = Pattern()
|
lib["sub"] = Pattern()
|
||||||
b = Builder(lib)
|
b = Builder(lib)
|
||||||
|
|
@ -70,4 +73,3 @@ def test_builder_set_dead():
|
||||||
|
|
||||||
b.place("sub")
|
b.place("sub")
|
||||||
assert not b.pattern.has_refs()
|
assert not b.pattern.has_refs()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,4 +22,3 @@ def test_circle_mirror():
|
||||||
assert cc.offset[0] == -10
|
assert cc.offset[0] == -10
|
||||||
assert cc.offset[1] == -20
|
assert cc.offset[1] == -20
|
||||||
assert cc.radius == 4
|
assert cc.radius == 4
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
import pytest
|
from pathlib import Path
|
||||||
import os
|
|
||||||
import numpy
|
import numpy
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
from numpy.testing import assert_equal, assert_allclose
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from ..pattern import Pattern
|
from ..pattern import Pattern
|
||||||
from ..library import Library
|
from ..library import Library
|
||||||
from ..file import gdsii
|
from ..file import gdsii
|
||||||
from ..shapes import Polygon, Path as MPath
|
from ..shapes import Path as MPath
|
||||||
|
|
||||||
def test_gdsii_roundtrip(tmp_path):
|
|
||||||
|
def test_gdsii_roundtrip(tmp_path: Path) -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
|
|
||||||
# Simple polygon cell
|
# Simple polygon cell
|
||||||
|
|
@ -54,7 +53,8 @@ def test_gdsii_roundtrip(tmp_path):
|
||||||
assert_equal(read_ref.offset, [50, 50])
|
assert_equal(read_ref.offset, [50, 50])
|
||||||
assert_allclose(read_ref.rotation, numpy.pi / 2, atol=1e-5)
|
assert_allclose(read_ref.rotation, numpy.pi / 2, atol=1e-5)
|
||||||
|
|
||||||
def test_gdsii_annotations(tmp_path):
|
|
||||||
|
def test_gdsii_annotations(tmp_path: Path) -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
pat = Pattern()
|
pat = Pattern()
|
||||||
# GDS only supports integer keys in range [1, 126] for properties
|
# GDS only supports integer keys in range [1, 126] for properties
|
||||||
|
|
@ -67,4 +67,3 @@ def test_gdsii_annotations(tmp_path):
|
||||||
read_lib, _ = gdsii.readfile(gds_file)
|
read_lib, _ = gdsii.readfile(gds_file)
|
||||||
read_ann = read_lib["cell"].shapes[(1, 0)][0].annotations
|
read_ann = read_lib["cell"].shapes[(1, 0)][0].annotations
|
||||||
assert read_ann["1"] == ["hello"]
|
assert read_ann["1"] == ["hello"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,39 @@
|
||||||
import pytest
|
import copy
|
||||||
import numpy
|
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
from numpy.testing import assert_equal, assert_allclose
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from ..label import Label
|
from ..label import Label
|
||||||
from ..repetition import Grid
|
from ..repetition import Grid
|
||||||
|
|
||||||
def test_label_init():
|
|
||||||
l = Label("test", offset=(10, 20))
|
|
||||||
assert l.string == "test"
|
|
||||||
assert_equal(l.offset, [10, 20])
|
|
||||||
|
|
||||||
def test_label_transform():
|
def test_label_init() -> None:
|
||||||
l = Label("test", offset=(10, 0))
|
lbl = Label("test", offset=(10, 20))
|
||||||
|
assert lbl.string == "test"
|
||||||
|
assert_equal(lbl.offset, [10, 20])
|
||||||
|
|
||||||
|
|
||||||
|
def test_label_transform() -> None:
|
||||||
|
lbl = Label("test", offset=(10, 0))
|
||||||
# Rotate 90 deg CCW around (0,0)
|
# Rotate 90 deg CCW around (0,0)
|
||||||
l.rotate_around((0, 0), pi/2)
|
lbl.rotate_around((0, 0), pi / 2)
|
||||||
assert_allclose(l.offset, [0, 10], atol=1e-10)
|
assert_allclose(lbl.offset, [0, 10], atol=1e-10)
|
||||||
|
|
||||||
# Translate
|
# Translate
|
||||||
l.translate((5, 5))
|
lbl.translate((5, 5))
|
||||||
assert_allclose(l.offset, [5, 15], atol=1e-10)
|
assert_allclose(lbl.offset, [5, 15], atol=1e-10)
|
||||||
|
|
||||||
def test_label_repetition():
|
|
||||||
|
def test_label_repetition() -> None:
|
||||||
rep = Grid(a_vector=(10, 0), a_count=3)
|
rep = Grid(a_vector=(10, 0), a_count=3)
|
||||||
l = Label("rep", offset=(0, 0), repetition=rep)
|
lbl = Label("rep", offset=(0, 0), repetition=rep)
|
||||||
assert l.repetition is rep
|
assert lbl.repetition is rep
|
||||||
assert_equal(l.get_bounds_single(), [[0, 0], [0, 0]])
|
assert_equal(lbl.get_bounds_single(), [[0, 0], [0, 0]])
|
||||||
# Note: Bounded.get_bounds_nonempty() for labels with repetition doesn't
|
# Note: Bounded.get_bounds_nonempty() for labels with repetition doesn't
|
||||||
# seem to automatically include repetition bounds in label.py itself,
|
# seem to automatically include repetition bounds in label.py itself,
|
||||||
# it's handled during pattern bounding.
|
# it's handled during pattern bounding.
|
||||||
|
|
||||||
def test_label_copy():
|
|
||||||
|
def test_label_copy() -> None:
|
||||||
l1 = Label("test", offset=(1, 2), annotations={"a": [1]})
|
l1 = Label("test", offset=(1, 2), annotations={"a": [1]})
|
||||||
l2 = copy.deepcopy(l1)
|
l2 = copy.deepcopy(l1)
|
||||||
|
|
||||||
|
|
@ -38,11 +41,10 @@ def test_label_copy():
|
||||||
print(f"l2: string={l2.string}, offset={l2.offset}, repetition={l2.repetition}, annotations={l2.annotations}")
|
print(f"l2: string={l2.string}, offset={l2.offset}, repetition={l2.repetition}, annotations={l2.annotations}")
|
||||||
|
|
||||||
from ..utils import annotations_eq
|
from ..utils import annotations_eq
|
||||||
|
|
||||||
print(f"annotations_eq: {annotations_eq(l1.annotations, l2.annotations)}")
|
print(f"annotations_eq: {annotations_eq(l1.annotations, l2.annotations)}")
|
||||||
|
|
||||||
assert l1 == l2
|
assert l1 == l2
|
||||||
assert l1 is not l2
|
assert l1 is not l2
|
||||||
l2.offset[0] = 100
|
l2.offset[0] = 100
|
||||||
assert l1.offset[0] == 1
|
assert l1.offset[0] == 1
|
||||||
|
|
||||||
import copy
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import pytest
|
import pytest
|
||||||
from ..library import Library, LazyLibrary, LibraryView
|
from ..library import Library, LazyLibrary
|
||||||
from ..pattern import Pattern
|
from ..pattern import Pattern
|
||||||
from ..ref import Ref
|
|
||||||
from ..error import LibraryError
|
from ..error import LibraryError
|
||||||
|
|
||||||
def test_library_basic():
|
|
||||||
|
def test_library_basic() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
pat = Pattern()
|
pat = Pattern()
|
||||||
lib["cell1"] = pat
|
lib["cell1"] = pat
|
||||||
|
|
@ -16,7 +16,8 @@ def test_library_basic():
|
||||||
with pytest.raises(LibraryError):
|
with pytest.raises(LibraryError):
|
||||||
lib["cell1"] = Pattern() # Overwriting not allowed
|
lib["cell1"] = Pattern() # Overwriting not allowed
|
||||||
|
|
||||||
def test_library_tops():
|
|
||||||
|
def test_library_tops() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
lib["child"] = Pattern()
|
lib["child"] = Pattern()
|
||||||
lib["parent"] = Pattern()
|
lib["parent"] = Pattern()
|
||||||
|
|
@ -25,14 +26,16 @@ def test_library_tops():
|
||||||
assert set(lib.tops()) == {"parent"}
|
assert set(lib.tops()) == {"parent"}
|
||||||
assert lib.top() == "parent"
|
assert lib.top() == "parent"
|
||||||
|
|
||||||
def test_library_dangling():
|
|
||||||
|
def test_library_dangling() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
lib["parent"] = Pattern()
|
lib["parent"] = Pattern()
|
||||||
lib["parent"].ref("missing")
|
lib["parent"].ref("missing")
|
||||||
|
|
||||||
assert lib.dangling_refs() == {"missing"}
|
assert lib.dangling_refs() == {"missing"}
|
||||||
|
|
||||||
def test_library_flatten():
|
|
||||||
|
def test_library_flatten() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
child = Pattern()
|
child = Pattern()
|
||||||
child.polygon((1, 0), vertices=[[0, 0], [1, 0], [0, 1]])
|
child.polygon((1, 0), vertices=[[0, 0], [1, 0], [0, 1]])
|
||||||
|
|
@ -51,10 +54,12 @@ def test_library_flatten():
|
||||||
assert_vertices = flat_parent.shapes[(1, 0)][0].vertices
|
assert_vertices = flat_parent.shapes[(1, 0)][0].vertices
|
||||||
assert tuple(assert_vertices[0]) == (10.0, 10.0)
|
assert tuple(assert_vertices[0]) == (10.0, 10.0)
|
||||||
|
|
||||||
def test_lazy_library():
|
|
||||||
|
def test_lazy_library() -> None:
|
||||||
lib = LazyLibrary()
|
lib = LazyLibrary()
|
||||||
called = 0
|
called = 0
|
||||||
def make_pat():
|
|
||||||
|
def make_pat() -> Pattern:
|
||||||
nonlocal called
|
nonlocal called
|
||||||
called += 1
|
called += 1
|
||||||
return Pattern()
|
return Pattern()
|
||||||
|
|
@ -71,7 +76,8 @@ def test_lazy_library():
|
||||||
assert called == 1
|
assert called == 1
|
||||||
assert pat is pat2
|
assert pat is pat2
|
||||||
|
|
||||||
def test_library_rename():
|
|
||||||
|
def test_library_rename() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
lib["old"] = Pattern()
|
lib["old"] = Pattern()
|
||||||
lib["parent"] = Pattern()
|
lib["parent"] = Pattern()
|
||||||
|
|
@ -84,7 +90,8 @@ def test_library_rename():
|
||||||
assert "new" in lib["parent"].refs
|
assert "new" in lib["parent"].refs
|
||||||
assert "old" not in lib["parent"].refs
|
assert "old" not in lib["parent"].refs
|
||||||
|
|
||||||
def test_library_subtree():
|
|
||||||
|
def test_library_subtree() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
lib["a"] = Pattern()
|
lib["a"] = Pattern()
|
||||||
lib["b"] = Pattern()
|
lib["b"] = Pattern()
|
||||||
|
|
@ -96,7 +103,8 @@ def test_library_subtree():
|
||||||
assert "b" in sub
|
assert "b" in sub
|
||||||
assert "c" not in sub
|
assert "c" not in sub
|
||||||
|
|
||||||
def test_library_get_name():
|
|
||||||
|
def test_library_get_name() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
lib["cell"] = Pattern()
|
lib["cell"] = Pattern()
|
||||||
|
|
||||||
|
|
@ -106,4 +114,3 @@ def test_library_get_name():
|
||||||
|
|
||||||
name2 = lib.get_name("other")
|
name2 = lib.get_name("other")
|
||||||
assert name2 == "other"
|
assert name2 == "other"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import pytest
|
|
||||||
import numpy
|
|
||||||
from numpy.testing import assert_equal
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import pytest
|
||||||
|
from numpy.testing import assert_equal
|
||||||
|
|
||||||
from ..pattern import Pattern
|
from ..pattern import Pattern
|
||||||
from ..library import Library
|
from ..library import Library
|
||||||
from ..file import oasis
|
from ..file import oasis
|
||||||
|
|
||||||
def test_oasis_roundtrip(tmp_path):
|
|
||||||
|
def test_oasis_roundtrip(tmp_path: Path) -> None:
|
||||||
# Skip if fatamorgana is not installed
|
# Skip if fatamorgana is not installed
|
||||||
pytest.importorskip("fatamorgana")
|
pytest.importorskip("fatamorgana")
|
||||||
|
|
||||||
|
|
@ -25,4 +25,3 @@ def test_oasis_roundtrip(tmp_path):
|
||||||
|
|
||||||
# Check bounds
|
# Check bounds
|
||||||
assert_equal(read_lib["cell1"].get_bounds(), [[0, 0], [10, 10]])
|
assert_equal(read_lib["cell1"].get_bounds(), [[0, 0], [10, 10]])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import pytest
|
|
||||||
import numpy
|
|
||||||
from numpy.testing import assert_equal
|
|
||||||
|
|
||||||
from ..utils.pack2d import maxrects_bssf, pack_patterns
|
from ..utils.pack2d import maxrects_bssf, pack_patterns
|
||||||
from ..library import Library
|
from ..library import Library
|
||||||
from ..pattern import Pattern
|
from ..pattern import Pattern
|
||||||
|
|
||||||
def test_maxrects_bssf_simple():
|
|
||||||
|
def test_maxrects_bssf_simple() -> None:
|
||||||
# Pack two 10x10 squares into one 20x10 container
|
# Pack two 10x10 squares into one 20x10 container
|
||||||
rects = [[10, 10], [10, 10]]
|
rects = [[10, 10], [10, 10]]
|
||||||
containers = [[0, 0, 20, 10]]
|
containers = [[0, 0, 20, 10]]
|
||||||
|
|
@ -15,9 +12,10 @@ def test_maxrects_bssf_simple():
|
||||||
|
|
||||||
assert not rejects
|
assert not rejects
|
||||||
# They should be at (0,0) and (10,0)
|
# They should be at (0,0) and (10,0)
|
||||||
assert set([tuple(l) for l in locs]) == {(0.0, 0.0), (10.0, 0.0)}
|
assert {tuple(loc) for loc in locs} == {(0.0, 0.0), (10.0, 0.0)}
|
||||||
|
|
||||||
def test_maxrects_bssf_reject():
|
|
||||||
|
def test_maxrects_bssf_reject() -> None:
|
||||||
# Try to pack a too-large rectangle
|
# Try to pack a too-large rectangle
|
||||||
rects = [[10, 10], [30, 30]]
|
rects = [[10, 10], [30, 30]]
|
||||||
containers = [[0, 0, 20, 20]]
|
containers = [[0, 0, 20, 20]]
|
||||||
|
|
@ -26,7 +24,8 @@ def test_maxrects_bssf_reject():
|
||||||
assert 1 in rejects # Second rect rejected
|
assert 1 in rejects # Second rect rejected
|
||||||
assert 0 not in rejects
|
assert 0 not in rejects
|
||||||
|
|
||||||
def test_pack_patterns():
|
|
||||||
|
def test_pack_patterns() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
p1 = Pattern()
|
p1 = Pattern()
|
||||||
p1.polygon((1, 0), vertices=[[0, 0], [10, 0], [10, 10], [0, 10]])
|
p1.polygon((1, 0), vertices=[[0, 0], [10, 0], [10, 10], [0, 10]])
|
||||||
|
|
@ -50,4 +49,3 @@ def test_pack_patterns():
|
||||||
# p1 size 10x10, effectively 12x12
|
# p1 size 10x10, effectively 12x12
|
||||||
# p2 size 5x5, effectively 7x7
|
# p2 size 5x5, effectively 7x7
|
||||||
# Both should fit in 20x20
|
# Both should fit in 20x20
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
import pytest
|
from numpy.testing import assert_equal
|
||||||
import numpy
|
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
|
||||||
from numpy import pi
|
|
||||||
|
|
||||||
from ..shapes import Path
|
from ..shapes import Path
|
||||||
|
|
||||||
def test_path_init():
|
|
||||||
|
def test_path_init() -> None:
|
||||||
p = Path(vertices=[[0, 0], [10, 0]], width=2, cap=Path.Cap.Flush)
|
p = Path(vertices=[[0, 0], [10, 0]], width=2, cap=Path.Cap.Flush)
|
||||||
assert_equal(p.vertices, [[0, 0], [10, 0]])
|
assert_equal(p.vertices, [[0, 0], [10, 0]])
|
||||||
assert p.width == 2
|
assert p.width == 2
|
||||||
assert p.cap == Path.Cap.Flush
|
assert p.cap == Path.Cap.Flush
|
||||||
|
|
||||||
def test_path_to_polygons_flush():
|
|
||||||
|
def test_path_to_polygons_flush() -> None:
|
||||||
p = Path(vertices=[[0, 0], [10, 0]], width=2, cap=Path.Cap.Flush)
|
p = Path(vertices=[[0, 0], [10, 0]], width=2, cap=Path.Cap.Flush)
|
||||||
polys = p.to_polygons()
|
polys = p.to_polygons()
|
||||||
assert len(polys) == 1
|
assert len(polys) == 1
|
||||||
|
|
@ -19,7 +18,8 @@ def test_path_to_polygons_flush():
|
||||||
bounds = polys[0].get_bounds_single()
|
bounds = polys[0].get_bounds_single()
|
||||||
assert_equal(bounds, [[0, -1], [10, 1]])
|
assert_equal(bounds, [[0, -1], [10, 1]])
|
||||||
|
|
||||||
def test_path_to_polygons_square():
|
|
||||||
|
def test_path_to_polygons_square() -> None:
|
||||||
p = Path(vertices=[[0, 0], [10, 0]], width=2, cap=Path.Cap.Square)
|
p = Path(vertices=[[0, 0], [10, 0]], width=2, cap=Path.Cap.Square)
|
||||||
polys = p.to_polygons()
|
polys = p.to_polygons()
|
||||||
assert len(polys) == 1
|
assert len(polys) == 1
|
||||||
|
|
@ -28,7 +28,8 @@ def test_path_to_polygons_square():
|
||||||
bounds = polys[0].get_bounds_single()
|
bounds = polys[0].get_bounds_single()
|
||||||
assert_equal(bounds, [[-1, -1], [11, 1]])
|
assert_equal(bounds, [[-1, -1], [11, 1]])
|
||||||
|
|
||||||
def test_path_to_polygons_circle():
|
|
||||||
|
def test_path_to_polygons_circle() -> None:
|
||||||
p = Path(vertices=[[0, 0], [10, 0]], width=2, cap=Path.Cap.Circle)
|
p = Path(vertices=[[0, 0], [10, 0]], width=2, cap=Path.Cap.Circle)
|
||||||
polys = p.to_polygons(num_vertices=32)
|
polys = p.to_polygons(num_vertices=32)
|
||||||
# Path.to_polygons for Circle cap returns 1 polygon for the path + polygons for the caps
|
# Path.to_polygons for Circle cap returns 1 polygon for the path + polygons for the caps
|
||||||
|
|
@ -39,7 +40,8 @@ def test_path_to_polygons_circle():
|
||||||
bounds = p.get_bounds_single()
|
bounds = p.get_bounds_single()
|
||||||
assert_equal(bounds, [[-1, -1], [11, 1]])
|
assert_equal(bounds, [[-1, -1], [11, 1]])
|
||||||
|
|
||||||
def test_path_custom_cap():
|
|
||||||
|
def test_path_custom_cap() -> None:
|
||||||
p = Path(vertices=[[0, 0], [10, 0]], width=2, cap=Path.Cap.SquareCustom, cap_extensions=(5, 10))
|
p = Path(vertices=[[0, 0], [10, 0]], width=2, cap=Path.Cap.SquareCustom, cap_extensions=(5, 10))
|
||||||
polys = p.to_polygons()
|
polys = p.to_polygons()
|
||||||
assert len(polys) == 1
|
assert len(polys) == 1
|
||||||
|
|
@ -48,7 +50,8 @@ def test_path_custom_cap():
|
||||||
bounds = polys[0].get_bounds_single()
|
bounds = polys[0].get_bounds_single()
|
||||||
assert_equal(bounds, [[-5, -1], [20, 1]])
|
assert_equal(bounds, [[-5, -1], [20, 1]])
|
||||||
|
|
||||||
def test_path_bend():
|
|
||||||
|
def test_path_bend() -> None:
|
||||||
# L-shaped path
|
# L-shaped path
|
||||||
p = Path(vertices=[[0, 0], [10, 0], [10, 10]], width=2)
|
p = Path(vertices=[[0, 0], [10, 0], [10, 10]], width=2)
|
||||||
polys = p.to_polygons()
|
polys = p.to_polygons()
|
||||||
|
|
@ -64,14 +67,15 @@ def test_path_bend():
|
||||||
# So bounds should be x: [0, 11], y: [-1, 10]
|
# So bounds should be x: [0, 11], y: [-1, 10]
|
||||||
assert_equal(bounds, [[0, -1], [11, 10]])
|
assert_equal(bounds, [[0, -1], [11, 10]])
|
||||||
|
|
||||||
def test_path_mirror():
|
|
||||||
|
def test_path_mirror() -> None:
|
||||||
p = Path(vertices=[[10, 5], [20, 10]], width=2)
|
p = Path(vertices=[[10, 5], [20, 10]], width=2)
|
||||||
p.mirror(0) # Mirror across x axis (y -> -y)
|
p.mirror(0) # Mirror across x axis (y -> -y)
|
||||||
assert_equal(p.vertices, [[10, -5], [20, -10]])
|
assert_equal(p.vertices, [[10, -5], [20, -10]])
|
||||||
|
|
||||||
def test_path_scale():
|
|
||||||
|
def test_path_scale() -> None:
|
||||||
p = Path(vertices=[[0, 0], [10, 0]], width=2)
|
p = Path(vertices=[[0, 0], [10, 0]], width=2)
|
||||||
p.scale_by(2)
|
p.scale_by(2)
|
||||||
assert_equal(p.vertices, [[0, 0], [20, 0]])
|
assert_equal(p.vertices, [[0, 0], [20, 0]])
|
||||||
assert p.width == 4
|
assert p.width == 4
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
import pytest
|
import pytest
|
||||||
import numpy
|
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
from numpy.testing import assert_equal, assert_allclose
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from ..builder import Pather
|
from ..builder import Pather
|
||||||
from ..builder.tools import PathTool
|
from ..builder.tools import PathTool
|
||||||
from ..library import Library
|
from ..library import Library
|
||||||
from ..pattern import Pattern
|
|
||||||
from ..ports import Port
|
from ..ports import Port
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def pather_setup():
|
def pather_setup() -> tuple[Pather, PathTool, Library]:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
# Simple PathTool: 2um width on layer (1,0)
|
# Simple PathTool: 2um width on layer (1,0)
|
||||||
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
||||||
|
|
@ -21,7 +20,8 @@ def pather_setup():
|
||||||
p.ports["start"] = Port((0, 0), pi / 2, ptype="wire")
|
p.ports["start"] = Port((0, 0), pi / 2, ptype="wire")
|
||||||
return p, tool, lib
|
return p, tool, lib
|
||||||
|
|
||||||
def test_pather_straight(pather_setup):
|
|
||||||
|
def test_pather_straight(pather_setup: tuple[Pather, PathTool, Library]) -> None:
|
||||||
p, tool, lib = pather_setup
|
p, tool, lib = pather_setup
|
||||||
# Route 10um "forward"
|
# Route 10um "forward"
|
||||||
p.path("start", ccw=None, length=10)
|
p.path("start", ccw=None, length=10)
|
||||||
|
|
@ -30,7 +30,8 @@ def test_pather_straight(pather_setup):
|
||||||
assert_allclose(p.ports["start"].offset, [0, -10], atol=1e-10)
|
assert_allclose(p.ports["start"].offset, [0, -10], atol=1e-10)
|
||||||
assert_allclose(p.ports["start"].rotation, pi / 2, atol=1e-10)
|
assert_allclose(p.ports["start"].rotation, pi / 2, atol=1e-10)
|
||||||
|
|
||||||
def test_pather_bend(pather_setup):
|
|
||||||
|
def test_pather_bend(pather_setup: tuple[Pather, PathTool, Library]) -> None:
|
||||||
p, tool, lib = pather_setup
|
p, tool, lib = pather_setup
|
||||||
# Start (0,0) rot pi/2 (North).
|
# Start (0,0) rot pi/2 (North).
|
||||||
# Path 10um "forward" (South), then turn Clockwise (ccw=False).
|
# Path 10um "forward" (South), then turn Clockwise (ccw=False).
|
||||||
|
|
@ -47,14 +48,16 @@ def test_pather_bend(pather_setup):
|
||||||
# Actual behavior results in 0 (East) - apparently rotation is flipped.
|
# Actual behavior results in 0 (East) - apparently rotation is flipped.
|
||||||
assert_allclose(p.ports["start"].rotation, 0, atol=1e-10)
|
assert_allclose(p.ports["start"].rotation, 0, atol=1e-10)
|
||||||
|
|
||||||
def test_pather_path_to(pather_setup):
|
|
||||||
|
def test_pather_path_to(pather_setup: tuple[Pather, PathTool, Library]) -> None:
|
||||||
p, tool, lib = pather_setup
|
p, tool, lib = pather_setup
|
||||||
# start at (0,0) rot pi/2 (North)
|
# start at (0,0) rot pi/2 (North)
|
||||||
# path "forward" (South) to y=-50
|
# path "forward" (South) to y=-50
|
||||||
p.path_to("start", ccw=None, y=-50)
|
p.path_to("start", ccw=None, y=-50)
|
||||||
assert_equal(p.ports["start"].offset, [0, -50])
|
assert_equal(p.ports["start"].offset, [0, -50])
|
||||||
|
|
||||||
def test_pather_mpath(pather_setup):
|
|
||||||
|
def test_pather_mpath(pather_setup: tuple[Pather, PathTool, Library]) -> None:
|
||||||
p, tool, lib = pather_setup
|
p, tool, lib = pather_setup
|
||||||
p.ports["A"] = Port((0, 0), pi / 2, ptype="wire")
|
p.ports["A"] = Port((0, 0), pi / 2, ptype="wire")
|
||||||
p.ports["B"] = Port((10, 0), pi / 2, ptype="wire")
|
p.ports["B"] = Port((10, 0), pi / 2, ptype="wire")
|
||||||
|
|
@ -64,7 +67,8 @@ def test_pather_mpath(pather_setup):
|
||||||
assert_equal(p.ports["A"].offset, [0, -20])
|
assert_equal(p.ports["A"].offset, [0, -20])
|
||||||
assert_equal(p.ports["B"].offset, [10, -20])
|
assert_equal(p.ports["B"].offset, [10, -20])
|
||||||
|
|
||||||
def test_pather_at_chaining(pather_setup):
|
|
||||||
|
def test_pather_at_chaining(pather_setup: tuple[Pather, PathTool, Library]) -> None:
|
||||||
p, tool, lib = pather_setup
|
p, tool, lib = pather_setup
|
||||||
# Fluent API test
|
# Fluent API test
|
||||||
p.at("start").path(ccw=None, length=10).path(ccw=True, length=10)
|
p.at("start").path(ccw=None, length=10).path(ccw=True, length=10)
|
||||||
|
|
@ -77,4 +81,3 @@ def test_pather_at_chaining(pather_setup):
|
||||||
# pi/2 (North) + CCW (90 deg) -> 0 (East)?
|
# pi/2 (North) + CCW (90 deg) -> 0 (East)?
|
||||||
# Actual behavior results in pi (West).
|
# Actual behavior results in pi (West).
|
||||||
assert_allclose(p.ports["start"].rotation, pi, atol=1e-10)
|
assert_allclose(p.ports["start"].rotation, pi, atol=1e-10)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
import pytest
|
|
||||||
import numpy
|
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
from numpy.testing import assert_equal, assert_allclose
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from ..pattern import Pattern
|
from ..pattern import Pattern
|
||||||
from ..shapes import Polygon, Circle
|
from ..shapes import Polygon
|
||||||
from ..ref import Ref
|
from ..ref import Ref
|
||||||
from ..ports import Port
|
from ..ports import Port
|
||||||
from ..label import Label
|
from ..label import Label
|
||||||
|
|
||||||
def test_pattern_init():
|
|
||||||
|
def test_pattern_init() -> None:
|
||||||
pat = Pattern()
|
pat = Pattern()
|
||||||
assert pat.is_empty()
|
assert pat.is_empty()
|
||||||
assert not pat.has_shapes()
|
assert not pat.has_shapes()
|
||||||
|
|
@ -17,18 +16,14 @@ def test_pattern_init():
|
||||||
assert not pat.has_labels()
|
assert not pat.has_labels()
|
||||||
assert not pat.has_ports()
|
assert not pat.has_ports()
|
||||||
|
|
||||||
def test_pattern_with_elements():
|
|
||||||
|
def test_pattern_with_elements() -> None:
|
||||||
poly = Polygon.square(10)
|
poly = Polygon.square(10)
|
||||||
label = Label("test", offset=(5, 5))
|
label = Label("test", offset=(5, 5))
|
||||||
ref = Ref(offset=(100, 100))
|
ref = Ref(offset=(100, 100))
|
||||||
port = Port((0, 0), 0)
|
port = Port((0, 0), 0)
|
||||||
|
|
||||||
pat = Pattern(
|
pat = Pattern(shapes={(1, 0): [poly]}, labels={(1, 2): [label]}, refs={"sub": [ref]}, ports={"P1": port})
|
||||||
shapes={(1, 0): [poly]},
|
|
||||||
labels={(1, 2): [label]},
|
|
||||||
refs={"sub": [ref]},
|
|
||||||
ports={"P1": port}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert pat.has_shapes()
|
assert pat.has_shapes()
|
||||||
assert pat.has_labels()
|
assert pat.has_labels()
|
||||||
|
|
@ -40,7 +35,8 @@ def test_pattern_with_elements():
|
||||||
assert pat.refs["sub"] == [ref]
|
assert pat.refs["sub"] == [ref]
|
||||||
assert pat.ports["P1"] == port
|
assert pat.ports["P1"] == port
|
||||||
|
|
||||||
def test_pattern_append():
|
|
||||||
|
def test_pattern_append() -> None:
|
||||||
pat1 = Pattern()
|
pat1 = Pattern()
|
||||||
pat1.polygon((1, 0), vertices=[[0, 0], [1, 0], [1, 1]])
|
pat1.polygon((1, 0), vertices=[[0, 0], [1, 0], [1, 1]])
|
||||||
|
|
||||||
|
|
@ -51,7 +47,8 @@ def test_pattern_append():
|
||||||
assert len(pat1.shapes[(1, 0)]) == 1
|
assert len(pat1.shapes[(1, 0)]) == 1
|
||||||
assert len(pat1.shapes[(2, 0)]) == 1
|
assert len(pat1.shapes[(2, 0)]) == 1
|
||||||
|
|
||||||
def test_pattern_translate():
|
|
||||||
|
def test_pattern_translate() -> None:
|
||||||
pat = Pattern()
|
pat = Pattern()
|
||||||
pat.polygon((1, 0), vertices=[[0, 0], [1, 0], [1, 1]])
|
pat.polygon((1, 0), vertices=[[0, 0], [1, 0], [1, 1]])
|
||||||
pat.ports["P1"] = Port((5, 5), 0)
|
pat.ports["P1"] = Port((5, 5), 0)
|
||||||
|
|
@ -62,7 +59,8 @@ def test_pattern_translate():
|
||||||
assert_equal(pat.shapes[(1, 0)][0].vertices[0], [10, 20])
|
assert_equal(pat.shapes[(1, 0)][0].vertices[0], [10, 20])
|
||||||
assert_equal(pat.ports["P1"].offset, [15, 25])
|
assert_equal(pat.ports["P1"].offset, [15, 25])
|
||||||
|
|
||||||
def test_pattern_scale():
|
|
||||||
|
def test_pattern_scale() -> None:
|
||||||
pat = Pattern()
|
pat = Pattern()
|
||||||
# Polygon.rect sets an offset in its constructor which is immediately translated into vertices
|
# Polygon.rect sets an offset in its constructor which is immediately translated into vertices
|
||||||
pat.rect((1, 0), xmin=0, xmax=1, ymin=0, ymax=1)
|
pat.rect((1, 0), xmin=0, xmax=1, ymin=0, ymax=1)
|
||||||
|
|
@ -71,7 +69,8 @@ def test_pattern_scale():
|
||||||
# Vertices should be scaled
|
# Vertices should be scaled
|
||||||
assert_equal(pat.shapes[(1, 0)][0].vertices, [[0, 0], [0, 2], [2, 2], [2, 0]])
|
assert_equal(pat.shapes[(1, 0)][0].vertices, [[0, 0], [0, 2], [2, 2], [2, 0]])
|
||||||
|
|
||||||
def test_pattern_rotate():
|
|
||||||
|
def test_pattern_rotate() -> None:
|
||||||
pat = Pattern()
|
pat = Pattern()
|
||||||
pat.polygon((1, 0), vertices=[[10, 0], [11, 0], [10, 1]])
|
pat.polygon((1, 0), vertices=[[10, 0], [11, 0], [10, 1]])
|
||||||
# Rotate 90 degrees CCW around (0,0)
|
# Rotate 90 degrees CCW around (0,0)
|
||||||
|
|
@ -80,7 +79,8 @@ def test_pattern_rotate():
|
||||||
# [10, 0] rotated 90 deg around (0,0) is [0, 10]
|
# [10, 0] rotated 90 deg around (0,0) is [0, 10]
|
||||||
assert_allclose(pat.shapes[(1, 0)][0].vertices[0], [0, 10], atol=1e-10)
|
assert_allclose(pat.shapes[(1, 0)][0].vertices[0], [0, 10], atol=1e-10)
|
||||||
|
|
||||||
def test_pattern_mirror():
|
|
||||||
|
def test_pattern_mirror() -> None:
|
||||||
pat = Pattern()
|
pat = Pattern()
|
||||||
pat.polygon((1, 0), vertices=[[10, 5], [11, 5], [10, 6]])
|
pat.polygon((1, 0), vertices=[[10, 5], [11, 5], [10, 6]])
|
||||||
# Mirror across X axis (y -> -y)
|
# Mirror across X axis (y -> -y)
|
||||||
|
|
@ -88,7 +88,8 @@ def test_pattern_mirror():
|
||||||
|
|
||||||
assert_equal(pat.shapes[(1, 0)][0].vertices[0], [10, -5])
|
assert_equal(pat.shapes[(1, 0)][0].vertices[0], [10, -5])
|
||||||
|
|
||||||
def test_pattern_get_bounds():
|
|
||||||
|
def test_pattern_get_bounds() -> None:
|
||||||
pat = Pattern()
|
pat = Pattern()
|
||||||
pat.polygon((1, 0), vertices=[[0, 0], [10, 0], [10, 10]])
|
pat.polygon((1, 0), vertices=[[0, 0], [10, 0], [10, 10]])
|
||||||
pat.polygon((1, 0), vertices=[[-5, -5], [5, -5], [5, 5]])
|
pat.polygon((1, 0), vertices=[[-5, -5], [5, -5], [5, 5]])
|
||||||
|
|
@ -96,7 +97,8 @@ def test_pattern_get_bounds():
|
||||||
bounds = pat.get_bounds()
|
bounds = pat.get_bounds()
|
||||||
assert_equal(bounds, [[-5, -5], [10, 10]])
|
assert_equal(bounds, [[-5, -5], [10, 10]])
|
||||||
|
|
||||||
def test_pattern_interface():
|
|
||||||
|
def test_pattern_interface() -> None:
|
||||||
source = Pattern()
|
source = Pattern()
|
||||||
source.ports["A"] = Port((10, 20), 0, ptype="test")
|
source.ports["A"] = Port((10, 20), 0, ptype="test")
|
||||||
|
|
||||||
|
|
@ -108,4 +110,3 @@ def test_pattern_interface():
|
||||||
assert_allclose(iface.ports["out_A"].rotation, 0, atol=1e-10)
|
assert_allclose(iface.ports["out_A"].rotation, 0, atol=1e-10)
|
||||||
assert iface.ports["in_A"].ptype == "test"
|
assert iface.ports["in_A"].ptype == "test"
|
||||||
assert iface.ports["out_A"].ptype == "test"
|
assert iface.ports["out_A"].ptype == "test"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import pytest
|
import pytest
|
||||||
import numpy
|
import numpy
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
from numpy.testing import assert_equal
|
||||||
|
|
||||||
|
|
||||||
from ..shapes import Polygon
|
from ..shapes import Polygon
|
||||||
|
|
@ -9,29 +9,36 @@ from ..error import PatternError
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def polygon():
|
def polygon() -> Polygon:
|
||||||
return Polygon([[0, 0], [1, 0], [1, 1], [0, 1]])
|
return Polygon([[0, 0], [1, 0], [1, 1], [0, 1]])
|
||||||
|
|
||||||
def test_vertices(polygon) -> None:
|
|
||||||
|
def test_vertices(polygon: Polygon) -> None:
|
||||||
assert_equal(polygon.vertices, [[0, 0], [1, 0], [1, 1], [0, 1]])
|
assert_equal(polygon.vertices, [[0, 0], [1, 0], [1, 1], [0, 1]])
|
||||||
|
|
||||||
def test_xs(polygon) -> None:
|
|
||||||
|
def test_xs(polygon: Polygon) -> None:
|
||||||
assert_equal(polygon.xs, [0, 1, 1, 0])
|
assert_equal(polygon.xs, [0, 1, 1, 0])
|
||||||
|
|
||||||
def test_ys(polygon) -> None:
|
|
||||||
|
def test_ys(polygon: Polygon) -> None:
|
||||||
assert_equal(polygon.ys, [0, 0, 1, 1])
|
assert_equal(polygon.ys, [0, 0, 1, 1])
|
||||||
|
|
||||||
def test_offset(polygon) -> None:
|
|
||||||
|
def test_offset(polygon: Polygon) -> None:
|
||||||
assert_equal(polygon.offset, [0, 0])
|
assert_equal(polygon.offset, [0, 0])
|
||||||
|
|
||||||
|
|
||||||
def test_square() -> None:
|
def test_square() -> None:
|
||||||
square = Polygon.square(1)
|
square = Polygon.square(1)
|
||||||
assert_equal(square.vertices, [[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]])
|
assert_equal(square.vertices, [[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]])
|
||||||
|
|
||||||
|
|
||||||
def test_rectangle() -> None:
|
def test_rectangle() -> None:
|
||||||
rectangle = Polygon.rectangle(1, 2)
|
rectangle = Polygon.rectangle(1, 2)
|
||||||
assert_equal(rectangle.vertices, [[-0.5, -1], [-0.5, 1], [0.5, 1], [0.5, -1]])
|
assert_equal(rectangle.vertices, [[-0.5, -1], [-0.5, 1], [0.5, 1], [0.5, -1]])
|
||||||
|
|
||||||
|
|
||||||
def test_rect() -> None:
|
def test_rect() -> None:
|
||||||
rect1 = Polygon.rect(xmin=0, xmax=1, ymin=-1, ymax=1)
|
rect1 = Polygon.rect(xmin=0, xmax=1, ymin=-1, ymax=1)
|
||||||
assert_equal(rect1.vertices, [[0, -1], [0, 1], [1, 1], [1, -1]])
|
assert_equal(rect1.vertices, [[0, -1], [0, 1], [1, 1], [1, -1]])
|
||||||
|
|
@ -70,41 +77,49 @@ def test_octagon() -> None:
|
||||||
side_len = numpy.sqrt((diff * diff).sum(axis=1))
|
side_len = numpy.sqrt((diff * diff).sum(axis=1))
|
||||||
assert numpy.allclose(side_len, 1)
|
assert numpy.allclose(side_len, 1)
|
||||||
|
|
||||||
def test_to_polygons(polygon) -> None:
|
|
||||||
|
def test_to_polygons(polygon: Polygon) -> None:
|
||||||
assert polygon.to_polygons() == [polygon]
|
assert polygon.to_polygons() == [polygon]
|
||||||
|
|
||||||
def test_get_bounds_single(polygon) -> None:
|
|
||||||
|
def test_get_bounds_single(polygon: Polygon) -> None:
|
||||||
assert_equal(polygon.get_bounds_single(), [[0, 0], [1, 1]])
|
assert_equal(polygon.get_bounds_single(), [[0, 0], [1, 1]])
|
||||||
|
|
||||||
def test_rotate(polygon) -> None:
|
|
||||||
|
def test_rotate(polygon: Polygon) -> None:
|
||||||
rotated_polygon = polygon.rotate(R90)
|
rotated_polygon = polygon.rotate(R90)
|
||||||
assert_equal(rotated_polygon.vertices, [[0, 0], [0, 1], [-1, 1], [-1, 0]])
|
assert_equal(rotated_polygon.vertices, [[0, 0], [0, 1], [-1, 1], [-1, 0]])
|
||||||
|
|
||||||
def test_mirror(polygon) -> None:
|
|
||||||
|
def test_mirror(polygon: Polygon) -> None:
|
||||||
mirrored_by_y = polygon.deepcopy().mirror(1)
|
mirrored_by_y = polygon.deepcopy().mirror(1)
|
||||||
assert_equal(mirrored_by_y.vertices, [[0, 0], [-1, 0], [-1, 1], [0, 1]])
|
assert_equal(mirrored_by_y.vertices, [[0, 0], [-1, 0], [-1, 1], [0, 1]])
|
||||||
print(polygon.vertices)
|
print(polygon.vertices)
|
||||||
mirrored_by_x = polygon.deepcopy().mirror(0)
|
mirrored_by_x = polygon.deepcopy().mirror(0)
|
||||||
assert_equal(mirrored_by_x.vertices, [[0, 0], [1, 0], [1, -1], [0, -1]])
|
assert_equal(mirrored_by_x.vertices, [[0, 0], [1, 0], [1, -1], [0, -1]])
|
||||||
|
|
||||||
def test_scale_by(polygon) -> None:
|
|
||||||
|
def test_scale_by(polygon: Polygon) -> None:
|
||||||
scaled_polygon = polygon.scale_by(2)
|
scaled_polygon = polygon.scale_by(2)
|
||||||
assert_equal(scaled_polygon.vertices, [[0, 0], [2, 0], [2, 2], [0, 2]])
|
assert_equal(scaled_polygon.vertices, [[0, 0], [2, 0], [2, 2], [0, 2]])
|
||||||
|
|
||||||
def test_clean_vertices(polygon) -> None:
|
|
||||||
|
def test_clean_vertices(polygon: Polygon) -> None:
|
||||||
polygon = Polygon([[0, 0], [1, 1], [2, 2], [2, 2], [2, -4], [2, 0], [0, 0]]).clean_vertices()
|
polygon = Polygon([[0, 0], [1, 1], [2, 2], [2, 2], [2, -4], [2, 0], [0, 0]]).clean_vertices()
|
||||||
assert_equal(polygon.vertices, [[0, 0], [2, 2], [2, 0]])
|
assert_equal(polygon.vertices, [[0, 0], [2, 2], [2, 0]])
|
||||||
|
|
||||||
|
|
||||||
def test_remove_duplicate_vertices() -> None:
|
def test_remove_duplicate_vertices() -> None:
|
||||||
polygon = Polygon([[0, 0], [1, 1], [2, 2], [2, 2], [2, 0], [0, 0]]).remove_duplicate_vertices()
|
polygon = Polygon([[0, 0], [1, 1], [2, 2], [2, 2], [2, 0], [0, 0]]).remove_duplicate_vertices()
|
||||||
assert_equal(polygon.vertices, [[0, 0], [1, 1], [2, 2], [2, 0]])
|
assert_equal(polygon.vertices, [[0, 0], [1, 1], [2, 2], [2, 0]])
|
||||||
|
|
||||||
|
|
||||||
def test_remove_colinear_vertices() -> None:
|
def test_remove_colinear_vertices() -> None:
|
||||||
polygon = Polygon([[0, 0], [1, 1], [2, 2], [2, 2], [2, 0], [0, 0]]).remove_colinear_vertices()
|
polygon = Polygon([[0, 0], [1, 1], [2, 2], [2, 2], [2, 0], [0, 0]]).remove_colinear_vertices()
|
||||||
assert_equal(polygon.vertices, [[0, 0], [2, 2], [2, 0]])
|
assert_equal(polygon.vertices, [[0, 0], [2, 2], [2, 0]])
|
||||||
|
|
||||||
def test_vertices_dtype():
|
|
||||||
|
def test_vertices_dtype() -> None:
|
||||||
polygon = Polygon(numpy.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]], dtype=numpy.int32))
|
polygon = Polygon(numpy.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]], dtype=numpy.int32))
|
||||||
polygon.scale_by(0.5)
|
polygon.scale_by(0.5)
|
||||||
assert_equal(polygon.vertices, [[0, 0], [0.5, 0], [0.5, 0.5], [0, 0.5], [0, 0]])
|
assert_equal(polygon.vertices, [[0, 0], [0.5, 0], [0.5, 0.5], [0, 0.5], [0, 0]])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
import pytest
|
import pytest
|
||||||
import numpy
|
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
from numpy.testing import assert_equal, assert_allclose
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from ..ports import Port, PortList
|
from ..ports import Port, PortList
|
||||||
from ..error import PortError
|
from ..error import PortError
|
||||||
|
|
||||||
def test_port_init():
|
|
||||||
|
def test_port_init() -> None:
|
||||||
p = Port(offset=(10, 20), rotation=pi / 2, ptype="test")
|
p = Port(offset=(10, 20), rotation=pi / 2, ptype="test")
|
||||||
assert_equal(p.offset, [10, 20])
|
assert_equal(p.offset, [10, 20])
|
||||||
assert p.rotation == pi / 2
|
assert p.rotation == pi / 2
|
||||||
assert p.ptype == "test"
|
assert p.ptype == "test"
|
||||||
|
|
||||||
def test_port_transform():
|
|
||||||
|
def test_port_transform() -> None:
|
||||||
p = Port(offset=(10, 0), rotation=0)
|
p = Port(offset=(10, 0), rotation=0)
|
||||||
p.rotate_around((0, 0), pi / 2)
|
p.rotate_around((0, 0), pi / 2)
|
||||||
assert_allclose(p.offset, [0, 10], atol=1e-10)
|
assert_allclose(p.offset, [0, 10], atol=1e-10)
|
||||||
|
|
@ -23,14 +24,16 @@ def test_port_transform():
|
||||||
# rotation was pi/2 (90 deg), mirror across x (0 deg) -> -pi/2 == 3pi/2
|
# rotation was pi/2 (90 deg), mirror across x (0 deg) -> -pi/2 == 3pi/2
|
||||||
assert_allclose(p.rotation, 3 * pi / 2, atol=1e-10)
|
assert_allclose(p.rotation, 3 * pi / 2, atol=1e-10)
|
||||||
|
|
||||||
def test_port_flip_across():
|
|
||||||
|
def test_port_flip_across() -> None:
|
||||||
p = Port(offset=(10, 0), rotation=0)
|
p = Port(offset=(10, 0), rotation=0)
|
||||||
p.flip_across(axis=1) # Mirror across x=0: flips x-offset
|
p.flip_across(axis=1) # Mirror across x=0: flips x-offset
|
||||||
assert_equal(p.offset, [-10, 0])
|
assert_equal(p.offset, [-10, 0])
|
||||||
# rotation was 0, mirrored(1) -> pi
|
# rotation was 0, mirrored(1) -> pi
|
||||||
assert_allclose(p.rotation, pi, atol=1e-10)
|
assert_allclose(p.rotation, pi, atol=1e-10)
|
||||||
|
|
||||||
def test_port_measure_travel():
|
|
||||||
|
def test_port_measure_travel() -> None:
|
||||||
p1 = Port((0, 0), 0)
|
p1 = Port((0, 0), 0)
|
||||||
p2 = Port((10, 5), pi) # Facing each other
|
p2 = Port((10, 5), pi) # Facing each other
|
||||||
|
|
||||||
|
|
@ -39,49 +42,60 @@ def test_port_measure_travel():
|
||||||
assert jog == 5
|
assert jog == 5
|
||||||
assert rotation == pi
|
assert rotation == pi
|
||||||
|
|
||||||
def test_port_list_rename():
|
|
||||||
|
def test_port_list_rename() -> None:
|
||||||
class MyPorts(PortList):
|
class MyPorts(PortList):
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self._ports = {"A": Port((0, 0), 0)}
|
self._ports = {"A": Port((0, 0), 0)}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ports(self): return self._ports
|
def ports(self) -> dict[str, Port]:
|
||||||
|
return self._ports
|
||||||
|
|
||||||
@ports.setter
|
@ports.setter
|
||||||
def ports(self, val): self._ports = val
|
def ports(self, val: dict[str, Port]) -> None:
|
||||||
|
self._ports = val
|
||||||
|
|
||||||
pl = MyPorts()
|
pl = MyPorts()
|
||||||
pl.rename_ports({"A": "B"})
|
pl.rename_ports({"A": "B"})
|
||||||
assert "A" not in pl.ports
|
assert "A" not in pl.ports
|
||||||
assert "B" in pl.ports
|
assert "B" in pl.ports
|
||||||
|
|
||||||
def test_port_list_plugged():
|
|
||||||
|
def test_port_list_plugged() -> None:
|
||||||
class MyPorts(PortList):
|
class MyPorts(PortList):
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self._ports = {
|
self._ports = {"A": Port((10, 10), 0), "B": Port((10, 10), pi)}
|
||||||
"A": Port((10, 10), 0),
|
|
||||||
"B": Port((10, 10), pi)
|
|
||||||
}
|
|
||||||
@property
|
@property
|
||||||
def ports(self): return self._ports
|
def ports(self) -> dict[str, Port]:
|
||||||
|
return self._ports
|
||||||
|
|
||||||
@ports.setter
|
@ports.setter
|
||||||
def ports(self, val): self._ports = val
|
def ports(self, val: dict[str, Port]) -> None:
|
||||||
|
self._ports = val
|
||||||
|
|
||||||
pl = MyPorts()
|
pl = MyPorts()
|
||||||
pl.plugged({"A": "B"})
|
pl.plugged({"A": "B"})
|
||||||
assert not pl.ports # Both should be removed
|
assert not pl.ports # Both should be removed
|
||||||
|
|
||||||
def test_port_list_plugged_mismatch():
|
|
||||||
|
def test_port_list_plugged_mismatch() -> None:
|
||||||
class MyPorts(PortList):
|
class MyPorts(PortList):
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self._ports = {
|
self._ports = {
|
||||||
"A": Port((10, 10), 0),
|
"A": Port((10, 10), 0),
|
||||||
"B": Port((11, 10), pi) # Offset mismatch
|
"B": Port((11, 10), pi), # Offset mismatch
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ports(self): return self._ports
|
def ports(self) -> dict[str, Port]:
|
||||||
|
return self._ports
|
||||||
|
|
||||||
@ports.setter
|
@ports.setter
|
||||||
def ports(self, val): self._ports = val
|
def ports(self, val: dict[str, Port]) -> None:
|
||||||
|
self._ports = val
|
||||||
|
|
||||||
pl = MyPorts()
|
pl = MyPorts()
|
||||||
with pytest.raises(PortError):
|
with pytest.raises(PortError):
|
||||||
pl.plugged({"A": "B"})
|
pl.plugged({"A": "B"})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import pytest
|
|
||||||
import numpy
|
import numpy
|
||||||
from numpy.testing import assert_allclose
|
from numpy.testing import assert_allclose
|
||||||
|
|
||||||
|
|
@ -7,7 +6,8 @@ from ..pattern import Pattern
|
||||||
from ..ports import Port
|
from ..ports import Port
|
||||||
from ..library import Library
|
from ..library import Library
|
||||||
|
|
||||||
def test_ports2data_roundtrip():
|
|
||||||
|
def test_ports2data_roundtrip() -> None:
|
||||||
pat = Pattern()
|
pat = Pattern()
|
||||||
pat.ports["P1"] = Port((10, 20), numpy.pi / 2, ptype="test")
|
pat.ports["P1"] = Port((10, 20), numpy.pi / 2, ptype="test")
|
||||||
|
|
||||||
|
|
@ -28,7 +28,8 @@ def test_ports2data_roundtrip():
|
||||||
assert_allclose(pat2.ports["P1"].rotation, numpy.pi / 2, atol=1e-10)
|
assert_allclose(pat2.ports["P1"].rotation, numpy.pi / 2, atol=1e-10)
|
||||||
assert pat2.ports["P1"].ptype == "test"
|
assert pat2.ports["P1"].ptype == "test"
|
||||||
|
|
||||||
def test_data_to_ports_hierarchical():
|
|
||||||
|
def test_data_to_ports_hierarchical() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
|
|
||||||
# Child has port data in labels
|
# Child has port data in labels
|
||||||
|
|
@ -52,4 +53,3 @@ def test_data_to_ports_hierarchical():
|
||||||
assert "A" in parent.ports
|
assert "A" in parent.ports
|
||||||
assert_allclose(parent.ports["A"].offset, [100, 105], atol=1e-10)
|
assert_allclose(parent.ports["A"].offset, [100, 105], atol=1e-10)
|
||||||
assert_allclose(parent.ports["A"].rotation, numpy.pi / 2, atol=1e-10)
|
assert_allclose(parent.ports["A"].rotation, numpy.pi / 2, atol=1e-10)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import pytest
|
|
||||||
import numpy
|
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
from numpy.testing import assert_equal, assert_allclose
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
|
|
@ -7,14 +5,16 @@ from ..pattern import Pattern
|
||||||
from ..ref import Ref
|
from ..ref import Ref
|
||||||
from ..repetition import Grid
|
from ..repetition import Grid
|
||||||
|
|
||||||
def test_ref_init():
|
|
||||||
|
def test_ref_init() -> None:
|
||||||
ref = Ref(offset=(10, 20), rotation=pi / 4, mirrored=True, scale=2.0)
|
ref = Ref(offset=(10, 20), rotation=pi / 4, mirrored=True, scale=2.0)
|
||||||
assert_equal(ref.offset, [10, 20])
|
assert_equal(ref.offset, [10, 20])
|
||||||
assert ref.rotation == pi / 4
|
assert ref.rotation == pi / 4
|
||||||
assert ref.mirrored is True
|
assert ref.mirrored is True
|
||||||
assert ref.scale == 2.0
|
assert ref.scale == 2.0
|
||||||
|
|
||||||
def test_ref_as_pattern():
|
|
||||||
|
def test_ref_as_pattern() -> None:
|
||||||
sub_pat = Pattern()
|
sub_pat = Pattern()
|
||||||
sub_pat.polygon((1, 0), vertices=[[0, 0], [1, 0], [0, 1]])
|
sub_pat.polygon((1, 0), vertices=[[0, 0], [1, 0], [0, 1]])
|
||||||
|
|
||||||
|
|
@ -30,7 +30,8 @@ def test_ref_as_pattern():
|
||||||
|
|
||||||
assert_allclose(shape.vertices, [[10, 10], [10, 12], [8, 10]], atol=1e-10)
|
assert_allclose(shape.vertices, [[10, 10], [10, 12], [8, 10]], atol=1e-10)
|
||||||
|
|
||||||
def test_ref_with_repetition():
|
|
||||||
|
def test_ref_with_repetition() -> None:
|
||||||
sub_pat = Pattern()
|
sub_pat = Pattern()
|
||||||
sub_pat.polygon((1, 0), vertices=[[0, 0], [1, 0], [0, 1]])
|
sub_pat.polygon((1, 0), vertices=[[0, 0], [1, 0], [0, 1]])
|
||||||
|
|
||||||
|
|
@ -44,7 +45,8 @@ def test_ref_with_repetition():
|
||||||
first_verts = sorted([tuple(s.vertices[0]) for s in repeated_pat.shapes[(1, 0)]])
|
first_verts = sorted([tuple(s.vertices[0]) for s in repeated_pat.shapes[(1, 0)]])
|
||||||
assert first_verts == [(0.0, 0.0), (0.0, 10.0), (10.0, 0.0), (10.0, 10.0)]
|
assert first_verts == [(0.0, 0.0), (0.0, 10.0), (10.0, 0.0), (10.0, 10.0)]
|
||||||
|
|
||||||
def test_ref_get_bounds():
|
|
||||||
|
def test_ref_get_bounds() -> None:
|
||||||
sub_pat = Pattern()
|
sub_pat = Pattern()
|
||||||
sub_pat.polygon((1, 0), vertices=[[0, 0], [5, 0], [0, 5]])
|
sub_pat.polygon((1, 0), vertices=[[0, 0], [5, 0], [0, 5]])
|
||||||
|
|
||||||
|
|
@ -55,7 +57,8 @@ def test_ref_get_bounds():
|
||||||
# translated [[10,10], [20,20]]
|
# translated [[10,10], [20,20]]
|
||||||
assert_equal(bounds, [[10, 10], [20, 20]])
|
assert_equal(bounds, [[10, 10], [20, 20]])
|
||||||
|
|
||||||
def test_ref_copy():
|
|
||||||
|
def test_ref_copy() -> None:
|
||||||
ref1 = Ref(offset=(1, 2), rotation=0.5, annotations={"a": [1]})
|
ref1 = Ref(offset=(1, 2), rotation=0.5, annotations={"a": [1]})
|
||||||
ref2 = ref1.copy()
|
ref2 = ref1.copy()
|
||||||
assert ref1 == ref2
|
assert ref1 == ref2
|
||||||
|
|
@ -63,4 +66,3 @@ def test_ref_copy():
|
||||||
|
|
||||||
ref2.offset[0] = 100
|
ref2.offset[0] = 100
|
||||||
assert ref1.offset[0] == 1
|
assert ref1.offset[0] == 1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import pytest
|
import pytest
|
||||||
import numpy
|
from numpy.testing import assert_allclose
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from ..builder import RenderPather
|
from ..builder import RenderPather
|
||||||
|
|
@ -8,15 +7,17 @@ from ..builder.tools import PathTool
|
||||||
from ..library import Library
|
from ..library import Library
|
||||||
from ..ports import Port
|
from ..ports import Port
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def rpather_setup():
|
def rpather_setup() -> tuple[RenderPather, PathTool, Library]:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
||||||
rp = RenderPather(lib, tools=tool)
|
rp = RenderPather(lib, tools=tool)
|
||||||
rp.ports["start"] = Port((0, 0), pi / 2, ptype="wire")
|
rp.ports["start"] = Port((0, 0), pi / 2, ptype="wire")
|
||||||
return rp, tool, lib
|
return rp, tool, lib
|
||||||
|
|
||||||
def test_renderpather_basic(rpather_setup):
|
|
||||||
|
def test_renderpather_basic(rpather_setup: tuple[RenderPather, PathTool, Library]) -> None:
|
||||||
rp, tool, lib = rpather_setup
|
rp, tool, lib = rpather_setup
|
||||||
# Plan two segments
|
# Plan two segments
|
||||||
rp.at("start").path(ccw=None, length=10).path(ccw=None, length=10)
|
rp.at("start").path(ccw=None, length=10).path(ccw=None, length=10)
|
||||||
|
|
@ -40,7 +41,8 @@ def test_renderpather_basic(rpather_setup):
|
||||||
assert len(path_shape.vertices) == 3
|
assert len(path_shape.vertices) == 3
|
||||||
assert_allclose(path_shape.vertices, [[0, 0], [0, -10], [0, -20]], atol=1e-10)
|
assert_allclose(path_shape.vertices, [[0, 0], [0, -10], [0, -20]], atol=1e-10)
|
||||||
|
|
||||||
def test_renderpather_bend(rpather_setup):
|
|
||||||
|
def test_renderpather_bend(rpather_setup: tuple[RenderPather, PathTool, Library]) -> None:
|
||||||
rp, tool, lib = rpather_setup
|
rp, tool, lib = rpather_setup
|
||||||
# Plan straight then bend
|
# Plan straight then bend
|
||||||
rp.at("start").path(ccw=None, length=10).path(ccw=False, length=10)
|
rp.at("start").path(ccw=None, length=10).path(ccw=False, length=10)
|
||||||
|
|
@ -58,7 +60,8 @@ def test_renderpather_bend(rpather_setup):
|
||||||
assert len(path_shape.vertices) == 4
|
assert len(path_shape.vertices) == 4
|
||||||
assert_allclose(path_shape.vertices, [[0, 0], [0, -10], [0, -20], [-1, -20]], atol=1e-10)
|
assert_allclose(path_shape.vertices, [[0, 0], [0, -10], [0, -20], [-1, -20]], atol=1e-10)
|
||||||
|
|
||||||
def test_renderpather_retool(rpather_setup):
|
|
||||||
|
def test_renderpather_retool(rpather_setup: tuple[RenderPather, PathTool, Library]) -> None:
|
||||||
rp, tool1, lib = rpather_setup
|
rp, tool1, lib = rpather_setup
|
||||||
tool2 = PathTool(layer=(2, 0), width=4, ptype="wire")
|
tool2 = PathTool(layer=(2, 0), width=4, ptype="wire")
|
||||||
|
|
||||||
|
|
@ -70,4 +73,3 @@ def test_renderpather_retool(rpather_setup):
|
||||||
# Different tools should cause different batches/shapes
|
# Different tools should cause different batches/shapes
|
||||||
assert len(rp.pattern.shapes[(1, 0)]) == 1
|
assert len(rp.pattern.shapes[(1, 0)]) == 1
|
||||||
assert len(rp.pattern.shapes[(2, 0)]) == 1
|
assert len(rp.pattern.shapes[(2, 0)]) == 1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,35 @@
|
||||||
import pytest
|
|
||||||
import numpy
|
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
from numpy.testing import assert_equal, assert_allclose
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from ..repetition import Grid, Arbitrary
|
from ..repetition import Grid, Arbitrary
|
||||||
|
|
||||||
def test_grid_displacements():
|
|
||||||
|
def test_grid_displacements() -> None:
|
||||||
# 2x2 grid
|
# 2x2 grid
|
||||||
grid = Grid(a_vector=(10, 0), b_vector=(0, 5), a_count=2, b_count=2)
|
grid = Grid(a_vector=(10, 0), b_vector=(0, 5), a_count=2, b_count=2)
|
||||||
disps = sorted([tuple(d) for d in grid.displacements])
|
disps = sorted([tuple(d) for d in grid.displacements])
|
||||||
assert disps == [(0.0, 0.0), (0.0, 5.0), (10.0, 0.0), (10.0, 5.0)]
|
assert disps == [(0.0, 0.0), (0.0, 5.0), (10.0, 0.0), (10.0, 5.0)]
|
||||||
|
|
||||||
def test_grid_1d():
|
|
||||||
|
def test_grid_1d() -> None:
|
||||||
grid = Grid(a_vector=(10, 0), a_count=3)
|
grid = Grid(a_vector=(10, 0), a_count=3)
|
||||||
disps = sorted([tuple(d) for d in grid.displacements])
|
disps = sorted([tuple(d) for d in grid.displacements])
|
||||||
assert disps == [(0.0, 0.0), (10.0, 0.0), (20.0, 0.0)]
|
assert disps == [(0.0, 0.0), (10.0, 0.0), (20.0, 0.0)]
|
||||||
|
|
||||||
def test_grid_rotate():
|
|
||||||
|
def test_grid_rotate() -> None:
|
||||||
grid = Grid(a_vector=(10, 0), a_count=2)
|
grid = Grid(a_vector=(10, 0), a_count=2)
|
||||||
grid.rotate(pi / 2)
|
grid.rotate(pi / 2)
|
||||||
assert_allclose(grid.a_vector, [0, 10], atol=1e-10)
|
assert_allclose(grid.a_vector, [0, 10], atol=1e-10)
|
||||||
|
|
||||||
def test_grid_get_bounds():
|
|
||||||
|
def test_grid_get_bounds() -> None:
|
||||||
grid = Grid(a_vector=(10, 0), b_vector=(0, 5), a_count=2, b_count=2)
|
grid = Grid(a_vector=(10, 0), b_vector=(0, 5), a_count=2, b_count=2)
|
||||||
bounds = grid.get_bounds()
|
bounds = grid.get_bounds()
|
||||||
assert_equal(bounds, [[0, 0], [10, 5]])
|
assert_equal(bounds, [[0, 0], [10, 5]])
|
||||||
|
|
||||||
def test_arbitrary_displacements():
|
|
||||||
|
def test_arbitrary_displacements() -> None:
|
||||||
pts = [[0, 0], [10, 20], [-5, 30]]
|
pts = [[0, 0], [10, 20], [-5, 30]]
|
||||||
arb = Arbitrary(pts)
|
arb = Arbitrary(pts)
|
||||||
# They should be sorted by displacements.setter
|
# They should be sorted by displacements.setter
|
||||||
|
|
@ -36,7 +39,8 @@ def test_arbitrary_displacements():
|
||||||
assert any((disps == [10, 20]).all(axis=1))
|
assert any((disps == [10, 20]).all(axis=1))
|
||||||
assert any((disps == [-5, 30]).all(axis=1))
|
assert any((disps == [-5, 30]).all(axis=1))
|
||||||
|
|
||||||
def test_arbitrary_transform():
|
|
||||||
|
def test_arbitrary_transform() -> None:
|
||||||
arb = Arbitrary([[10, 0]])
|
arb = Arbitrary([[10, 0]])
|
||||||
arb.rotate(pi / 2)
|
arb.rotate(pi / 2)
|
||||||
assert_allclose(arb.displacements, [[0, 10]], atol=1e-10)
|
assert_allclose(arb.displacements, [[0, 10]], atol=1e-10)
|
||||||
|
|
@ -45,4 +49,3 @@ def test_arbitrary_transform():
|
||||||
# self.displacements[:, 1 - axis] *= -1
|
# self.displacements[:, 1 - axis] *= -1
|
||||||
# if axis=0, 1-axis=1, so y *= -1
|
# if axis=0, 1-axis=1, so y *= -1
|
||||||
assert_allclose(arb.displacements, [[0, -10]], atol=1e-10)
|
assert_allclose(arb.displacements, [[0, -10]], atol=1e-10)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
|
from pathlib import Path
|
||||||
import pytest
|
import pytest
|
||||||
import numpy
|
import numpy
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
from numpy.testing import assert_equal, assert_allclose
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
import os
|
|
||||||
|
|
||||||
from ..shapes import Arc, Ellipse, Circle, Polygon, Path, Text, PolyCollection
|
from ..shapes import Arc, Ellipse, Circle, Polygon, Path as MPath, Text, PolyCollection
|
||||||
from ..error import PatternError
|
from ..error import PatternError
|
||||||
|
|
||||||
|
|
||||||
# 1. Text shape tests
|
# 1. Text shape tests
|
||||||
def test_text_to_polygons():
|
def test_text_to_polygons() -> None:
|
||||||
font_path = "/usr/share/fonts/truetype/dejavu/DejaVuMathTeXGyre.ttf"
|
font_path = "/usr/share/fonts/truetype/dejavu/DejaVuMathTeXGyre.ttf"
|
||||||
if not os.path.exists(font_path):
|
if not Path(font_path).exists():
|
||||||
pytest.skip("Font file not found")
|
pytest.skip("Font file not found")
|
||||||
|
|
||||||
t = Text("Hi", height=10, font_path=font_path)
|
t = Text("Hi", height=10, font_path=font_path)
|
||||||
|
|
@ -24,8 +25,9 @@ def test_text_to_polygons():
|
||||||
char_x_means = [p.vertices[:, 0].mean() for p in polys]
|
char_x_means = [p.vertices[:, 0].mean() for p in polys]
|
||||||
assert len(set(char_x_means)) >= 2
|
assert len(set(char_x_means)) >= 2
|
||||||
|
|
||||||
|
|
||||||
# 2. Manhattanization tests
|
# 2. Manhattanization tests
|
||||||
def test_manhattanize():
|
def test_manhattanize() -> None:
|
||||||
# Diamond shape
|
# Diamond shape
|
||||||
poly = Polygon([[0, 5], [5, 10], [10, 5], [5, 0]])
|
poly = Polygon([[0, 5], [5, 10], [10, 5], [5, 0]])
|
||||||
grid = numpy.arange(0, 11, 1)
|
grid = numpy.arange(0, 11, 1)
|
||||||
|
|
@ -38,8 +40,9 @@ def test_manhattanize():
|
||||||
# For each segment, either dx or dy must be zero
|
# For each segment, either dx or dy must be zero
|
||||||
assert numpy.all((dv[:, 0] == 0) | (dv[:, 1] == 0))
|
assert numpy.all((dv[:, 0] == 0) | (dv[:, 1] == 0))
|
||||||
|
|
||||||
|
|
||||||
# 3. Comparison and Sorting tests
|
# 3. Comparison and Sorting tests
|
||||||
def test_shape_comparisons():
|
def test_shape_comparisons() -> None:
|
||||||
c1 = Circle(radius=10)
|
c1 = Circle(radius=10)
|
||||||
c2 = Circle(radius=20)
|
c2 = Circle(radius=20)
|
||||||
assert c1 < c2
|
assert c1 < c2
|
||||||
|
|
@ -53,24 +56,27 @@ def test_shape_comparisons():
|
||||||
assert c1 < p1 or p1 < c1
|
assert c1 < p1 or p1 < c1
|
||||||
assert (c1 < p1) != (p1 < c1)
|
assert (c1 < p1) != (p1 < c1)
|
||||||
|
|
||||||
|
|
||||||
# 4. Arc/Path Edge Cases
|
# 4. Arc/Path Edge Cases
|
||||||
def test_arc_edge_cases():
|
def test_arc_edge_cases() -> None:
|
||||||
# Wrapped arc (> 360 deg)
|
# Wrapped arc (> 360 deg)
|
||||||
a = Arc(radii=(10, 10), angles=(0, 3 * pi), width=2)
|
a = Arc(radii=(10, 10), angles=(0, 3 * pi), width=2)
|
||||||
polys = a.to_polygons(num_vertices=64)
|
a.to_polygons(num_vertices=64)
|
||||||
# Should basically be a ring
|
# Should basically be a ring
|
||||||
bounds = a.get_bounds_single()
|
bounds = a.get_bounds_single()
|
||||||
assert_allclose(bounds, [[-11, -11], [11, 11]], atol=1e-10)
|
assert_allclose(bounds, [[-11, -11], [11, 11]], atol=1e-10)
|
||||||
|
|
||||||
def test_path_edge_cases():
|
|
||||||
|
def test_path_edge_cases() -> None:
|
||||||
# Zero-length segments
|
# Zero-length segments
|
||||||
p = Path(vertices=[[0, 0], [0, 0], [10, 0]], width=2)
|
p = MPath(vertices=[[0, 0], [0, 0], [10, 0]], width=2)
|
||||||
polys = p.to_polygons()
|
polys = p.to_polygons()
|
||||||
assert len(polys) == 1
|
assert len(polys) == 1
|
||||||
assert_equal(polys[0].get_bounds_single(), [[0, -1], [10, 1]])
|
assert_equal(polys[0].get_bounds_single(), [[0, -1], [10, 1]])
|
||||||
|
|
||||||
|
|
||||||
# 5. PolyCollection with holes
|
# 5. PolyCollection with holes
|
||||||
def test_poly_collection_holes():
|
def test_poly_collection_holes() -> None:
|
||||||
# Outer square, inner square hole
|
# Outer square, inner square hole
|
||||||
# PolyCollection doesn't explicitly support holes, but its constituents (Polygons) do?
|
# PolyCollection doesn't explicitly support holes, but its constituents (Polygons) do?
|
||||||
# wait, Polygon in masque is just a boundary. Holes are usually handled by having multiple
|
# wait, Polygon in masque is just a boundary. Holes are usually handled by having multiple
|
||||||
|
|
@ -80,8 +86,14 @@ def test_poly_collection_holes():
|
||||||
|
|
||||||
# Let's test PolyCollection with multiple polygons
|
# Let's test PolyCollection with multiple polygons
|
||||||
verts = [
|
verts = [
|
||||||
[0, 0], [10, 0], [10, 10], [0, 10], # Poly 1
|
[0, 0],
|
||||||
[2, 2], [2, 8], [8, 8], [8, 2] # Poly 2
|
[10, 0],
|
||||||
|
[10, 10],
|
||||||
|
[0, 10], # Poly 1
|
||||||
|
[2, 2],
|
||||||
|
[2, 8],
|
||||||
|
[8, 8],
|
||||||
|
[8, 2], # Poly 2
|
||||||
]
|
]
|
||||||
offsets = [0, 4]
|
offsets = [0, 4]
|
||||||
pc = PolyCollection(verts, offsets)
|
pc = PolyCollection(verts, offsets)
|
||||||
|
|
@ -90,15 +102,21 @@ def test_poly_collection_holes():
|
||||||
assert_equal(polys[0].vertices, [[0, 0], [10, 0], [10, 10], [0, 10]])
|
assert_equal(polys[0].vertices, [[0, 0], [10, 0], [10, 10], [0, 10]])
|
||||||
assert_equal(polys[1].vertices, [[2, 2], [2, 8], [8, 8], [8, 2]])
|
assert_equal(polys[1].vertices, [[2, 2], [2, 8], [8, 8], [8, 2]])
|
||||||
|
|
||||||
def test_poly_collection_constituent_empty():
|
|
||||||
|
def test_poly_collection_constituent_empty() -> None:
|
||||||
# One real triangle, one "empty" polygon (0 vertices), one real square
|
# One real triangle, one "empty" polygon (0 vertices), one real square
|
||||||
# Note: Polygon requires 3 vertices, so "empty" here might mean just some junk
|
# Note: Polygon requires 3 vertices, so "empty" here might mean just some junk
|
||||||
# that to_polygons should handle.
|
# that to_polygons should handle.
|
||||||
# Actually PolyCollection doesn't check vertex count per polygon.
|
# Actually PolyCollection doesn't check vertex count per polygon.
|
||||||
verts = [
|
verts = [
|
||||||
[0, 0], [1, 0], [0, 1], # Tri
|
[0, 0],
|
||||||
|
[1, 0],
|
||||||
|
[0, 1], # Tri
|
||||||
# Empty space
|
# Empty space
|
||||||
[10, 10], [11, 10], [11, 11], [10, 11] # Square
|
[10, 10],
|
||||||
|
[11, 10],
|
||||||
|
[11, 11],
|
||||||
|
[10, 11], # Square
|
||||||
]
|
]
|
||||||
offsets = [0, 3, 3] # Index 3 is start of "empty", Index 3 is also start of Square?
|
offsets = [0, 3, 3] # Index 3 is start of "empty", Index 3 is also start of Square?
|
||||||
# No, offsets should be strictly increasing or handle 0-length slices.
|
# No, offsets should be strictly increasing or handle 0-length slices.
|
||||||
|
|
@ -113,22 +131,14 @@ def test_poly_collection_constituent_empty():
|
||||||
with pytest.raises(PatternError):
|
with pytest.raises(PatternError):
|
||||||
pc.to_polygons()
|
pc.to_polygons()
|
||||||
|
|
||||||
def test_poly_collection_valid():
|
|
||||||
verts = [
|
def test_poly_collection_valid() -> None:
|
||||||
[0, 0], [1, 0], [0, 1],
|
verts = [[0, 0], [1, 0], [0, 1], [10, 10], [11, 10], [11, 11], [10, 11]]
|
||||||
[10, 10], [11, 10], [11, 11], [10, 11]
|
|
||||||
]
|
|
||||||
offsets = [0, 3]
|
offsets = [0, 3]
|
||||||
pc = PolyCollection(verts, offsets)
|
pc = PolyCollection(verts, offsets)
|
||||||
assert len(pc.to_polygons()) == 2
|
assert len(pc.to_polygons()) == 2
|
||||||
shapes = [
|
shapes = [Circle(radius=20), Circle(radius=10), Polygon([[0, 0], [10, 0], [10, 10]]), Ellipse(radii=(5, 5))]
|
||||||
Circle(radius=20),
|
|
||||||
Circle(radius=10),
|
|
||||||
Polygon([[0, 0], [10, 0], [10, 10]]),
|
|
||||||
Ellipse(radii=(5, 5))
|
|
||||||
]
|
|
||||||
sorted_shapes = sorted(shapes)
|
sorted_shapes = sorted(shapes)
|
||||||
assert len(sorted_shapes) == 4
|
assert len(sorted_shapes) == 4
|
||||||
# Just verify it doesn't crash and is stable
|
# Just verify it doesn't crash and is stable
|
||||||
assert sorted(sorted_shapes) == sorted_shapes
|
assert sorted(sorted_shapes) == sorted_shapes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import pytest
|
|
||||||
import numpy
|
import numpy
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
from numpy.testing import assert_equal, assert_allclose
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from ..shapes import Arc, Ellipse, Circle, Polygon, PolyCollection
|
from ..shapes import Arc, Ellipse, Circle, Polygon, PolyCollection
|
||||||
|
|
||||||
def test_poly_collection_init():
|
|
||||||
|
def test_poly_collection_init() -> None:
|
||||||
# Two squares: [[0,0], [1,0], [1,1], [0,1]] and [[10,10], [11,10], [11,11], [10,11]]
|
# Two squares: [[0,0], [1,0], [1,1], [0,1]] and [[10,10], [11,10], [11,11], [10,11]]
|
||||||
verts = [[0, 0], [1, 0], [1, 1], [0, 1], [10, 10], [11, 10], [11, 11], [10, 11]]
|
verts = [[0, 0], [1, 0], [1, 1], [0, 1], [10, 10], [11, 10], [11, 11], [10, 11]]
|
||||||
offsets = [0, 4]
|
offsets = [0, 4]
|
||||||
|
|
@ -13,7 +13,8 @@ def test_poly_collection_init():
|
||||||
assert len(list(pc.polygon_vertices)) == 2
|
assert len(list(pc.polygon_vertices)) == 2
|
||||||
assert_equal(pc.get_bounds_single(), [[0, 0], [11, 11]])
|
assert_equal(pc.get_bounds_single(), [[0, 0], [11, 11]])
|
||||||
|
|
||||||
def test_poly_collection_to_polygons():
|
|
||||||
|
def test_poly_collection_to_polygons() -> None:
|
||||||
verts = [[0, 0], [1, 0], [1, 1], [0, 1], [10, 10], [11, 10], [11, 11], [10, 11]]
|
verts = [[0, 0], [1, 0], [1, 1], [0, 1], [10, 10], [11, 10], [11, 11], [10, 11]]
|
||||||
offsets = [0, 4]
|
offsets = [0, 4]
|
||||||
pc = PolyCollection(vertex_lists=verts, vertex_offsets=offsets)
|
pc = PolyCollection(vertex_lists=verts, vertex_offsets=offsets)
|
||||||
|
|
@ -22,12 +23,14 @@ def test_poly_collection_to_polygons():
|
||||||
assert_equal(polys[0].vertices, [[0, 0], [1, 0], [1, 1], [0, 1]])
|
assert_equal(polys[0].vertices, [[0, 0], [1, 0], [1, 1], [0, 1]])
|
||||||
assert_equal(polys[1].vertices, [[10, 10], [11, 10], [11, 11], [10, 11]])
|
assert_equal(polys[1].vertices, [[10, 10], [11, 10], [11, 11], [10, 11]])
|
||||||
|
|
||||||
def test_circle_init():
|
|
||||||
|
def test_circle_init() -> None:
|
||||||
c = Circle(radius=10, offset=(5, 5))
|
c = Circle(radius=10, offset=(5, 5))
|
||||||
assert c.radius == 10
|
assert c.radius == 10
|
||||||
assert_equal(c.offset, [5, 5])
|
assert_equal(c.offset, [5, 5])
|
||||||
|
|
||||||
def test_circle_to_polygons():
|
|
||||||
|
def test_circle_to_polygons() -> None:
|
||||||
c = Circle(radius=10)
|
c = Circle(radius=10)
|
||||||
polys = c.to_polygons(num_vertices=32)
|
polys = c.to_polygons(num_vertices=32)
|
||||||
assert len(polys) == 1
|
assert len(polys) == 1
|
||||||
|
|
@ -36,26 +39,30 @@ def test_circle_to_polygons():
|
||||||
bounds = polys[0].get_bounds_single()
|
bounds = polys[0].get_bounds_single()
|
||||||
assert_allclose(bounds, [[-10, -10], [10, 10]], atol=1e-10)
|
assert_allclose(bounds, [[-10, -10], [10, 10]], atol=1e-10)
|
||||||
|
|
||||||
def test_ellipse_init():
|
|
||||||
|
def test_ellipse_init() -> None:
|
||||||
e = Ellipse(radii=(10, 5), offset=(1, 2), rotation=pi / 4)
|
e = Ellipse(radii=(10, 5), offset=(1, 2), rotation=pi / 4)
|
||||||
assert_equal(e.radii, [10, 5])
|
assert_equal(e.radii, [10, 5])
|
||||||
assert_equal(e.offset, [1, 2])
|
assert_equal(e.offset, [1, 2])
|
||||||
assert e.rotation == pi / 4
|
assert e.rotation == pi / 4
|
||||||
|
|
||||||
def test_ellipse_to_polygons():
|
|
||||||
|
def test_ellipse_to_polygons() -> None:
|
||||||
e = Ellipse(radii=(10, 5))
|
e = Ellipse(radii=(10, 5))
|
||||||
polys = e.to_polygons(num_vertices=64)
|
polys = e.to_polygons(num_vertices=64)
|
||||||
assert len(polys) == 1
|
assert len(polys) == 1
|
||||||
bounds = polys[0].get_bounds_single()
|
bounds = polys[0].get_bounds_single()
|
||||||
assert_allclose(bounds, [[-10, -5], [10, 5]], atol=1e-10)
|
assert_allclose(bounds, [[-10, -5], [10, 5]], atol=1e-10)
|
||||||
|
|
||||||
def test_arc_init():
|
|
||||||
|
def test_arc_init() -> None:
|
||||||
a = Arc(radii=(10, 10), angles=(0, pi / 2), width=2, offset=(0, 0))
|
a = Arc(radii=(10, 10), angles=(0, pi / 2), width=2, offset=(0, 0))
|
||||||
assert_equal(a.radii, [10, 10])
|
assert_equal(a.radii, [10, 10])
|
||||||
assert_equal(a.angles, [0, pi / 2])
|
assert_equal(a.angles, [0, pi / 2])
|
||||||
assert a.width == 2
|
assert a.width == 2
|
||||||
|
|
||||||
def test_arc_to_polygons():
|
|
||||||
|
def test_arc_to_polygons() -> None:
|
||||||
# Quarter circle arc
|
# Quarter circle arc
|
||||||
a = Arc(radii=(10, 10), angles=(0, pi / 2), width=2)
|
a = Arc(radii=(10, 10), angles=(0, pi / 2), width=2)
|
||||||
polys = a.to_polygons(num_vertices=32)
|
polys = a.to_polygons(num_vertices=32)
|
||||||
|
|
@ -70,7 +77,8 @@ def test_arc_to_polygons():
|
||||||
# So x ranges from 0 to 11, y ranges from 0 to 11.
|
# So x ranges from 0 to 11, y ranges from 0 to 11.
|
||||||
assert_allclose(bounds, [[0, 0], [11, 11]], atol=1e-10)
|
assert_allclose(bounds, [[0, 0], [11, 11]], atol=1e-10)
|
||||||
|
|
||||||
def test_shape_mirror():
|
|
||||||
|
def test_shape_mirror() -> None:
|
||||||
e = Ellipse(radii=(10, 5), offset=(10, 20), rotation=pi / 4)
|
e = Ellipse(radii=(10, 5), offset=(10, 20), rotation=pi / 4)
|
||||||
e.mirror(0) # Mirror across x axis (axis 0): in-place relative to offset
|
e.mirror(0) # Mirror across x axis (axis 0): in-place relative to offset
|
||||||
assert_equal(e.offset, [10, 20])
|
assert_equal(e.offset, [10, 20])
|
||||||
|
|
@ -83,7 +91,8 @@ def test_shape_mirror():
|
||||||
# For Arc, mirror(0) negates rotation and angles
|
# For Arc, mirror(0) negates rotation and angles
|
||||||
assert_allclose(a.angles, [0, -pi / 4], atol=1e-10)
|
assert_allclose(a.angles, [0, -pi / 4], atol=1e-10)
|
||||||
|
|
||||||
def test_shape_flip_across():
|
|
||||||
|
def test_shape_flip_across() -> None:
|
||||||
e = Ellipse(radii=(10, 5), offset=(10, 20), rotation=pi / 4)
|
e = Ellipse(radii=(10, 5), offset=(10, 20), rotation=pi / 4)
|
||||||
e.flip_across(axis=0) # Mirror across y=0: flips y-offset
|
e.flip_across(axis=0) # Mirror across y=0: flips y-offset
|
||||||
assert_equal(e.offset, [10, -20])
|
assert_equal(e.offset, [10, -20])
|
||||||
|
|
@ -95,7 +104,8 @@ def test_shape_flip_across():
|
||||||
# y=20 mirrored across y=10 -> y=0
|
# y=20 mirrored across y=10 -> y=0
|
||||||
assert_equal(e.offset, [10, 0])
|
assert_equal(e.offset, [10, 0])
|
||||||
|
|
||||||
def test_shape_scale():
|
|
||||||
|
def test_shape_scale() -> None:
|
||||||
e = Ellipse(radii=(10, 5))
|
e = Ellipse(radii=(10, 5))
|
||||||
e.scale_by(2)
|
e.scale_by(2)
|
||||||
assert_equal(e.radii, [20, 10])
|
assert_equal(e.radii, [20, 10])
|
||||||
|
|
@ -105,7 +115,8 @@ def test_shape_scale():
|
||||||
assert_equal(a.radii, [5, 2.5])
|
assert_equal(a.radii, [5, 2.5])
|
||||||
assert a.width == 1
|
assert a.width == 1
|
||||||
|
|
||||||
def test_shape_arclen():
|
|
||||||
|
def test_shape_arclen() -> None:
|
||||||
# Test that max_arclen correctly limits segment lengths
|
# Test that max_arclen correctly limits segment lengths
|
||||||
|
|
||||||
# Ellipse
|
# Ellipse
|
||||||
|
|
@ -129,4 +140,3 @@ def test_shape_arclen():
|
||||||
# Let's just check that all segment lengths are within limit
|
# Let's just check that all segment lengths are within limit
|
||||||
dist = numpy.sqrt(numpy.sum(numpy.diff(v, axis=0, append=v[:1]) ** 2, axis=1))
|
dist = numpy.sqrt(numpy.sum(numpy.diff(v, axis=0, append=v[:1]) ** 2, axis=1))
|
||||||
assert numpy.all(dist <= 2.000001)
|
assert numpy.all(dist <= 2.000001)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,11 @@
|
||||||
import pytest
|
|
||||||
import numpy
|
import numpy
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
from numpy.testing import assert_equal, assert_allclose
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from ..utils import (
|
from ..utils import remove_duplicate_vertices, remove_colinear_vertices, poly_contains_points, rotation_matrix_2d, apply_transforms
|
||||||
remove_duplicate_vertices,
|
|
||||||
remove_colinear_vertices,
|
|
||||||
poly_contains_points,
|
|
||||||
rotation_matrix_2d,
|
|
||||||
apply_transforms
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_remove_duplicate_vertices():
|
|
||||||
|
def test_remove_duplicate_vertices() -> None:
|
||||||
# Closed path (default)
|
# Closed path (default)
|
||||||
v = [[0, 0], [1, 1], [1, 1], [2, 2], [0, 0]]
|
v = [[0, 0], [1, 1], [1, 1], [2, 2], [0, 0]]
|
||||||
v_clean = remove_duplicate_vertices(v, closed_path=True)
|
v_clean = remove_duplicate_vertices(v, closed_path=True)
|
||||||
|
|
@ -22,7 +16,8 @@ def test_remove_duplicate_vertices():
|
||||||
v_clean_open = remove_duplicate_vertices(v, closed_path=False)
|
v_clean_open = remove_duplicate_vertices(v, closed_path=False)
|
||||||
assert_equal(v_clean_open, [[0, 0], [1, 1], [2, 2], [0, 0]])
|
assert_equal(v_clean_open, [[0, 0], [1, 1], [2, 2], [0, 0]])
|
||||||
|
|
||||||
def test_remove_colinear_vertices():
|
|
||||||
|
def test_remove_colinear_vertices() -> None:
|
||||||
v = [[0, 0], [1, 0], [2, 0], [2, 1], [2, 2], [1, 1], [0, 0]]
|
v = [[0, 0], [1, 0], [2, 0], [2, 1], [2, 2], [1, 1], [0, 0]]
|
||||||
v_clean = remove_colinear_vertices(v, closed_path=True)
|
v_clean = remove_colinear_vertices(v, closed_path=True)
|
||||||
# [1, 0] is between [0, 0] and [2, 0]
|
# [1, 0] is between [0, 0] and [2, 0]
|
||||||
|
|
@ -30,7 +25,8 @@ def test_remove_colinear_vertices():
|
||||||
# [1, 1] is between [2, 2] and [0, 0]
|
# [1, 1] is between [2, 2] and [0, 0]
|
||||||
assert_equal(v_clean, [[0, 0], [2, 0], [2, 2]])
|
assert_equal(v_clean, [[0, 0], [2, 0], [2, 2]])
|
||||||
|
|
||||||
def test_remove_colinear_vertices_exhaustive():
|
|
||||||
|
def test_remove_colinear_vertices_exhaustive() -> None:
|
||||||
# U-turn
|
# U-turn
|
||||||
v = [[0, 0], [10, 0], [0, 0]]
|
v = [[0, 0], [10, 0], [0, 0]]
|
||||||
v_clean = remove_colinear_vertices(v, closed_path=False)
|
v_clean = remove_colinear_vertices(v, closed_path=False)
|
||||||
|
|
@ -43,30 +39,35 @@ def test_remove_colinear_vertices_exhaustive():
|
||||||
v_clean = remove_colinear_vertices(v, closed_path=True)
|
v_clean = remove_colinear_vertices(v, closed_path=True)
|
||||||
assert len(v_clean) == 2
|
assert len(v_clean) == 2
|
||||||
|
|
||||||
def test_poly_contains_points():
|
|
||||||
|
def test_poly_contains_points() -> None:
|
||||||
v = [[0, 0], [10, 0], [10, 10], [0, 10]]
|
v = [[0, 0], [10, 0], [10, 10], [0, 10]]
|
||||||
pts = [[5, 5], [-1, -1], [10, 10], [11, 5]]
|
pts = [[5, 5], [-1, -1], [10, 10], [11, 5]]
|
||||||
inside = poly_contains_points(v, pts)
|
inside = poly_contains_points(v, pts)
|
||||||
assert_equal(inside, [True, False, True, False])
|
assert_equal(inside, [True, False, True, False])
|
||||||
|
|
||||||
def test_rotation_matrix_2d():
|
|
||||||
|
def test_rotation_matrix_2d() -> None:
|
||||||
m = rotation_matrix_2d(pi / 2)
|
m = rotation_matrix_2d(pi / 2)
|
||||||
assert_allclose(m, [[0, -1], [1, 0]], atol=1e-10)
|
assert_allclose(m, [[0, -1], [1, 0]], atol=1e-10)
|
||||||
|
|
||||||
def test_rotation_matrix_non_manhattan():
|
|
||||||
|
def test_rotation_matrix_non_manhattan() -> None:
|
||||||
# 45 degrees
|
# 45 degrees
|
||||||
m = rotation_matrix_2d(pi / 4)
|
m = rotation_matrix_2d(pi / 4)
|
||||||
s = numpy.sqrt(2) / 2
|
s = numpy.sqrt(2) / 2
|
||||||
assert_allclose(m, [[s, -s], [s, s]], atol=1e-10)
|
assert_allclose(m, [[s, -s], [s, s]], atol=1e-10)
|
||||||
|
|
||||||
def test_apply_transforms():
|
|
||||||
|
def test_apply_transforms() -> None:
|
||||||
# cumulative [x_offset, y_offset, rotation (rad), mirror_x (0 or 1)]
|
# cumulative [x_offset, y_offset, rotation (rad), mirror_x (0 or 1)]
|
||||||
t1 = [10, 20, 0, 0]
|
t1 = [10, 20, 0, 0]
|
||||||
t2 = [[5, 0, 0, 0], [0, 5, 0, 0]]
|
t2 = [[5, 0, 0, 0], [0, 5, 0, 0]]
|
||||||
combined = apply_transforms(t1, t2)
|
combined = apply_transforms(t1, t2)
|
||||||
assert_equal(combined, [[15, 20, 0, 0], [10, 25, 0, 0]])
|
assert_equal(combined, [[15, 20, 0, 0], [10, 25, 0, 0]])
|
||||||
|
|
||||||
def test_apply_transforms_advanced():
|
|
||||||
|
def test_apply_transforms_advanced() -> None:
|
||||||
# Ox4: (x, y, rot, mir)
|
# Ox4: (x, y, rot, mir)
|
||||||
# Outer: mirror x (axis 0), then rotate 90 deg CCW
|
# Outer: mirror x (axis 0), then rotate 90 deg CCW
|
||||||
# apply_transforms logic for mirror uses y *= -1 (which is axis 0 mirror)
|
# apply_transforms logic for mirror uses y *= -1 (which is axis 0 mirror)
|
||||||
|
|
@ -80,4 +81,3 @@ def test_apply_transforms_advanced():
|
||||||
# 2. rotate by outer rotation (pi/2): (10, 0) -> (0, 10)
|
# 2. rotate by outer rotation (pi/2): (10, 0) -> (0, 10)
|
||||||
# 3. add outer offset (0, 0) -> (0, 10)
|
# 3. add outer offset (0, 0) -> (0, 10)
|
||||||
assert_allclose(combined[0], [0, 10, pi / 2, 1], atol=1e-10)
|
assert_allclose(combined[0], [0, 10, pi / 2, 1], atol=1e-10)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue