189 lines
7.4 KiB
Python
189 lines
7.4 KiB
Python
from typing import Any
|
|
|
|
import pytest
|
|
import numpy
|
|
from numpy import pi
|
|
from numpy.testing import assert_equal
|
|
|
|
from masque import Pather, Library, Pattern, Port
|
|
from masque.builder.tools import PathTool, Tool
|
|
from masque.error import BuildError, PortError, PatternError
|
|
|
|
|
|
@pytest.fixture
|
|
def trace_into_setup() -> tuple[Pather, PathTool, Library]:
|
|
lib = Library()
|
|
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
|
p = Pather(lib, tools=tool, auto_render=True, auto_render_append=False)
|
|
return p, tool, lib
|
|
|
|
def test_path_into_straight(trace_into_setup: tuple[Pather, PathTool, Library]) -> None:
|
|
p, _tool, _lib = trace_into_setup
|
|
p.ports["src"] = Port((0, 0), 0, ptype="wire")
|
|
p.ports["dst"] = Port((-20, 0), pi, ptype="wire")
|
|
|
|
p.trace_into("src", "dst")
|
|
|
|
assert "src" not in p.ports
|
|
assert "dst" not in p.ports
|
|
assert len(p.pattern.refs) == 1
|
|
|
|
def test_path_into_bend(trace_into_setup: tuple[Pather, PathTool, Library]) -> None:
|
|
p, _tool, _lib = trace_into_setup
|
|
p.ports["src"] = Port((0, 0), 0, ptype="wire")
|
|
p.ports["dst"] = Port((-20, -20), 3 * pi / 2, ptype="wire")
|
|
|
|
p.trace_into("src", "dst")
|
|
|
|
assert "src" not in p.ports
|
|
assert "dst" not in p.ports
|
|
# `trace_into()` batches internal legs before auto-rendering so the operation
|
|
# rolls back cleanly on later failures.
|
|
assert len(p.pattern.refs) == 1
|
|
|
|
def test_path_into_sbend(trace_into_setup: tuple[Pather, PathTool, Library]) -> None:
|
|
p, _tool, _lib = trace_into_setup
|
|
p.ports["src"] = Port((0, 0), 0, ptype="wire")
|
|
p.ports["dst"] = Port((-20, -10), pi, ptype="wire")
|
|
|
|
p.trace_into("src", "dst")
|
|
|
|
assert "src" not in p.ports
|
|
assert "dst" not in p.ports
|
|
|
|
def test_path_into_thru(trace_into_setup: tuple[Pather, PathTool, Library]) -> None:
|
|
p, _tool, _lib = trace_into_setup
|
|
p.ports["src"] = Port((0, 0), 0, ptype="wire")
|
|
p.ports["dst"] = Port((-20, 0), pi, ptype="wire")
|
|
p.ports["other"] = Port((10, 10), 0)
|
|
|
|
p.trace_into("src", "dst", thru="other")
|
|
|
|
assert "src" in p.ports
|
|
assert_equal(p.ports["src"].offset, [10, 10])
|
|
assert "other" not in p.ports
|
|
|
|
def test_pather_trace_into() -> None:
|
|
lib = Library()
|
|
tool = PathTool(layer='M1', width=1000)
|
|
p = Pather(lib, tools=tool, auto_render=False)
|
|
|
|
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
|
p.pattern.ports['B'] = Port((-10000, 0), rotation=pi)
|
|
p.at('A').trace_into('B', plug_destination=False)
|
|
assert 'B' in p.pattern.ports
|
|
assert 'A' in p.pattern.ports
|
|
assert numpy.allclose(p.pattern.ports['A'].offset, (-10000, 0))
|
|
|
|
p.pattern.ports['C'] = Port((0, 0), rotation=0)
|
|
p.pattern.ports['D'] = Port((-5000, 5000), rotation=pi/2)
|
|
p.at('C').trace_into('D', plug_destination=False)
|
|
assert 'D' in p.pattern.ports
|
|
assert 'C' in p.pattern.ports
|
|
assert numpy.allclose(p.pattern.ports['C'].offset, (-5000, 5000))
|
|
|
|
p.pattern.ports['E'] = Port((0, 0), rotation=0)
|
|
p.pattern.ports['F'] = Port((-10000, 2000), rotation=pi)
|
|
p.at('E').trace_into('F', plug_destination=False)
|
|
assert 'F' in p.pattern.ports
|
|
assert 'E' in p.pattern.ports
|
|
assert numpy.allclose(p.pattern.ports['E'].offset, (-10000, 2000))
|
|
|
|
p.pattern.ports['G'] = Port((0, 0), rotation=0)
|
|
p.pattern.ports['H'] = Port((-10000, 2000), rotation=0)
|
|
p.at('G').trace_into('H', plug_destination=False)
|
|
assert 'H' in p.pattern.ports
|
|
assert 'G' in p.pattern.ports
|
|
assert numpy.allclose(p.pattern.ports['G'].offset, (-10000, 2000))
|
|
assert p.pattern.ports['G'].rotation is not None
|
|
assert numpy.isclose(p.pattern.ports['G'].rotation, pi)
|
|
|
|
p.pattern.ports['I'] = Port((0, 0), rotation=pi / 2)
|
|
p.pattern.ports['J'] = Port((0, -10000), rotation=3 * pi / 2)
|
|
p.at('I').trace_into('J', plug_destination=False)
|
|
assert 'J' in p.pattern.ports
|
|
assert 'I' in p.pattern.ports
|
|
assert numpy.allclose(p.pattern.ports['I'].offset, (0, -10000))
|
|
assert p.pattern.ports['I'].rotation is not None
|
|
assert numpy.isclose(p.pattern.ports['I'].rotation, pi / 2)
|
|
|
|
def test_pather_trace_into_dead_updates_ports_without_geometry() -> None:
|
|
lib = Library()
|
|
tool = PathTool(layer='M1', width=1000, ptype='wire')
|
|
p = Pather(lib, tools=tool, auto_render=False)
|
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
|
p.pattern.ports['B'] = Port((-10000, 0), rotation=pi, ptype='wire')
|
|
p.set_dead()
|
|
|
|
p.trace_into('A', 'B', plug_destination=False)
|
|
|
|
assert set(p.pattern.ports) == {'A', 'B'}
|
|
assert numpy.allclose(p.pattern.ports['A'].offset, (-10000, 0))
|
|
assert p.pattern.ports['A'].rotation is not None
|
|
assert numpy.isclose(p.pattern.ports['A'].rotation, 0)
|
|
assert len(p.paths['A']) == 0
|
|
assert not p.pattern.has_shapes()
|
|
assert not p.pattern.has_refs()
|
|
|
|
def test_pather_trace_into_failure_rolls_back_ports_and_paths() -> None:
|
|
lib = Library()
|
|
tool = PathTool(layer='M1', width=1, ptype='wire')
|
|
p = Pather(lib, tools=tool)
|
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
|
p.pattern.ports['B'] = Port((-5, 5), rotation=pi / 2, ptype='wire')
|
|
|
|
with pytest.raises(BuildError, match='does not match path ptype'):
|
|
p.trace_into('A', 'B', plug_destination=False, out_ptype='other')
|
|
|
|
assert numpy.allclose(p.pattern.ports['A'].offset, (0, 0))
|
|
assert numpy.isclose(p.pattern.ports['A'].rotation, 0)
|
|
assert numpy.allclose(p.pattern.ports['B'].offset, (-5, 5))
|
|
assert numpy.isclose(p.pattern.ports['B'].rotation, pi / 2)
|
|
assert len(p.paths['A']) == 0
|
|
|
|
def test_pather_trace_into_rename_failure_rolls_back_ports_and_paths() -> None:
|
|
lib = Library()
|
|
tool = PathTool(layer='M1', width=1, ptype='wire')
|
|
p = Pather(lib, tools=tool)
|
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
|
p.pattern.ports['B'] = Port((-10, 0), rotation=pi, ptype='wire')
|
|
p.pattern.ports['other'] = Port((3, 4), rotation=0, ptype='wire')
|
|
|
|
with pytest.raises(PortError, match='overwritten'):
|
|
p.trace_into('A', 'B', plug_destination=False, thru='other')
|
|
|
|
assert set(p.pattern.ports) == {'A', 'B', 'other'}
|
|
assert numpy.allclose(p.pattern.ports['A'].offset, (0, 0))
|
|
assert numpy.allclose(p.pattern.ports['B'].offset, (-10, 0))
|
|
assert numpy.allclose(p.pattern.ports['other'].offset, (3, 4))
|
|
assert len(p.paths['A']) == 0
|
|
|
|
@pytest.mark.parametrize(
|
|
('dst', 'kwargs', 'match'),
|
|
(
|
|
(Port((-5, 5), rotation=pi / 2, ptype='wire'), {'x': -99}, r'trace_to\(\) arguments: x'),
|
|
(Port((-10, 2), rotation=pi, ptype='wire'), {'length': 1}, r'jog\(\) arguments: length'),
|
|
(Port((-10, 2), rotation=0, ptype='wire'), {'length': 1}, r'uturn\(\) arguments: length'),
|
|
),
|
|
)
|
|
def test_pather_trace_into_rejects_reserved_route_kwargs(
|
|
dst: Port,
|
|
kwargs: dict[str, Any],
|
|
match: str,
|
|
) -> None:
|
|
lib = Library()
|
|
tool = PathTool(layer='M1', width=1, ptype='wire')
|
|
p = Pather(lib, tools=tool)
|
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
|
p.pattern.ports['B'] = dst
|
|
|
|
with pytest.raises(BuildError, match=match):
|
|
p.trace_into('A', 'B', plug_destination=False, **kwargs)
|
|
|
|
assert numpy.allclose(p.pattern.ports['A'].offset, (0, 0))
|
|
assert numpy.isclose(p.pattern.ports['A'].rotation, 0)
|
|
assert numpy.allclose(p.pattern.ports['B'].offset, dst.offset)
|
|
assert dst.rotation is not None
|
|
assert p.pattern.ports['B'].rotation is not None
|
|
assert numpy.isclose(p.pattern.ports['B'].rotation, dst.rotation)
|
|
assert len(p.paths['A']) == 0
|