masque/masque/test/test_pather_autotool.py

107 lines
3.7 KiB
Python

import pytest
import numpy
from numpy import pi
from numpy.testing import assert_allclose
from masque import Pather, Library, Pattern, Port
from masque.builder.tools import AutoTool
def make_straight(length: float, width: float = 2, ptype: str = "wire") -> Pattern:
pat = Pattern()
pat.rect((1, 0), xmin=0, xmax=length, yctr=0, ly=width)
pat.ports["A"] = Port((0, 0), 0, ptype=ptype)
pat.ports["B"] = Port((length, 0), pi, ptype=ptype)
return pat
def make_bend(radius: float, width: float = 2, ptype: str = "wire", clockwise: bool = True) -> Pattern:
pat = Pattern()
# Rectangular approximation of a 90 degree bend.
if clockwise:
pat.rect((1, 0), xmin=0, xmax=radius, yctr=0, ly=width)
pat.rect((1, 0), xctr=radius, lx=width, ymin=-radius, ymax=0)
pat.ports["A"] = Port((0, 0), 0, ptype=ptype)
pat.ports["B"] = Port((radius, -radius), pi/2, ptype=ptype)
else:
pat.rect((1, 0), xmin=0, xmax=radius, yctr=0, ly=width)
pat.rect((1, 0), xctr=radius, lx=width, ymin=0, ymax=radius)
pat.ports["A"] = Port((0, 0), 0, ptype=ptype)
pat.ports["B"] = Port((radius, radius), -pi/2, ptype=ptype)
return pat
@pytest.fixture
def multi_bend_tool() -> tuple[AutoTool, Library]:
lib = Library()
lib["b1"] = make_bend(2, ptype="wire")
b1_abs = lib.abstract("b1")
lib["b2"] = make_bend(5, ptype="wire")
b2_abs = lib.abstract("b2")
tool = (
AutoTool()
.add_straight(make_straight, "wire", "A", length_range=(0, 10))
.add_straight(lambda length: make_straight(length, width=4), "wire", "A", length_range=(10, 1e8))
.add_bend(b1_abs, "A", "B", clockwise=True, mirror=True)
.add_bend(b2_abs, "A", "B", clockwise=True, mirror=True)
)
return tool, lib
def test_autotool_uturn() -> None:
from masque.builder.tools import AutoTool
lib = Library()
def make_straight(length: float) -> Pattern:
pat = Pattern()
pat.rect(layer='M1', xmin=0, xmax=length, yctr=0, ly=1000)
pat.ports['in'] = Port((0, 0), 0)
pat.ports['out'] = Port((length, 0), pi)
return pat
bend_pat = Pattern()
bend_pat.polygon(layer='M1', vertices=[(0, -500), (0, 500), (1000, -500)])
bend_pat.ports['in'] = Port((0, 0), 0)
bend_pat.ports['out'] = Port((500, -500), pi/2)
lib['bend'] = bend_pat
tool = (
AutoTool()
.add_straight(make_straight, 'wire', 'in')
.add_bend(lib.abstract('bend'), 'in', 'out', clockwise=True)
)
p = Pather(lib, tools=tool)
p.pattern.ports['A'] = Port((0, 0), 0)
p.at('A').uturn(offset=-2000, length=1000)
# U-turn plan output is transformed into the port extension frame.
assert numpy.allclose(p.pattern.ports['A'].offset, (-1000, 2000))
assert p.pattern.ports['A'].rotation is not None
assert numpy.isclose(p.pattern.ports['A'].rotation, pi)
def test_deferred_render_autotool_double_L(multi_bend_tool: tuple[AutoTool, Library]) -> None:
tool, lib = multi_bend_tool
rp = Pather(lib, tools=tool)
rp.ports["A"] = Port((0,0), 0, ptype="wire")
rp.jog("A", 10, length=20)
assert_allclose(rp.ports["A"].offset, [-20, -10])
assert_allclose(rp.ports["A"].rotation, 0)
rp.render()
assert len(rp.pattern.refs) > 0
def test_pather_uturn_fallback_no_heuristic(multi_bend_tool: tuple[AutoTool, Library]) -> None:
tool, lib = multi_bend_tool
p = Pather(lib, tools=tool)
p.ports["A"] = Port((0,0), 0, ptype="wire")
p.uturn("A", 10, length=5)
# Fallback U-turn uses two CCW bends: (7, 2) then (8, 2) in local tool frames,
# yielding a global endpoint at (-5, -10).
assert_allclose(p.ports["A"].offset, [-5, -10])
assert_allclose(p.ports["A"].rotation, pi)