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:
		
							
								
								
									
										5
									
								
								doc/python_api/examples/mathutils.Matrix.LocRotScale.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								doc/python_api/examples/mathutils.Matrix.LocRotScale.py
									
									
									
									
									
										Normal 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)
 | 
				
			||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user