diff --git a/source/blender/python/mathutils/mathutils.cc b/source/blender/python/mathutils/mathutils.cc index 5b3fe15e45e..5b4cb2bbf4a 100644 --- a/source/blender/python/mathutils/mathutils.cc +++ b/source/blender/python/mathutils/mathutils.cc @@ -612,6 +612,24 @@ void _BaseMathObject_RaiseNotFrozenExc(const BaseMathObject *self) PyExc_TypeError, "%s is not frozen (mutable), call freeze first", Py_TYPE(self)->tp_name); } +int _BaseMathObject_ResizeOkOrRaiseExc(BaseMathObject *self, const char *error_prefix) +{ + if (UNLIKELY(self->flag & BASE_MATH_FLAG_IS_FROZEN)) { + PyErr_Format(PyExc_ValueError, "%s: cannot resize frozen data", error_prefix); + return -1; + } + if (UNLIKELY(self->flag & BASE_MATH_FLAG_IS_WRAP)) { + PyErr_Format( + PyExc_TypeError, "%s: cannot resize wrapped data - only Python vectors", error_prefix); + return -1; + } + if (UNLIKELY(self->cb_user)) { + PyErr_Format(PyExc_TypeError, "%s: cannot resize a vector that has an owner", error_prefix); + return -1; + } + return 0; +} + /* #BaseMathObject generic functions for all mathutils types. */ char BaseMathObject_owner_doc[] = "The item this is wrapping or None (read-only)."; diff --git a/source/blender/python/mathutils/mathutils.hh b/source/blender/python/mathutils/mathutils.hh index 1a626019faf..93ef21d12c0 100644 --- a/source/blender/python/mathutils/mathutils.hh +++ b/source/blender/python/mathutils/mathutils.hh @@ -118,6 +118,9 @@ struct Mathutils_Callback { [[nodiscard]] int _BaseMathObject_WriteCallback(BaseMathObject *self); [[nodiscard]] int _BaseMathObject_ReadIndexCallback(BaseMathObject *self, int index); [[nodiscard]] int _BaseMathObject_WriteIndexCallback(BaseMathObject *self, int index); +/** To implement #BaseMath_Prepare_ForResize. */ +[[nodiscard]] int _BaseMathObject_ResizeOkOrRaiseExc(BaseMathObject *self, + const char *error_prefix); void _BaseMathObject_RaiseFrozenExc(const BaseMathObject *self); void _BaseMathObject_RaiseNotFrozenExc(const BaseMathObject *self); @@ -154,6 +157,12 @@ void _BaseMathObject_RaiseNotFrozenExc(const BaseMathObject *self); (UNLIKELY(((_self)->flag & BASE_MATH_FLAG_IS_FROZEN) == 0) ? \ (_BaseMathObject_RaiseNotFrozenExc((BaseMathObject *)_self), -1) : \ 0) +/** + * Helper to de-duplicate checks for in-place resizing. + * \return -1 and set an exception if the vector `_self` cannot be resized. + */ +#define BaseMathObject_Prepare_ForResize(_self, error_prefix) \ + _BaseMathObject_ResizeOkOrRaiseExc((BaseMathObject *)_self, error_prefix) /* utility func */ /** diff --git a/source/blender/python/mathutils/mathutils_Matrix.cc b/source/blender/python/mathutils/mathutils_Matrix.cc index 440a00ecf54..aa540207f68 100644 --- a/source/blender/python/mathutils/mathutils_Matrix.cc +++ b/source/blender/python/mathutils/mathutils_Matrix.cc @@ -1391,16 +1391,8 @@ static PyObject *Matrix_resize_4x4(MatrixObject *self) float mat[4][4]; int col; - if (self->flag & BASE_MATH_FLAG_IS_WRAP) { - PyErr_SetString(PyExc_ValueError, - "Matrix.resize_4x4(): " - "cannot resize wrapped data - make a copy and resize that"); - return nullptr; - } - if (self->cb_user) { - PyErr_SetString(PyExc_ValueError, - "Matrix.resize_4x4(): " - "cannot resize owned data - make a copy and resize that"); + if (UNLIKELY(BaseMathObject_Prepare_ForResize(self, "Matrix.resize_4x4()") == -1)) { + /* An exception has been raised. */ return nullptr; } diff --git a/source/blender/python/mathutils/mathutils_Vector.cc b/source/blender/python/mathutils/mathutils_Vector.cc index 865ee25cd41..8d80aa9d11a 100644 --- a/source/blender/python/mathutils/mathutils_Vector.cc +++ b/source/blender/python/mathutils/mathutils_Vector.cc @@ -497,16 +497,8 @@ static PyObject *Vector_resize(VectorObject *self, PyObject *value) { int vec_num; - if (self->flag & BASE_MATH_FLAG_IS_WRAP) { - PyErr_SetString(PyExc_TypeError, - "Vector.resize(): " - "cannot resize wrapped data - only Python vectors"); - return nullptr; - } - if (self->cb_user) { - PyErr_SetString(PyExc_TypeError, - "Vector.resize(): " - "cannot resize a vector that has an owner"); + if (UNLIKELY(BaseMathObject_Prepare_ForResize(self, "Vector.resize()") == -1)) { + /* An exception has been raised. */ return nullptr; } @@ -585,16 +577,8 @@ PyDoc_STRVAR( " Resize the vector to 2D (x, y).\n"); static PyObject *Vector_resize_2d(VectorObject *self) { - if (self->flag & BASE_MATH_FLAG_IS_WRAP) { - PyErr_SetString(PyExc_TypeError, - "Vector.resize_2d(): " - "cannot resize wrapped data - only Python vectors"); - return nullptr; - } - if (self->cb_user) { - PyErr_SetString(PyExc_TypeError, - "Vector.resize_2d(): " - "cannot resize a vector that has an owner"); + if (UNLIKELY(BaseMathObject_Prepare_ForResize(self, "Vector.resize_2d()") == -1)) { + /* An exception has been raised. */ return nullptr; } @@ -618,16 +602,8 @@ PyDoc_STRVAR( " Resize the vector to 3D (x, y, z).\n"); static PyObject *Vector_resize_3d(VectorObject *self) { - if (self->flag & BASE_MATH_FLAG_IS_WRAP) { - PyErr_SetString(PyExc_TypeError, - "Vector.resize_3d(): " - "cannot resize wrapped data - only Python vectors"); - return nullptr; - } - if (self->cb_user) { - PyErr_SetString(PyExc_TypeError, - "Vector.resize_3d(): " - "cannot resize a vector that has an owner"); + if (UNLIKELY(BaseMathObject_Prepare_ForResize(self, "Vector.resize_3d()") == -1)) { + /* An exception has been raised. */ return nullptr; } @@ -655,16 +631,8 @@ PyDoc_STRVAR( " Resize the vector to 4D (x, y, z, w).\n"); static PyObject *Vector_resize_4d(VectorObject *self) { - if (self->flag & BASE_MATH_FLAG_IS_WRAP) { - PyErr_SetString(PyExc_TypeError, - "Vector.resize_4d(): " - "cannot resize wrapped data - only Python vectors"); - return nullptr; - } - if (self->cb_user) { - PyErr_SetString(PyExc_TypeError, - "Vector.resize_4d(): " - "cannot resize a vector that has an owner"); + if (UNLIKELY(BaseMathObject_Prepare_ForResize(self, "Vector.resize_4d()") == -1)) { + /* An exception has been raised. */ return nullptr; } diff --git a/tests/python/bl_pyapi_mathutils.py b/tests/python/bl_pyapi_mathutils.py index d6bbe1ff0a4..d4c5bc58ad2 100644 --- a/tests/python/bl_pyapi_mathutils.py +++ b/tests/python/bl_pyapi_mathutils.py @@ -249,6 +249,14 @@ class MatrixTesting(unittest.TestCase): result = Matrix.LocRotScale((1, 2, 3), euler.to_matrix(), (4, 5, 6)) self.assertAlmostEqualMatrix(result, expected, 4) + def test_matrix_freeze_is_read_only(self): + mat = Matrix(((1, 1, 1),) * 3) + mat.freeze() + with self.assertRaises(ValueError): + mat.resize_4x4() + with self.assertRaises(TypeError): + mat[0][0] = 0.0 + def assertAlmostEqualMatrix(self, first, second, size, *, places=6, msg=None, delta=None): for i in range(size): for j in range(size): @@ -306,6 +314,15 @@ class VectorTesting(unittest.TestCase): vec *= 2 self.assertEqual(vec, prod2) + def test_vector_freeze_is_read_only(self): + vec = Vector((1, 3, 5)) + + vec.freeze() + with self.assertRaises(ValueError): + vec.resize(2) + with self.assertRaises(TypeError): + vec[0] = 0.0 + class QuaternionTesting(unittest.TestCase):