2026-02-15 01:41:31 -08:00
|
|
|
import numpy
|
|
|
|
|
from numpy.testing import assert_equal, assert_allclose
|
|
|
|
|
from numpy import pi
|
|
|
|
|
|
2026-02-15 12:36:13 -08:00
|
|
|
from ..utils import remove_duplicate_vertices, remove_colinear_vertices, poly_contains_points, rotation_matrix_2d, apply_transforms
|
2026-02-15 01:41:31 -08:00
|
|
|
|
2026-02-15 12:36:13 -08:00
|
|
|
|
|
|
|
|
def test_remove_duplicate_vertices() -> None:
|
2026-02-15 01:41:31 -08:00
|
|
|
# Closed path (default)
|
|
|
|
|
v = [[0, 0], [1, 1], [1, 1], [2, 2], [0, 0]]
|
|
|
|
|
v_clean = remove_duplicate_vertices(v, closed_path=True)
|
|
|
|
|
# The last [0,0] is a duplicate of the first [0,0] if closed_path=True
|
|
|
|
|
assert_equal(v_clean, [[0, 0], [1, 1], [2, 2]])
|
2026-02-15 12:36:13 -08:00
|
|
|
|
2026-02-15 01:41:31 -08:00
|
|
|
# Open path
|
|
|
|
|
v_clean_open = remove_duplicate_vertices(v, closed_path=False)
|
|
|
|
|
assert_equal(v_clean_open, [[0, 0], [1, 1], [2, 2], [0, 0]])
|
|
|
|
|
|
2026-02-15 12:36:13 -08:00
|
|
|
|
|
|
|
|
def test_remove_colinear_vertices() -> None:
|
2026-02-15 01:41:31 -08:00
|
|
|
v = [[0, 0], [1, 0], [2, 0], [2, 1], [2, 2], [1, 1], [0, 0]]
|
|
|
|
|
v_clean = remove_colinear_vertices(v, closed_path=True)
|
|
|
|
|
# [1, 0] is between [0, 0] and [2, 0]
|
|
|
|
|
# [2, 1] is between [2, 0] and [2, 2]
|
|
|
|
|
# [1, 1] is between [2, 2] and [0, 0]
|
|
|
|
|
assert_equal(v_clean, [[0, 0], [2, 0], [2, 2]])
|
|
|
|
|
|
2026-02-15 12:36:13 -08:00
|
|
|
|
|
|
|
|
def test_remove_colinear_vertices_exhaustive() -> None:
|
2026-02-15 01:41:31 -08:00
|
|
|
# U-turn
|
|
|
|
|
v = [[0, 0], [10, 0], [0, 0]]
|
2026-03-09 03:28:31 -07:00
|
|
|
v_clean = remove_colinear_vertices(v, closed_path=False, preserve_uturns=True)
|
2026-02-15 12:36:13 -08:00
|
|
|
# Open path should keep ends. [10,0] is between [0,0] and [0,0]?
|
2026-03-09 03:28:31 -07:00
|
|
|
# They are colinear, but it's a 180 degree turn.
|
|
|
|
|
# We preserve 180 degree turns if preserve_uturns is True.
|
|
|
|
|
assert len(v_clean) == 3
|
|
|
|
|
|
|
|
|
|
v_collapsed = remove_colinear_vertices(v, closed_path=False, preserve_uturns=False)
|
|
|
|
|
# If not preserving u-turns, it should collapse to just the endpoints
|
|
|
|
|
assert len(v_collapsed) == 2
|
2026-02-15 12:36:13 -08:00
|
|
|
|
2026-02-15 01:41:31 -08:00
|
|
|
# 180 degree U-turn in closed path
|
|
|
|
|
v = [[0, 0], [10, 0], [5, 0]]
|
2026-03-09 03:28:31 -07:00
|
|
|
v_clean = remove_colinear_vertices(v, closed_path=True, preserve_uturns=False)
|
2026-02-15 12:36:13 -08:00
|
|
|
assert len(v_clean) == 2
|
2026-02-15 01:41:31 -08:00
|
|
|
|
2026-02-15 12:36:13 -08:00
|
|
|
|
|
|
|
|
def test_poly_contains_points() -> None:
|
2026-02-15 01:41:31 -08:00
|
|
|
v = [[0, 0], [10, 0], [10, 10], [0, 10]]
|
|
|
|
|
pts = [[5, 5], [-1, -1], [10, 10], [11, 5]]
|
|
|
|
|
inside = poly_contains_points(v, pts)
|
|
|
|
|
assert_equal(inside, [True, False, True, False])
|
|
|
|
|
|
2026-02-15 12:36:13 -08:00
|
|
|
|
|
|
|
|
def test_rotation_matrix_2d() -> None:
|
|
|
|
|
m = rotation_matrix_2d(pi / 2)
|
2026-02-15 01:41:31 -08:00
|
|
|
assert_allclose(m, [[0, -1], [1, 0]], atol=1e-10)
|
|
|
|
|
|
2026-02-15 12:36:13 -08:00
|
|
|
|
|
|
|
|
def test_rotation_matrix_non_manhattan() -> None:
|
2026-02-15 01:41:31 -08:00
|
|
|
# 45 degrees
|
2026-02-15 12:36:13 -08:00
|
|
|
m = rotation_matrix_2d(pi / 4)
|
|
|
|
|
s = numpy.sqrt(2) / 2
|
2026-02-15 01:41:31 -08:00
|
|
|
assert_allclose(m, [[s, -s], [s, s]], atol=1e-10)
|
|
|
|
|
|
2026-02-15 12:36:13 -08:00
|
|
|
|
|
|
|
|
def test_apply_transforms() -> None:
|
2026-02-15 01:41:31 -08:00
|
|
|
# cumulative [x_offset, y_offset, rotation (rad), mirror_x (0 or 1)]
|
|
|
|
|
t1 = [10, 20, 0, 0]
|
|
|
|
|
t2 = [[5, 0, 0, 0], [0, 5, 0, 0]]
|
|
|
|
|
combined = apply_transforms(t1, t2)
|
2026-03-09 02:35:35 -07:00
|
|
|
assert_equal(combined, [[15, 20, 0, 0, 1], [10, 25, 0, 0, 1]])
|
2026-02-15 01:41:31 -08:00
|
|
|
|
2026-02-15 12:36:13 -08:00
|
|
|
|
|
|
|
|
def test_apply_transforms_advanced() -> None:
|
2026-02-15 01:41:31 -08:00
|
|
|
# Ox4: (x, y, rot, mir)
|
|
|
|
|
# Outer: mirror x (axis 0), then rotate 90 deg CCW
|
|
|
|
|
# apply_transforms logic for mirror uses y *= -1 (which is axis 0 mirror)
|
2026-02-15 12:36:13 -08:00
|
|
|
outer = [0, 0, pi / 2, 1]
|
|
|
|
|
|
2026-02-15 01:41:31 -08:00
|
|
|
# Inner: (10, 0, 0, 0)
|
|
|
|
|
inner = [10, 0, 0, 0]
|
2026-02-15 12:36:13 -08:00
|
|
|
|
2026-02-15 01:41:31 -08:00
|
|
|
combined = apply_transforms(outer, inner)
|
|
|
|
|
# 1. mirror inner y if outer mirrored: (10, 0) -> (10, 0)
|
|
|
|
|
# 2. rotate by outer rotation (pi/2): (10, 0) -> (0, 10)
|
|
|
|
|
# 3. add outer offset (0, 0) -> (0, 10)
|
2026-03-09 02:35:35 -07:00
|
|
|
assert_allclose(combined[0], [0, 10, pi / 2, 1, 1], atol=1e-10)
|