226 lines
7.7 KiB
Python
226 lines
7.7 KiB
Python
import pytest
|
|
from numpy.testing import assert_allclose
|
|
from numpy import pi
|
|
|
|
from masque.builder.tools import AutoTool
|
|
from masque.pattern import Pattern
|
|
from masque.ports import Port
|
|
from masque.library import Library
|
|
from masque.builder.pather import Pather, RenderPather
|
|
|
|
def make_straight(length, width=2, ptype="wire"):
|
|
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(R, width=2, ptype="wire", clockwise=True):
|
|
pat = Pattern()
|
|
# 90 degree arc approximation (just two rects for start and end)
|
|
if clockwise:
|
|
# (0,0) rot 0 to (R, -R) rot pi/2
|
|
pat.rect((1, 0), xmin=0, xmax=R, yctr=0, ly=width)
|
|
pat.rect((1, 0), xctr=R, lx=width, ymin=-R, ymax=0)
|
|
pat.ports["A"] = Port((0, 0), 0, ptype=ptype)
|
|
pat.ports["B"] = Port((R, -R), pi/2, ptype=ptype)
|
|
else:
|
|
# (0,0) rot 0 to (R, R) rot -pi/2
|
|
pat.rect((1, 0), xmin=0, xmax=R, yctr=0, ly=width)
|
|
pat.rect((1, 0), xctr=R, lx=width, ymin=0, ymax=R)
|
|
pat.ports["A"] = Port((0, 0), 0, ptype=ptype)
|
|
pat.ports["B"] = Port((R, R), -pi/2, ptype=ptype)
|
|
return pat
|
|
|
|
@pytest.fixture
|
|
def multi_bend_tool():
|
|
lib = Library()
|
|
|
|
# Bend 1: R=2
|
|
lib["b1"] = make_bend(2, ptype="wire")
|
|
b1_abs = lib.abstract("b1")
|
|
# Bend 2: R=5
|
|
lib["b2"] = make_bend(5, ptype="wire")
|
|
b2_abs = lib.abstract("b2")
|
|
|
|
tool = AutoTool(
|
|
straights=[
|
|
# Straight 1: only for length < 10
|
|
AutoTool.Straight(ptype="wire", fn=make_straight, in_port_name="A", out_port_name="B", length_range=(0, 10)),
|
|
# Straight 2: for length >= 10
|
|
AutoTool.Straight(ptype="wire", fn=lambda l: make_straight(l, width=4), in_port_name="A", out_port_name="B", length_range=(10, 1e8))
|
|
],
|
|
bends=[
|
|
AutoTool.Bend(b1_abs, "A", "B", clockwise=True, mirror=True),
|
|
AutoTool.Bend(b2_abs, "A", "B", clockwise=True, mirror=True)
|
|
],
|
|
sbends=[],
|
|
transitions={},
|
|
default_out_ptype="wire"
|
|
)
|
|
return tool, lib
|
|
|
|
def test_autotool_planL_selection(multi_bend_tool) -> None:
|
|
tool, _ = multi_bend_tool
|
|
|
|
# Small length: should pick straight 1 and bend 1 (R=2)
|
|
# L = straight + R. If L=5, straight=3.
|
|
p, data = tool.planL(True, 5)
|
|
assert data.straight.length_range == (0, 10)
|
|
assert data.straight_length == 3
|
|
assert data.bend.abstract.name == "b1"
|
|
assert_allclose(p.offset, [5, 2])
|
|
|
|
# Large length: should pick straight 2 and bend 1 (R=2)
|
|
# If L=15, straight=13.
|
|
p, data = tool.planL(True, 15)
|
|
assert data.straight.length_range == (10, 1e8)
|
|
assert data.straight_length == 13
|
|
assert_allclose(p.offset, [15, 2])
|
|
|
|
def test_autotool_planU_consistency(multi_bend_tool) -> None:
|
|
tool, lib = multi_bend_tool
|
|
|
|
# length=10, jog=20.
|
|
# U-turn: Straight1 -> Bend1 -> Straight_mid -> Straight3(0) -> Bend2
|
|
# X = L1_total - R2 = length
|
|
# Y = R1 + L2_mid + R2 = jog
|
|
|
|
p, data = tool.planU(20, length=10)
|
|
assert data.ldata0.straight_length == 7
|
|
assert data.ldata0.bend.abstract.name == "b2"
|
|
assert data.l2_length == 13
|
|
assert data.ldata1.straight_length == 0
|
|
assert data.ldata1.bend.abstract.name == "b1"
|
|
|
|
def test_autotool_planS_double_L(multi_bend_tool) -> None:
|
|
tool, lib = multi_bend_tool
|
|
|
|
# length=20, jog=10. S-bend (ccw1, cw2)
|
|
# X = L1_total + R2 = length
|
|
# Y = R1 + L2_mid + R2 = jog
|
|
|
|
p, data = tool.planS(20, 10)
|
|
assert_allclose(p.offset, [20, 10])
|
|
assert_allclose(p.rotation, pi)
|
|
|
|
assert data.ldata0.straight_length == 16
|
|
assert data.ldata1.straight_length == 0
|
|
assert data.l2_length == 6
|
|
|
|
|
|
def test_autotool_planS_pure_sbend_with_transition_dx() -> None:
|
|
lib = Library()
|
|
|
|
def make_straight(length: float) -> Pattern:
|
|
pat = Pattern()
|
|
pat.ports["A"] = Port((0, 0), 0, ptype="core")
|
|
pat.ports["B"] = Port((length, 0), pi, ptype="core")
|
|
return pat
|
|
|
|
def make_sbend(jog: float) -> Pattern:
|
|
pat = Pattern()
|
|
pat.ports["A"] = Port((0, 0), 0, ptype="core")
|
|
pat.ports["B"] = Port((10, jog), pi, ptype="core")
|
|
return pat
|
|
|
|
trans_pat = Pattern()
|
|
trans_pat.ports["EXT"] = Port((0, 0), 0, ptype="ext")
|
|
trans_pat.ports["CORE"] = Port((5, 0), pi, ptype="core")
|
|
lib["xin"] = trans_pat
|
|
|
|
tool = AutoTool(
|
|
straights=[
|
|
AutoTool.Straight(
|
|
ptype="core",
|
|
fn=make_straight,
|
|
in_port_name="A",
|
|
out_port_name="B",
|
|
length_range=(1, 1e8),
|
|
)
|
|
],
|
|
bends=[],
|
|
sbends=[
|
|
AutoTool.SBend(
|
|
ptype="core",
|
|
fn=make_sbend,
|
|
in_port_name="A",
|
|
out_port_name="B",
|
|
jog_range=(0, 1e8),
|
|
)
|
|
],
|
|
transitions={
|
|
("ext", "core"): AutoTool.Transition(lib.abstract("xin"), "EXT", "CORE"),
|
|
},
|
|
default_out_ptype="core",
|
|
)
|
|
|
|
p, data = tool.planS(15, 4, in_ptype="ext")
|
|
|
|
assert_allclose(p.offset, [15, 4])
|
|
assert_allclose(p.rotation, pi)
|
|
assert data.straight_length == 0
|
|
assert data.jog_remaining == 4
|
|
assert data.in_transition is not None
|
|
|
|
|
|
def test_renderpather_autotool_double_L(multi_bend_tool) -> None:
|
|
tool, lib = multi_bend_tool
|
|
rp = RenderPather(lib, tools=tool)
|
|
rp.ports["A"] = Port((0,0), 0, ptype="wire")
|
|
|
|
# This should trigger double-L fallback in planS
|
|
rp.jog("A", 10, length=20)
|
|
|
|
# port_rot=0 -> forward is -x. jog=10 (left) is -y.
|
|
assert_allclose(rp.ports["A"].offset, [-20, -10])
|
|
assert_allclose(rp.ports["A"].rotation, 0) # jog rot is pi relative to input, input rot is pi relative to port.
|
|
# Wait, planS returns out_port at (length, jog) rot pi relative to input (0,0) rot 0.
|
|
# Input rot relative to port is pi.
|
|
# Rotate (length, jog) rot pi by pi: (-length, -jog) rot 0. Correct.
|
|
|
|
rp.render()
|
|
assert len(rp.pattern.refs) > 0
|
|
|
|
def test_pather_uturn_fallback_no_heuristic(multi_bend_tool) -> None:
|
|
tool, lib = multi_bend_tool
|
|
|
|
class BasicTool(AutoTool):
|
|
def planU(self, *args, **kwargs):
|
|
raise NotImplementedError()
|
|
|
|
tool_basic = BasicTool(
|
|
straights=tool.straights,
|
|
bends=tool.bends,
|
|
sbends=tool.sbends,
|
|
transitions=tool.transitions,
|
|
default_out_ptype=tool.default_out_ptype
|
|
)
|
|
|
|
p = Pather(lib, tools=tool_basic)
|
|
p.ports["A"] = Port((0,0), 0, ptype="wire") # facing West (Actually East points Inwards, West is Extension)
|
|
|
|
# uturn jog=10, length=5.
|
|
# R=2. L1 = 5+2=7. L2 = 10-2=8.
|
|
p.uturn("A", 10, length=5)
|
|
|
|
# port_rot=0 -> forward is -x. jog=10 (left) is -y.
|
|
# L1=7 along -x -> (-7, 0). Bend1 (ccw) -> rot -pi/2 (South).
|
|
# L2=8 along -y -> (-7, -8). Bend2 (ccw) -> rot 0 (East).
|
|
# wait. CCW turn from facing South (-y): turn towards East (+x).
|
|
# Wait.
|
|
# Input facing -x. CCW turn -> face -y.
|
|
# Input facing -y. CCW turn -> face +x.
|
|
# So final rotation is 0.
|
|
# Bend1 (ccw) relative to -x: global offset is (-7, -2)?
|
|
# Let's re-run my manual calculation.
|
|
# Port rot 0. Wire input rot pi. Wire output relative to input:
|
|
# L1=7, R1=2, CCW=True. Output (7, 2) rot pi/2.
|
|
# Rotate wire by pi: output (-7, -2) rot 3pi/2.
|
|
# Second turn relative to (-7, -2) rot 3pi/2:
|
|
# local output (8, 2) rot pi/2.
|
|
# global: (-7, -2) + 8*rot(3pi/2)*x + 2*rot(3pi/2)*y
|
|
# = (-7, -2) + 8*(0, -1) + 2*(1, 0) = (-7, -2) + (0, -8) + (2, 0) = (-5, -10).
|
|
# YES! ACTUAL result was (-5, -10).
|
|
assert_allclose(p.ports["A"].offset, [-5, -10])
|
|
assert_allclose(p.ports["A"].rotation, pi)
|