bl_pyapi_mathutils.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. # Apache License, Version 2.0
  2. # ./blender.bin --background -noaudio --python tests/python/bl_pyapi_mathutils.py -- --verbose
  3. import unittest
  4. from mathutils import Matrix, Vector, Quaternion
  5. from mathutils import kdtree
  6. import math
  7. # keep globals immutable
  8. vector_data = (
  9. (1.0, 0.0, 0.0),
  10. (0.0, 1.0, 0.0),
  11. (0.0, 0.0, 1.0),
  12. (1.0, 1.0, 1.0),
  13. (0.33783, 0.715698, -0.611206),
  14. (-0.944031, -0.326599, -0.045624),
  15. (-0.101074, -0.416443, -0.903503),
  16. (0.799286, 0.49411, -0.341949),
  17. (-0.854645, 0.518036, 0.033936),
  18. (0.42514, -0.437866, -0.792114),
  19. (-0.358948, 0.597046, 0.717377),
  20. (-0.985413, 0.144714, 0.089294),
  21. )
  22. # get data at different scales
  23. vector_data = sum(
  24. (tuple(tuple(a * scale for a in v) for v in vector_data)
  25. for scale in (s * sign for s in (0.0001, 0.1, 1.0, 10.0, 1000.0, 100000.0)
  26. for sign in (1.0, -1.0))), ()) + ((0.0, 0.0, 0.0),)
  27. class MatrixTesting(unittest.TestCase):
  28. def test_matrix_column_access(self):
  29. # mat =
  30. # [ 1 2 3 4 ]
  31. # [ 1 2 3 4 ]
  32. # [ 1 2 3 4 ]
  33. mat = Matrix(((1, 11, 111),
  34. (2, 22, 222),
  35. (3, 33, 333),
  36. (4, 44, 444)))
  37. self.assertEqual(mat[0], Vector((1, 11, 111)))
  38. self.assertEqual(mat[1], Vector((2, 22, 222)))
  39. self.assertEqual(mat[2], Vector((3, 33, 333)))
  40. self.assertEqual(mat[3], Vector((4, 44, 444)))
  41. def test_item_access(self):
  42. args = ((1, 4, 0, -1),
  43. (2, -1, 2, -2),
  44. (0, 3, 8, 3),
  45. (-2, 9, 1, 0))
  46. mat = Matrix(args)
  47. for row in range(4):
  48. for col in range(4):
  49. self.assertEqual(mat[row][col], args[row][col])
  50. self.assertEqual(mat[0][2], 0)
  51. self.assertEqual(mat[3][1], 9)
  52. self.assertEqual(mat[2][3], 3)
  53. self.assertEqual(mat[0][0], 1)
  54. self.assertEqual(mat[3][3], 0)
  55. def test_item_assignment(self):
  56. mat = Matrix() - Matrix()
  57. indices = (0, 0), (1, 3), (2, 0), (3, 2), (3, 1)
  58. checked_indices = []
  59. for row, col in indices:
  60. mat[row][col] = 1
  61. for row in range(4):
  62. for col in range(4):
  63. if mat[row][col]:
  64. checked_indices.append((row, col))
  65. for item in checked_indices:
  66. self.assertIn(item, indices)
  67. def test_matrix_to_3x3(self):
  68. # mat =
  69. # [ 1 2 3 4 ]
  70. # [ 2 4 6 8 ]
  71. # [ 3 6 9 12 ]
  72. # [ 4 8 12 16 ]
  73. mat = Matrix(tuple((i, 2 * i, 3 * i, 4 * i) for i in range(1, 5)))
  74. mat_correct = Matrix(((1, 2, 3), (2, 4, 6), (3, 6, 9)))
  75. self.assertEqual(mat.to_3x3(), mat_correct)
  76. def test_matrix_to_translation(self):
  77. mat = Matrix()
  78. mat[0][3] = 1
  79. mat[1][3] = 2
  80. mat[2][3] = 3
  81. self.assertEqual(mat.to_translation(), Vector((1, 2, 3)))
  82. def test_matrix_translation(self):
  83. mat = Matrix()
  84. mat.translation = Vector((1, 2, 3))
  85. self.assertEqual(mat[0][3], 1)
  86. self.assertEqual(mat[1][3], 2)
  87. self.assertEqual(mat[2][3], 3)
  88. def test_matrix_non_square_matmul(self):
  89. mat1 = Matrix(((1, 2, 3),
  90. (4, 5, 6)))
  91. mat2 = Matrix(((1, 2),
  92. (3, 4),
  93. (5, 6)))
  94. prod_mat1 = Matrix(((22, 28),
  95. (49, 64)))
  96. prod_mat2 = Matrix(((9, 12, 15),
  97. (19, 26, 33),
  98. (29, 40, 51)))
  99. self.assertEqual(mat1 @ mat2, prod_mat1)
  100. self.assertEqual(mat2 @ mat1, prod_mat2)
  101. def test_mat4x4_vec3D_matmul(self):
  102. mat = Matrix(((1, 0, 2, 0),
  103. (0, 6, 0, 0),
  104. (0, 0, 1, 1),
  105. (0, 0, 0, 1)))
  106. vec = Vector((1, 2, 3))
  107. prod_mat_vec = Vector((7, 12, 4))
  108. prod_vec_mat = Vector((1, 12, 5))
  109. self.assertEqual(mat @ vec, prod_mat_vec)
  110. self.assertEqual(vec @ mat, prod_vec_mat)
  111. def test_mat_vec_matmul(self):
  112. mat1 = Matrix()
  113. vec = Vector((1, 2))
  114. self.assertRaises(ValueError, mat1.__matmul__, vec)
  115. self.assertRaises(ValueError, vec.__matmul__, mat1)
  116. mat2 = Matrix(((1, 2),
  117. (-2, 3)))
  118. prod = Vector((5, 4))
  119. self.assertEqual(mat2 @ vec, prod)
  120. def test_matrix_square_matmul(self):
  121. mat1 = Matrix(((1, 0),
  122. (1, 2)))
  123. mat2 = Matrix(((1, 2),
  124. (-2, 3)))
  125. prod1 = Matrix(((1, 2),
  126. (-3, 8)))
  127. prod2 = Matrix(((3, 4),
  128. (1, 6)))
  129. self.assertEqual(mat1 @ mat2, prod1)
  130. self.assertEqual(mat2 @ mat1, prod2)
  131. """
  132. # tests for element-wise multiplication
  133. def test_matrix_mul(self):
  134. mat1 = Matrix(((1, 0),
  135. (1, 2)))
  136. mat2 = Matrix(((1, 2),
  137. (-2, 3)))
  138. mat3 = Matrix(((1, 0, 2, 0),
  139. (0, 6, 0, 0),
  140. (0, 0, 1, 1),
  141. (0, 0, 0, 1)))
  142. prod = Matrix(((1, 0),
  143. (-2, 6)))
  144. self.assertEqual(mat1 * mat2, prod)
  145. self.assertEqual(mat2 * mat1, prod)
  146. self.assertRaises(ValueError, mat1.__mul__, mat3)
  147. """
  148. def test_matrix_inverse(self):
  149. mat = Matrix(((1, 4, 0, -1),
  150. (2, -1, 2, -2),
  151. (0, 3, 8, 3),
  152. (-2, 9, 1, 0)))
  153. inv_mat = (1 / 285) * Matrix(((195, -57, 27, -102),
  154. (50, -19, 4, 6),
  155. (-60, 57, 18, 27),
  156. (110, -133, 43, -78)))
  157. self.assertEqual(mat.inverted(), inv_mat)
  158. def test_matrix_inverse_safe(self):
  159. mat = Matrix(((1, 4, 0, -1),
  160. (2, -1, 0, -2),
  161. (0, 3, 0, 3),
  162. (-2, 9, 0, 0)))
  163. # Warning, if we change epsilon in py api we have to update this!!!
  164. epsilon = 1e-8
  165. inv_mat_safe = mat.copy()
  166. inv_mat_safe[0][0] += epsilon
  167. inv_mat_safe[1][1] += epsilon
  168. inv_mat_safe[2][2] += epsilon
  169. inv_mat_safe[3][3] += epsilon
  170. inv_mat_safe.invert()
  171. '''
  172. inv_mat_safe = Matrix(((1.0, -0.5, 0.0, -0.5),
  173. (0.222222, -0.111111, -0.0, 0.0),
  174. (-333333344.0, 316666656.0, 100000000.0, 150000000.0),
  175. (0.888888, -0.9444444, 0.0, -0.5)))
  176. '''
  177. self.assertEqual(mat.inverted_safe(), inv_mat_safe)
  178. def test_matrix_matmult(self):
  179. mat = Matrix(((1, 4, 0, -1),
  180. (2, -1, 2, -2),
  181. (0, 3, 8, 3),
  182. (-2, 9, 1, 0)))
  183. prod_mat = Matrix(((11, -9, 7, -9),
  184. (4, -3, 12, 6),
  185. (0, 48, 73, 18),
  186. (16, -14, 26, -13)))
  187. self.assertEqual(mat @ mat, prod_mat)
  188. class VectorTesting(unittest.TestCase):
  189. def test_orthogonal(self):
  190. angle_90d = math.pi / 2.0
  191. for v in vector_data:
  192. v = Vector(v)
  193. if v.length_squared != 0.0:
  194. self.assertAlmostEqual(v.angle(v.orthogonal()), angle_90d)
  195. def test_vector_matmul(self):
  196. # produces dot product for vectors
  197. vec1 = Vector((1, 3, 5))
  198. vec2 = Vector((1, 2))
  199. self.assertRaises(ValueError, vec1.__matmul__, vec2)
  200. self.assertEqual(vec1 @ vec1, 35)
  201. self.assertEqual(vec2 @ vec2, 5)
  202. def test_vector_imatmul(self):
  203. vec = Vector((1, 3, 5))
  204. with self.assertRaises(TypeError):
  205. vec @= vec
  206. """
  207. # tests for element-wise multiplication
  208. def test_vector_mul(self):
  209. # element-wise multiplication
  210. vec1 = Vector((1, 3, 5))
  211. vec2 = Vector((1, 2))
  212. prod1 = Vector((1, 9, 25))
  213. prod2 = Vector((2, 6, 10))
  214. self.assertRaises(ValueError, vec1.__mul__, vec2)
  215. self.assertEqual(vec1 * vec1, prod1)
  216. self.assertEqual(2 * vec1, prod2)
  217. def test_vector_imul(self):
  218. # inplace element-wise multiplication
  219. vec = Vector((1, 3, 5))
  220. prod1 = Vector((1, 9, 25))
  221. prod2 = Vector((2, 18, 50))
  222. vec *= vec
  223. self.assertEqual(vec, prod1)
  224. vec *= 2
  225. self.assertEqual(vec, prod2)
  226. """
  227. class QuaternionTesting(unittest.TestCase):
  228. def test_to_expmap(self):
  229. q = Quaternion((0, 0, 1), math.radians(90))
  230. e = q.to_exponential_map()
  231. self.assertAlmostEqual(e.x, 0)
  232. self.assertAlmostEqual(e.y, 0)
  233. self.assertAlmostEqual(e.z, math.radians(90), 6)
  234. def test_expmap_axis_normalization(self):
  235. q = Quaternion((1, 1, 0), 2)
  236. e = q.to_exponential_map()
  237. self.assertAlmostEqual(e.x, 2 * math.sqrt(0.5), 6)
  238. self.assertAlmostEqual(e.y, 2 * math.sqrt(0.5), 6)
  239. self.assertAlmostEqual(e.z, 0)
  240. def test_from_expmap(self):
  241. e = Vector((1, 1, 0))
  242. q = Quaternion(e)
  243. axis, angle = q.to_axis_angle()
  244. self.assertAlmostEqual(angle, math.sqrt(2), 6)
  245. self.assertAlmostEqual(axis.x, math.sqrt(0.5), 6)
  246. self.assertAlmostEqual(axis.y, math.sqrt(0.5), 6)
  247. self.assertAlmostEqual(axis.z, 0)
  248. class KDTreeTesting(unittest.TestCase):
  249. @staticmethod
  250. def kdtree_create_grid_3d_data(tot):
  251. index = 0
  252. mul = 1.0 / (tot - 1)
  253. for x in range(tot):
  254. for y in range(tot):
  255. for z in range(tot):
  256. yield (x * mul, y * mul, z * mul), index
  257. index += 1
  258. @staticmethod
  259. def kdtree_create_grid_3d(tot, *, filter_fn=None):
  260. k = kdtree.KDTree(tot * tot * tot)
  261. for co, index in KDTreeTesting.kdtree_create_grid_3d_data(tot):
  262. if (filter_fn is not None) and (not filter_fn(co, index)):
  263. continue
  264. k.insert(co, index)
  265. k.balance()
  266. return k
  267. def assertAlmostEqualVector(self, first, second, places=7, msg=None, delta=None):
  268. self.assertAlmostEqual(first[0], second[0], places=places, msg=msg, delta=delta)
  269. self.assertAlmostEqual(first[1], second[1], places=places, msg=msg, delta=delta)
  270. self.assertAlmostEqual(first[2], second[2], places=places, msg=msg, delta=delta)
  271. def test_kdtree_single(self):
  272. co = (0,) * 3
  273. index = 2
  274. k = kdtree.KDTree(1)
  275. k.insert(co, index)
  276. k.balance()
  277. co_found, index_found, dist_found = k.find(co)
  278. self.assertEqual(tuple(co_found), co)
  279. self.assertEqual(index_found, index)
  280. self.assertEqual(dist_found, 0.0)
  281. def test_kdtree_empty(self):
  282. co = (0,) * 3
  283. k = kdtree.KDTree(0)
  284. k.balance()
  285. co_found, index_found, dist_found = k.find(co)
  286. self.assertIsNone(co_found)
  287. self.assertIsNone(index_found)
  288. self.assertIsNone(dist_found)
  289. def test_kdtree_line(self):
  290. tot = 10
  291. k = kdtree.KDTree(tot)
  292. for i in range(tot):
  293. k.insert((i,) * 3, i)
  294. k.balance()
  295. co_found, index_found, dist_found = k.find((-1,) * 3)
  296. self.assertEqual(tuple(co_found), (0,) * 3)
  297. co_found, index_found, dist_found = k.find((tot,) * 3)
  298. self.assertEqual(tuple(co_found), (tot - 1,) * 3)
  299. def test_kdtree_grid(self):
  300. size = 10
  301. k = self.kdtree_create_grid_3d(size)
  302. # find_range
  303. ret = k.find_range((0.5,) * 3, 2.0)
  304. self.assertEqual(len(ret), size * size * size)
  305. ret = k.find_range((1.0,) * 3, 1.0 / size)
  306. self.assertEqual(len(ret), 1)
  307. ret = k.find_range((1.0,) * 3, 2.0 / size)
  308. self.assertEqual(len(ret), 8)
  309. ret = k.find_range((10,) * 3, 0.5)
  310. self.assertEqual(len(ret), 0)
  311. # find_n
  312. tot = 0
  313. ret = k.find_n((1.0,) * 3, tot)
  314. self.assertEqual(len(ret), tot)
  315. tot = 10
  316. ret = k.find_n((1.0,) * 3, tot)
  317. self.assertEqual(len(ret), tot)
  318. self.assertEqual(ret[0][2], 0.0)
  319. tot = size * size * size
  320. ret = k.find_n((1.0,) * 3, tot)
  321. self.assertEqual(len(ret), tot)
  322. def test_kdtree_grid_filter_simple(self):
  323. size = 10
  324. k = self.kdtree_create_grid_3d(size)
  325. # filter exact index
  326. ret_regular = k.find((1.0,) * 3)
  327. ret_filter = k.find((1.0,) * 3, filter=lambda i: i == ret_regular[1])
  328. self.assertEqual(ret_regular, ret_filter)
  329. ret_filter = k.find((-1.0,) * 3, filter=lambda i: i == ret_regular[1])
  330. self.assertEqual(ret_regular[:2], ret_filter[:2]) # ignore distance
  331. def test_kdtree_grid_filter_pairs(self):
  332. size = 10
  333. k_all = self.kdtree_create_grid_3d(size)
  334. k_odd = self.kdtree_create_grid_3d(size, filter_fn=lambda co, i: (i % 2) == 1)
  335. k_evn = self.kdtree_create_grid_3d(size, filter_fn=lambda co, i: (i % 2) == 0)
  336. samples = 5
  337. mul = 1 / (samples - 1)
  338. for x in range(samples):
  339. for y in range(samples):
  340. for z in range(samples):
  341. co = (x * mul, y * mul, z * mul)
  342. ret_regular = k_odd.find(co)
  343. self.assertEqual(ret_regular[1] % 2, 1)
  344. ret_filter = k_all.find(co, lambda i: (i % 2) == 1)
  345. self.assertAlmostEqualVector(ret_regular, ret_filter)
  346. ret_regular = k_evn.find(co)
  347. self.assertEqual(ret_regular[1] % 2, 0)
  348. ret_filter = k_all.find(co, lambda i: (i % 2) == 0)
  349. self.assertAlmostEqualVector(ret_regular, ret_filter)
  350. # filter out all values (search odd tree for even values and the reverse)
  351. co = (0,) * 3
  352. ret_filter = k_odd.find(co, lambda i: (i % 2) == 0)
  353. self.assertEqual(ret_filter[1], None)
  354. ret_filter = k_evn.find(co, lambda i: (i % 2) == 1)
  355. self.assertEqual(ret_filter[1], None)
  356. def test_kdtree_invalid_size(self):
  357. with self.assertRaises(ValueError):
  358. kdtree.KDTree(-1)
  359. def test_kdtree_invalid_balance(self):
  360. co = (0,) * 3
  361. index = 2
  362. k = kdtree.KDTree(2)
  363. k.insert(co, index)
  364. k.balance()
  365. k.insert(co, index)
  366. with self.assertRaises(RuntimeError):
  367. k.find(co)
  368. def test_kdtree_invalid_filter(self):
  369. k = kdtree.KDTree(1)
  370. k.insert((0,) * 3, 0)
  371. k.balance()
  372. # not callable
  373. with self.assertRaises(TypeError):
  374. k.find((0,) * 3, filter=None)
  375. # no args
  376. with self.assertRaises(TypeError):
  377. k.find((0,) * 3, filter=lambda: None)
  378. # bad return value
  379. with self.assertRaises(ValueError):
  380. k.find((0,) * 3, filter=lambda i: None)
  381. if __name__ == '__main__':
  382. import sys
  383. sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
  384. unittest.main()