807 lines
31 KiB
Python
807 lines
31 KiB
Python
# coding=utf-8
|
|
"""
|
|
Test Inkex transformational logic.
|
|
"""
|
|
|
|
from math import sqrt, pi
|
|
|
|
import pytest
|
|
import numpy as np
|
|
from inkex.transforms import (
|
|
Vector2d,
|
|
ImmutableVector2d,
|
|
BoundingBox,
|
|
BoundingInterval,
|
|
Transform,
|
|
DirectedLineSegment,
|
|
)
|
|
from inkex.utils import PY3
|
|
from inkex.tester import TestCase
|
|
|
|
|
|
class ImmutableVector2dTest(TestCase):
|
|
"""Test the ImmutableVector2d object"""
|
|
|
|
@pytest.mark.xfail
|
|
def test_numpy_conversion(self):
|
|
"""Check that vectors work fine in numpy datatypes (they are complex under
|
|
the hood)"""
|
|
import numpy as np
|
|
|
|
vecs = [ImmutableVector2d(1, 2), 1 + 2j, Vector2d(0, 5)]
|
|
res = np.array(vecs)
|
|
assert "complex" in str(res.dtype)
|
|
assert np.allclose(vecs, [1 + 2j, 1 + 2j, 0 + 5j])
|
|
|
|
def test_vector_creation(self):
|
|
"""Test ImmutableVector2d creation"""
|
|
vec0 = ImmutableVector2d(15, 22)
|
|
self.assertEqual(vec0.x, 15)
|
|
self.assertEqual(vec0.y, 22)
|
|
|
|
vec1 = ImmutableVector2d()
|
|
self.assertEqual(vec1.x, 0)
|
|
self.assertEqual(vec1.y, 0)
|
|
|
|
vec2 = ImmutableVector2d((17, 32))
|
|
self.assertEqual(vec2.x, 17)
|
|
self.assertEqual(vec2.y, 32)
|
|
|
|
vec3 = ImmutableVector2d(vec0)
|
|
self.assertEqual(vec3.x, 15)
|
|
self.assertEqual(vec3.y, 22)
|
|
|
|
vec4 = ImmutableVector2d("-5,8")
|
|
self.assertEqual(vec4.x, -5)
|
|
self.assertEqual(vec4.y, 8)
|
|
|
|
self.assertRaises(ValueError, ImmutableVector2d, (1, 2, 3))
|
|
|
|
def test_binary_operators(self):
|
|
"""Test binary operators for vector2d"""
|
|
vec1 = ImmutableVector2d(15, 22)
|
|
vec2 = ImmutableVector2d(5, 3)
|
|
|
|
self.assertTrue((vec1 - vec2).is_close((10, 19)))
|
|
self.assertTrue((vec1 - (5, 3)).is_close((10, 19)))
|
|
self.assertTrue(((15, 22) - vec2).is_close((10, 19)))
|
|
self.assertTrue((vec1 + vec2).is_close((20, 25)))
|
|
self.assertTrue((vec1 + (5, 3)).is_close((20, 25)))
|
|
self.assertTrue(((15, 22) + vec2).is_close((20, 25)))
|
|
self.assertTrue((vec1 * 2).is_close((30, 44)))
|
|
self.assertTrue((2 * vec1).is_close((30, 44)))
|
|
self.assertTrue((vec1 / 2).is_close((7.5, 11)))
|
|
self.assertTrue((vec1.__div__(2)).is_close((7.5, 11)))
|
|
self.assertTrue((vec1 // 2).is_close((7.5, 11)))
|
|
|
|
def test_ioperators(self):
|
|
"""Test operators for vector2d"""
|
|
vec0 = vec = ImmutableVector2d(15, 22)
|
|
vec += (1, 1)
|
|
vec = ImmutableVector2d(vec)
|
|
self.assertTrue(vec.is_close((16, 23)))
|
|
vec -= (10, 20)
|
|
vec = ImmutableVector2d(vec)
|
|
self.assertTrue(vec.is_close((6, 3)))
|
|
vec *= 5
|
|
vec = ImmutableVector2d(vec)
|
|
self.assertTrue(vec.is_close((30, 15)))
|
|
vec /= 90
|
|
vec = ImmutableVector2d(vec)
|
|
self.assertTrue(vec.is_close((1.0 / 3, 1.0 / 6)))
|
|
vec //= 1.0 / 3
|
|
vec = ImmutableVector2d(vec)
|
|
self.assertTrue(vec.is_close((1, 0.5)))
|
|
self.assertTrue(vec0.is_close((15, 22)))
|
|
self.assertFalse(vec0.is_close(vec))
|
|
|
|
def test_unary_operators(self):
|
|
"""Test unary operators"""
|
|
vec = ImmutableVector2d(1, 2)
|
|
self.assertTrue((-vec).is_close((-1, -2)))
|
|
self.assertTrue((+vec).is_close(vec))
|
|
self.assertAlmostEqual(abs(vec), sqrt(5))
|
|
self.assertTrue(+vec is not vec) # returned value is a copy
|
|
|
|
def test_representations(self):
|
|
"""Test ImmutableVector2d Repr"""
|
|
self.assertEqual(str(ImmutableVector2d(1, 2)), "1, 2")
|
|
self.assertEqual(repr(ImmutableVector2d(1, 2)), "Vector2d(1, 2)")
|
|
self.assertEqual(ImmutableVector2d(1, 2).to_tuple(), (1, 2))
|
|
|
|
def test_assign(self):
|
|
"""Test ImmutableVector2d assignement"""
|
|
vec = ImmutableVector2d(10, 20)
|
|
with pytest.raises(AttributeError):
|
|
vec.assign(5, 10)
|
|
|
|
def test_getitem(self):
|
|
"""Test getitem for ImmutableVector2d"""
|
|
vec = ImmutableVector2d(10, 20)
|
|
self.assertEqual(len(vec), 2)
|
|
self.assertEqual(vec[0], 10)
|
|
self.assertEqual(vec[1], 20)
|
|
|
|
def test_cross(self):
|
|
"""Test cross product for ImmutableVector2d"""
|
|
vec1 = ImmutableVector2d(0, 2)
|
|
vec2 = ImmutableVector2d(0, 3)
|
|
vec3 = ImmutableVector2d(0, -3)
|
|
vec4 = ImmutableVector2d(3, 0)
|
|
vec5 = ImmutableVector2d(-3, 0)
|
|
vec6 = ImmutableVector2d(1, 1)
|
|
self.assertAlmostEqual(vec1.cross(vec2), 0)
|
|
self.assertAlmostEqual(vec2.cross(vec3), 0)
|
|
self.assertAlmostEqual(vec1.cross(vec4), -6)
|
|
self.assertAlmostEqual(vec1.cross(vec5), 6)
|
|
self.assertAlmostEqual(vec1.cross(vec6), -2.0)
|
|
self.assertAlmostEqual(vec6.cross(vec1), 2.0)
|
|
|
|
|
|
class Vector2dTest(TestCase):
|
|
"""Test the Vector2d object"""
|
|
|
|
def test_vector_creation(self):
|
|
"""Test Vector2D creation"""
|
|
vec0 = Vector2d(15, 22)
|
|
self.assertEqual(vec0.x, 15)
|
|
self.assertEqual(vec0.y, 22)
|
|
|
|
vec1 = Vector2d()
|
|
self.assertEqual(vec1.x, 0)
|
|
self.assertEqual(vec1.y, 0)
|
|
|
|
vec2 = Vector2d((17, 32))
|
|
self.assertEqual(vec2.x, 17)
|
|
self.assertEqual(vec2.y, 32)
|
|
|
|
vec3 = Vector2d(vec0)
|
|
self.assertEqual(vec3.x, 15)
|
|
self.assertEqual(vec3.y, 22)
|
|
|
|
self.assertRaises(ValueError, Vector2d, (1, 2, 3))
|
|
self.assertRaises(ValueError, Vector2d, 1, 2, 3)
|
|
|
|
def test_vector_default_creation(self):
|
|
"""Test fallback for vectors"""
|
|
|
|
# no fallback
|
|
vec0 = Vector2d("1,2", fallback=None)
|
|
self.assertEqual(vec0.x, 1)
|
|
self.assertEqual(vec0.y, 2)
|
|
|
|
self.assertRaises(ValueError, Vector2d, "a,2")
|
|
|
|
# fallback
|
|
vec0 = Vector2d("a,3", fallback=(1, 2))
|
|
self.assertEqual(vec0.x, 1)
|
|
self.assertEqual(vec0.y, 2)
|
|
|
|
vec0 = Vector2d(("a", "b"), fallback=(1, 2))
|
|
self.assertEqual(vec0.x, 1)
|
|
self.assertEqual(vec0.y, 2)
|
|
|
|
vec0 = Vector2d((3, 4, 5), fallback=(1, 2))
|
|
self.assertEqual(vec0.x, 1)
|
|
self.assertEqual(vec0.y, 2)
|
|
|
|
def test_binary_operators(self):
|
|
"""Test binary operators for vector2d"""
|
|
vec1 = Vector2d(15, 22)
|
|
vec2 = Vector2d(5, 3)
|
|
|
|
self.assertTrue((vec1 - vec2).is_close((10, 19)))
|
|
self.assertTrue((vec1 - (5, 3)).is_close((10, 19)))
|
|
self.assertTrue(((15, 22) - vec2).is_close((10, 19)))
|
|
self.assertTrue((vec1 + vec2).is_close((20, 25)))
|
|
self.assertTrue((vec1 + (5, 3)).is_close((20, 25)))
|
|
self.assertTrue(((15, 22) + vec2).is_close((20, 25)))
|
|
self.assertTrue((vec1 * 2).is_close((30, 44)))
|
|
self.assertTrue((2 * vec1).is_close((30, 44)))
|
|
self.assertTrue((vec1 / 2).is_close((7.5, 11)))
|
|
self.assertTrue((vec1.__div__(2)).is_close((7.5, 11)))
|
|
self.assertTrue((vec1 // 2).is_close((7.5, 11)))
|
|
|
|
def test_ioperators(self):
|
|
"""Test operators for vector2d"""
|
|
vec0 = vec = Vector2d(15, 22)
|
|
vec += (1, 1)
|
|
self.assertTrue(vec.is_close((16, 23)))
|
|
vec -= (10, 20)
|
|
self.assertTrue(vec.is_close((6, 3)))
|
|
vec *= 5
|
|
self.assertTrue(vec.is_close((30, 15)))
|
|
vec /= 90
|
|
self.assertTrue(vec.is_close((1.0 / 3, 1.0 / 6)))
|
|
vec //= 1.0 / 3
|
|
self.assertTrue(vec.is_close((1, 0.5)))
|
|
|
|
def test_unary_operators(self):
|
|
"""Test unary operators"""
|
|
vec = Vector2d(1, 2)
|
|
self.assertTrue((-vec).is_close((-1, -2)))
|
|
self.assertTrue((+vec).is_close(vec))
|
|
self.assertTrue(+vec is not vec) # returned value is a copy
|
|
|
|
def test_representations(self):
|
|
"""Test Vector2D Repr"""
|
|
self.assertEqual(str(Vector2d(1, 2)), "1, 2")
|
|
self.assertEqual(repr(Vector2d(1, 2)), "Vector2d(1, 2)")
|
|
self.assertEqual(Vector2d(1, 2).to_tuple(), (1, 2))
|
|
|
|
def test_getitem(self):
|
|
"""Test getitem for Vector2D"""
|
|
vec = Vector2d(10, 20)
|
|
self.assertEqual(len(vec), 2)
|
|
self.assertEqual(vec[0], 10)
|
|
self.assertEqual(vec[1], 20)
|
|
|
|
def test_polar_operations(self):
|
|
"""Test polar coordinates operations"""
|
|
# x y r pi
|
|
equivilents = [
|
|
(0, 0, 0, 0),
|
|
(0, 0, 0, 1),
|
|
(0, 0, 0, -1),
|
|
(0, 0, 0, 0.5),
|
|
(1, 0, 1, 0),
|
|
(0, 1, 1, 0.5),
|
|
(0, -1, 1, -0.5),
|
|
(3, 0, 3, 0),
|
|
(0, 3, 3, 0.5),
|
|
(0, -3, 3, -0.5),
|
|
(sqrt(2), sqrt(2), 2, 0.25),
|
|
(-sqrt(2), sqrt(2), 2, 0.75),
|
|
(sqrt(2), -sqrt(2), 2, -0.25),
|
|
(-sqrt(2), -sqrt(2), 2, -0.75),
|
|
]
|
|
for x, y, r, t in equivilents:
|
|
theta = t * pi if r != 0 else None
|
|
for ts in [0, 2, -2]:
|
|
ctx_msg = "Test values are x: {} y: {} r: {} θ: {} * pi".format(
|
|
x, y, r, t + ts
|
|
)
|
|
polar = Vector2d.from_polar(r, (t + ts) * pi)
|
|
cart = Vector2d(x, y)
|
|
self.assertEqual(cart.length, r, msg=ctx_msg)
|
|
self.assertEqual(polar.length, r, msg=ctx_msg)
|
|
self.assertAlmostEqual(cart.angle, theta, msg=ctx_msg, delta=1e-12)
|
|
self.assertAlmostEqual(polar.angle, theta, msg=ctx_msg, delta=1e-12)
|
|
self.assertEqual(cart.to_polar_tuple(), (r, cart.angle), msg=ctx_msg)
|
|
self.assertEqual(polar.to_polar_tuple(), (r, polar.angle), msg=ctx_msg)
|
|
self.assertEqual(cart.to_tuple(), (x, y), msg=ctx_msg)
|
|
self.assertAlmostEqual(polar.to_tuple()[0], x, msg=ctx_msg, delta=1e-12)
|
|
self.assertAlmostEqual(polar.to_tuple()[1], y, msg=ctx_msg, delta=1e-12)
|
|
# Test special handling of from_polar with None theta
|
|
self.assertEqual(Vector2d.from_polar(0, None).to_tuple(), (0.0, 0.0))
|
|
self.assertIsNone(Vector2d.from_polar(4, None))
|
|
|
|
|
|
class TransformTest(TestCase):
|
|
"""Test transformation API and calculations"""
|
|
|
|
def test_new_empty(self):
|
|
"""Create a transformation from two triplets matrix"""
|
|
self.assertEqual(Transform(), ((1, 0, 0), (0, 1, 0)))
|
|
|
|
def test_new_from_triples(self):
|
|
"""Create a transformation from two triplets matrix"""
|
|
self.assertEqual(Transform(((1, 2, 3), (4, 5, 6))), ((1, 2, 3), (4, 5, 6)))
|
|
|
|
def test_new_from_sextlet(self):
|
|
"""Create a transformation from a list of six numbers"""
|
|
self.assertEqual(Transform((1, 2, 3, 4, 5, 6)), ((1, 3, 5), (2, 4, 6)))
|
|
|
|
def test_new_from_matrix_str(self):
|
|
"""Create a transformation from a list of six numbers"""
|
|
self.assertEqual(Transform("matrix(1, 2, 3, 4, 5, 6)"), ((1, 3, 5), (2, 4, 6)))
|
|
|
|
def test_new_from_scale(self):
|
|
"""Create a scale based transformation"""
|
|
self.assertEqual(Transform("scale(10)"), ((10, 0, 0), (0, 10, 0)))
|
|
self.assertEqual(Transform("scale(10, 3.3)"), ((10, 0, 0), (0, 3.3, 0)))
|
|
|
|
def test_new_from_translate(self):
|
|
"""Create a translate transformation"""
|
|
self.assertEqual(Transform("translate(12)"), ((1, 0, 12), (0, 1, 0)))
|
|
self.assertEqual(Transform("translate(12, 14)"), ((1, 0, 12), (0, 1, 14)))
|
|
|
|
def test_new_from_rotate(self):
|
|
"""Create a rotational transformation"""
|
|
self.assertEqual(str(Transform("rotate(90)")), "rotate(90)")
|
|
self.assertEqual(
|
|
str(Transform("rotate(90 10 12)")),
|
|
"matrix(6.12323e-17 1 -1 6.12323e-17 22 2)",
|
|
)
|
|
|
|
def test_new_from_skew(self):
|
|
"""Create skew x/y transformations"""
|
|
self.assertEqual(str(Transform("skewX(10)")), "matrix(1 0 0.176327 1 0 0)")
|
|
self.assertEqual(str(Transform("skewY(10)")), "matrix(1 0.176327 0 1 0 0)")
|
|
|
|
def test_invalid_creation_string(self):
|
|
"""Test creating invalid transforms"""
|
|
self.assertEqual(Transform("boo(4)"), ((1, 0, 0), (0, 1, 0)))
|
|
|
|
def test_invalid_creation_matrix(self):
|
|
"""Test creating invalid transforms"""
|
|
self.assertRaises(ValueError, Transform, 0.0)
|
|
self.assertRaises(ValueError, Transform, (0.0,))
|
|
self.assertRaises(ValueError, Transform, (0.0, 0.0, 0.0))
|
|
|
|
def test_repr(self):
|
|
"""Test repr string"""
|
|
self.assertEqual(repr(Transform()), "Transform(((1, 0, 0), (0, 1, 0)))")
|
|
|
|
def test_matrix_inversion(self):
|
|
"""Test the negative of a transformation"""
|
|
self.assertEqual(-Transform("rotate(45)"), Transform("rotate(-45)"))
|
|
self.assertEqual(
|
|
-Transform("translate(12, 10)"), Transform("translate(-12, -10)")
|
|
)
|
|
self.assertEqual(-Transform("scale(4)"), Transform("scale(0.25)"))
|
|
|
|
def test_apply_to_point(self):
|
|
"""Test applying the transformation to a point"""
|
|
trans = Transform("translate(10, 10)")
|
|
self.assertEqual(trans.apply_to_point((10, 10)).to_tuple(), (20, 20))
|
|
self.assertRaises(ValueError, trans.apply_to_point, "")
|
|
|
|
def test_translate(self):
|
|
"""Test making translate specific items"""
|
|
self.assertEqual(
|
|
str(Transform(translate=(10.6, 99.9))), "translate(10.6, 99.9)"
|
|
)
|
|
|
|
def test_scale(self):
|
|
"""Test making scale specific items"""
|
|
self.assertEqual(str(Transform(scale=(1.0, 2.2))), "scale(1, 2.2)")
|
|
|
|
def test_rotate(self):
|
|
"""Test making rotate specific items"""
|
|
self.assertEqual(str(Transform(rotate=45)), "rotate(45)")
|
|
self.assertEqual(
|
|
str(Transform(rotate=(45, 10, 10))),
|
|
"matrix(0.707107 0.707107 -0.707107 0.707107 10 -4.14214)",
|
|
)
|
|
|
|
def test_add_transform(self):
|
|
"""Test add_TRANSFORM syntax for quickly composing known transforms"""
|
|
tr1 = Transform()
|
|
tr1.add_scale(5.0, 1.0)
|
|
self.assertEqual(str(tr1), "scale(5, 1)")
|
|
tr1.add_translate(10, 10)
|
|
self.assertEqual(str(tr1), "matrix(5 0 0 1 50 10)")
|
|
self.assertEqual(str(Transform().add_scale(5.0, 1.0)), "scale(5, 1)")
|
|
self.assertEqual(
|
|
str(Transform().add_scale(5.0, 1.0).add_translate(10, 10)),
|
|
"matrix(5 0 0 1 50 10)",
|
|
)
|
|
tr2 = Transform()
|
|
self.assertTrue(
|
|
tr2.add_scale(1, 1)
|
|
.add_translate(0, 0)
|
|
.add_skewy(0)
|
|
.add_skewx(0)
|
|
.add_rotate(0)
|
|
is tr2
|
|
)
|
|
self.assertEqual(
|
|
str(tr2.add_kwargs(translate=(10, 10), scale=(5.0, 1.0))),
|
|
"matrix(5 0 0 1 50 10)",
|
|
)
|
|
|
|
def test_is_unity(self):
|
|
"""Test that unix matrix looks like rotate, scale, and translate"""
|
|
unity = Transform()
|
|
self.assertTrue(unity.is_rotate())
|
|
self.assertTrue(unity.is_scale())
|
|
self.assertTrue(unity.is_translate())
|
|
|
|
def test_is_rotation(self):
|
|
"""Test that rotations about origin are correctly identified"""
|
|
rot1 = Transform(rotate=21)
|
|
rot2 = Transform(rotate=35)
|
|
rot3 = Transform(rotate=53)
|
|
|
|
self.assertFalse(Transform(translate=1e-9).is_rotate(exactly=True))
|
|
self.assertFalse(Transform(scale=1 + 1e-9).is_rotate(exactly=True))
|
|
self.assertFalse(Transform(skewx=1e-9).is_rotate(exactly=True))
|
|
self.assertFalse(Transform(skewy=1e-9).is_rotate(exactly=True))
|
|
|
|
self.assertTrue(Transform(translate=1e-9).is_rotate(exactly=False))
|
|
self.assertTrue(Transform(scale=1 + 1e-9).is_rotate(exactly=False))
|
|
self.assertTrue(Transform(skewx=1e-9).is_rotate(exactly=False))
|
|
self.assertTrue(Transform(skewy=1e-9).is_rotate(exactly=False))
|
|
|
|
self.assertTrue(rot1.is_rotate())
|
|
self.assertTrue(rot2.is_rotate())
|
|
self.assertTrue(rot3.is_rotate())
|
|
|
|
self.assertFalse(rot1.is_translate())
|
|
self.assertFalse(rot2.is_translate())
|
|
self.assertFalse(rot3.is_translate())
|
|
|
|
self.assertFalse(rot1.is_scale())
|
|
self.assertFalse(rot2.is_scale())
|
|
self.assertFalse(rot3.is_scale())
|
|
|
|
self.assertTrue((rot1 @ rot1).is_rotate())
|
|
self.assertTrue((rot1 @ rot2).is_rotate())
|
|
self.assertTrue((rot1 @ rot2 @ rot3 @ rot2 @ rot1).is_rotate())
|
|
|
|
def test_is_translate(self):
|
|
"""Test that translations are correctly identified"""
|
|
tr1 = Transform(translate=(1.1,))
|
|
tr2 = Transform(translate=(1.3, 2.7))
|
|
tr3 = Transform(translate=(sqrt(2) / 2, pi))
|
|
|
|
self.assertFalse(Transform(rotate=1e-9).is_translate(exactly=True))
|
|
self.assertFalse(Transform(scale=1 + 1e-9).is_translate(exactly=True))
|
|
self.assertFalse(Transform(skewx=1e-9).is_translate(exactly=True))
|
|
self.assertFalse(Transform(skewy=1e-9).is_translate(exactly=True))
|
|
|
|
self.assertTrue(Transform(rotate=1e-9).is_translate(exactly=False))
|
|
self.assertTrue(Transform(scale=1 + 1e-9).is_translate(exactly=False))
|
|
self.assertTrue(Transform(skewx=1e-9).is_translate(exactly=False))
|
|
self.assertTrue(Transform(skewy=1e-9).is_translate(exactly=False))
|
|
|
|
self.assertTrue(tr1.is_translate())
|
|
self.assertTrue(tr2.is_translate())
|
|
self.assertTrue(tr3.is_translate())
|
|
self.assertFalse(tr1.is_rotate())
|
|
self.assertFalse(tr2.is_rotate())
|
|
self.assertFalse(tr3.is_rotate())
|
|
self.assertFalse(tr1.is_scale())
|
|
self.assertFalse(tr2.is_scale())
|
|
self.assertFalse(tr3.is_scale())
|
|
|
|
self.assertTrue((tr1 @ tr1).is_translate())
|
|
self.assertTrue((tr1 @ tr2).is_translate())
|
|
self.assertTrue((tr1 @ tr2 @ tr3 @ tr2 @ tr1).is_translate())
|
|
self.assertFalse(tr1 @ tr2 @ tr3 @ -tr1 @ -tr2 @ -tr3) # is almost unity
|
|
|
|
def test_is_scale(self):
|
|
"""Test that scale transformations are correctly identified"""
|
|
s1 = Transform(scale=(1.1,))
|
|
s2 = Transform(scale=(1.3, 2.7))
|
|
s3 = Transform(scale=(sqrt(2) / 2, pi))
|
|
|
|
self.assertFalse(Transform(translate=1e-9).is_scale(exactly=True))
|
|
self.assertFalse(Transform(rotate=1e-9).is_scale(exactly=True))
|
|
self.assertFalse(Transform(skewx=1e-9).is_scale(exactly=True))
|
|
self.assertFalse(Transform(skewy=1e-9).is_scale(exactly=True))
|
|
|
|
self.assertTrue(Transform(translate=1e-9).is_scale(exactly=False))
|
|
self.assertTrue(Transform(rotate=1e-9).is_scale(exactly=False))
|
|
self.assertTrue(Transform(skewx=1e-9).is_scale(exactly=False))
|
|
self.assertTrue(Transform(skewy=1e-9).is_scale(exactly=False))
|
|
|
|
self.assertFalse(s1.is_translate())
|
|
self.assertFalse(s2.is_translate())
|
|
self.assertFalse(s3.is_translate())
|
|
self.assertFalse(s1.is_rotate())
|
|
self.assertFalse(s2.is_rotate())
|
|
self.assertFalse(s3.is_rotate())
|
|
self.assertTrue(s1.is_scale())
|
|
self.assertTrue(s2.is_scale())
|
|
self.assertTrue(s3.is_scale())
|
|
|
|
def test_rotation_degrees(self):
|
|
"""Test parsing and composition of different rotations"""
|
|
self.assertAlmostEqual(Transform(rotate=30).rotation_degrees(), 30)
|
|
self.assertAlmostEqual(Transform(translate=(10, 20)).rotation_degrees(), 0)
|
|
self.assertAlmostEqual(Transform(scale=(1, 1)).rotation_degrees(), 0)
|
|
|
|
self.assertAlmostEqual(
|
|
Transform(rotate=35, translate=(10, 20)).rotation_degrees(), 35
|
|
)
|
|
self.assertAlmostEqual(
|
|
Transform(rotate=35, translate=(10, 20), scale=5).rotation_degrees(), 35
|
|
)
|
|
self.assertAlmostEqual(
|
|
Transform(rotate=35, translate=(10, 20), scale=(5, 5)).rotation_degrees(),
|
|
35,
|
|
)
|
|
|
|
def rotation_degrees(**kwargs):
|
|
return Transform(**kwargs).rotation_degrees()
|
|
|
|
self.assertRaises(ValueError, rotation_degrees, rotate=35, skewx=1)
|
|
self.assertRaises(ValueError, rotation_degrees, rotate=35, skewy=1)
|
|
self.assertRaises(ValueError, rotation_degrees, rotate=35, scale=(10, 11))
|
|
self.assertRaises(ValueError, rotation_degrees, rotate=35, scale=(10, 11))
|
|
|
|
def test_construction_order(self):
|
|
"""Test transform kwargs construction order"""
|
|
if not PY3:
|
|
self.skipTest("Construction order is known to fail on python2 (by design).")
|
|
return
|
|
|
|
self.assertEqual(
|
|
str(Transform(scale=2.0, translate=(5, 6))), "matrix(2 0 0 2 5 6)"
|
|
)
|
|
self.assertEqual(
|
|
str(Transform(scale=2.0, rotate=45)),
|
|
"matrix(1.41421 1.41421 -1.41421 1.41421 0 0)",
|
|
)
|
|
|
|
x, y, angle = 5, 7, 31
|
|
rotation = Transform(rotate=angle)
|
|
translation = Transform(translate=(x, y))
|
|
|
|
rotation_then_translation = translation @ rotation
|
|
translation_then_rotation = rotation @ translation
|
|
|
|
tr1 = Transform(rotate=angle, translate=(x, y))
|
|
tr2 = Transform(translate=(x, y), rotate=angle)
|
|
|
|
self.assertNotEqual(tr1, tr2)
|
|
self.assertDeepAlmostEqual(tr1.matrix, rotation_then_translation.matrix)
|
|
self.assertDeepAlmostEqual(tr2.matrix, translation_then_rotation.matrix)
|
|
|
|
def test_interpolate(self):
|
|
"""Test interpolate with other transform"""
|
|
t1 = Transform((0, 0, 0, 0, 0, 0))
|
|
t2 = Transform((1, 1, 1, 1, 1, 1))
|
|
val = t1.interpolate(t2, 0.5)
|
|
assert all(getattr(val, a) == pytest.approx(0.5, 1e-3) for a in "abcdef")
|
|
|
|
|
|
class ScaleTest(TestCase):
|
|
"""Test scale class"""
|
|
|
|
def test_creation(self):
|
|
"""Creating scales"""
|
|
self.assertEqual(BoundingInterval(0, 0), (0, 0))
|
|
self.assertEqual(BoundingInterval(1), (1, 1))
|
|
self.assertEqual(BoundingInterval(10), (10, 10))
|
|
self.assertEqual(BoundingInterval(10, 20), (10, 20))
|
|
self.assertEqual(BoundingInterval((2, 50)), (2, 50))
|
|
self.assertEqual(repr(BoundingInterval((5, 10))), "BoundingInterval(5, 10)")
|
|
|
|
def test_center(self):
|
|
"""Center of a scale"""
|
|
self.assertEqual(BoundingInterval(0, 0).center, 0)
|
|
self.assertEqual(BoundingInterval(0, 10).center, 5)
|
|
self.assertEqual(BoundingInterval(-10, 10).center, 0)
|
|
|
|
def test_neg(self):
|
|
"""-Span(...)"""
|
|
self.assertEqual(tuple(-BoundingInterval(-10, 10)), (-10, 10))
|
|
self.assertEqual(tuple(-BoundingInterval(-15, 2)), (-2, 15))
|
|
self.assertEqual(tuple(-BoundingInterval(100, 110)), (-110, -100))
|
|
self.assertEqual(tuple(-BoundingInterval(-110, -100)), (100, 110))
|
|
|
|
def test_size(self):
|
|
"""Size of the scale"""
|
|
self.assertEqual(BoundingInterval(0, 0).size, 0)
|
|
self.assertEqual(BoundingInterval(10, 30).size, 20)
|
|
self.assertEqual(BoundingInterval(-10, 10).size, 20)
|
|
self.assertEqual(BoundingInterval(-30, -10).size, 20)
|
|
|
|
def test_combine(self):
|
|
"""Combine scales together"""
|
|
self.assertEqual(BoundingInterval(9, 10) + BoundingInterval(4, 5), (4, 10))
|
|
self.assertEqual(
|
|
sum([BoundingInterval(4), BoundingInterval(3), BoundingInterval(10)], None),
|
|
(3, 10),
|
|
)
|
|
self.assertEqual(BoundingInterval(2, 2) * 2, (4, 4))
|
|
|
|
def test_errors(self):
|
|
"""Expected errors"""
|
|
self.assertRaises(ValueError, BoundingInterval, "foo")
|
|
|
|
|
|
class BoundingBoxTest(TestCase):
|
|
"""Test bounding box calculations"""
|
|
|
|
def test_bbox(self):
|
|
"""Creating bounding boxes"""
|
|
self.assertEqual(tuple(BoundingBox(1, 3)), ((1, 1), (3, 3)))
|
|
self.assertEqual(tuple(BoundingBox((1, 2), 3)), ((1, 2), (3, 3)))
|
|
self.assertEqual(tuple(BoundingBox(1, (3, 4))), ((1, 1), (3, 4)))
|
|
self.assertEqual(tuple(BoundingBox((1, 2), (3, 4))), ((1, 2), (3, 4)))
|
|
self.assertEqual(
|
|
repr(BoundingBox((1, 2), (3, 4))), "BoundingBox((1, 2),(3, 4))"
|
|
)
|
|
|
|
def test_bbox_sum(self):
|
|
"""Test adding bboxes together"""
|
|
self.assertEqual(
|
|
tuple(BoundingBox((0, 10), (0, 10)) + BoundingBox((-10, 0), (-10, 0))),
|
|
((-10, 10), (-10, 10)),
|
|
)
|
|
ret = sum(
|
|
[
|
|
BoundingBox((-5, 0), (0, 0)),
|
|
BoundingBox((0, 5), (0, 0)),
|
|
BoundingBox((0, 0), (-5, 0)),
|
|
BoundingBox((0, 0), (0, 5)),
|
|
],
|
|
None,
|
|
)
|
|
self.assertEqual(tuple(ret), ((-5, 5), (-5, 5)))
|
|
self.assertEqual(tuple(BoundingBox(-10, 2) + ret), ((-10, 5), (-5, 5)))
|
|
self.assertEqual(tuple(ret + BoundingBox(1, -10)), ((-5, 5), (-10, 5)))
|
|
|
|
def test_bbox_neg(self):
|
|
self.assertEqual(tuple(-BoundingBox(-10, 2)), ((10, 10), (-2, -2)))
|
|
self.assertEqual(
|
|
tuple(-BoundingBox((-10, 15), (2, 10))), ((-15, 10), (-10, -2))
|
|
)
|
|
|
|
def test_bbox_scale(self):
|
|
"""Bounding Boxes can be scaled"""
|
|
self.assertEqual(tuple(BoundingBox(1, 3) * 2), ((2, 2), (6, 6)))
|
|
|
|
def test_bbox_area(self):
|
|
self.assertEqual(BoundingBox((-3, 7), (-5, 5)).area, 100)
|
|
|
|
def test_bbox_anchor_left_right(self):
|
|
"""Bunding box anchoring (left to right)"""
|
|
bbox = BoundingBox((-1, 1), (10, 20))
|
|
self.assertEqual(
|
|
[
|
|
bbox.get_anchor("l", "t", "lr"),
|
|
bbox.get_anchor("m", "t", "lr"),
|
|
bbox.get_anchor("r", "t", "lr"),
|
|
bbox.get_anchor("l", "t", "rl"),
|
|
bbox.get_anchor("m", "t", "rl"),
|
|
bbox.get_anchor("r", "t", "rl"),
|
|
],
|
|
[-1, 0.0, 1, 1, -0.0, -1],
|
|
)
|
|
|
|
def test_bbox_anchor_top_bottom(self):
|
|
"""Bunding box anchoring (top to bottom)"""
|
|
bbox = BoundingBox((10, 20), (-1, 1))
|
|
self.assertEqual(
|
|
[
|
|
bbox.get_anchor("l", "t", "tb"),
|
|
bbox.get_anchor("l", "m", "tb"),
|
|
bbox.get_anchor("l", "b", "tb"),
|
|
bbox.get_anchor("l", "t", "bt"),
|
|
bbox.get_anchor("l", "m", "bt"),
|
|
bbox.get_anchor("l", "b", "bt"),
|
|
],
|
|
[-1, 0.0, 1, 1, -0.0, -1],
|
|
)
|
|
|
|
def test_bbox_anchor_custom(self):
|
|
"""Bounding box anchoring custom angle"""
|
|
bbox = BoundingBox((10, 10), (5, 5))
|
|
self.assertEqual(
|
|
[
|
|
bbox.get_anchor("l", "t", 0),
|
|
bbox.get_anchor("l", "t", 90),
|
|
bbox.get_anchor("l", "t", 180),
|
|
bbox.get_anchor("l", "t", 270),
|
|
bbox.get_anchor("l", "t", 45),
|
|
],
|
|
[10, -5, -10, 5, 3.5355339059327378],
|
|
)
|
|
|
|
def test_bbox_anchor_radial(self):
|
|
"""Bounding box anchoring radial in/out"""
|
|
bbox = BoundingBox((10, 10), (5, 5))
|
|
self.assertRaises(ValueError, bbox.get_anchor, "m", "m", "ro")
|
|
selbox = BoundingBox((100, 100), (100, 100))
|
|
self.assertEqual(int(bbox.get_anchor("m", "m", "ro", selbox)), 130)
|
|
|
|
|
|
class SegmentTest(TestCase):
|
|
"""Test special Segments"""
|
|
|
|
def test_segment_creation(self):
|
|
"""Test segments"""
|
|
self.assertEqual(DirectedLineSegment((1, 2), (3, 4)), (1, 3, 2, 4))
|
|
self.assertEqual(
|
|
repr(DirectedLineSegment((1, 2), (3, 4))),
|
|
"DirectedLineSegment((1, 2), (3, 4))",
|
|
)
|
|
|
|
def test_segment_maths(self):
|
|
"""Segments have calculations"""
|
|
self.assertEqual(DirectedLineSegment((0, 0), (10, 0)).angle, 0)
|
|
self.assertAlmostEqual(
|
|
DirectedLineSegment((0, 0), (0.5 * sqrt(3), 0.5)).angle, pi / 6, delta=1e-6
|
|
)
|
|
|
|
def test_segment_dx(self):
|
|
"""Test segment dx calculation"""
|
|
self.assertEqual(DirectedLineSegment((0, 0), (0, 0)).dx, 0)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (0, 3)).dx, 0)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (3, 0)).dx, 3)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (-3, 0)).dx, -3)
|
|
self.assertEqual(DirectedLineSegment((5, 0), (1, 0)).dx, -4)
|
|
self.assertEqual(DirectedLineSegment((-3, 0), (1, 0)).dx, 4)
|
|
|
|
def test_segment_dy(self):
|
|
"""Test segment dy calculation"""
|
|
self.assertEqual(DirectedLineSegment((0, 0), (0, 0)).dy, 0)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (3, 0)).dy, 0)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (0, 3)).dy, 3)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (0, -3)).dy, -3)
|
|
self.assertEqual(DirectedLineSegment((0, 5), (0, 1)).dy, -4)
|
|
self.assertEqual(DirectedLineSegment((0, -3), (0, 1)).dy, 4)
|
|
|
|
def test_segment_vector(self):
|
|
"""Test segment delta vector"""
|
|
self.assertEqual(DirectedLineSegment((0, 0), (2, 3)).vector.to_tuple(), (2, 3))
|
|
self.assertEqual(
|
|
DirectedLineSegment((-2, -3), (2, 3)).vector.to_tuple(), (4, 6)
|
|
)
|
|
|
|
def test_segment_length(self):
|
|
"""Test segment length calculation"""
|
|
self.assertEqual(DirectedLineSegment((0, 0), (0, 0)).length, 0)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (3, 0)).length, 3)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (-3, 0)).length, 3)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (0, 5)).length, 5)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (0, -5)).length, 5)
|
|
self.assertEqual(DirectedLineSegment((2, 0), (0, 0)).length, 2)
|
|
self.assertEqual(DirectedLineSegment((-2, 0), (0, 0)).length, 2)
|
|
self.assertEqual(DirectedLineSegment((0, 4), (0, 0)).length, 4)
|
|
self.assertEqual(DirectedLineSegment((0, -4), (0, 0)).length, 4)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (3, 4)).length, 5)
|
|
self.assertEqual(DirectedLineSegment((-3, -4), (0, 0)).length, 5)
|
|
|
|
def test_segment_angle(self):
|
|
"""Test segment angle calculation"""
|
|
self.assertEqual(DirectedLineSegment((0, 0), (3, 0)).angle, 0)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (-3, 0)).angle, pi)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (0, 5)).angle, pi / 2)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (0, -5)).angle, -pi / 2)
|
|
self.assertEqual(DirectedLineSegment((2, 0), (0, 0)).angle, pi)
|
|
self.assertEqual(DirectedLineSegment((-2, 0), (0, 0)).angle, 0)
|
|
self.assertEqual(DirectedLineSegment((0, 4), (0, 0)).angle, -pi / 2)
|
|
self.assertEqual(DirectedLineSegment((0, -4), (0, 0)).angle, pi / 2)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (1, 1)).angle, pi / 4)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (-1, 1)).angle, 3 * pi / 4)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (-1, -1)).angle, -3 * pi / 4)
|
|
self.assertEqual(DirectedLineSegment((0, 0), (1, -1)).angle, -pi / 4)
|
|
|
|
|
|
class ExtremaTest(TestCase):
|
|
"""Test school formula implementation"""
|
|
|
|
def test_cubic_extrema_1(self):
|
|
from inkex.transforms import cubic_extrema
|
|
|
|
a, b, c, d = (
|
|
14.644651000000003194,
|
|
-4.881549508464541276,
|
|
-4.8815495084645448287,
|
|
14.644651000000003194,
|
|
)
|
|
cmin, cmax = cubic_extrema(a, b, c, d)
|
|
self.assertAlmostEqual(cmin, 0, delta=1e-6)
|
|
self.assertAlmostEqual(cmax, a, delta=1e-6)
|
|
|
|
def test_quadratic_extrema_1(self):
|
|
from inkex.transforms import quadratic_extrema
|
|
|
|
a, b = 5.0, 12.0
|
|
cmin, cmax = quadratic_extrema(a, b, a)
|
|
self.assertAlmostEqual(cmin, 5, delta=1e-6)
|
|
self.assertAlmostEqual(cmax, 8.5, delta=1e-6)
|
|
|
|
def test_quadratic_extrema_2(self):
|
|
from inkex.transforms import quadratic_extrema
|
|
|
|
a = 5.0
|
|
cmin, cmax = quadratic_extrema(a, a, a)
|
|
self.assertAlmostEqual(cmin, a, delta=1e-6)
|
|
self.assertAlmostEqual(cmax, a, delta=1e-6)
|
|
|
|
@pytest.mark.xfail
|
|
def test_numpy_conversion(self):
|
|
"""Conversion to numpy"""
|
|
|
|
arr = [1 + 2j, Vector2d(2, 3)]
|
|
npa = np.array(arr)
|
|
assert npa.dtype == np.complex128
|
|
assert npa[0] == 1 + 2j
|
|
assert npa[1] == 2 + 3j
|