From ebfe1b559cef412259bdda51f638c51a5c8aae94 Mon Sep 17 00:00:00 2001 From: jan Date: Mon, 16 Feb 2026 17:58:34 -0800 Subject: [PATCH] misc cleanup (mostly type-related) --- examples/tutorial/basic_shapes.py | 2 +- masque/builder/pather.py | 2 ++ masque/builder/renderpather.py | 2 +- masque/builder/tools.py | 1 + masque/pattern.py | 2 +- masque/test/test_abstract.py | 4 ++++ masque/test/test_builder.py | 2 ++ masque/test/test_file_roundtrip.py | 14 ++++++++------ masque/test/test_gdsii.py | 8 +++++--- masque/test/test_library.py | 6 +++++- masque/test/test_pather.py | 3 +++ masque/test/test_pattern.py | 11 +++++++---- masque/test/test_ports.py | 3 +++ masque/test/test_ports2data.py | 2 ++ masque/test/test_ref.py | 8 ++++++-- masque/test/test_renderpather.py | 8 ++++++-- 16 files changed, 57 insertions(+), 21 deletions(-) diff --git a/examples/tutorial/basic_shapes.py b/examples/tutorial/basic_shapes.py index 8664b4d..d8f7e1e 100644 --- a/examples/tutorial/basic_shapes.py +++ b/examples/tutorial/basic_shapes.py @@ -2,7 +2,7 @@ import numpy 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 import masque.file.gdsii diff --git a/masque/builder/pather.py b/masque/builder/pather.py index c23e240..387b0d8 100644 --- a/masque/builder/pather.py +++ b/masque/builder/pather.py @@ -311,6 +311,7 @@ class Pather(Builder, PatherMixin): # Fallback for dead pather: manually update the port instead of plugging port = self.pattern[portspec] port_rot = port.rotation + assert port_rot is not None if ccw is None: out_rot = pi elif bool(ccw): @@ -414,6 +415,7 @@ class Pather(Builder, PatherMixin): # Fallback for dead pather: manually update the port instead of plugging port = self.pattern[portspec] port_rot = port.rotation + assert port_rot is not None out_port = Port((length, jog), rotation=pi, ptype=in_ptype) out_port.rotate_around((0, 0), pi + port_rot) out_port.translate(port.offset) diff --git a/masque/builder/renderpather.py b/masque/builder/renderpather.py index ca8cf8a..c47232f 100644 --- a/masque/builder/renderpather.py +++ b/masque/builder/renderpather.py @@ -520,7 +520,7 @@ class RenderPather(PatherMixin): ccw0 = jog > 0 kwargs_no_out = (kwargs | {'out_ptype': None}) 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] 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] diff --git a/masque/builder/tools.py b/masque/builder/tools.py index 1ffaa4d..27bc27e 100644 --- a/masque/builder/tools.py +++ b/masque/builder/tools.py @@ -643,6 +643,7 @@ class AutoTool(Tool, metaclass=ABCMeta): if out_transition is not None: out_ptype_actual = out_transition.their_port.ptype elif ccw is not None: + assert bend is not None out_ptype_actual = bend.out_port.ptype elif not numpy.isclose(straight_length, 0): out_ptype_actual = straight.ptype diff --git a/masque/pattern.py b/masque/pattern.py index acebf62..f4fb649 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -693,7 +693,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): """ for entry in chain(chain_elements(self.shapes, self.labels, self.refs), self.ports.values()): cast('Positionable', entry).translate(offset) - self._log_bulk_update(f"translate({offset})") + self._log_bulk_update(f"translate({offset!r})") return self def scale_elements(self, c: float) -> Self: diff --git a/masque/test/test_abstract.py b/masque/test/test_abstract.py index 907cedc..7c2dbbb 100644 --- a/masque/test/test_abstract.py +++ b/masque/test/test_abstract.py @@ -20,6 +20,7 @@ def test_abstract_transform() -> None: abs_obj.rotate_around((0, 0), pi / 2) # (10, 0) rot 0 -> (0, 10) rot pi/2 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) # Mirror across x axis (axis 0): flips y-offset @@ -27,6 +28,7 @@ def test_abstract_transform() -> None: # (0, 10) mirrored(0) -> (0, -10) # rotation pi/2 mirrored(0) -> -pi/2 == 3pi/2 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) @@ -48,6 +50,7 @@ def test_abstract_ref_transform() -> None: # (0, 10) -> (100, 110) 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) @@ -57,4 +60,5 @@ def test_abstract_undo_transform() -> None: abs_obj.undo_ref_transform(ref) 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) diff --git a/masque/test/test_builder.py b/masque/test/test_builder.py index bfbd1df..0ad6e80 100644 --- a/masque/test/test_builder.py +++ b/masque/test/test_builder.py @@ -50,6 +50,7 @@ def test_builder_plug() -> None: assert "start" in b.ports 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) @@ -126,4 +127,5 @@ def test_dead_plug_best_effort() -> None: # 3. Translate by s_port.offset (0,0): (-10,-10) assert_allclose(b.ports['B'].offset, [-10, -10], atol=1e-10) # 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) diff --git a/masque/test/test_file_roundtrip.py b/masque/test/test_file_roundtrip.py index fbadd7b..c7536a5 100644 --- a/masque/test/test_file_roundtrip.py +++ b/masque/test/test_file_roundtrip.py @@ -1,4 +1,5 @@ from pathlib import Path +from typing import cast import pytest 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, # but here they should match the order they were added if dict order is preserved. # 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 - 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 - 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 - 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_extensions is not None assert_allclose(p_custom.cap_extensions, (1, 5)) # Check Refs with repetitions @@ -125,8 +127,8 @@ def test_oasis_full_roundtrip(tmp_path: Path) -> None: # Check Path caps read_paths = read_lib["paths"] - assert 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, 0)][0]).cap == MPath.Cap.Flush + assert cast("MPath", read_paths.shapes[(2, 1)][0]).cap == MPath.Cap.Square # OASIS HalfWidth is Square. masque's Square is also HalfWidth extension. # Wait, Circle cap in OASIS? # masque/file/oasis.py: diff --git a/masque/test/test_gdsii.py b/masque/test/test_gdsii.py index 86e4bbc..7ce8c88 100644 --- a/masque/test/test_gdsii.py +++ b/masque/test/test_gdsii.py @@ -1,11 +1,12 @@ from pathlib import Path +from typing import cast import numpy from numpy.testing import assert_equal, assert_allclose from ..pattern import Pattern from ..library import Library 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: @@ -36,14 +37,14 @@ def test_gdsii_roundtrip(tmp_path: Path) -> None: assert "ref_cell" in read_lib # 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 assert len(read_poly.vertices) >= 4 # Check bounds as a proxy for geometry correctness assert_equal(read_lib["poly_cell"].get_bounds(), [[0, 0], [10, 10]]) # 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 read_path.width == 10 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_ann = read_lib["cell"].shapes[(1, 0)][0].annotations + assert read_ann is not None assert read_ann["1"] == ["hello"] diff --git a/masque/test/test_library.py b/masque/test/test_library.py index 0012219..22ad42a 100644 --- a/masque/test/test_library.py +++ b/masque/test/test_library.py @@ -1,8 +1,12 @@ import pytest +from typing import cast, TYPE_CHECKING from ..library import Library, LazyLibrary from ..pattern import Pattern from ..error import LibraryError +if TYPE_CHECKING: + from ..shapes import Polygon + def test_library_basic() -> None: lib = Library() @@ -51,7 +55,7 @@ def test_library_flatten() -> None: assert not flat_parent.has_refs() assert len(flat_parent.shapes[(1, 0)]) == 1 # 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) diff --git a/masque/test/test_pather.py b/masque/test/test_pather.py index 336458f..35e9f53 100644 --- a/masque/test/test_pather.py +++ b/masque/test/test_pather.py @@ -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. 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) @@ -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) # North (pi/2) + CW (90 deg) -> West (pi)? # 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) @@ -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) # pi/2 (North) + CCW (90 deg) -> 0 (East)? # Actual behavior results in pi (West). + assert p.ports["start"].rotation is not None assert_allclose(p.ports["start"].rotation, pi, atol=1e-10) diff --git a/masque/test/test_pattern.py b/masque/test/test_pattern.py index e66e9d5..f5da195 100644 --- a/masque/test/test_pattern.py +++ b/masque/test/test_pattern.py @@ -1,3 +1,4 @@ +from typing import cast from numpy.testing import assert_equal, assert_allclose from numpy import pi @@ -56,7 +57,7 @@ def test_pattern_translate() -> None: pat.translate_elements((10, 20)) # 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]) @@ -67,7 +68,7 @@ def test_pattern_scale() -> None: pat.scale_by(2) # 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: @@ -77,7 +78,7 @@ def test_pattern_rotate() -> None: pat.rotate_around((0, 0), pi / 2) # [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: @@ -86,7 +87,7 @@ def test_pattern_mirror() -> None: # Mirror across X axis (y -> -y) 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: @@ -106,7 +107,9 @@ def test_pattern_interface() -> None: assert "in_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 iface.ports["out_A"].rotation is not None assert_allclose(iface.ports["out_A"].rotation, 0, atol=1e-10) assert iface.ports["in_A"].ptype == "test" assert iface.ports["out_A"].ptype == "test" diff --git a/masque/test/test_ports.py b/masque/test/test_ports.py index 4354bff..e1dab87 100644 --- a/masque/test/test_ports.py +++ b/masque/test/test_ports.py @@ -17,11 +17,13 @@ def test_port_transform() -> None: p = Port(offset=(10, 0), rotation=0) p.rotate_around((0, 0), pi / 2) assert_allclose(p.offset, [0, 10], atol=1e-10) + assert p.rotation is not None assert_allclose(p.rotation, pi / 2, atol=1e-10) p.mirror(0) # Mirror across x axis (axis 0): in-place relative to offset assert_allclose(p.offset, [0, 10], atol=1e-10) # 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) @@ -30,6 +32,7 @@ def test_port_flip_across() -> None: p.flip_across(axis=1) # Mirror across x=0: flips x-offset assert_equal(p.offset, [-10, 0]) # rotation was 0, mirrored(1) -> pi + assert p.rotation is not None assert_allclose(p.rotation, pi, atol=1e-10) diff --git a/masque/test/test_ports2data.py b/masque/test/test_ports2data.py index 32bc367..f461cb8 100644 --- a/masque/test/test_ports2data.py +++ b/masque/test/test_ports2data.py @@ -25,6 +25,7 @@ def test_ports2data_roundtrip() -> None: assert "P1" in pat2.ports 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 pat2.ports["P1"].ptype == "test" @@ -52,4 +53,5 @@ def test_data_to_ports_hierarchical() -> None: # rot 0 + pi/2 = pi/2 assert "A" in parent.ports 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) diff --git a/masque/test/test_ref.py b/masque/test/test_ref.py index 8872699..e2d266b 100644 --- a/masque/test/test_ref.py +++ b/masque/test/test_ref.py @@ -1,3 +1,4 @@ +from typing import cast, TYPE_CHECKING from numpy.testing import assert_equal, assert_allclose from numpy import pi @@ -5,6 +6,9 @@ from ..pattern import Pattern from ..ref import Ref from ..repetition import Grid +if TYPE_CHECKING: + from ..shapes import Polygon + def test_ref_init() -> None: 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) # 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: # 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]] @@ -42,7 +46,7 @@ def test_ref_with_repetition() -> None: # Should have 4 shapes 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)] diff --git a/masque/test/test_renderpather.py b/masque/test/test_renderpather.py index 3948214..5d2c8c3 100644 --- a/masque/test/test_renderpather.py +++ b/masque/test/test_renderpather.py @@ -1,4 +1,5 @@ import pytest +from typing import cast, TYPE_CHECKING from numpy.testing import assert_allclose from numpy import pi @@ -7,6 +8,9 @@ from ..builder.tools import PathTool from ..library import Library from ..ports import Port +if TYPE_CHECKING: + from ..shapes import Path + @pytest.fixture 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. # (10, 0) rotated 3pi/2 -> (0, -10) # 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_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.render() - path_shape = rp.pattern.shapes[(1, 0)][0] + path_shape = cast("Path", rp.pattern.shapes[(1, 0)][0]) # Path vertices: # 1. Start (0,0) # 2. Straight end: (0, -10)