misc cleanup (mostly type-related)

This commit is contained in:
jan 2026-02-16 17:58:34 -08:00
commit ebfe1b559c
16 changed files with 57 additions and 21 deletions

View file

@ -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

View file

@ -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)

View file

@ -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]

View file

@ -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

View file

@ -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:

View file

@ -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)

View file

@ -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)

View file

@ -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:

View file

@ -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"]

View file

@ -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)

View file

@ -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)

View file

@ -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"

View file

@ -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)

View file

@ -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)

View file

@ -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)]

View file

@ -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)