Mathutils: add a Matrix.LocRotScale constructor for combining channels.

Combining location, rotation and scale channels into a matrix is
a standard task, so while it is easily accomplished by constructing
and multiplying 3 matrices, having a standard utility allows for
more clear code.

The new constructor builds a 4x4 matrix from separate location,
rotation and scale values. Rotation can be represented as a 3x3
Matrix, Quaternion or Euler value, while the other two inputs
are vectors. Unneeded inputs can be replaced with None.

Differential Revision: https://developer.blender.org/D11264
This commit is contained in:
2021-05-14 19:46:19 +03:00
parent f09606cc68
commit a86e815dd8
4 changed files with 134 additions and 2 deletions

View File

@@ -0,0 +1,5 @@
# Compute local object transformation matrix:
if obj.rotation_mode == 'QUATERNION':
matrix = mathutils.Matrix.LocRotScale(obj.location, obj.rotation_quaternion, obj.scale)
else:
matrix = mathutils.Matrix.LocRotScale(obj.location, obj.rotation_euler, obj.scale)

View File

@@ -14,10 +14,14 @@ mat_rot = mathutils.Matrix.Rotation(math.radians(45.0), 4, 'X')
mat_out = mat_loc @ mat_rot @ mat_sca mat_out = mat_loc @ mat_rot @ mat_sca
print(mat_out) print(mat_out)
# extract components back out of the matrix # extract components back out of the matrix as two vectors and a quaternion
loc, rot, sca = mat_out.decompose() loc, rot, sca = mat_out.decompose()
print(loc, rot, sca) print(loc, rot, sca)
# recombine extracted components
mat_out2 = mathutils.Matrix.LocRotScale(loc, rot, sca)
print(mat_out2)
# it can also be useful to access components of a matrix directly # it can also be useful to access components of a matrix directly
mat = mathutils.Matrix() mat = mathutils.Matrix()
mat[0][0], mat[1][0], mat[2][0] = 0.0, 1.0, 2.0 mat[0][0], mat[1][0], mat[2][0] = 0.0, 1.0, 2.0

View File

@@ -969,6 +969,104 @@ static PyObject *C_Matrix_Shear(PyObject *cls, PyObject *args)
return Matrix_CreatePyObject(mat, matSize, matSize, (PyTypeObject *)cls); return Matrix_CreatePyObject(mat, matSize, matSize, (PyTypeObject *)cls);
} }
PyDoc_STRVAR(
C_Matrix_LocRotScale_doc,
".. classmethod:: LocRotScale(location, rotation, scale)\n"
"\n"
" Create a matrix combining translation, rotation and scale,\n"
" acting as the inverse of the decompose() method.\n"
"\n"
" Any of the inputs may be replaced with None if not needed.\n"
"\n"
" :arg location: The translation component.\n"
" :type location: :class:`Vector` or None\n"
" :arg rotation: The rotation component.\n"
" :type rotation: 3x3 :class:`Matrix`, :class:`Quaternion`, :class:`Euler` or None\n"
" :arg scale: The scale component.\n"
" :type scale: :class:`Vector` or None\n"
" :return: Combined transformation matrix. \n"
" :rtype: 4x4 :class:`Matrix`\n");
static PyObject *C_Matrix_LocRotScale(PyObject *cls, PyObject *args)
{
PyObject *loc_obj, *rot_obj, *scale_obj;
float mat[4][4], loc[3];
if (!PyArg_ParseTuple(args, "OOO:Matrix.LocRotScale", &loc_obj, &rot_obj, &scale_obj)) {
return NULL;
}
/* Decode location. */
if (loc_obj == Py_None) {
zero_v3(loc);
}
else if (mathutils_array_parse(
loc, 3, 3, loc_obj, "Matrix.LocRotScale(), invalid location argument") == -1) {
return NULL;
}
/* Decode rotation. */
if (rot_obj == Py_None) {
unit_m4(mat);
}
else if (QuaternionObject_Check(rot_obj)) {
QuaternionObject *quat_obj = (QuaternionObject *)rot_obj;
if (BaseMath_ReadCallback(quat_obj) == -1) {
return NULL;
}
quat_to_mat4(mat, quat_obj->quat);
}
else if (EulerObject_Check(rot_obj)) {
EulerObject *eul_obj = (EulerObject *)rot_obj;
if (BaseMath_ReadCallback(eul_obj) == -1) {
return NULL;
}
eulO_to_mat4(mat, eul_obj->eul, eul_obj->order);
}
else if (MatrixObject_Check(rot_obj)) {
MatrixObject *mat_obj = (MatrixObject *)rot_obj;
if (BaseMath_ReadCallback(mat_obj) == -1) {
return NULL;
}
if (mat_obj->num_col == 3 && mat_obj->num_row == 3) {
copy_m4_m3(mat, (float(*)[3])mat_obj->matrix);
}
else {
PyErr_SetString(PyExc_ValueError,
"Matrix.LocRotScale(): "
"inappropriate rotation matrix size - expects 3x3 matrix");
return NULL;
}
}
else {
PyErr_SetString(PyExc_ValueError,
"Matrix.LocRotScale(): "
"rotation argument must be Matrix, Quaternion, Euler or None");
return NULL;
}
/* Decode scale. */
if (scale_obj != Py_None) {
float scale[3];
if (mathutils_array_parse(
scale, 3, 3, scale_obj, "Matrix.LocRotScale(), invalid scale argument") == -1) {
return NULL;
}
rescale_m4(mat, scale);
}
copy_v3_v3(mat[3], loc);
return Matrix_CreatePyObject(&mat[0][0], 4, 4, (PyTypeObject *)cls);
}
void matrix_as_3x3(float mat[3][3], MatrixObject *self) void matrix_as_3x3(float mat[3][3], MatrixObject *self)
{ {
copy_v3_v3(mat[0], MATRIX_COL_PTR(self, 0)); copy_v3_v3(mat[0], MATRIX_COL_PTR(self, 0));
@@ -3111,6 +3209,10 @@ static struct PyMethodDef Matrix_methods[] = {
(PyCFunction)C_Matrix_OrthoProjection, (PyCFunction)C_Matrix_OrthoProjection,
METH_VARARGS | METH_CLASS, METH_VARARGS | METH_CLASS,
C_Matrix_OrthoProjection_doc}, C_Matrix_OrthoProjection_doc},
{"LocRotScale",
(PyCFunction)C_Matrix_LocRotScale,
METH_VARARGS | METH_CLASS,
C_Matrix_LocRotScale_doc},
{NULL, NULL, 0, NULL}, {NULL, NULL, 0, NULL},
}; };

View File

@@ -2,7 +2,7 @@
# ./blender.bin --background -noaudio --python tests/python/bl_pyapi_mathutils.py -- --verbose # ./blender.bin --background -noaudio --python tests/python/bl_pyapi_mathutils.py -- --verbose
import unittest import unittest
from mathutils import Matrix, Vector, Quaternion from mathutils import Matrix, Vector, Quaternion, Euler
from mathutils import kdtree, geometry from mathutils import kdtree, geometry
import math import math
@@ -233,6 +233,27 @@ class MatrixTesting(unittest.TestCase):
self.assertEqual(mat @ mat, prod_mat) self.assertEqual(mat @ mat, prod_mat)
def test_loc_rot_scale(self):
euler = Euler((math.radians(90), 0, math.radians(90)), 'ZYX')
expected = Matrix(((0, -5, 0, 1),
(0, 0, -6, 2),
(4, 0, 0, 3),
(0, 0, 0, 1)))
result = Matrix.LocRotScale((1, 2, 3), euler, (4, 5, 6))
self.assertAlmostEqualMatrix(result, expected, 4)
result = Matrix.LocRotScale((1, 2, 3), euler.to_quaternion(), (4, 5, 6))
self.assertAlmostEqualMatrix(result, expected, 4)
result = Matrix.LocRotScale((1, 2, 3), euler.to_matrix(), (4, 5, 6))
self.assertAlmostEqualMatrix(result, expected, 4)
def assertAlmostEqualMatrix(self, first, second, size, *, places=6, msg=None, delta=None):
for i in range(size):
for j in range(size):
self.assertAlmostEqual(first[i][j], second[i][j], places=places, msg=msg, delta=delta)
class VectorTesting(unittest.TestCase): class VectorTesting(unittest.TestCase):