/* SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup pythonintern * * This file defines `_bpy.ops`, an internal python module which gives Python * the ability to inspect and call operators (defined by C or Python). * * \note * This C module is private, it should only be used by `release/scripts/modules/bpy/ops.py` which * exposes operators as dynamically defined modules & callable objects to access all operators. */ #include #include "RNA_types.h" #include "BLI_listbase.h" #include "BLI_utildefines.h" #include "../generic/py_capi_rna.h" #include "../generic/py_capi_utils.h" #include "../generic/python_utildefines.h" #include "BPY_extern.h" #include "bpy_capi_utils.h" #include "bpy_operator.h" #include "bpy_operator_wrap.h" #include "bpy_rna.h" /* for setting argument properties & type method `get_rna_type`. */ #include "RNA_access.h" #include "RNA_enum_types.h" #include "RNA_prototypes.h" #include "WM_api.h" #include "WM_types.h" #include "MEM_guardedalloc.h" #include "BLI_ghash.h" #include "BKE_context.h" #include "BKE_report.h" /* so operators called can spawn threads which acquire the GIL */ #define BPY_RELEASE_GIL static wmOperatorType *ot_lookup_from_py_string(PyObject *value, const char *py_fn_id) { const char *opname = PyUnicode_AsUTF8(value); if (opname == NULL) { PyErr_Format(PyExc_TypeError, "%s() expects a string argument", py_fn_id); return NULL; } wmOperatorType *ot = WM_operatortype_find(opname, true); if (ot == NULL) { PyErr_Format(PyExc_KeyError, "%s(\"%s\") not found", py_fn_id, opname); return NULL; } return ot; } static void op_context_override_deprecated_warning(const char *action, const char *opname) { if (PyErr_WarnFormat( PyExc_DeprecationWarning, /* Use stack level 2 as this call is wrapped by `release/scripts/modules/bpy/ops.py`, * An extra stack level is needed to show the warning in the authors script. */ 2, "Passing in context overrides is deprecated in favor of " "Context.temp_override(..), %s \"%s\"", action, opname) < 0) { /* The function has no return value, the exception cannot * be reported to the caller, so just log it. */ PyErr_WriteUnraisable(NULL); } } static PyObject *pyop_poll(PyObject *UNUSED(self), PyObject *args) { wmOperatorType *ot; const char *opname; PyObject *context_dict = NULL; /* optional args */ const char *context_str = NULL; PyObject *ret; wmOperatorCallContext context = WM_OP_EXEC_DEFAULT; /* XXX TODO: work out a better solution for passing on context, * could make a tuple from self and pack the name and Context into it. */ bContext *C = BPY_context_get(); if (C == NULL) { PyErr_SetString(PyExc_RuntimeError, "Context is None, can't poll any operators"); return NULL; } if (!PyArg_ParseTuple(args, "s|Os:_bpy.ops.poll", &opname, &context_dict, &context_str)) { return NULL; } ot = WM_operatortype_find(opname, true); if (ot == NULL) { PyErr_Format(PyExc_AttributeError, "Polling operator \"bpy.ops.%s\" error, " "could not be found", opname); return NULL; } if (context_str) { int context_int = context; if (RNA_enum_value_from_id(rna_enum_operator_context_items, context_str, &context_int) == 0) { char *enum_str = pyrna_enum_repr(rna_enum_operator_context_items); PyErr_Format(PyExc_TypeError, "Calling operator \"bpy.ops.%s.poll\" error, " "expected a string enum in (%s)", opname, enum_str); MEM_freeN(enum_str); return NULL; } /* Copy back to the properly typed enum. */ context = context_int; } if (ELEM(context_dict, NULL, Py_None)) { context_dict = NULL; } else if (PyDict_Check(context_dict)) { op_context_override_deprecated_warning("polling", opname); } else { PyErr_Format(PyExc_TypeError, "Calling operator \"bpy.ops.%s.poll\" error, " "custom context expected a dict or None, got a %.200s", opname, Py_TYPE(context_dict)->tp_name); return NULL; } struct bContext_PyState context_py_state; if (context_dict != NULL) { CTX_py_state_push(C, &context_py_state, (void *)context_dict); Py_INCREF(context_dict); /* so we don't lose it */ } /* main purpose of this function */ ret = WM_operator_poll_context((bContext *)C, ot, context) ? Py_True : Py_False; if (context_dict != NULL) { PyObject *context_dict_test = CTX_py_dict_get(C); if (context_dict_test != context_dict) { Py_DECREF(context_dict_test); } /* Restore with original context dict, * probably NULL but need this for nested operator calls. */ Py_DECREF(context_dict); CTX_py_state_pop(C, &context_py_state); } return Py_INCREF_RET(ret); } static PyObject *pyop_call(PyObject *UNUSED(self), PyObject *args) { wmOperatorType *ot; int error_val = 0; PointerRNA ptr; int operator_ret = OPERATOR_CANCELLED; const char *opname; const char *context_str = NULL; PyObject *kw = NULL; /* optional args */ PyObject *context_dict = NULL; /* optional args */ wmOperatorCallContext context = WM_OP_EXEC_DEFAULT; int is_undo = false; /* XXX TODO: work out a better solution for passing on context, * could make a tuple from self and pack the name and Context into it. */ bContext *C = BPY_context_get(); if (C == NULL) { PyErr_SetString(PyExc_RuntimeError, "Context is None, can't poll any operators"); return NULL; } if (!PyArg_ParseTuple(args, "sO|O!si:_bpy.ops.call", &opname, &context_dict, &PyDict_Type, &kw, &context_str, &is_undo)) { return NULL; } ot = WM_operatortype_find(opname, true); if (ot == NULL) { PyErr_Format(PyExc_AttributeError, "Calling operator \"bpy.ops.%s\" error, " "could not be found", opname); return NULL; } if (!pyrna_write_check()) { PyErr_Format(PyExc_RuntimeError, "Calling operator \"bpy.ops.%s\" error, " "can't modify blend data in this state (drawing/rendering)", opname); return NULL; } if (context_str) { int context_int = context; if (RNA_enum_value_from_id(rna_enum_operator_context_items, context_str, &context_int) == 0) { char *enum_str = pyrna_enum_repr(rna_enum_operator_context_items); PyErr_Format(PyExc_TypeError, "Calling operator \"bpy.ops.%s\" error, " "expected a string enum in (%s)", opname, enum_str); MEM_freeN(enum_str); return NULL; } /* Copy back to the properly typed enum. */ context = context_int; } if (ELEM(context_dict, NULL, Py_None)) { context_dict = NULL; } else if (PyDict_Check(context_dict)) { op_context_override_deprecated_warning("calling", opname); } else { PyErr_Format(PyExc_TypeError, "Calling operator \"bpy.ops.%s\" error, " "custom context expected a dict or None, got a %.200s", opname, Py_TYPE(context_dict)->tp_name); return NULL; } /** * It might be that there is already a Python context override. We don't want to remove that * except when this operator call sets a new override explicitly. This is necessary so that * called operator runs in the same context as the calling code by default. */ struct bContext_PyState context_py_state; if (context_dict != NULL) { CTX_py_state_push(C, &context_py_state, (void *)context_dict); Py_INCREF(context_dict); /* so we don't lose it */ } if (WM_operator_poll_context((bContext *)C, ot, context) == false) { bool msg_free = false; const char *msg = CTX_wm_operator_poll_msg_get(C, &msg_free); PyErr_Format(PyExc_RuntimeError, "Operator bpy.ops.%.200s.poll() %.200s", opname, msg ? msg : "failed, context is incorrect"); CTX_wm_operator_poll_msg_clear(C); if (msg_free) { MEM_freeN((void *)msg); } error_val = -1; } else { WM_operator_properties_create_ptr(&ptr, ot); WM_operator_properties_sanitize(&ptr, 0); if (kw && PyDict_Size(kw)) { error_val = pyrna_pydict_to_props( &ptr, kw, false, "Converting py args to operator properties: "); } if (error_val == 0) { ReportList *reports; reports = MEM_mallocN(sizeof(ReportList), "wmOperatorReportList"); /* Own so these don't move into global reports. */ BKE_reports_init(reports, RPT_STORE | RPT_OP_HOLD | RPT_PRINT_HANDLED_BY_OWNER); #ifdef BPY_RELEASE_GIL /* release GIL, since a thread could be started from an operator * that updates a driver */ /* NOTE: I have not seen any examples of code that does this * so it may not be officially supported but seems to work ok. */ { PyThreadState *ts = PyEval_SaveThread(); #endif operator_ret = WM_operator_call_py(C, ot, context, &ptr, reports, is_undo); #ifdef BPY_RELEASE_GIL /* regain GIL */ PyEval_RestoreThread(ts); } #endif error_val = BPy_reports_to_error(reports, PyExc_RuntimeError, false); /* operator output is nice to have in the terminal/console too */ if (!BLI_listbase_is_empty(&reports->list)) { BPy_reports_write_stdout(reports, NULL); } BKE_reports_clear(reports); if ((reports->flag & RPT_FREE) == 0) { MEM_freeN(reports); } else { /* The WM is now responsible for running the modal operator, * show reports in the info window. */ reports->flag &= ~RPT_OP_HOLD; } } WM_operator_properties_free(&ptr); #if 0 /* if there is some way to know an operator takes args we should use this */ { /* no props */ if (kw != NULL) { PyErr_Format(PyExc_AttributeError, "Operator \"%s\" does not take any args", opname); return NULL; } WM_operator_name_call(C, opname, WM_OP_EXEC_DEFAULT, NULL, NULL); } #endif } if (context_dict != NULL) { PyObject *context_dict_test = CTX_py_dict_get(C); if (context_dict_test != context_dict) { Py_DECREF(context_dict_test); } /* Restore with original context dict, * probably NULL but need this for nested operator calls. */ Py_DECREF(context_dict); CTX_py_state_pop(C, &context_py_state); } if (error_val == -1) { return NULL; } /* When calling `bpy.ops.wm.read_factory_settings()` `bpy.data's` main pointer * is freed by clear_globals(), further access will crash blender. * Setting context is not needed in this case, only calling because this * function corrects bpy.data (internal Main pointer) */ BPY_modules_update(); /* return operator_ret as a bpy enum */ return pyrna_enum_bitfield_as_set(rna_enum_operator_return_items, operator_ret); } static PyObject *pyop_as_string(PyObject *UNUSED(self), PyObject *args) { wmOperatorType *ot; PointerRNA ptr; const char *opname; PyObject *kw = NULL; /* optional args */ bool all_args = true; bool macro_args = true; int error_val = 0; char *buf = NULL; PyObject *pybuf; bContext *C = BPY_context_get(); if (C == NULL) { PyErr_SetString(PyExc_RuntimeError, "Context is None, can't get the string representation of this object."); return NULL; } if (!PyArg_ParseTuple(args, "s|O!O&O&:_bpy.ops.as_string", &opname, &PyDict_Type, &kw, PyC_ParseBool, &all_args, PyC_ParseBool, ¯o_args)) { return NULL; } ot = WM_operatortype_find(opname, true); if (ot == NULL) { PyErr_Format(PyExc_AttributeError, "_bpy.ops.as_string: operator \"%.200s\" " "could not be found", opname); return NULL; } // WM_operator_properties_create(&ptr, opname); /* Save another lookup */ RNA_pointer_create(NULL, ot->srna, NULL, &ptr); if (kw && PyDict_Size(kw)) { error_val = pyrna_pydict_to_props( &ptr, kw, false, "Converting py args to operator properties: "); } if (error_val == 0) { buf = WM_operator_pystring_ex(C, NULL, all_args, macro_args, ot, &ptr); } WM_operator_properties_free(&ptr); if (error_val == -1) { return NULL; } if (buf) { pybuf = PyUnicode_FromString(buf); MEM_freeN(buf); } else { pybuf = PyUnicode_FromString(""); } return pybuf; } static PyObject *pyop_dir(PyObject *UNUSED(self)) { GHashIterator iter; PyObject *list; int i; WM_operatortype_iter(&iter); list = PyList_New(BLI_ghash_len(iter.gh)); for (i = 0; !BLI_ghashIterator_done(&iter); BLI_ghashIterator_step(&iter), i++) { wmOperatorType *ot = BLI_ghashIterator_getValue(&iter); PyList_SET_ITEM(list, i, PyUnicode_FromString(ot->idname)); } return list; } static PyObject *pyop_getrna_type(PyObject *UNUSED(self), PyObject *value) { wmOperatorType *ot; if ((ot = ot_lookup_from_py_string(value, "get_rna_type")) == NULL) { return NULL; } PointerRNA ptr; RNA_pointer_create(NULL, &RNA_Struct, ot->srna, &ptr); BPy_StructRNA *pyrna = (BPy_StructRNA *)pyrna_struct_CreatePyObject(&ptr); return (PyObject *)pyrna; } static PyObject *pyop_get_bl_options(PyObject *UNUSED(self), PyObject *value) { wmOperatorType *ot; if ((ot = ot_lookup_from_py_string(value, "get_bl_options")) == NULL) { return NULL; } return pyrna_enum_bitfield_as_set(rna_enum_operator_type_flag_items, ot->flag); } static struct PyMethodDef bpy_ops_methods[] = { {"poll", (PyCFunction)pyop_poll, METH_VARARGS, NULL}, {"call", (PyCFunction)pyop_call, METH_VARARGS, NULL}, {"as_string", (PyCFunction)pyop_as_string, METH_VARARGS, NULL}, {"dir", (PyCFunction)pyop_dir, METH_NOARGS, NULL}, {"get_rna_type", (PyCFunction)pyop_getrna_type, METH_O, NULL}, {"get_bl_options", (PyCFunction)pyop_get_bl_options, METH_O, NULL}, {"macro_define", (PyCFunction)PYOP_wrap_macro_define, METH_VARARGS, NULL}, {NULL, NULL, 0, NULL}, }; static struct PyModuleDef bpy_ops_module = { PyModuleDef_HEAD_INIT, /*m_name*/ "_bpy.ops", /*m_doc*/ NULL, /*m_size*/ -1, /* multiple "initialization" just copies the module dict. */ /*m_methods*/ bpy_ops_methods, /*m_slots*/ NULL, /*m_traverse*/ NULL, /*m_clear*/ NULL, /*m_free*/ NULL, }; PyObject *BPY_operator_module(void) { PyObject *submodule; submodule = PyModule_Create(&bpy_ops_module); return submodule; }