143 lines
4.8 KiB
Python
143 lines
4.8 KiB
Python
import pytest
|
|
from typing import cast, TYPE_CHECKING
|
|
from numpy.testing import assert_allclose
|
|
from numpy import pi
|
|
|
|
from ..builder import RenderPather
|
|
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]:
|
|
lib = Library()
|
|
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
|
rp = RenderPather(lib, tools=tool)
|
|
rp.ports["start"] = Port((0, 0), pi / 2, ptype="wire")
|
|
return rp, tool, lib
|
|
|
|
|
|
def test_renderpather_basic(rpather_setup: tuple[RenderPather, PathTool, Library]) -> None:
|
|
rp, tool, lib = rpather_setup
|
|
# Plan two segments
|
|
rp.at("start").straight(10).straight(10)
|
|
|
|
# Before rendering, no shapes in pattern
|
|
assert not rp.pattern.has_shapes()
|
|
assert len(rp.paths["start"]) == 2
|
|
|
|
# Render
|
|
rp.render()
|
|
assert rp.pattern.has_shapes()
|
|
assert len(rp.pattern.shapes[(1, 0)]) == 1
|
|
|
|
# Path vertices should be (0,0), (0,-10), (0,-20)
|
|
# transformed by start port (rot pi/2 -> 270 deg transform)
|
|
# wait, PathTool.render for opcode L uses rotation_matrix_2d(port_rot + pi)
|
|
# 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 = 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)
|
|
|
|
|
|
def test_renderpather_bend(rpather_setup: tuple[RenderPather, PathTool, Library]) -> None:
|
|
rp, tool, lib = rpather_setup
|
|
# Plan straight then bend
|
|
rp.at("start").straight(10).cw(10)
|
|
|
|
rp.render()
|
|
path_shape = cast("Path", rp.pattern.shapes[(1, 0)][0])
|
|
# Path vertices:
|
|
# 1. Start (0,0)
|
|
# 2. Straight end: (0, -10)
|
|
# 3. Bend end: (-1, -20)
|
|
# PathTool.planL(ccw=False, length=10) returns data=[10, -1]
|
|
# start_port for 2nd segment is at (0, -10) with rotation pi/2
|
|
# dxy = rot(pi/2 + pi) @ (10, 0) = (0, -10). So vertex at (0, -20).
|
|
# and final end_port.offset is (-1, -20).
|
|
assert len(path_shape.vertices) == 4
|
|
assert_allclose(path_shape.vertices, [[0, 0], [0, -10], [0, -20], [-1, -20]], atol=1e-10)
|
|
|
|
|
|
def test_renderpather_mirror_preserves_planned_bend_geometry(rpather_setup: tuple[RenderPather, PathTool, Library]) -> None:
|
|
rp, tool, lib = rpather_setup
|
|
rp.at("start").straight(10).cw(10)
|
|
|
|
rp.mirror(0)
|
|
rp.render()
|
|
|
|
path_shape = cast("Path", rp.pattern.shapes[(1, 0)][0])
|
|
assert_allclose(path_shape.vertices, [[0, 0], [0, 10], [0, 20], [-1, 20]], atol=1e-10)
|
|
|
|
|
|
def test_renderpather_retool(rpather_setup: tuple[RenderPather, PathTool, Library]) -> None:
|
|
rp, tool1, lib = rpather_setup
|
|
tool2 = PathTool(layer=(2, 0), width=4, ptype="wire")
|
|
|
|
rp.at("start").straight(10)
|
|
rp.retool(tool2, keys=["start"])
|
|
rp.at("start").straight(10)
|
|
|
|
rp.render()
|
|
# Different tools should cause different batches/shapes
|
|
assert len(rp.pattern.shapes[(1, 0)]) == 1
|
|
assert len(rp.pattern.shapes[(2, 0)]) == 1
|
|
|
|
|
|
def test_renderpather_dead_ports() -> None:
|
|
lib = Library()
|
|
tool = PathTool(layer=(1, 0), width=1)
|
|
rp = RenderPather(lib, ports={"in": Port((0, 0), 0)}, tools=tool)
|
|
rp.set_dead()
|
|
|
|
# Impossible path
|
|
rp.straight("in", -10)
|
|
|
|
# port_rot=0, forward is -x. path(-10) means moving -10 in -x direction -> +10 in x.
|
|
assert_allclose(rp.ports["in"].offset, [10, 0], atol=1e-10)
|
|
|
|
# Verify no render steps were added
|
|
assert len(rp.paths["in"]) == 0
|
|
|
|
# Verify no geometry
|
|
rp.render()
|
|
assert not rp.pattern.has_shapes()
|
|
|
|
|
|
def test_renderpather_rename_port(rpather_setup: tuple[RenderPather, PathTool, Library]) -> None:
|
|
rp, tool, lib = rpather_setup
|
|
rp.at("start").straight(10)
|
|
# Rename port while path is planned
|
|
rp.rename_ports({"start": "new_start"})
|
|
# Continue path on new name
|
|
rp.at("new_start").straight(10)
|
|
|
|
assert "start" not in rp.paths
|
|
assert len(rp.paths["new_start"]) == 2
|
|
|
|
rp.render()
|
|
assert rp.pattern.has_shapes()
|
|
assert len(rp.pattern.shapes[(1, 0)]) == 1
|
|
# Total length 20. start_port rot pi/2 -> 270 deg transform.
|
|
# Vertices (0,0), (0,-10), (0,-20)
|
|
path_shape = cast("Path", rp.pattern.shapes[(1, 0)][0])
|
|
assert_allclose(path_shape.vertices, [[0, 0], [0, -10], [0, -20]], atol=1e-10)
|
|
assert "new_start" in rp.ports
|
|
assert_allclose(rp.ports["new_start"].offset, [0, -20], atol=1e-10)
|
|
|
|
|
|
def test_pathtool_traceL_bend_geometry_matches_ports() -> None:
|
|
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
|
|
|
tree = tool.traceL(True, 10)
|
|
pat = tree.top_pattern()
|
|
path_shape = cast("Path", pat.shapes[(1, 0)][0])
|
|
|
|
assert_allclose(path_shape.vertices, [[0, 0], [10, 0], [10, 1]], atol=1e-10)
|
|
assert_allclose(pat.ports["B"].offset, [10, 1], atol=1e-10)
|