150 lines
4.5 KiB
Python
150 lines
4.5 KiB
Python
import pytest
|
|
from typing import cast
|
|
from numpy.testing import assert_equal, assert_allclose
|
|
from numpy import pi
|
|
|
|
from ..error import PatternError
|
|
from ..pattern import Pattern
|
|
from ..shapes import Polygon
|
|
from ..ref import Ref
|
|
from ..ports import Port
|
|
from ..label import Label
|
|
from ..repetition import Grid
|
|
|
|
|
|
def test_pattern_init() -> None:
|
|
pat = Pattern()
|
|
assert pat.is_empty()
|
|
assert not pat.has_shapes()
|
|
assert not pat.has_refs()
|
|
assert not pat.has_labels()
|
|
assert not pat.has_ports()
|
|
|
|
|
|
def test_pattern_with_elements() -> None:
|
|
poly = Polygon.square(10)
|
|
label = Label("test", offset=(5, 5))
|
|
ref = Ref(offset=(100, 100))
|
|
port = Port((0, 0), 0)
|
|
|
|
pat = Pattern(shapes={(1, 0): [poly]}, labels={(1, 2): [label]}, refs={"sub": [ref]}, ports={"P1": port})
|
|
|
|
assert pat.has_shapes()
|
|
assert pat.has_labels()
|
|
assert pat.has_refs()
|
|
assert pat.has_ports()
|
|
assert not pat.is_empty()
|
|
assert pat.shapes[(1, 0)] == [poly]
|
|
assert pat.labels[(1, 2)] == [label]
|
|
assert pat.refs["sub"] == [ref]
|
|
assert pat.ports["P1"] == port
|
|
|
|
|
|
def test_pattern_append() -> None:
|
|
pat1 = Pattern()
|
|
pat1.polygon((1, 0), vertices=[[0, 0], [1, 0], [1, 1]])
|
|
|
|
pat2 = Pattern()
|
|
pat2.polygon((2, 0), vertices=[[10, 10], [11, 10], [11, 11]])
|
|
|
|
pat1.append(pat2)
|
|
assert len(pat1.shapes[(1, 0)]) == 1
|
|
assert len(pat1.shapes[(2, 0)]) == 1
|
|
|
|
|
|
def test_pattern_translate() -> None:
|
|
pat = Pattern()
|
|
pat.polygon((1, 0), vertices=[[0, 0], [1, 0], [1, 1]])
|
|
pat.ports["P1"] = Port((5, 5), 0)
|
|
|
|
pat.translate_elements((10, 20))
|
|
|
|
# Polygon.translate adds to vertices, and offset is always (0,0)
|
|
assert_equal(cast("Polygon", pat.shapes[(1, 0)][0]).vertices[0], [10, 20])
|
|
assert_equal(pat.ports["P1"].offset, [15, 25])
|
|
|
|
|
|
def test_pattern_scale() -> None:
|
|
pat = Pattern()
|
|
# 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.scale_by(2)
|
|
|
|
# Vertices should be scaled
|
|
assert_equal(cast("Polygon", pat.shapes[(1, 0)][0]).vertices, [[0, 0], [0, 2], [2, 2], [2, 0]])
|
|
|
|
|
|
def test_pattern_rotate() -> None:
|
|
pat = Pattern()
|
|
pat.polygon((1, 0), vertices=[[10, 0], [11, 0], [10, 1]])
|
|
# Rotate 90 degrees CCW around (0,0)
|
|
pat.rotate_around((0, 0), pi / 2)
|
|
|
|
# [10, 0] rotated 90 deg around (0,0) is [0, 10]
|
|
assert_allclose(cast("Polygon", pat.shapes[(1, 0)][0]).vertices[0], [0, 10], atol=1e-10)
|
|
|
|
|
|
def test_pattern_mirror() -> None:
|
|
pat = Pattern()
|
|
pat.polygon((1, 0), vertices=[[10, 5], [11, 5], [10, 6]])
|
|
# Mirror across X axis (y -> -y)
|
|
pat.mirror(0)
|
|
|
|
assert_equal(cast("Polygon", pat.shapes[(1, 0)][0]).vertices[0], [10, -5])
|
|
|
|
|
|
def test_pattern_get_bounds() -> None:
|
|
pat = Pattern()
|
|
pat.polygon((1, 0), vertices=[[0, 0], [10, 0], [10, 10]])
|
|
pat.polygon((1, 0), vertices=[[-5, -5], [5, -5], [5, 5]])
|
|
|
|
bounds = pat.get_bounds()
|
|
assert_equal(bounds, [[-5, -5], [10, 10]])
|
|
|
|
|
|
def test_pattern_flatten_preserves_ports_only_child() -> None:
|
|
child = Pattern(ports={"P1": Port((1, 2), 0)})
|
|
|
|
parent = Pattern()
|
|
parent.ref("child", offset=(10, 10))
|
|
|
|
parent.flatten({"child": child}, flatten_ports=True)
|
|
|
|
assert set(parent.ports) == {"P1"}
|
|
assert parent.ports["P1"].rotation == 0
|
|
assert tuple(parent.ports["P1"].offset) == (11.0, 12.0)
|
|
|
|
|
|
def test_pattern_flatten_repeated_ref_with_ports_raises() -> None:
|
|
child = Pattern(ports={"P1": Port((1, 2), 0)})
|
|
child.polygon((1, 0), vertices=[[0, 0], [1, 0], [0, 1]])
|
|
|
|
parent = Pattern()
|
|
parent.ref("child", repetition=Grid(a_vector=(10, 0), a_count=2))
|
|
|
|
with pytest.raises(PatternError, match='Cannot flatten ports from repeated ref'):
|
|
parent.flatten({"child": child}, flatten_ports=True)
|
|
|
|
|
|
def test_pattern_place_requires_abstract_for_reference() -> None:
|
|
parent = Pattern()
|
|
child = Pattern()
|
|
|
|
with pytest.raises(PatternError, match='Must provide an `Abstract`'):
|
|
parent.place(child)
|
|
|
|
|
|
def test_pattern_interface() -> None:
|
|
source = Pattern()
|
|
source.ports["A"] = Port((10, 20), 0, ptype="test")
|
|
|
|
iface = Pattern.interface(source, in_prefix="in_", out_prefix="out_")
|
|
|
|
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"
|