From aa5a96430ea0741fac39b87fe17fb0faddadd3cf Mon Sep 17 00:00:00 2001 From: Andrew Hale Date: Fri, 10 Aug 2018 14:53:38 +0200 Subject: Python: Add support for @ infix operator matrix multiplication This differential revision implements the code for T56276 Reviewers: campbellbarton Reviewed By: campbellbarton Differential Revision: https://developer.blender.org/D3587 --- source/blender/python/mathutils/mathutils_Matrix.c | 195 ++++++++++++++++++--- .../python/mathutils/mathutils_Quaternion.c | 147 ++++++++++++++-- source/blender/python/mathutils/mathutils_Vector.c | 166 ++++++++++++++---- tests/python/bl_pyapi_mathutils.py | 102 +++++++++-- 4 files changed, 522 insertions(+), 88 deletions(-) diff --git a/source/blender/python/mathutils/mathutils_Matrix.c b/source/blender/python/mathutils/mathutils_Matrix.c index 70c400f99b8..3bd40cca5c6 100644 --- a/source/blender/python/mathutils/mathutils_Matrix.c +++ b/source/blender/python/mathutils/mathutils_Matrix.c @@ -2321,7 +2321,7 @@ static PyObject *Matrix_sub(PyObject *m1, PyObject *m2) return Matrix_CreatePyObject(mat, mat1->num_col, mat1->num_row, Py_TYPE(mat1)); } /*------------------------obj * obj------------------------------ - * multiplication */ + * element-wise multiplication */ static PyObject *matrix_mul_float(MatrixObject *mat, const float scalar) { float tmat[MATRIX_MAX_DIM * MATRIX_MAX_DIM]; @@ -2332,7 +2332,6 @@ static PyObject *matrix_mul_float(MatrixObject *mat, const float scalar) static PyObject *Matrix_mul(PyObject *m1, PyObject *m2) { float scalar; - int vec_size; MatrixObject *mat1 = NULL, *mat2 = NULL; @@ -2348,15 +2347,124 @@ static PyObject *Matrix_mul(PyObject *m1, PyObject *m2) } if (mat1 && mat2) { +#ifdef USE_MATHUTILS_ELEM_MUL /* MATRIX * MATRIX */ float mat[MATRIX_MAX_DIM * MATRIX_MAX_DIM]; + if ((mat1->num_row != mat2->num_row) || (mat1->num_col != mat2->num_col)) { + PyErr_SetString(PyExc_ValueError, + "matrix1 * matrix2: matrix1 number of rows/columns " + "and the matrix2 number of rows/columns must be the same"); + return NULL; + } + + mul_vn_vnvn(mat, mat1->matrix, mat2->matrix, mat1->num_col * mat1->num_row); + + return Matrix_CreatePyObject(mat, mat2->num_col, mat1->num_row, Py_TYPE(mat1)); +#endif + } + else if (mat2) { + /*FLOAT/INT * MATRIX */ + if (((scalar = PyFloat_AsDouble(m1)) == -1.0f && PyErr_Occurred()) == 0) { + return matrix_mul_float(mat2, scalar); + } + } + else if (mat1) { + /* MATRIX * FLOAT/INT */ + if (((scalar = PyFloat_AsDouble(m2)) == -1.0f && PyErr_Occurred()) == 0) { + return matrix_mul_float(mat1, scalar); + } + } + + PyErr_Format(PyExc_TypeError, + "Element-wise multiplication: " + "not supported between '%.200s' and '%.200s' types", + Py_TYPE(m1)->tp_name, Py_TYPE(m2)->tp_name); + return NULL; +} +/*------------------------obj *= obj------------------------------ + * Inplace element-wise multiplication */ +static PyObject *Matrix_imul(PyObject *m1, PyObject *m2) +{ + float scalar; + + MatrixObject *mat1 = NULL, *mat2 = NULL; + + if (MatrixObject_Check(m1)) { + mat1 = (MatrixObject *)m1; + if (BaseMath_ReadCallback(mat1) == -1) + return NULL; + } + if (MatrixObject_Check(m2)) { + mat2 = (MatrixObject *)m2; + if (BaseMath_ReadCallback(mat2) == -1) + return NULL; + } + + if (mat1 && mat2) { +#ifdef USE_MATHUTILS_ELEM_MUL + /* MATRIX *= MATRIX */ + if ((mat1->num_row != mat2->num_row) || (mat1->num_col != mat2->num_col)) { + PyErr_SetString(PyExc_ValueError, + "matrix1 *= matrix2: matrix1 number of rows/columns " + "and the matrix2 number of rows/columns must be the same"); + return NULL; + } + + mul_vn_vn(mat1->matrix, mat2->matrix, mat1->num_col * mat1->num_row); +#else + PyErr_Format(PyExc_TypeError, + "Inplace element-wise multiplication: " + "not supported between '%.200s' and '%.200s' types", + Py_TYPE(m1)->tp_name, Py_TYPE(m2)->tp_name); + return NULL; +#endif + } + else if (mat1 && (((scalar = PyFloat_AsDouble(m2)) == -1.0f && PyErr_Occurred()) == 0)) { + /* MATRIX *= FLOAT/INT */ + mul_vn_fl(mat1->matrix, mat1->num_row * mat1->num_col, scalar); + } + else { + PyErr_Format(PyExc_TypeError, + "Inplace element-wise multiplication: " + "not supported between '%.200s' and '%.200s' types", + Py_TYPE(m1)->tp_name, Py_TYPE(m2)->tp_name); + return NULL; + } + + (void)BaseMath_WriteCallback(mat1); + Py_INCREF(m1); + return m1; +} +/*------------------------obj @ obj------------------------------ + * matrix multiplication */ +static PyObject *Matrix_matmul(PyObject *m1, PyObject *m2) +{ + int vec_size; + + MatrixObject *mat1 = NULL, *mat2 = NULL; + + if (MatrixObject_Check(m1)) { + mat1 = (MatrixObject *)m1; + if (BaseMath_ReadCallback(mat1) == -1) + return NULL; + } + if (MatrixObject_Check(m2)) { + mat2 = (MatrixObject *)m2; + if (BaseMath_ReadCallback(mat2) == -1) + return NULL; + } + + if (mat1 && mat2) { + /* MATRIX @ MATRIX */ + float mat[MATRIX_MAX_DIM * MATRIX_MAX_DIM]; + int col, row, item; if (mat1->num_col != mat2->num_row) { PyErr_SetString(PyExc_ValueError, - "matrix1 * matrix2: matrix1 number of columns " - "and the matrix2 number of rows must be the same"); + "matrix1 * matrix2: matrix1 number of columns " + "and the matrix2 number of rows must be the same"); return NULL; } @@ -2372,14 +2480,8 @@ static PyObject *Matrix_mul(PyObject *m1, PyObject *m2) return Matrix_CreatePyObject(mat, mat2->num_col, mat1->num_row, Py_TYPE(mat1)); } - else if (mat2) { - /*FLOAT/INT * MATRIX */ - if (((scalar = PyFloat_AsDouble(m1)) == -1.0f && PyErr_Occurred()) == 0) { - return matrix_mul_float(mat2, scalar); - } - } else if (mat1) { - /* MATRIX * VECTOR */ + /* MATRIX @ VECTOR */ if (VectorObject_Check(m2)) { VectorObject *vec2 = (VectorObject *)m2; float tvec[MATRIX_MAX_DIM]; @@ -2398,20 +2500,69 @@ static PyObject *Matrix_mul(PyObject *m1, PyObject *m2) return Vector_CreatePyObject(tvec, vec_size, Py_TYPE(m2)); } - /*FLOAT/INT * MATRIX */ - else if (((scalar = PyFloat_AsDouble(m2)) == -1.0f && PyErr_Occurred()) == 0) { - return matrix_mul_float(mat1, scalar); + } + + PyErr_Format(PyExc_TypeError, + "Matrix multiplication: " + "not supported between '%.200s' and '%.200s' types", + Py_TYPE(m1)->tp_name, Py_TYPE(m2)->tp_name); + return NULL; +} +/*------------------------obj @= obj------------------------------ + * inplace matrix multiplication */ +static PyObject *Matrix_imatmul(PyObject *m1, PyObject *m2) +{ + MatrixObject *mat1 = NULL, *mat2 = NULL; + + if (MatrixObject_Check(m1)) { + mat1 = (MatrixObject *)m1; + if (BaseMath_ReadCallback(mat1) == -1) + return NULL; + } + if (MatrixObject_Check(m2)) { + mat2 = (MatrixObject *)m2; + if (BaseMath_ReadCallback(mat2) == -1) + return NULL; + } + + if (mat1 && mat2) { + /* MATRIX @= MATRIX */ + float mat[MATRIX_MAX_DIM * MATRIX_MAX_DIM]; + int col, row, item; + + if (mat1->num_col != mat2->num_row) { + PyErr_SetString(PyExc_ValueError, + "matrix1 * matrix2: matrix1 number of columns " + "and the matrix2 number of rows must be the same"); + return NULL; } + + for (col = 0; col < mat2->num_col; col++) { + for (row = 0; row < mat1->num_row; row++) { + double dot = 0.0f; + for (item = 0; item < mat1->num_col; item++) { + dot += (double)(MATRIX_ITEM(mat1, row, item) * MATRIX_ITEM(mat2, item, col)); + } + /* store in new matrix as overwriting original at this point will cause + * subsequent iterations to use incorrect values */ + mat[(col * mat1->num_row) + row] = (float)dot; + } + } + + /* copy matrix back */ + memcpy(mat1->matrix, mat, mat1->num_row * mat1->num_col); } else { - BLI_assert(!"internal error"); + PyErr_Format(PyExc_TypeError, + "Inplace matrix multiplication: " + "not supported between '%.200s' and '%.200s' types", + Py_TYPE(m1)->tp_name, Py_TYPE(m2)->tp_name); + return NULL; } - PyErr_Format(PyExc_TypeError, - "Matrix multiplication: " - "not supported between '%.200s' and '%.200s' types", - Py_TYPE(m1)->tp_name, Py_TYPE(m2)->tp_name); - return NULL; + (void)BaseMath_WriteCallback(mat1); + Py_INCREF(m1); + return m1; } /*-----------------PROTOCOL DECLARATIONS--------------------------*/ @@ -2527,7 +2678,7 @@ static PyNumberMethods Matrix_NumMethods = { NULL, /*nb_float*/ NULL, /* nb_inplace_add */ NULL, /* nb_inplace_subtract */ - NULL, /* nb_inplace_multiply */ + (binaryfunc) Matrix_imul, /* nb_inplace_multiply */ NULL, /* nb_inplace_remainder */ NULL, /* nb_inplace_power */ NULL, /* nb_inplace_lshift */ @@ -2540,6 +2691,8 @@ static PyNumberMethods Matrix_NumMethods = { NULL, /* nb_inplace_floor_divide */ NULL, /* nb_inplace_true_divide */ NULL, /* nb_index */ + (binaryfunc) Matrix_matmul, /* nb_matrix_multiply */ + (binaryfunc) Matrix_imatmul, /* nb_inplace_matrix_multiply */ }; PyDoc_STRVAR(Matrix_translation_doc, diff --git a/source/blender/python/mathutils/mathutils_Quaternion.c b/source/blender/python/mathutils/mathutils_Quaternion.c index 48c18dd20c1..bb5983af535 100644 --- a/source/blender/python/mathutils/mathutils_Quaternion.c +++ b/source/blender/python/mathutils/mathutils_Quaternion.c @@ -834,7 +834,7 @@ static PyObject *quat_mul_float(QuaternionObject *quat, const float scalar) * multiplication */ static PyObject *Quaternion_mul(PyObject *q1, PyObject *q2) { - float quat[QUAT_SIZE], scalar; + float scalar; QuaternionObject *quat1 = NULL, *quat2 = NULL; if (QuaternionObject_Check(q1)) { @@ -848,9 +848,12 @@ static PyObject *Quaternion_mul(PyObject *q1, PyObject *q2) return NULL; } - if (quat1 && quat2) { /* QUAT * QUAT (cross product) */ - mul_qt_qtqt(quat, quat1->quat, quat2->quat); + if (quat1 && quat2) { /* QUAT * QUAT (element-wise product) */ +#ifdef USE_MATHUTILS_ELEM_MUL + float quat[QUAT_SIZE]; + mul_vn_vnvn(quat, quat1->quat, quat2->quat, QUAT_SIZE); return Quaternion_CreatePyObject(quat, Py_TYPE(q1)); +#endif } /* the only case this can happen (for a supported type is "FLOAT * QUAT") */ else if (quat2) { /* FLOAT * QUAT */ @@ -858,17 +861,96 @@ static PyObject *Quaternion_mul(PyObject *q1, PyObject *q2) return quat_mul_float(quat2, scalar); } } + else if (quat1) { /* QUAT * FLOAT */ + if ((((scalar = PyFloat_AsDouble(q2)) == -1.0f && PyErr_Occurred()) == 0)) { + return quat_mul_float(quat1, scalar); + } + } + + PyErr_Format(PyExc_TypeError, + "Element-wise multiplication: " + "not supported between '%.200s' and '%.200s' types", + Py_TYPE(q1)->tp_name, Py_TYPE(q2)->tp_name); + return NULL; +} +/*------------------------obj *= obj------------------------------ + * inplace multiplication */ +static PyObject *Quaternion_imul(PyObject *q1, PyObject *q2) +{ + float scalar; + QuaternionObject *quat1 = NULL, *quat2 = NULL; + + if (QuaternionObject_Check(q1)) { + quat1 = (QuaternionObject *)q1; + if (BaseMath_ReadCallback(quat1) == -1) + return NULL; + } + if (QuaternionObject_Check(q2)) { + quat2 = (QuaternionObject *)q2; + if (BaseMath_ReadCallback(quat2) == -1) + return NULL; + } + + if (quat1 && quat2) { /* QUAT *= QUAT (inplace element-wise product) */ +#ifdef USE_MATHUTILS_ELEM_MUL + mul_vn_vn(quat1->quat, quat2->quat, QUAT_SIZE); +#else + PyErr_Format(PyExc_TypeError, + "Inplace element-wise multiplication: " + "not supported between '%.200s' and '%.200s' types", + Py_TYPE(q1)->tp_name, Py_TYPE(q2)->tp_name); + return NULL; +#endif + } + else if (quat1 && (((scalar = PyFloat_AsDouble(q2)) == -1.0f && PyErr_Occurred()) == 0)) { + /* QUAT *= FLOAT */ + mul_qt_fl(quat1->quat, scalar); + } + else { + PyErr_Format(PyExc_TypeError, + "Element-wise multiplication: " + "not supported between '%.200s' and '%.200s' types", + Py_TYPE(q1)->tp_name, Py_TYPE(q2)->tp_name); + return NULL; + } + + (void)BaseMath_WriteCallback(quat1); + Py_INCREF(q1); + return q1; +} +/*------------------------obj @ obj------------------------------ + * quaternion multiplication */ +static PyObject *Quaternion_matmul(PyObject *q1, PyObject *q2) +{ + float quat[QUAT_SIZE]; + QuaternionObject *quat1 = NULL, *quat2 = NULL; + + if (QuaternionObject_Check(q1)) { + quat1 = (QuaternionObject *)q1; + if (BaseMath_ReadCallback(quat1) == -1) + return NULL; + } + if (QuaternionObject_Check(q2)) { + quat2 = (QuaternionObject *)q2; + if (BaseMath_ReadCallback(quat2) == -1) + return NULL; + } + + if (quat1 && quat2) { /* QUAT @ QUAT (cross product) */ + mul_qt_qtqt(quat, quat1->quat, quat2->quat); + return Quaternion_CreatePyObject(quat, Py_TYPE(q1)); + } else if (quat1) { - /* QUAT * VEC */ + /* QUAT @ VEC */ if (VectorObject_Check(q2)) { VectorObject *vec2 = (VectorObject *)q2; float tvec[3]; if (vec2->size != 3) { PyErr_SetString(PyExc_ValueError, - "Vector multiplication: " - "only 3D vector rotations (with quats) " - "currently supported"); + "Vector multiplication: " + "only 3D vector rotations (with quats) " + "currently supported"); return NULL; } if (BaseMath_ReadCallback(vec2) == -1) { @@ -880,21 +962,48 @@ static PyObject *Quaternion_mul(PyObject *q1, PyObject *q2) return Vector_CreatePyObject(tvec, 3, Py_TYPE(vec2)); } - /* QUAT * FLOAT */ - else if ((((scalar = PyFloat_AsDouble(q2)) == -1.0f && PyErr_Occurred()) == 0)) { - return quat_mul_float(quat1, scalar); - } - } - else { - BLI_assert(!"internal error"); } PyErr_Format(PyExc_TypeError, - "Quaternion multiplication: " - "not supported between '%.200s' and '%.200s' types", - Py_TYPE(q1)->tp_name, Py_TYPE(q2)->tp_name); + "Quaternion multiplication: " + "not supported between '%.200s' and '%.200s' types", + Py_TYPE(q1)->tp_name, Py_TYPE(q2)->tp_name); return NULL; } +/*------------------------obj @= obj------------------------------ + * inplace quaternion multiplication */ +static PyObject *Quaternion_imatmul(PyObject *q1, PyObject *q2) +{ + float quat[QUAT_SIZE]; + QuaternionObject *quat1 = NULL, *quat2 = NULL; + + if (QuaternionObject_Check(q1)) { + quat1 = (QuaternionObject *)q1; + if (BaseMath_ReadCallback(quat1) == -1) + return NULL; + } + if (QuaternionObject_Check(q2)) { + quat2 = (QuaternionObject *)q2; + if (BaseMath_ReadCallback(quat2) == -1) + return NULL; + } + + if (quat1 && quat2) { /* QUAT @ QUAT (cross product) */ + mul_qt_qtqt(quat, quat1->quat, quat2->quat); + copy_qt_qt(quat1->quat, quat); + } + else { + PyErr_Format(PyExc_TypeError, + "Inplace quaternion multiplication: " + "not supported between '%.200s' and '%.200s' types", + Py_TYPE(q1)->tp_name, Py_TYPE(q2)->tp_name); + return NULL; + } + + (void)BaseMath_WriteCallback(quat1); + Py_INCREF(q1); + return q1; +} /* -obj * returns the negative of this object*/ @@ -952,7 +1061,7 @@ static PyNumberMethods Quaternion_NumMethods = { NULL, /*nb_float*/ NULL, /* nb_inplace_add */ NULL, /* nb_inplace_subtract */ - NULL, /* nb_inplace_multiply */ + (binaryfunc) Quaternion_imul, /* nb_inplace_multiply */ NULL, /* nb_inplace_remainder */ NULL, /* nb_inplace_power */ NULL, /* nb_inplace_lshift */ @@ -965,6 +1074,8 @@ static PyNumberMethods Quaternion_NumMethods = { NULL, /* nb_inplace_floor_divide */ NULL, /* nb_inplace_true_divide */ NULL, /* nb_index */ + (binaryfunc) Quaternion_matmul, /* nb_matrix_multiply */ + (binaryfunc) Quaternion_imatmul, /* nb_inplace_matrix_multiply */ }; PyDoc_STRVAR(Quaternion_axis_doc, diff --git a/source/blender/python/mathutils/mathutils_Vector.c b/source/blender/python/mathutils/mathutils_Vector.c index 6a40f22d9df..dc05f463d22 100644 --- a/source/blender/python/mathutils/mathutils_Vector.c +++ b/source/blender/python/mathutils/mathutils_Vector.c @@ -1706,12 +1706,25 @@ static PyObject *vector_mul_float(VectorObject *vec, const float scalar) mul_vn_vn_fl(tvec, vec->vec, vec->size, scalar); return Vector_CreatePyObject_alloc(tvec, vec->size, Py_TYPE(vec)); } +#ifdef USE_MATHUTILS_ELEM_MUL +static PyObject *vector_mul_vec(VectorObject *vec1, VectorObject *vec2) +{ + float *tvec = PyMem_Malloc(vec1->size * sizeof(float)); + if (tvec == NULL) { + PyErr_SetString(PyExc_MemoryError, + "vec * vec: " + "problem allocating pointer space"); + return NULL; + } + mul_vn_vnvn(tvec, vec1->vec, vec2->vec, vec1->size); + return Vector_CreatePyObject_alloc(tvec, vec1->size, Py_TYPE(vec1)); +} +#endif static PyObject *Vector_mul(PyObject *v1, PyObject *v2) { VectorObject *vec1 = NULL, *vec2 = NULL; float scalar; - int vec_size; if (VectorObject_Check(v1)) { vec1 = (VectorObject *)v1; @@ -1729,6 +1742,7 @@ static PyObject *Vector_mul(PyObject *v1, PyObject *v2) /* make sure v1 is always the vector */ if (vec1 && vec2) { +#ifdef USE_MATHUTILS_ELEM_MUL if (vec1->size != vec2->size) { PyErr_SetString(PyExc_ValueError, "Vector multiplication: " @@ -1736,30 +1750,12 @@ static PyObject *Vector_mul(PyObject *v1, PyObject *v2) return NULL; } - /*dot product*/ - return PyFloat_FromDouble(dot_vn_vn(vec1->vec, vec2->vec, vec1->size)); + /* element-wise product */ + return vector_mul_vec(vec1, vec2); +#endif } else if (vec1) { - if (MatrixObject_Check(v2)) { - /* VEC * MATRIX */ - float tvec[MAX_DIMENSIONS]; - - if (BaseMath_ReadCallback((MatrixObject *)v2) == -1) - return NULL; - if (row_vector_multiplication(tvec, vec1, (MatrixObject *)v2) == -1) { - return NULL; - } - - if (((MatrixObject *)v2)->num_row == 4 && vec1->size == 3) { - vec_size = 3; - } - else { - vec_size = ((MatrixObject *)v2)->num_col; - } - - return Vector_CreatePyObject(tvec, vec_size, Py_TYPE(vec1)); - } - else if (((scalar = PyFloat_AsDouble(v2)) == -1.0f && PyErr_Occurred()) == 0) { /* VEC * FLOAT */ + if (((scalar = PyFloat_AsDouble(v2)) == -1.0f && PyErr_Occurred()) == 0) { /* VEC * FLOAT */ return vector_mul_float(vec1, scalar); } } @@ -1768,12 +1764,9 @@ static PyObject *Vector_mul(PyObject *v1, PyObject *v2) return vector_mul_float(vec2, scalar); } } - else { - BLI_assert(!"internal error"); - } PyErr_Format(PyExc_TypeError, - "Vector multiplication: " + "Element-wise multiplication: " "not supported between '%.200s' and '%.200s' types", Py_TYPE(v1)->tp_name, Py_TYPE(v2)->tp_name); return NULL; @@ -1782,32 +1775,129 @@ static PyObject *Vector_mul(PyObject *v1, PyObject *v2) /* multiplication in-place: obj *= obj */ static PyObject *Vector_imul(PyObject *v1, PyObject *v2) { - VectorObject *vec = (VectorObject *)v1; + VectorObject *vec1 = NULL, *vec2 = NULL; float scalar; - if (BaseMath_ReadCallback_ForWrite(vec) == -1) + if (VectorObject_Check(v1)) { + vec1 = (VectorObject *)v1; + if (BaseMath_ReadCallback(vec1) == -1) + return NULL; + } + if (VectorObject_Check(v2)) { + vec2 = (VectorObject *)v2; + if (BaseMath_ReadCallback(vec2) == -1) + return NULL; + } + + if (BaseMath_ReadCallback_ForWrite(vec1) == -1) return NULL; /* Intentionally don't support (Quaternion, Matrix) here, uses reverse order instead. */ - /* only support 'vec *= float' - * vec*=vec result is a float so that wont work */ - if (((scalar = PyFloat_AsDouble(v2)) == -1.0f && PyErr_Occurred()) == 0) { /* VEC *= FLOAT */ - mul_vn_fl(vec->vec, vec->size, scalar); + if (vec1 && vec2) { +#ifdef USE_MATHUTILS_ELEM_MUL + if (vec1->size != vec2->size) { + PyErr_SetString(PyExc_ValueError, + "Vector multiplication: " + "vectors must have the same dimensions for this operation"); + return NULL; + } + + /* element-wise product inplace */ + mul_vn_vn(vec1->vec, vec2->vec, vec1->size); +#else + PyErr_Format(PyExc_TypeError, + "Inplace element-wise multiplication: " + "not supported between '%.200s' and '%.200s' types", + Py_TYPE(v1)->tp_name, Py_TYPE(v2)->tp_name); + return NULL; +#endif + } + else if (vec1 && (((scalar = PyFloat_AsDouble(v2)) == -1.0f && PyErr_Occurred()) == 0)) { /* VEC *= FLOAT */ + mul_vn_fl(vec1->vec, vec1->size, scalar); } else { PyErr_Format(PyExc_TypeError, - "Vector multiplication: (%s *= %s) " - "invalid type for this operation", - Py_TYPE(v1)->tp_name, Py_TYPE(v2)->tp_name); + "Inplace element-wise multiplication: " + "not supported between '%.200s' and '%.200s' types", + Py_TYPE(v1)->tp_name, Py_TYPE(v2)->tp_name); return NULL; } - (void)BaseMath_WriteCallback(vec); + (void)BaseMath_WriteCallback(vec1); Py_INCREF(v1); return v1; } +static PyObject *Vector_matmul(PyObject *v1, PyObject *v2) +{ + VectorObject *vec1 = NULL, *vec2 = NULL; + int vec_size; + + if (VectorObject_Check(v1)) { + vec1 = (VectorObject *)v1; + if (BaseMath_ReadCallback(vec1) == -1) + return NULL; + } + if (VectorObject_Check(v2)) { + vec2 = (VectorObject *)v2; + if (BaseMath_ReadCallback(vec2) == -1) + return NULL; + } + + + /* Intentionally don't support (Quaternion) here, uses reverse order instead. */ + + /* make sure v1 is always the vector */ + if (vec1 && vec2) { + if (vec1->size != vec2->size) { + PyErr_SetString(PyExc_ValueError, + "Vector multiplication: " + "vectors must have the same dimensions for this operation"); + return NULL; + } + + /*dot product*/ + return PyFloat_FromDouble(dot_vn_vn(vec1->vec, vec2->vec, vec1->size)); + } + else if (vec1) { + if (MatrixObject_Check(v2)) { + /* VEC @ MATRIX */ + float tvec[MAX_DIMENSIONS]; + + if (BaseMath_ReadCallback((MatrixObject *)v2) == -1) + return NULL; + if (row_vector_multiplication(tvec, vec1, (MatrixObject *)v2) == -1) { + return NULL; + } + + if (((MatrixObject *)v2)->num_row == 4 && vec1->size == 3) { + vec_size = 3; + } + else { + vec_size = ((MatrixObject *)v2)->num_col; + } + + return Vector_CreatePyObject(tvec, vec_size, Py_TYPE(vec1)); + } + } + + PyErr_Format(PyExc_TypeError, + "Vector multiplication: " + "not supported between '%.200s' and '%.200s' types", + Py_TYPE(v1)->tp_name, Py_TYPE(v2)->tp_name); + return NULL; +} + +static PyObject *Vector_imatmul(PyObject *v1, PyObject *v2) +{ + PyErr_Format(PyExc_TypeError, + "Inplace vector multiplication: " + "not supported between '%.200s' and '%.200s' types", + Py_TYPE(v1)->tp_name, Py_TYPE(v2)->tp_name); + return NULL; +} + /* divid: obj / obj */ static PyObject *Vector_div(PyObject *v1, PyObject *v2) { @@ -2119,6 +2209,8 @@ static PyNumberMethods Vector_NumMethods = { NULL, /* nb_inplace_floor_divide */ Vector_idiv, /* nb_inplace_true_divide */ NULL, /* nb_index */ + (binaryfunc) Vector_matmul, /* nb_matrix_multiply */ + (binaryfunc) Vector_imatmul, /* nb_inplace_matrix_multiply */ }; /*------------------PY_OBECT DEFINITION--------------------------*/ diff --git a/tests/python/bl_pyapi_mathutils.py b/tests/python/bl_pyapi_mathutils.py index 57bbbc7e73e..5aa95f9a5f7 100644 --- a/tests/python/bl_pyapi_mathutils.py +++ b/tests/python/bl_pyapi_mathutils.py @@ -104,7 +104,7 @@ class MatrixTesting(unittest.TestCase): self.assertEqual(mat[1][3], 2) self.assertEqual(mat[2][3], 3) - def test_non_square_mult(self): + def test_matrix_non_square_matmul(self): mat1 = Matrix(((1, 2, 3), (4, 5, 6))) mat2 = Matrix(((1, 2), @@ -117,10 +117,10 @@ class MatrixTesting(unittest.TestCase): (19, 26, 33), (29, 40, 51))) - self.assertEqual(mat1 * mat2, prod_mat1) - self.assertEqual(mat2 * mat1, prod_mat2) + self.assertEqual(mat1 @ mat2, prod_mat1) + self.assertEqual(mat2 @ mat1, prod_mat2) - def test_mat4x4_vec3D_mult(self): + def test_mat4x4_vec3D_matmul(self): mat = Matrix(((1, 0, 2, 0), (0, 6, 0, 0), (0, 0, 1, 1), @@ -131,23 +131,58 @@ class MatrixTesting(unittest.TestCase): 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) + self.assertEqual(mat @ vec, prod_mat_vec) + self.assertEqual(vec @ mat, prod_vec_mat) - def test_mat_vec_mult(self): + def test_mat_vec_matmul(self): mat1 = Matrix() vec = Vector((1, 2)) - self.assertRaises(ValueError, mat1.__mul__, vec) - self.assertRaises(ValueError, vec.__mul__, mat1) + self.assertRaises(ValueError, mat1.__matmul__, vec) + self.assertRaises(ValueError, vec.__matmul__, mat1) mat2 = Matrix(((1, 2), (-2, 3))) prod = Vector((5, 4)) - self.assertEqual(mat2 * vec, prod) + self.assertEqual(mat2 @ vec, prod) + + def test_matrix_square_matmul(self): + mat1 = Matrix(((1, 0), + (1, 2))) + mat2 = Matrix(((1, 2), + (-2, 3))) + + prod1 = Matrix(((1, 2), + (-3, 8))) + prod2 = Matrix(((3, 4), + (1, 6))) + + self.assertEqual(mat1 @ mat2, prod1) + self.assertEqual(mat2 @ mat1, prod2) + + """ + # tests for element-wise multiplication + + def test_matrix_mul(self): + mat1 = Matrix(((1, 0), + (1, 2))) + mat2 = Matrix(((1, 2), + (-2, 3))) + mat3 = Matrix(((1, 0, 2, 0), + (0, 6, 0, 0), + (0, 0, 1, 1), + (0, 0, 0, 1))) + + prod = Matrix(((1, 0), + (-2, 6))) + + self.assertEqual(mat1 * mat2, prod) + self.assertEqual(mat2 * mat1, prod) + self.assertRaises(ValueError, mat1.__mul__, mat3) + """ def test_matrix_inverse(self): mat = Matrix(((1, 4, 0, -1), @@ -185,7 +220,7 @@ class MatrixTesting(unittest.TestCase): self.assertEqual(mat.inverted_safe(), inv_mat_safe) - def test_matrix_mult(self): + def test_matrix_matmult(self): mat = Matrix(((1, 4, 0, -1), (2, -1, 2, -2), (0, 3, 8, 3), @@ -196,7 +231,7 @@ class MatrixTesting(unittest.TestCase): (0, 48, 73, 18), (16, -14, 26, -13))) - self.assertEqual(mat * mat, prod_mat) + self.assertEqual(mat @ mat, prod_mat) class VectorTesting(unittest.TestCase): @@ -209,6 +244,49 @@ class VectorTesting(unittest.TestCase): if v.length_squared != 0.0: self.assertAlmostEqual(v.angle(v.orthogonal()), angle_90d) + def test_vector_matmul(self): + # produces dot product for vectors + vec1 = Vector((1, 3, 5)) + vec2 = Vector((1, 2)) + + self.assertRaises(ValueError, vec1.__matmul__, vec2) + self.assertEqual(vec1 @ vec1, 35) + self.assertEqual(vec2 @ vec2, 5) + + def test_vector_imatmul(self): + vec = Vector((1, 3, 5)) + + with self.assertRaises(TypeError): + vec @= vec + + """ + # tests for element-wise multiplication + + def test_vector_mul(self): + # element-wise multiplication + vec1 = Vector((1, 3, 5)) + vec2 = Vector((1, 2)) + + prod1 = Vector((1, 9, 25)) + prod2 = Vector((2, 6, 10)) + + self.assertRaises(ValueError, vec1.__mul__, vec2) + self.assertEqual(vec1 * vec1, prod1) + self.assertEqual(2 * vec1, prod2) + + def test_vector_imul(self): + # inplace element-wise multiplication + vec = Vector((1, 3, 5)) + prod1 = Vector((1, 9, 25)) + prod2 = Vector((2, 18, 50)) + + vec *= vec + self.assertEqual(vec, prod1) + + vec *= 2 + self.assertEqual(vec, prod2) + """ + class QuaternionTesting(unittest.TestCase): -- cgit v1.2.3