mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
Fix #155928: Undo argument can no longer be passed to the operator
Resolve regression in [0] which didn't account for a single positional
boolean argument.
[0]: 877283a09a
Ref !155997
Pull Request: https://projects.blender.org/blender/blender/pulls/157122
This commit is contained in:
parent
2edae0c5e0
commit
e622d316e2
2 changed files with 61 additions and 21 deletions
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "../generic/py_capi_utils.hh"
|
||||
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
|
||||
#include "../generic/python_utildefines.hh"
|
||||
|
||||
#include "bpy_capi_utils.hh"
|
||||
#include "bpy_operator_function.hh"
|
||||
|
|
@ -139,35 +140,30 @@ static PyObject *bpy_op_fn_call(BPyOpFunction *self, PyObject *args, PyObject *k
|
|||
char idname_py[OP_MAX_TYPENAME];
|
||||
WM_operator_py_idname(idname_py, self->idname);
|
||||
|
||||
PyObject *opname = PyUnicode_FromString(idname_py);
|
||||
if (!opname) {
|
||||
/* Parse the user's positional args (context string, undo boolean)
|
||||
* before forwarding to `pyop_call` which expects strict types. */
|
||||
const char *context_str;
|
||||
bool is_undo;
|
||||
if (!bpy_op_fn_parse_args(args, &context_str, &is_undo)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PyObject *kwobj = kwargs ? Py_NewRef(kwargs) : PyDict_New();
|
||||
if (!kwobj) {
|
||||
Py_DECREF(opname);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Build args tuple for `pyop_call: (opname, kw, ...extra args...)`.
|
||||
* Create the child objects first so we can handle allocation failures cleanly. */
|
||||
Py_ssize_t args_len = PyTuple_GET_SIZE(args);
|
||||
PyObject *new_args = PyTuple_New(2 + args_len);
|
||||
/* Build args tuple for `pyop_call`: (opname, kw, context_str, is_undo).
|
||||
* Items are set immediately so `Py_DECREF(new_args)` handles cleanup on error. */
|
||||
PyObject *new_args = PyTuple_New(4);
|
||||
if (!new_args) {
|
||||
Py_DECREF(opname);
|
||||
Py_DECREF(kwobj);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Steal references into the tuple. */
|
||||
PyTuple_SET_ITEM(new_args, 0, opname);
|
||||
PyTuple_SET_ITEM(new_args, 1, kwobj);
|
||||
PyTuple_SET_ITEMS(new_args,
|
||||
PyUnicode_FromString(idname_py),
|
||||
kwargs ? Py_NewRef(kwargs) : PyDict_New(),
|
||||
PyUnicode_FromString(context_str),
|
||||
PyLong_FromLong(is_undo));
|
||||
|
||||
for (Py_ssize_t i = 0; i < args_len; i++) {
|
||||
PyObject *item = Py_NewRef(PyTuple_GET_ITEM(args, i));
|
||||
BLI_assert(item);
|
||||
PyTuple_SET_ITEM(new_args, i + 2, item);
|
||||
if (PyErr_Occurred()) {
|
||||
Py_DECREF(new_args);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Pre-call view-layer update.
|
||||
|
|
|
|||
|
|
@ -37,12 +37,56 @@ class TestOperatorCallPositionalArgs(unittest.TestCase):
|
|||
def test_invoke_default(self):
|
||||
self.assertEqual(bpy.ops.wm.clear_recent_files('INVOKE_DEFAULT'), {'FINISHED'})
|
||||
|
||||
def test_undo_flag(self):
|
||||
self.assertEqual(bpy.ops.wm.clear_recent_files(True), {'FINISHED'})
|
||||
|
||||
def test_undo_flag_false(self):
|
||||
self.assertEqual(bpy.ops.wm.clear_recent_files(False), {'FINISHED'})
|
||||
|
||||
def test_undo_flag_int(self):
|
||||
self.assertEqual(bpy.ops.wm.clear_recent_files(1), {'FINISHED'})
|
||||
|
||||
def test_undo_flag_int_zero(self):
|
||||
self.assertEqual(bpy.ops.wm.clear_recent_files(0), {'FINISHED'})
|
||||
|
||||
def test_context_and_undo(self):
|
||||
self.assertEqual(bpy.ops.wm.clear_recent_files('EXEC_DEFAULT', True), {'FINISHED'})
|
||||
|
||||
def test_context_and_undo_int(self):
|
||||
self.assertEqual(bpy.ops.wm.clear_recent_files('EXEC_DEFAULT', 1), {'FINISHED'})
|
||||
|
||||
def test_negative_int(self):
|
||||
self.assertEqual(bpy.ops.wm.clear_recent_files(-1), {'FINISHED'})
|
||||
|
||||
|
||||
class TestOperatorCallPositionalArgsInvalid(unittest.TestCase):
|
||||
|
||||
def test_args_invalid_type(self):
|
||||
for arg in (1.0, None, [], {}, (), set(), b'EXEC_DEFAULT'):
|
||||
with self.assertRaises(ValueError):
|
||||
bpy.ops.wm.clear_recent_files(arg)
|
||||
|
||||
def test_args_invalid_order(self):
|
||||
for args in (
|
||||
(True, 'EXEC_DEFAULT'),
|
||||
(1, 'EXEC_DEFAULT'),
|
||||
):
|
||||
with self.assertRaises(ValueError):
|
||||
bpy.ops.wm.clear_recent_files(*args)
|
||||
|
||||
def test_args_invalid_duplicates(self):
|
||||
for args in (
|
||||
(True, True),
|
||||
(False, False),
|
||||
(1, 1),
|
||||
(1, True),
|
||||
('EXEC_DEFAULT', 'EXEC_DEFAULT'),
|
||||
('EXEC_DEFAULT', True, 'extra'),
|
||||
(True, True, 'EXEC_DEFAULT', 'EXEC_DEFAULT'),
|
||||
):
|
||||
with self.assertRaises(ValueError):
|
||||
bpy.ops.wm.clear_recent_files(*args)
|
||||
|
||||
|
||||
class TestOperatorCallKeywordArgs(unittest.TestCase):
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue