From 89db50a712358330a1794a83701881715dde6905 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 22 Dec 2011 01:05:03 +0000 Subject: patch [#29534] Change Matrix Representation and Access in Python to Conform with Standard Notation from Andrew Hale Scripts which access matrix row/columns directly and scripts that create new matrices with elements defined will need updating. For more info see... * Guide for updating scripts http://wiki.blender.org/index.php/User:TrumanBlending/Matrix_Indexing * Discussion thread http://markmail.org/message/4bpqpxkcvq4wjyfu --- source/blender/python/mathutils/mathutils_Matrix.c | 98 ++++++++++++---------- source/tests/bl_pyapi_mathutils.py | 74 +++++++++++++--- 2 files changed, 119 insertions(+), 53 deletions(-) diff --git a/source/blender/python/mathutils/mathutils_Matrix.c b/source/blender/python/mathutils/mathutils_Matrix.c index 19675d2a863..de098ce8bd8 100644 --- a/source/blender/python/mathutils/mathutils_Matrix.c +++ b/source/blender/python/mathutils/mathutils_Matrix.c @@ -42,7 +42,7 @@ static PyObject *Matrix_copy(MatrixObject *self); static int Matrix_ass_slice(MatrixObject *self, int begin, int end, PyObject *value); static PyObject *matrix__apply_to_copy(PyNoArgsFunction matrix_func, MatrixObject *self); -/* matrix vector callbacks */ +/* matrix row callbacks */ int mathutils_matrix_vector_cb_index= -1; static int mathutils_matrix_vector_check(BaseMathObject *bmo) @@ -51,56 +51,56 @@ static int mathutils_matrix_vector_check(BaseMathObject *bmo) return BaseMath_ReadCallback(self); } -static int mathutils_matrix_vector_get(BaseMathObject *bmo, int col) +static int mathutils_matrix_vector_get(BaseMathObject *bmo, int row) { MatrixObject *self= (MatrixObject *)bmo->cb_user; - int row; + int col; if (BaseMath_ReadCallback(self) == -1) return -1; - for (row=0; row < self->num_row; row++) { - bmo->data[row] = MATRIX_ITEM(self, row, col); + for (col=0; col < self->num_col; col++) { + bmo->data[col] = MATRIX_ITEM(self, row, col); } return 0; } -static int mathutils_matrix_vector_set(BaseMathObject *bmo, int col) +static int mathutils_matrix_vector_set(BaseMathObject *bmo, int row) { MatrixObject *self= (MatrixObject *)bmo->cb_user; - int row; + int col; if (BaseMath_ReadCallback(self) == -1) return -1; - for (row=0; row < self->num_row; row++) { - MATRIX_ITEM(self, row, col) = bmo->data[row]; + for (col=0; col < self->num_col; col++) { + MATRIX_ITEM(self, row, col) = bmo->data[col]; } (void)BaseMath_WriteCallback(self); return 0; } -static int mathutils_matrix_vector_get_index(BaseMathObject *bmo, int col, int row) +static int mathutils_matrix_vector_get_index(BaseMathObject *bmo, int row, int col) { MatrixObject *self= (MatrixObject *)bmo->cb_user; if (BaseMath_ReadCallback(self) == -1) return -1; - bmo->data[row]= MATRIX_ITEM(self, row, col); + bmo->data[col]= MATRIX_ITEM(self, row, col); return 0; } -static int mathutils_matrix_vector_set_index(BaseMathObject *bmo, int col, int row) +static int mathutils_matrix_vector_set_index(BaseMathObject *bmo, int row, int col) { MatrixObject *self= (MatrixObject *)bmo->cb_user; if (BaseMath_ReadCallback(self) == -1) return -1; - MATRIX_ITEM(self, row, col) = bmo->data[row]; + MATRIX_ITEM(self, row, col) = bmo->data[col]; (void)BaseMath_WriteCallback(self); return 0; @@ -215,15 +215,19 @@ static PyObject *Matrix_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *arg= PyTuple_GET_ITEM(args, 0); + /* Input is now as a sequence of rows so length of sequence + * is the number of rows */ /* -1 is an error, size checks will accunt for this */ - const unsigned short num_col= PySequence_Size(arg); + const unsigned short num_row= PySequence_Size(arg); - if (num_col >= 2 && num_col <= 4) { + if (num_row >= 2 && num_row <= 4) { PyObject *item= PySequence_GetItem(arg, 0); - const unsigned short num_row= PySequence_Size(item); + /* Since each item is a row, number of items is the + * same as the number of columns */ + const unsigned short num_col= PySequence_Size(item); Py_XDECREF(item); - if (num_row >= 2 && num_row <= 4) { + if (num_col >= 2 && num_col <= 4) { /* sane row & col size, new matrix and assign as slice */ PyObject *matrix= Matrix_CreatePyObject(NULL, num_col, num_row, Py_NEW, type); if (Matrix_ass_slice((MatrixObject *)matrix, 0, INT_MAX, arg) == 0) { @@ -1364,13 +1368,13 @@ static PyObject *Matrix_repr(MatrixObject *self) if (BaseMath_ReadCallback(self) == -1) return NULL; - for (col = 0; col < self->num_col; col++) { - rows[col]= PyTuple_New(self->num_row); - for (row = 0; row < self->num_row; row++) { - PyTuple_SET_ITEM(rows[col], row, PyFloat_FromDouble(MATRIX_ITEM(self, row, col))); + for (row = 0; row < self->num_row; row++) { + rows[row]= PyTuple_New(self->num_col); + for (col = 0; col < self->num_col; col++) { + PyTuple_SET_ITEM(rows[row], col, PyFloat_FromDouble(MATRIX_ITEM(self, row, col))); } } - switch (self->num_col) { + switch (self->num_row) { case 2: return PyUnicode_FromFormat("Matrix((%R,\n" " %R))", rows[0], rows[1]); @@ -1468,44 +1472,48 @@ static PyObject* Matrix_richcmpr(PyObject *a, PyObject *b, int op) sequence length*/ static int Matrix_len(MatrixObject *self) { - return (self->num_col); + return (self->num_row); } /*----------------------------object[]--------------------------- sequence accessor (get) the wrapped vector gives direct access to the matrix data*/ -static PyObject *Matrix_item(MatrixObject *self, int i) +static PyObject *Matrix_item(MatrixObject *self, int row) { if (BaseMath_ReadCallback(self) == -1) return NULL; - if (i < 0 || i >= self->num_col) { + if (row < 0 || row >= self->num_row) { PyErr_SetString(PyExc_IndexError, "matrix[attribute]: " "array index out of range"); return NULL; } - return Vector_CreatePyObject_cb((PyObject *)self, self->num_row, mathutils_matrix_vector_cb_index, i); + return Vector_CreatePyObject_cb((PyObject *)self, self->num_col, mathutils_matrix_vector_cb_index, row); } /*----------------------------object[]------------------------- sequence accessor (set) */ -static int Matrix_ass_item(MatrixObject *self, int i, PyObject *value) +static int Matrix_ass_item(MatrixObject *self, int row, PyObject *value) { + int col; float vec[4]; if (BaseMath_ReadCallback(self) == -1) return -1; - if (i >= self->num_col || i < 0) { + if (row >= self->num_row || row < 0) { PyErr_SetString(PyExc_IndexError, - "matrix[attribute] = x: bad column"); + "matrix[attribute] = x: bad row"); return -1; } - if (mathutils_array_parse(vec, self->num_row, self->num_row, value, "matrix[i] = value assignment") < 0) { + if (mathutils_array_parse(vec, self->num_col, self->num_col, value, "matrix[i] = value assignment") < 0) { return -1; } - memcpy(MATRIX_COL_PTR(self, i), vec, self->num_row * sizeof(float)); + /* Since we are assigning a row we cannot memcpy */ + for (col = 0; col < self->num_col; col++) { + MATRIX_ITEM(self, row, col) = vec[col]; + } (void)BaseMath_WriteCallback(self); return 0; @@ -1522,14 +1530,14 @@ static PyObject *Matrix_slice(MatrixObject *self, int begin, int end) if (BaseMath_ReadCallback(self) == -1) return NULL; - CLAMP(begin, 0, self->num_col); - CLAMP(end, 0, self->num_col); + CLAMP(begin, 0, self->num_row); + CLAMP(end, 0, self->num_row); begin= MIN2(begin, end); tuple= PyTuple_New(end - begin); for (count= begin; count < end; count++) { PyTuple_SET_ITEM(tuple, count - begin, - Vector_CreatePyObject_cb((PyObject *)self, self->num_row, mathutils_matrix_vector_cb_index, count)); + Vector_CreatePyObject_cb((PyObject *)self, self->num_col, mathutils_matrix_vector_cb_index, count)); } @@ -1544,8 +1552,8 @@ static int Matrix_ass_slice(MatrixObject *self, int begin, int end, PyObject *va if (BaseMath_ReadCallback(self) == -1) return -1; - CLAMP(begin, 0, self->num_col); - CLAMP(end, 0, self->num_col); + CLAMP(begin, 0, self->num_row); + CLAMP(end, 0, self->num_row); begin = MIN2(begin, end); /* non list/tuple cases */ @@ -1555,8 +1563,9 @@ static int Matrix_ass_slice(MatrixObject *self, int begin, int end, PyObject *va } else { const int size= end - begin; - int i; + int row, col; float mat[16]; + float vec[4]; if (PySequence_Fast_GET_SIZE(value_fast) != size) { Py_DECREF(value_fast); @@ -1566,22 +1575,25 @@ static int Matrix_ass_slice(MatrixObject *self, int begin, int end, PyObject *va return -1; } + memcpy(mat, self->matrix, self->num_col * self->num_row * sizeof(float)); + /*parse sub items*/ - for (i = 0; i < size; i++) { + for (row = begin; row < end; row++) { /*parse each sub sequence*/ - PyObject *item= PySequence_Fast_GET_ITEM(value_fast, i); + PyObject *item= PySequence_Fast_GET_ITEM(value_fast, row - begin); - if (mathutils_array_parse(&mat[i * self->num_row], self->num_row, self->num_row, item, - "matrix[begin:end] = value assignment") < 0) - { + if (mathutils_array_parse(vec, self->num_col, self->num_col, item, "matrix[begin:end] = value assignment") < 0) return -1; + + for (col = 0; col < self->num_col; col++) { + mat[col * self->num_row + row] = vec[col]; } } Py_DECREF(value_fast); /*parsed well - now set in matrix*/ - memcpy(self->matrix + (begin * self->num_row), mat, sizeof(float) * (size * self->num_row)); + memcpy(self->matrix, mat, self->num_col * self->num_row * sizeof(float)); (void)BaseMath_WriteCallback(self); return 0; diff --git a/source/tests/bl_pyapi_mathutils.py b/source/tests/bl_pyapi_mathutils.py index f1d6d96c68b..b2e9f27d9ec 100644 --- a/source/tests/bl_pyapi_mathutils.py +++ b/source/tests/bl_pyapi_mathutils.py @@ -27,9 +27,9 @@ class MatrixTesting(unittest.TestCase): mat = Matrix(args) - for i in range(4): - for j in range(4): - self.assertEqual(mat[i][j], args[i][j]) + for row in range(4): + for col in range(4): + self.assertEqual(mat[row][col], args[row][col]) self.assertEqual(mat[0][2], 0) self.assertEqual(mat[3][1], 9) @@ -41,13 +41,13 @@ class MatrixTesting(unittest.TestCase): mat = Matrix() - Matrix() indices = (0, 0), (1, 3), (2, 0), (3, 2), (3, 1) checked_indices = [] - for col, row in indices: - mat[col][row] = 1 + for row, col in indices: + mat[row][col] = 1 - for col in range(4): - for row in range(4): - if mat[col][row]: - checked_indices.append((col, row)) + for row in range(4): + for col in range(4): + if mat[row][col]: + checked_indices.append((row, col)) for item in checked_indices: self.assertIn(item, indices) @@ -64,9 +64,63 @@ class MatrixTesting(unittest.TestCase): def test_matrix_to_translation(self): mat = Matrix() - mat[3] = (1, 2, 3, 4) + mat[0][3] = 1 + mat[1][3] = 2 + mat[2][3] = 3 self.assertEqual(mat.to_translation(), Vector((1, 2, 3))) + def test_matrix_translation(self): + mat = Matrix() + mat.translation = Vector((1, 2, 3)) + self.assertEqual(mat[0][3], 1) + self.assertEqual(mat[1][3], 2) + self.assertEqual(mat[2][3], 3) + + def test_non_square_mult(self): + mat1 = Matrix(((1, 2, 3), + (4, 5, 6))) + mat2 = Matrix(((1, 2), + (3, 4), + (5, 6))) + + prod_mat1 = Matrix(((22, 28), + (49, 64))) + prod_mat2 = Matrix(((9, 12, 15), + (19, 26, 33), + (29, 40, 51))) + + self.assertEqual(mat1*mat2, prod_mat1) + self.assertEqual(mat2 * mat1, prod_mat2) + + def test_mat4x4_vec3D_mult(self): + mat = Matrix(((1, 0, 2, 0), + (0, 6, 0, 0), + (0, 0, 1, 1), + (0, 0, 0, 1))) + + vec = Vector((1, 2, 3)) + + prod_mat_vec = Vector((7, 12, 4)) + prod_vec_mat = Vector((1, 12, 5)) + + self.assertEqual(mat * vec, prod_mat_vec) + self.assertEqual(vec * mat, prod_vec_mat) + + def test_mat_vec_mult(self): + mat1 = Matrix() + + vec = Vector((1, 2)) + + self.assertRaises(TypeError, mat1.__mul__, vec) + self.assertRaises(ValueError, vec.__mul__, mat1) # Why are these different?! + + mat2 = Matrix(((1, 2), + (-2, 3))) + + prod = Vector((5, 4)) + + self.assertEqual(mat2 * vec, prod) + def test_matrix_inverse(self): mat = Matrix(((1, 4, 0, -1), (2, -1, 2, -2), -- cgit v1.2.3