Blender V5.0
bpy_operator.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
15
16#include <Python.h>
17
18#include "RNA_types.hh"
19
20#include "BLI_listbase.h"
21
24#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
25
26#include "BPY_extern.hh"
27#include "bpy_capi_utils.hh"
28#include "bpy_operator.hh"
29#include "bpy_operator_wrap.hh"
30#include "bpy_rna.hh" /* for setting argument properties & type method `get_rna_type`. */
31
32#include "RNA_access.hh"
33#include "RNA_enum_types.hh"
34#include "RNA_prototypes.hh"
35
36#include "WM_api.hh"
37#include "WM_types.hh"
38
39#include "MEM_guardedalloc.h"
40
41#include "BKE_context.hh"
42#include "BKE_report.hh"
43
44#include "CLG_log.h"
45
46/* so operators called can spawn threads which acquire the GIL */
47#define BPY_RELEASE_GIL
48
49static wmOperatorType *ot_lookup_from_py_string(PyObject *value, const char *py_fn_id)
50{
51 const char *opname = PyUnicode_AsUTF8(value);
52 if (opname == nullptr) {
53 PyErr_Format(PyExc_TypeError, "%s() expects a string argument", py_fn_id);
54 return nullptr;
55 }
56
57 wmOperatorType *ot = WM_operatortype_find(opname, true);
58 if (ot == nullptr) {
59 PyErr_Format(PyExc_KeyError, "%s(\"%s\") not found", py_fn_id, opname);
60 return nullptr;
61 }
62 return ot;
63}
64
65static PyObject *pyop_poll(PyObject * /*self*/, PyObject *args)
66{
68 const char *opname;
69 const char *context_str = nullptr;
70 PyObject *ret;
71
73
74 /* XXX TODO: work out a better solution for passing on context,
75 * could make a tuple from self and pack the name and Context into it. */
77
78 if (C == nullptr) {
79 PyErr_SetString(PyExc_RuntimeError, "Context is None, cannot poll any operators");
80 return nullptr;
81 }
82
83 /* All arguments are positional. */
84 static const char *_keywords[] = {"", "", nullptr};
85 static _PyArg_Parser _parser = {
87 "s" /* `opname` */
88 "|" /* Optional arguments. */
89 "s" /* `context_str` */
90 ":_bpy.ops.poll",
91 _keywords,
92 nullptr,
93 };
94 if (!_PyArg_ParseTupleAndKeywordsFast(args, nullptr, &_parser, &opname, &context_str)) {
95 return nullptr;
96 }
97
98 ot = WM_operatortype_find(opname, true);
99
100 if (ot == nullptr) {
101 PyErr_Format(PyExc_AttributeError,
102 "Polling operator \"bpy.ops.%s\" error, "
103 "could not be found",
104 opname);
105 return nullptr;
106 }
107
108 if (context_str) {
109 int context_int = int(context);
110
111 if (RNA_enum_value_from_id(rna_enum_operator_context_items, context_str, &context_int) == 0) {
113 PyErr_Format(PyExc_TypeError,
114 "Calling operator \"bpy.ops.%s.poll\" error, "
115 "expected a string enum in (%s)",
116 opname,
117 enum_str);
118 MEM_freeN(enum_str);
119 return nullptr;
120 }
121 /* Copy back to the properly typed enum. */
122 context = blender::wm::OpCallContext(context_int);
123 }
124
125 /* main purpose of this function */
126 ret = WM_operator_poll_context(C, ot, context) ? Py_True : Py_False;
127
128 return Py_NewRef(ret);
129}
130
131static PyObject *pyop_call(PyObject * /*self*/, PyObject *args)
132{
134 int error_val = 0;
137
138 const char *opname;
139 const char *context_str = nullptr;
140 PyObject *kw = nullptr; /* optional args */
141
143 int is_undo = false;
144
145 /* XXX TODO: work out a better solution for passing on context,
146 * could make a tuple from self and pack the name and Context into it. */
148
149 if (C == nullptr) {
150 PyErr_SetString(PyExc_RuntimeError, "Context is None, cannot poll any operators");
151 return nullptr;
152 }
153
154 /* All arguments are positional. */
155 static const char *_keywords[] = {"", "", "", "", nullptr};
156 static _PyArg_Parser _parser = {
158 "s" /* `opname` */
159 "|" /* Optional arguments. */
160 "O!" /* `kw` */
161 "s" /* `context_str` */
162 "i" /* `is_undo` */
163 ":_bpy.ops.call",
164 _keywords,
165 nullptr,
166 };
167 if (!_PyArg_ParseTupleAndKeywordsFast(
168 args, nullptr, &_parser, &opname, &PyDict_Type, &kw, &context_str, &is_undo))
169 {
170 return nullptr;
171 }
172
173 ot = WM_operatortype_find(opname, true);
174
175 if (ot == nullptr) {
176 PyErr_Format(PyExc_AttributeError,
177 "Calling operator \"bpy.ops.%s\" error, "
178 "could not be found",
179 opname);
180 return nullptr;
181 }
182
183 if (!pyrna_write_check()) {
184 PyErr_Format(PyExc_RuntimeError,
185 "Calling operator \"bpy.ops.%s\" error, "
186 "cannot modify blend data in this state (drawing/rendering)",
187 opname);
188 return nullptr;
189 }
190
191 if (context_str) {
192 int context_int = int(context);
193
194 if (RNA_enum_value_from_id(rna_enum_operator_context_items, context_str, &context_int) == 0) {
196 PyErr_Format(PyExc_TypeError,
197 "Calling operator \"bpy.ops.%s\" error, "
198 "expected a string enum in (%s)",
199 opname,
200 enum_str);
201 MEM_freeN(enum_str);
202 return nullptr;
203 }
204 /* Copy back to the properly typed enum. */
205 context = blender::wm::OpCallContext(context_int);
206 }
207
208 if (WM_operator_poll_context(C, ot, context) == false) {
209 bool msg_free = false;
210 const char *msg = CTX_wm_operator_poll_msg_get(C, &msg_free);
211 PyErr_Format(PyExc_RuntimeError,
212 "Operator bpy.ops.%.200s.poll() %.200s",
213 opname,
214 msg ? msg : "failed, context is incorrect");
216 if (msg_free) {
217 MEM_freeN(msg);
218 }
219 error_val = -1;
220 }
221 else {
224
225 if (kw && PyDict_Size(kw)) {
226 error_val = pyrna_pydict_to_props(
227 &ptr, kw, false, "Converting py args to operator properties:");
228 }
229
230 if (error_val == 0) {
231 ReportList *reports;
232
233 reports = MEM_mallocN<ReportList>("wmOperatorReportList");
234
235 /* Own so these don't move into global reports. */
237
238#ifdef BPY_RELEASE_GIL
239 /* release GIL, since a thread could be started from an operator
240 * that updates a driver */
241 /* NOTE: I have not seen any examples of code that does this
242 * so it may not be officially supported but seems to work ok. */
243 {
244 PyThreadState *ts = PyEval_SaveThread();
245#endif
246
247 retval = WM_operator_call_py(C, ot, context, &ptr, reports, is_undo);
248
249#ifdef BPY_RELEASE_GIL
250 /* regain GIL */
251 PyEval_RestoreThread(ts);
252 }
253#endif
254
255 error_val = BPy_reports_to_error(reports, PyExc_RuntimeError, false);
256
257 /* operator output is nice to have in the terminal/console too */
258 if (!BLI_listbase_is_empty(&reports->list)) {
259 /* Restore the print level as this is owned by the operator now. */
260 eReportType level = eReportType(reports->printlevel);
262 BPy_reports_write_stdout(reports, nullptr);
263 BKE_report_print_level_set(reports, level);
264 }
265
266 BKE_reports_clear(reports);
267 if ((reports->flag & RPT_FREE) == 0) {
268 BKE_reports_free(reports);
269 MEM_freeN(reports);
270 }
271 else {
272 /* The WM is now responsible for running the modal operator,
273 * show reports in the info window. */
274 reports->flag &= ~RPT_OP_HOLD;
275 }
276 }
277
279
280#if 0
281 /* if there is some way to know an operator takes args we should use this */
282 {
283 /* no props */
284 if (kw != nullptr) {
285 PyErr_Format(PyExc_AttributeError, "Operator \"%s\" does not take any args", opname);
286 return nullptr;
287 }
288
290 }
291#endif
292 }
293
294 if (error_val == -1) {
295 return nullptr;
296 }
297
298 /* When calling `bpy.ops.wm.read_factory_settings()` `bpy.data's` main pointer
299 * is freed by clear_globals(), further access will crash blender.
300 * Setting context is not needed in this case, only calling because this
301 * function corrects bpy.data (internal Main pointer) */
303
304 /* Return `retval` flag as a set. */
306}
307
308static PyObject *pyop_as_string(PyObject * /*self*/, PyObject *args)
309{
311
312 const char *opname;
313 PyObject *kw = nullptr; /* optional args */
314 bool all_args = true;
315 bool macro_args = true;
316 int error_val = 0;
317
319
320 if (C == nullptr) {
321 PyErr_SetString(PyExc_RuntimeError,
322 "Context is None, cannot get the string representation of this object.");
323 return nullptr;
324 }
325
326 /* All arguments are positional. */
327 static const char *_keywords[] = {"", "", "", "", nullptr};
328 static _PyArg_Parser _parser = {
330 "s" /* `opname` */
331 "|" /* Optional arguments. */
332 "O!" /* `kw` */
333 "O&" /* `all_args` */
334 "O&" /* `macro_args` */
335 ":_bpy.ops.as_string",
336 _keywords,
337 nullptr,
338 };
339 if (!_PyArg_ParseTupleAndKeywordsFast(args,
340 nullptr,
341 &_parser,
342 &opname,
343 &PyDict_Type,
344 &kw,
346 &all_args,
348 &macro_args))
349 {
350 return nullptr;
351 }
352
353 ot = WM_operatortype_find(opname, true);
354
355 if (ot == nullptr) {
356 PyErr_Format(PyExc_AttributeError,
357 "_bpy.ops.as_string: operator \"%.200s\" "
358 "could not be found",
359 opname);
360 return nullptr;
361 }
362
363 // WM_operator_properties_create(&ptr, opname);
364 /* Save another lookup */
365 PointerRNA ptr = RNA_pointer_create_discrete(nullptr, ot->srna, nullptr);
366
367 if (kw && PyDict_Size(kw)) {
368 error_val = pyrna_pydict_to_props(
369 &ptr, kw, false, "Converting py args to operator properties:");
370 }
371
372 std::string op_string;
373 if (error_val == 0) {
374 op_string = WM_operator_pystring_ex(C, nullptr, all_args, macro_args, ot, &ptr);
375 }
376
378
379 if (error_val == -1) {
380 return nullptr;
381 }
382
383 return PyC_UnicodeFromStdStr(op_string);
384}
385
386static PyObject *pyop_dir(PyObject * /*self*/)
387{
389 PyObject *list = PyList_New(types.size());
390
391 int i = 0;
392 for (wmOperatorType *ot : types) {
393 PyList_SET_ITEM(list, i, PyUnicode_FromString(ot->idname));
394 i++;
395 }
396
397 return list;
398}
399
400static PyObject *pyop_getrna_type(PyObject * /*self*/, PyObject *value)
401{
403 if ((ot = ot_lookup_from_py_string(value, "get_rna_type")) == nullptr) {
404 return nullptr;
405 }
406
407 PointerRNA ptr = RNA_pointer_create_discrete(nullptr, &RNA_Struct, ot->srna);
409 return (PyObject *)pyrna;
410}
411
412static PyObject *pyop_get_bl_options(PyObject * /*self*/, PyObject *value)
413{
415 if ((ot = ot_lookup_from_py_string(value, "get_bl_options")) == nullptr) {
416 return nullptr;
417 }
419}
420
421#ifdef __GNUC__
422# ifdef __clang__
423# pragma clang diagnostic push
424# pragma clang diagnostic ignored "-Wcast-function-type"
425# else
426# pragma GCC diagnostic push
427# pragma GCC diagnostic ignored "-Wcast-function-type"
428# endif
429#endif
430
431static PyMethodDef bpy_ops_methods[] = {
432 {"poll", (PyCFunction)pyop_poll, METH_VARARGS, nullptr},
433 {"call", (PyCFunction)pyop_call, METH_VARARGS, nullptr},
434 {"as_string", (PyCFunction)pyop_as_string, METH_VARARGS, nullptr},
435 {"dir", (PyCFunction)pyop_dir, METH_NOARGS, nullptr},
436 {"get_rna_type", (PyCFunction)pyop_getrna_type, METH_O, nullptr},
437 {"get_bl_options", (PyCFunction)pyop_get_bl_options, METH_O, nullptr},
438 {"macro_define", (PyCFunction)PYOP_wrap_macro_define, METH_VARARGS, nullptr},
439 {nullptr, nullptr, 0, nullptr},
440};
441
442#ifdef __GNUC__
443# ifdef __clang__
444# pragma clang diagnostic pop
445# else
446# pragma GCC diagnostic pop
447# endif
448#endif
449
450static PyModuleDef bpy_ops_module = {
451 /*m_base*/ PyModuleDef_HEAD_INIT,
452 /*m_name*/ "_bpy.ops",
453 /*m_doc*/ nullptr,
454 /*m_size*/ -1, /* multiple "initialization" just copies the module dict. */
455 /*m_methods*/ bpy_ops_methods,
456 /*m_slots*/ nullptr,
457 /*m_traverse*/ nullptr,
458 /*m_clear*/ nullptr,
459 /*m_free*/ nullptr,
460};
461
463{
464 PyObject *submodule;
465
466 submodule = PyModule_Create(&bpy_ops_module);
467
468 return submodule;
469}
void CTX_wm_operator_poll_msg_clear(bContext *C)
const char * CTX_wm_operator_poll_msg_get(bContext *C, bool *r_free)
@ RPT_OP_HOLD
Definition BKE_report.hh:58
@ RPT_PRINT_HANDLED_BY_OWNER
Definition BKE_report.hh:60
@ RPT_FREE
Definition BKE_report.hh:57
@ RPT_STORE
Definition BKE_report.hh:56
eReportType
Definition BKE_report.hh:33
@ RPT_DEBUG
Definition BKE_report.hh:34
@ RPT_WARNING
Definition BKE_report.hh:38
void BKE_reports_free(ReportList *reports)
Definition report.cc:97
void BKE_report_print_level_set(ReportList *reports, eReportType level)
Definition report.cc:265
void BKE_reports_clear(ReportList *reports)
Definition report.cc:109
void BKE_reports_init(ReportList *reports, int flag)
Definition report.cc:82
BLI_INLINE bool BLI_listbase_is_empty(const ListBase *lb)
void BPY_modules_update()
bool CLG_quiet_get()
Definition clog.cc:945
@ OPERATOR_CANCELLED
Read Guarded memory(de)allocation.
#define C
Definition RandGen.cpp:29
void BPy_reports_write_stdout(const ReportList *reports, const char *header)
short BPy_reports_to_error(ReportList *reports, PyObject *exception, const bool clear)
struct bContext * BPY_context_get()
static PyObject * pyop_getrna_type(PyObject *, PyObject *value)
static PyObject * pyop_get_bl_options(PyObject *, PyObject *value)
static PyObject * pyop_dir(PyObject *)
static PyMethodDef bpy_ops_methods[]
static PyObject * pyop_call(PyObject *, PyObject *args)
static PyObject * pyop_poll(PyObject *, PyObject *args)
static PyObject * pyop_as_string(PyObject *, PyObject *args)
static PyModuleDef bpy_ops_module
static wmOperatorType * ot_lookup_from_py_string(PyObject *value, const char *py_fn_id)
PyObject * BPY_operator_module()
PyObject * PYOP_wrap_macro_define(PyObject *, PyObject *args)
bool pyrna_write_check()
Definition bpy_rna.cc:457
int pyrna_pydict_to_props(PointerRNA *ptr, PyObject *kw, const bool all_args, const char *error_prefix)
Definition bpy_rna.cc:1530
PyObject * pyrna_struct_CreatePyObject(PointerRNA *ptr)
Definition bpy_rna.cc:8496
static char ** types
Definition makesdna.cc:71
void * MEM_mallocN(size_t len, const char *str)
Definition mallocn.cc:128
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
PyObject * pyrna_enum_bitfield_as_set(const EnumPropertyItem *items, int value)
char * pyrna_enum_repr(const EnumPropertyItem *item)
PyObject * PyC_UnicodeFromStdStr(const std::string &str)
int PyC_ParseBool(PyObject *o, void *p)
header-only compatibility defines.
#define PY_ARG_PARSER_HEAD_COMPAT()
return ret
bool RNA_enum_value_from_id(const EnumPropertyItem *item, const char *identifier, int *r_value)
PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
const EnumPropertyItem rna_enum_operator_context_items[]
Definition rna_ui.cc:33
const EnumPropertyItem rna_enum_operator_type_flag_items[]
Definition rna_wm.cc:539
const EnumPropertyItem rna_enum_operator_return_items[]
Definition rna_wm.cc:584
ListBase list
Definition BKE_report.hh:75
i
Definition text_draw.cc:230
bool WM_operator_poll_context(bContext *C, wmOperatorType *ot, blender::wm::OpCallContext context)
wmOperatorStatus WM_operator_call_py(bContext *C, wmOperatorType *ot, blender::wm::OpCallContext context, PointerRNA *properties, ReportList *reports, const bool is_undo)
wmOperatorStatus WM_operator_name_call(bContext *C, const char *opstring, blender::wm::OpCallContext context, PointerRNA *properties, const wmEvent *event)
PointerRNA * ptr
Definition wm_files.cc:4238
wmOperatorType * ot
Definition wm_files.cc:4237
blender::Span< wmOperatorType * > WM_operatortypes_registered_get()
wmOperatorType * WM_operatortype_find(const char *idname, bool quiet)
std::string WM_operator_pystring_ex(bContext *C, wmOperator *op, const bool all_args, const bool macro_args, wmOperatorType *ot, PointerRNA *opptr)
void WM_operator_properties_create_ptr(PointerRNA *ptr, wmOperatorType *ot)
void WM_operator_properties_free(PointerRNA *ptr)
void WM_operator_properties_sanitize(PointerRNA *ptr, const bool no_context)