mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-06 10:41:20 -08:00
194 lines
6 KiB
Python
194 lines
6 KiB
Python
"""
|
|
build123d imports
|
|
|
|
name: test_matrix.py
|
|
by: Gumyr
|
|
date: January 22, 2025
|
|
|
|
desc:
|
|
This python module contains tests for the build123d project.
|
|
|
|
license:
|
|
|
|
Copyright 2025 Gumyr
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
"""
|
|
|
|
import copy
|
|
import math
|
|
import unittest
|
|
|
|
from OCP.gp import gp_Ax1, gp_Dir, gp_Pnt, gp_Trsf
|
|
from build123d.geometry import Axis, Matrix, Vector
|
|
|
|
|
|
class TestMatrix(unittest.TestCase):
|
|
def test_matrix_creation_and_access(self):
|
|
def matrix_vals(m):
|
|
return [[m[r, c] for c in range(4)] for r in range(4)]
|
|
|
|
# default constructor creates a 4x4 identity matrix
|
|
m = Matrix()
|
|
identity = [
|
|
[1.0, 0.0, 0.0, 0.0],
|
|
[0.0, 1.0, 0.0, 0.0],
|
|
[0.0, 0.0, 1.0, 0.0],
|
|
[0.0, 0.0, 0.0, 1.0],
|
|
]
|
|
self.assertEqual(identity, matrix_vals(m))
|
|
|
|
vals4x4 = [
|
|
[1.0, 0.0, 0.0, 1.0],
|
|
[0.0, 1.0, 0.0, 2.0],
|
|
[0.0, 0.0, 1.0, 3.0],
|
|
[0.0, 0.0, 0.0, 1.0],
|
|
]
|
|
vals4x4_tuple = tuple(tuple(r) for r in vals4x4)
|
|
|
|
# test constructor with 16-value input
|
|
m = Matrix(vals4x4)
|
|
self.assertEqual(vals4x4, matrix_vals(m))
|
|
m = Matrix(vals4x4_tuple)
|
|
self.assertEqual(vals4x4, matrix_vals(m))
|
|
|
|
# test constructor with 12-value input (the last 4 are an implied
|
|
# [0,0,0,1])
|
|
m = Matrix(vals4x4[:3])
|
|
self.assertEqual(vals4x4, matrix_vals(m))
|
|
m = Matrix(vals4x4_tuple[:3])
|
|
self.assertEqual(vals4x4, matrix_vals(m))
|
|
|
|
# Test 16-value input with invalid values for the last 4
|
|
invalid = [
|
|
[1.0, 0.0, 0.0, 1.0],
|
|
[0.0, 1.0, 0.0, 2.0],
|
|
[0.0, 0.0, 1.0, 3.0],
|
|
[1.0, 2.0, 3.0, 4.0],
|
|
]
|
|
with self.assertRaises(ValueError):
|
|
Matrix(invalid)
|
|
# Test input with invalid type
|
|
with self.assertRaises(TypeError):
|
|
Matrix("invalid")
|
|
# Test input with invalid size / nested types
|
|
with self.assertRaises(TypeError):
|
|
Matrix([[1, 2, 3, 4], [1, 2, 3], [1, 2, 3, 4]])
|
|
with self.assertRaises(TypeError):
|
|
Matrix([1, 2, 3])
|
|
|
|
# Invalid sub-type
|
|
with self.assertRaises(TypeError):
|
|
Matrix([[1, 2, 3, 4], "abc", [1, 2, 3, 4]])
|
|
|
|
# test out-of-bounds access
|
|
m = Matrix()
|
|
with self.assertRaises(IndexError):
|
|
m[0, 4]
|
|
with self.assertRaises(IndexError):
|
|
m[4, 0]
|
|
with self.assertRaises(IndexError):
|
|
m["ab"]
|
|
|
|
# test __repr__ methods
|
|
m = Matrix(vals4x4)
|
|
mRepr = "Matrix([[1.0, 0.0, 0.0, 1.0],\n [0.0, 1.0, 0.0, 2.0],\n [0.0, 0.0, 1.0, 3.0],\n [0.0, 0.0, 0.0, 1.0]])"
|
|
self.assertEqual(repr(m), mRepr)
|
|
self.assertEqual(str(eval(repr(m))), mRepr)
|
|
|
|
def test_matrix_functionality(self):
|
|
# Test rotate methods
|
|
def matrix_almost_equal(m, target_matrix):
|
|
for r, row in enumerate(target_matrix):
|
|
for c, target_value in enumerate(row):
|
|
self.assertAlmostEqual(m[r, c], target_value)
|
|
|
|
root_3_over_2 = math.sqrt(3) / 2
|
|
m_rotate_x_30 = [
|
|
[1, 0, 0, 0],
|
|
[0, root_3_over_2, -1 / 2, 0],
|
|
[0, 1 / 2, root_3_over_2, 0],
|
|
[0, 0, 0, 1],
|
|
]
|
|
mx = Matrix()
|
|
mx.rotate(Axis.X, math.radians(30))
|
|
matrix_almost_equal(mx, m_rotate_x_30)
|
|
|
|
m_rotate_y_30 = [
|
|
[root_3_over_2, 0, 1 / 2, 0],
|
|
[0, 1, 0, 0],
|
|
[-1 / 2, 0, root_3_over_2, 0],
|
|
[0, 0, 0, 1],
|
|
]
|
|
my = Matrix()
|
|
my.rotate(Axis.Y, math.radians(30))
|
|
matrix_almost_equal(my, m_rotate_y_30)
|
|
|
|
m_rotate_z_30 = [
|
|
[root_3_over_2, -1 / 2, 0, 0],
|
|
[1 / 2, root_3_over_2, 0, 0],
|
|
[0, 0, 1, 0],
|
|
[0, 0, 0, 1],
|
|
]
|
|
mz = Matrix()
|
|
mz.rotate(Axis.Z, math.radians(30))
|
|
matrix_almost_equal(mz, m_rotate_z_30)
|
|
|
|
# Test matrix multiply vector
|
|
v = Vector(1, 0, 0)
|
|
self.assertAlmostEqual(mz.multiply(v), (root_3_over_2, 1 / 2, 0), 7)
|
|
|
|
# Test matrix multiply matrix
|
|
m_rotate_xy_30 = [
|
|
[root_3_over_2, 0, 1 / 2, 0],
|
|
[1 / 4, root_3_over_2, -root_3_over_2 / 2, 0],
|
|
[-root_3_over_2 / 2, 1 / 2, 3 / 4, 0],
|
|
[0, 0, 0, 1],
|
|
]
|
|
mxy = mx.multiply(my)
|
|
matrix_almost_equal(mxy, m_rotate_xy_30)
|
|
|
|
# Test matrix inverse
|
|
vals4x4 = [[1, 2, 3, 4], [5, 1, 6, 7], [8, 9, 1, 10], [0, 0, 0, 1]]
|
|
vals4x4_invert = [
|
|
[-53 / 144, 25 / 144, 1 / 16, -53 / 144],
|
|
[43 / 144, -23 / 144, 1 / 16, -101 / 144],
|
|
[37 / 144, 7 / 144, -1 / 16, -107 / 144],
|
|
[0, 0, 0, 1],
|
|
]
|
|
m = Matrix(vals4x4).inverse()
|
|
matrix_almost_equal(m, vals4x4_invert)
|
|
|
|
# Test matrix created from transfer function
|
|
rot_x = gp_Trsf()
|
|
θ = math.pi
|
|
rot_x.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)), θ)
|
|
m = Matrix(rot_x)
|
|
rot_x_matrix = [
|
|
[1, 0, 0, 0],
|
|
[0, math.cos(θ), -math.sin(θ), 0],
|
|
[0, math.sin(θ), math.cos(θ), 0],
|
|
[0, 0, 0, 1],
|
|
]
|
|
matrix_almost_equal(m, rot_x_matrix)
|
|
|
|
# Test copy
|
|
m2 = copy.copy(m)
|
|
matrix_almost_equal(m2, rot_x_matrix)
|
|
m3 = copy.deepcopy(m)
|
|
matrix_almost_equal(m3, rot_x_matrix)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|