misc cleanup (mostly type-related)
This commit is contained in:
parent
7ad59d6b89
commit
ebfe1b559c
16 changed files with 57 additions and 21 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
import numpy
|
import numpy
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
from masque import layer_t, Pattern, Circle, Arc, Polygon, Ref
|
from masque import layer_t, Pattern, Circle, Arc, Ref
|
||||||
from masque.repetition import Grid
|
from masque.repetition import Grid
|
||||||
import masque.file.gdsii
|
import masque.file.gdsii
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -311,6 +311,7 @@ class Pather(Builder, PatherMixin):
|
||||||
# Fallback for dead pather: manually update the port instead of plugging
|
# Fallback for dead pather: manually update the port instead of plugging
|
||||||
port = self.pattern[portspec]
|
port = self.pattern[portspec]
|
||||||
port_rot = port.rotation
|
port_rot = port.rotation
|
||||||
|
assert port_rot is not None
|
||||||
if ccw is None:
|
if ccw is None:
|
||||||
out_rot = pi
|
out_rot = pi
|
||||||
elif bool(ccw):
|
elif bool(ccw):
|
||||||
|
|
@ -414,6 +415,7 @@ class Pather(Builder, PatherMixin):
|
||||||
# Fallback for dead pather: manually update the port instead of plugging
|
# Fallback for dead pather: manually update the port instead of plugging
|
||||||
port = self.pattern[portspec]
|
port = self.pattern[portspec]
|
||||||
port_rot = port.rotation
|
port_rot = port.rotation
|
||||||
|
assert port_rot is not None
|
||||||
out_port = Port((length, jog), rotation=pi, ptype=in_ptype)
|
out_port = Port((length, jog), rotation=pi, ptype=in_ptype)
|
||||||
out_port.rotate_around((0, 0), pi + port_rot)
|
out_port.rotate_around((0, 0), pi + port_rot)
|
||||||
out_port.translate(port.offset)
|
out_port.translate(port.offset)
|
||||||
|
|
|
||||||
|
|
@ -520,7 +520,7 @@ class RenderPather(PatherMixin):
|
||||||
ccw0 = jog > 0
|
ccw0 = jog > 0
|
||||||
kwargs_no_out = (kwargs | {'out_ptype': None})
|
kwargs_no_out = (kwargs | {'out_ptype': None})
|
||||||
try:
|
try:
|
||||||
t_port0, _ = tool.planL( ccw0, length / 2, in_ptype=in_ptype, **kwargs_no_out) # TODO length/2 may fail with asymmetric ptypes
|
t_port0, _ = tool.planL( ccw0, length / 2, in_ptype=in_ptype, **kwargs_no_out) # TODO length/2 may fail w/asymmetric ptypes
|
||||||
jog0 = Port((0, 0), 0).measure_travel(t_port0)[0][1]
|
jog0 = Port((0, 0), 0).measure_travel(t_port0)[0][1]
|
||||||
t_port1, _ = tool.planL(not ccw0, abs(jog - jog0), in_ptype=t_port0.ptype, **kwargs)
|
t_port1, _ = tool.planL(not ccw0, abs(jog - jog0), in_ptype=t_port0.ptype, **kwargs)
|
||||||
jog1 = Port((0, 0), 0).measure_travel(t_port1)[0][1]
|
jog1 = Port((0, 0), 0).measure_travel(t_port1)[0][1]
|
||||||
|
|
|
||||||
|
|
@ -643,6 +643,7 @@ class AutoTool(Tool, metaclass=ABCMeta):
|
||||||
if out_transition is not None:
|
if out_transition is not None:
|
||||||
out_ptype_actual = out_transition.their_port.ptype
|
out_ptype_actual = out_transition.their_port.ptype
|
||||||
elif ccw is not None:
|
elif ccw is not None:
|
||||||
|
assert bend is not None
|
||||||
out_ptype_actual = bend.out_port.ptype
|
out_ptype_actual = bend.out_port.ptype
|
||||||
elif not numpy.isclose(straight_length, 0):
|
elif not numpy.isclose(straight_length, 0):
|
||||||
out_ptype_actual = straight.ptype
|
out_ptype_actual = straight.ptype
|
||||||
|
|
|
||||||
|
|
@ -693,7 +693,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
|
||||||
"""
|
"""
|
||||||
for entry in chain(chain_elements(self.shapes, self.labels, self.refs), self.ports.values()):
|
for entry in chain(chain_elements(self.shapes, self.labels, self.refs), self.ports.values()):
|
||||||
cast('Positionable', entry).translate(offset)
|
cast('Positionable', entry).translate(offset)
|
||||||
self._log_bulk_update(f"translate({offset})")
|
self._log_bulk_update(f"translate({offset!r})")
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def scale_elements(self, c: float) -> Self:
|
def scale_elements(self, c: float) -> Self:
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ def test_abstract_transform() -> None:
|
||||||
abs_obj.rotate_around((0, 0), pi / 2)
|
abs_obj.rotate_around((0, 0), pi / 2)
|
||||||
# (10, 0) rot 0 -> (0, 10) rot pi/2
|
# (10, 0) rot 0 -> (0, 10) rot pi/2
|
||||||
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 abs_obj.ports["A"].rotation is not None
|
||||||
assert_allclose(abs_obj.ports["A"].rotation, pi / 2, atol=1e-10)
|
assert_allclose(abs_obj.ports["A"].rotation, pi / 2, atol=1e-10)
|
||||||
|
|
||||||
# Mirror across x axis (axis 0): flips y-offset
|
# Mirror across x axis (axis 0): flips y-offset
|
||||||
|
|
@ -27,6 +28,7 @@ def test_abstract_transform() -> None:
|
||||||
# (0, 10) mirrored(0) -> (0, -10)
|
# (0, 10) mirrored(0) -> (0, -10)
|
||||||
# rotation pi/2 mirrored(0) -> -pi/2 == 3pi/2
|
# rotation pi/2 mirrored(0) -> -pi/2 == 3pi/2
|
||||||
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 abs_obj.ports["A"].rotation is not None
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -48,6 +50,7 @@ def test_abstract_ref_transform() -> None:
|
||||||
# (0, 10) -> (100, 110)
|
# (0, 10) -> (100, 110)
|
||||||
|
|
||||||
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 abs_obj.ports["A"].rotation is not None
|
||||||
assert_allclose(abs_obj.ports["A"].rotation, pi / 2, atol=1e-10)
|
assert_allclose(abs_obj.ports["A"].rotation, pi / 2, atol=1e-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -57,4 +60,5 @@ def test_abstract_undo_transform() -> None:
|
||||||
|
|
||||||
abs_obj.undo_ref_transform(ref)
|
abs_obj.undo_ref_transform(ref)
|
||||||
assert_allclose(abs_obj.ports["A"].offset, [10, 0], atol=1e-10)
|
assert_allclose(abs_obj.ports["A"].offset, [10, 0], atol=1e-10)
|
||||||
|
assert abs_obj.ports["A"].rotation is not None
|
||||||
assert_allclose(abs_obj.ports["A"].rotation, 0, atol=1e-10)
|
assert_allclose(abs_obj.ports["A"].rotation, 0, atol=1e-10)
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ def test_builder_plug() -> None:
|
||||||
|
|
||||||
assert "start" in b.ports
|
assert "start" in b.ports
|
||||||
assert_equal(b.ports["start"].offset, [90, 100])
|
assert_equal(b.ports["start"].offset, [90, 100])
|
||||||
|
assert b.ports["start"].rotation is not None
|
||||||
assert_allclose(b.ports["start"].rotation, 0, atol=1e-10)
|
assert_allclose(b.ports["start"].rotation, 0, atol=1e-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -126,4 +127,5 @@ def test_dead_plug_best_effort() -> None:
|
||||||
# 3. Translate by s_port.offset (0,0): (-10,-10)
|
# 3. Translate by s_port.offset (0,0): (-10,-10)
|
||||||
assert_allclose(b.ports['B'].offset, [-10, -10], atol=1e-10)
|
assert_allclose(b.ports['B'].offset, [-10, -10], atol=1e-10)
|
||||||
# P2 rot pi + transform rot -pi = 0
|
# P2 rot pi + transform rot -pi = 0
|
||||||
|
assert b.ports['B'].rotation is not None
|
||||||
assert_allclose(b.ports['B'].rotation, 0, atol=1e-10)
|
assert_allclose(b.ports['B'].rotation, 0, atol=1e-10)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import cast
|
||||||
import pytest
|
import pytest
|
||||||
from numpy.testing import assert_allclose
|
from numpy.testing import assert_allclose
|
||||||
|
|
||||||
|
|
@ -77,17 +78,18 @@ def test_gdsii_full_roundtrip(tmp_path: Path) -> None:
|
||||||
# Order might be different depending on how they were written,
|
# Order might be different depending on how they were written,
|
||||||
# but here they should match the order they were added if dict order is preserved.
|
# but here they should match the order they were added if dict order is preserved.
|
||||||
# Actually, they are grouped by layer.
|
# Actually, they are grouped by layer.
|
||||||
p_flush = read_paths.shapes[(2, 0)][0]
|
p_flush = cast("MPath", read_paths.shapes[(2, 0)][0])
|
||||||
assert p_flush.cap == MPath.Cap.Flush
|
assert p_flush.cap == MPath.Cap.Flush
|
||||||
|
|
||||||
p_square = read_paths.shapes[(2, 1)][0]
|
p_square = cast("MPath", read_paths.shapes[(2, 1)][0])
|
||||||
assert p_square.cap == MPath.Cap.Square
|
assert p_square.cap == MPath.Cap.Square
|
||||||
|
|
||||||
p_circle = read_paths.shapes[(2, 2)][0]
|
p_circle = cast("MPath", read_paths.shapes[(2, 2)][0])
|
||||||
assert p_circle.cap == MPath.Cap.Circle
|
assert p_circle.cap == MPath.Cap.Circle
|
||||||
|
|
||||||
p_custom = read_paths.shapes[(2, 3)][0]
|
p_custom = cast("MPath", read_paths.shapes[(2, 3)][0])
|
||||||
assert p_custom.cap == MPath.Cap.SquareCustom
|
assert p_custom.cap == MPath.Cap.SquareCustom
|
||||||
|
assert p_custom.cap_extensions is not None
|
||||||
assert_allclose(p_custom.cap_extensions, (1, 5))
|
assert_allclose(p_custom.cap_extensions, (1, 5))
|
||||||
|
|
||||||
# Check Refs with repetitions
|
# Check Refs with repetitions
|
||||||
|
|
@ -125,8 +127,8 @@ def test_oasis_full_roundtrip(tmp_path: Path) -> None:
|
||||||
|
|
||||||
# Check Path caps
|
# Check Path caps
|
||||||
read_paths = read_lib["paths"]
|
read_paths = read_lib["paths"]
|
||||||
assert read_paths.shapes[(2, 0)][0].cap == MPath.Cap.Flush
|
assert cast("MPath", read_paths.shapes[(2, 0)][0]).cap == MPath.Cap.Flush
|
||||||
assert read_paths.shapes[(2, 1)][0].cap == MPath.Cap.Square
|
assert cast("MPath", read_paths.shapes[(2, 1)][0]).cap == MPath.Cap.Square
|
||||||
# OASIS HalfWidth is Square. masque's Square is also HalfWidth extension.
|
# OASIS HalfWidth is Square. masque's Square is also HalfWidth extension.
|
||||||
# Wait, Circle cap in OASIS?
|
# Wait, Circle cap in OASIS?
|
||||||
# masque/file/oasis.py:
|
# masque/file/oasis.py:
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import cast
|
||||||
import numpy
|
import numpy
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
from numpy.testing import assert_equal, assert_allclose
|
||||||
|
|
||||||
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 Path as MPath
|
from ..shapes import Path as MPath, Polygon
|
||||||
|
|
||||||
|
|
||||||
def test_gdsii_roundtrip(tmp_path: Path) -> None:
|
def test_gdsii_roundtrip(tmp_path: Path) -> None:
|
||||||
|
|
@ -36,14 +37,14 @@ def test_gdsii_roundtrip(tmp_path: Path) -> None:
|
||||||
assert "ref_cell" in read_lib
|
assert "ref_cell" in read_lib
|
||||||
|
|
||||||
# Check polygon
|
# Check polygon
|
||||||
read_poly = read_lib["poly_cell"].shapes[(1, 0)][0]
|
read_poly = cast("Polygon", read_lib["poly_cell"].shapes[(1, 0)][0])
|
||||||
# GDSII closes polygons, so it might have an extra vertex or different order
|
# GDSII closes polygons, so it might have an extra vertex or different order
|
||||||
assert len(read_poly.vertices) >= 4
|
assert len(read_poly.vertices) >= 4
|
||||||
# Check bounds as a proxy for geometry correctness
|
# Check bounds as a proxy for geometry correctness
|
||||||
assert_equal(read_lib["poly_cell"].get_bounds(), [[0, 0], [10, 10]])
|
assert_equal(read_lib["poly_cell"].get_bounds(), [[0, 0], [10, 10]])
|
||||||
|
|
||||||
# Check path
|
# Check path
|
||||||
read_path = read_lib["path_cell"].shapes[(2, 5)][0]
|
read_path = cast("MPath", read_lib["path_cell"].shapes[(2, 5)][0])
|
||||||
assert isinstance(read_path, MPath)
|
assert isinstance(read_path, MPath)
|
||||||
assert read_path.width == 10
|
assert read_path.width == 10
|
||||||
assert_equal(read_path.vertices, [[0, 0], [100, 0]])
|
assert_equal(read_path.vertices, [[0, 0], [100, 0]])
|
||||||
|
|
@ -66,4 +67,5 @@ def test_gdsii_annotations(tmp_path: Path) -> None:
|
||||||
|
|
||||||
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 is not None
|
||||||
assert read_ann["1"] == ["hello"]
|
assert read_ann["1"] == ["hello"]
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
from typing import cast, TYPE_CHECKING
|
||||||
from ..library import Library, LazyLibrary
|
from ..library import Library, LazyLibrary
|
||||||
from ..pattern import Pattern
|
from ..pattern import Pattern
|
||||||
from ..error import LibraryError
|
from ..error import LibraryError
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..shapes import Polygon
|
||||||
|
|
||||||
|
|
||||||
def test_library_basic() -> None:
|
def test_library_basic() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
|
|
@ -51,7 +55,7 @@ def test_library_flatten() -> None:
|
||||||
assert not flat_parent.has_refs()
|
assert not flat_parent.has_refs()
|
||||||
assert len(flat_parent.shapes[(1, 0)]) == 1
|
assert len(flat_parent.shapes[(1, 0)]) == 1
|
||||||
# Transformations are baked into vertices for Polygon
|
# Transformations are baked into vertices for Polygon
|
||||||
assert_vertices = flat_parent.shapes[(1, 0)][0].vertices
|
assert_vertices = cast("Polygon", 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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ def test_pather_straight(pather_setup: tuple[Pather, PathTool, Library]) -> None
|
||||||
|
|
||||||
# port rot pi/2 (North). Travel +pi relative to port -> South.
|
# port rot pi/2 (North). Travel +pi relative to port -> South.
|
||||||
assert_allclose(p.ports["start"].offset, [0, -10], atol=1e-10)
|
assert_allclose(p.ports["start"].offset, [0, -10], atol=1e-10)
|
||||||
|
assert p.ports["start"].rotation is not None
|
||||||
assert_allclose(p.ports["start"].rotation, pi / 2, atol=1e-10)
|
assert_allclose(p.ports["start"].rotation, pi / 2, atol=1e-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -46,6 +47,7 @@ def test_pather_bend(pather_setup: tuple[Pather, PathTool, Library]) -> None:
|
||||||
assert_allclose(p.ports["start"].offset, [-1, -10], atol=1e-10)
|
assert_allclose(p.ports["start"].offset, [-1, -10], atol=1e-10)
|
||||||
# North (pi/2) + CW (90 deg) -> West (pi)?
|
# North (pi/2) + CW (90 deg) -> West (pi)?
|
||||||
# Actual behavior results in 0 (East) - apparently rotation is flipped.
|
# Actual behavior results in 0 (East) - apparently rotation is flipped.
|
||||||
|
assert p.ports["start"].rotation is not None
|
||||||
assert_allclose(p.ports["start"].rotation, 0, atol=1e-10)
|
assert_allclose(p.ports["start"].rotation, 0, atol=1e-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -80,6 +82,7 @@ def test_pather_at_chaining(pather_setup: tuple[Pather, PathTool, Library]) -> N
|
||||||
assert_allclose(p.ports["start"].offset, [1, -20], atol=1e-10)
|
assert_allclose(p.ports["start"].offset, [1, -20], atol=1e-10)
|
||||||
# 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 p.ports["start"].rotation is not None
|
||||||
assert_allclose(p.ports["start"].rotation, pi, atol=1e-10)
|
assert_allclose(p.ports["start"].rotation, pi, atol=1e-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from typing import cast
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
from numpy.testing import assert_equal, assert_allclose
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
|
|
@ -56,7 +57,7 @@ def test_pattern_translate() -> None:
|
||||||
pat.translate_elements((10, 20))
|
pat.translate_elements((10, 20))
|
||||||
|
|
||||||
# Polygon.translate adds to vertices, and offset is always (0,0)
|
# Polygon.translate adds to vertices, and offset is always (0,0)
|
||||||
assert_equal(pat.shapes[(1, 0)][0].vertices[0], [10, 20])
|
assert_equal(cast("Polygon", 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])
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -67,7 +68,7 @@ def test_pattern_scale() -> None:
|
||||||
pat.scale_by(2)
|
pat.scale_by(2)
|
||||||
|
|
||||||
# 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(cast("Polygon", pat.shapes[(1, 0)][0]).vertices, [[0, 0], [0, 2], [2, 2], [2, 0]])
|
||||||
|
|
||||||
|
|
||||||
def test_pattern_rotate() -> None:
|
def test_pattern_rotate() -> None:
|
||||||
|
|
@ -77,7 +78,7 @@ def test_pattern_rotate() -> None:
|
||||||
pat.rotate_around((0, 0), pi / 2)
|
pat.rotate_around((0, 0), pi / 2)
|
||||||
|
|
||||||
# [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(cast("Polygon", pat.shapes[(1, 0)][0]).vertices[0], [0, 10], atol=1e-10)
|
||||||
|
|
||||||
|
|
||||||
def test_pattern_mirror() -> None:
|
def test_pattern_mirror() -> None:
|
||||||
|
|
@ -86,7 +87,7 @@ def test_pattern_mirror() -> None:
|
||||||
# Mirror across X axis (y -> -y)
|
# Mirror across X axis (y -> -y)
|
||||||
pat.mirror(0)
|
pat.mirror(0)
|
||||||
|
|
||||||
assert_equal(pat.shapes[(1, 0)][0].vertices[0], [10, -5])
|
assert_equal(cast("Polygon", pat.shapes[(1, 0)][0]).vertices[0], [10, -5])
|
||||||
|
|
||||||
|
|
||||||
def test_pattern_get_bounds() -> None:
|
def test_pattern_get_bounds() -> None:
|
||||||
|
|
@ -106,7 +107,9 @@ def test_pattern_interface() -> None:
|
||||||
|
|
||||||
assert "in_A" in iface.ports
|
assert "in_A" in iface.ports
|
||||||
assert "out_A" in iface.ports
|
assert "out_A" in iface.ports
|
||||||
|
assert iface.ports["in_A"].rotation is not None
|
||||||
assert_allclose(iface.ports["in_A"].rotation, pi, atol=1e-10)
|
assert_allclose(iface.ports["in_A"].rotation, pi, atol=1e-10)
|
||||||
|
assert iface.ports["out_A"].rotation is not None
|
||||||
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"
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,13 @@ 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)
|
||||||
|
assert p.rotation is not None
|
||||||
assert_allclose(p.rotation, pi / 2, atol=1e-10)
|
assert_allclose(p.rotation, pi / 2, atol=1e-10)
|
||||||
|
|
||||||
p.mirror(0) # Mirror across x axis (axis 0): in-place relative to offset
|
p.mirror(0) # Mirror across x axis (axis 0): in-place relative to offset
|
||||||
assert_allclose(p.offset, [0, 10], atol=1e-10)
|
assert_allclose(p.offset, [0, 10], atol=1e-10)
|
||||||
# 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 p.rotation is not None
|
||||||
assert_allclose(p.rotation, 3 * pi / 2, atol=1e-10)
|
assert_allclose(p.rotation, 3 * pi / 2, atol=1e-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -30,6 +32,7 @@ def test_port_flip_across() -> None:
|
||||||
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 p.rotation is not None
|
||||||
assert_allclose(p.rotation, pi, atol=1e-10)
|
assert_allclose(p.rotation, pi, atol=1e-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ def test_ports2data_roundtrip() -> None:
|
||||||
|
|
||||||
assert "P1" in pat2.ports
|
assert "P1" in pat2.ports
|
||||||
assert_allclose(pat2.ports["P1"].offset, [10, 20], atol=1e-10)
|
assert_allclose(pat2.ports["P1"].offset, [10, 20], atol=1e-10)
|
||||||
|
assert pat2.ports["P1"].rotation is not None
|
||||||
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"
|
||||||
|
|
||||||
|
|
@ -52,4 +53,5 @@ def test_data_to_ports_hierarchical() -> None:
|
||||||
# rot 0 + pi/2 = pi/2
|
# rot 0 + pi/2 = pi/2
|
||||||
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 parent.ports["A"].rotation is not None
|
||||||
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,3 +1,4 @@
|
||||||
|
from typing import cast, TYPE_CHECKING
|
||||||
from numpy.testing import assert_equal, assert_allclose
|
from numpy.testing import assert_equal, assert_allclose
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
|
|
@ -5,6 +6,9 @@ from ..pattern import Pattern
|
||||||
from ..ref import Ref
|
from ..ref import Ref
|
||||||
from ..repetition import Grid
|
from ..repetition import Grid
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..shapes import Polygon
|
||||||
|
|
||||||
|
|
||||||
def test_ref_init() -> None:
|
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)
|
||||||
|
|
@ -22,7 +26,7 @@ def test_ref_as_pattern() -> None:
|
||||||
transformed_pat = ref.as_pattern(sub_pat)
|
transformed_pat = ref.as_pattern(sub_pat)
|
||||||
|
|
||||||
# Check transformed shape
|
# Check transformed shape
|
||||||
shape = transformed_pat.shapes[(1, 0)][0]
|
shape = cast("Polygon", transformed_pat.shapes[(1, 0)][0])
|
||||||
# ref.as_pattern deepcopies sub_pat then applies transformations:
|
# ref.as_pattern deepcopies sub_pat then applies transformations:
|
||||||
# 1. pattern.scale_by(2) -> vertices [[0,0], [2,0], [0,2]]
|
# 1. pattern.scale_by(2) -> vertices [[0,0], [2,0], [0,2]]
|
||||||
# 2. pattern.rotate_around((0,0), pi/2) -> vertices [[0,0], [0,2], [-2,0]]
|
# 2. pattern.rotate_around((0,0), pi/2) -> vertices [[0,0], [0,2], [-2,0]]
|
||||||
|
|
@ -42,7 +46,7 @@ def test_ref_with_repetition() -> None:
|
||||||
# Should have 4 shapes
|
# Should have 4 shapes
|
||||||
assert len(repeated_pat.shapes[(1, 0)]) == 4
|
assert len(repeated_pat.shapes[(1, 0)]) == 4
|
||||||
|
|
||||||
first_verts = sorted([tuple(s.vertices[0]) for s in repeated_pat.shapes[(1, 0)]])
|
first_verts = sorted([tuple(cast("Polygon", 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)]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
from typing import cast, TYPE_CHECKING
|
||||||
from numpy.testing import assert_allclose
|
from numpy.testing import assert_allclose
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
||||||
|
|
@ -7,6 +8,9 @@ from ..builder.tools import PathTool
|
||||||
from ..library import Library
|
from ..library import Library
|
||||||
from ..ports import Port
|
from ..ports import Port
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..shapes import Path
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def rpather_setup() -> tuple[RenderPather, PathTool, Library]:
|
def rpather_setup() -> tuple[RenderPather, PathTool, Library]:
|
||||||
|
|
@ -37,7 +41,7 @@ def test_renderpather_basic(rpather_setup: tuple[RenderPather, PathTool, Library
|
||||||
# start_port rot pi/2. pi/2 + pi = 3pi/2.
|
# start_port rot pi/2. pi/2 + pi = 3pi/2.
|
||||||
# (10, 0) rotated 3pi/2 -> (0, -10)
|
# (10, 0) rotated 3pi/2 -> (0, -10)
|
||||||
# So vertices: (0,0), (0,-10), (0,-20)
|
# So vertices: (0,0), (0,-10), (0,-20)
|
||||||
path_shape = rp.pattern.shapes[(1, 0)][0]
|
path_shape = cast("Path", rp.pattern.shapes[(1, 0)][0])
|
||||||
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)
|
||||||
|
|
||||||
|
|
@ -48,7 +52,7 @@ def test_renderpather_bend(rpather_setup: tuple[RenderPather, PathTool, Library]
|
||||||
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)
|
||||||
|
|
||||||
rp.render()
|
rp.render()
|
||||||
path_shape = rp.pattern.shapes[(1, 0)][0]
|
path_shape = cast("Path", rp.pattern.shapes[(1, 0)][0])
|
||||||
# Path vertices:
|
# Path vertices:
|
||||||
# 1. Start (0,0)
|
# 1. Start (0,0)
|
||||||
# 2. Straight end: (0, -10)
|
# 2. Straight end: (0, -10)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue