misc cleanup (mostly type-related)
This commit is contained in:
parent
7ad59d6b89
commit
ebfe1b559c
16 changed files with 57 additions and 21 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue