patch [#29534] Change Matrix Representation and Access in Python to Conform with...
authorCampbell Barton <ideasman42@gmail.com>
Thu, 22 Dec 2011 01:05:03 +0000 (01:05 +0000)
committerCampbell Barton <ideasman42@gmail.com>
Thu, 22 Dec 2011 01:05:03 +0000 (01:05 +0000)
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
source/tests/bl_pyapi_mathutils.py

index 19675d2a8633aa7426f9aa755b4ec893aed136a0..de098ce8bd87bd3171bb30a37247ef648078ef65 100644 (file)
@@ -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;
index f1d6d96c68bb0bc0e7ff6471638d4815bf31f252..b2e9f27d9ec5cc18dd3cccc36ac250da498d3a47 100644 (file)
@@ -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),