Blender V4.3
bpy_msgbus.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
10#include <Python.h>
11
16
18
19#include "BLI_utildefines.h"
20
21#include "BKE_context.hh"
22
23#include "WM_message.hh"
24
25#include "RNA_access.hh"
26
27#include "bpy_capi_utils.hh"
28#include "bpy_rna.hh"
29
30#include "bpy_msgbus.hh" /* own include */
31
32/* -------------------------------------------------------------------- */
36#define BPY_MSGBUS_RNA_MSGKEY_DOC \
37 " :arg key: Represents the type of data being subscribed to\n" \
38 "\n" \
39 " Arguments include\n" \
40 " - A property instance.\n" \
41 " - A struct type.\n" \
42 " - A tuple representing a (struct, property name) pair.\n" \
43 " :type key: :class:`bpy.types.Property` | " \
44 ":class:`bpy.types.Struct` | " \
45 "tuple[:class:`bpy.types.Struct`, str]\n"
46
57static int py_msgbus_rna_key_from_py(PyObject *py_sub,
58 wmMsgParams_RNA *msg_key_params,
59 const char *error_prefix)
60{
61
62 /* Allow common case, object rotation, location - etc. */
63 if (BaseMathObject_CheckExact(py_sub)) {
64 BaseMathObject *py_sub_math = (BaseMathObject *)py_sub;
65 if (py_sub_math->cb_user == nullptr) {
66 PyErr_Format(PyExc_TypeError, "%s: math argument has no owner", error_prefix);
67 return -1;
68 }
69 py_sub = py_sub_math->cb_user;
70 /* Common case will use BPy_PropertyRNA_Check below. */
71 }
72
73 if (BPy_PropertyRNA_Check(py_sub)) {
74 BPy_PropertyRNA *data_prop = (BPy_PropertyRNA *)py_sub;
75 PYRNA_PROP_CHECK_INT(data_prop);
76 msg_key_params->ptr = data_prop->ptr;
77 msg_key_params->prop = data_prop->prop;
78 }
79 else if (BPy_StructRNA_Check(py_sub)) {
80 /* NOTE: this isn't typically used since we don't edit structs directly. */
81 BPy_StructRNA *data_srna = (BPy_StructRNA *)py_sub;
82 PYRNA_STRUCT_CHECK_INT(data_srna);
83 msg_key_params->ptr = data_srna->ptr;
84 }
85 /* TODO: property / type, not instance. */
86 else if (PyType_Check(py_sub)) {
87 StructRNA *data_type = pyrna_struct_as_srna(py_sub, false, error_prefix);
88 if (data_type == nullptr) {
89 return -1;
90 }
91 msg_key_params->ptr.type = data_type;
92 }
93 else if (PyTuple_CheckExact(py_sub)) {
94 if (PyTuple_GET_SIZE(py_sub) == 2) {
95 PyObject *data_type_py = PyTuple_GET_ITEM(py_sub, 0);
96 PyObject *data_prop_py = PyTuple_GET_ITEM(py_sub, 1);
97 StructRNA *data_type = pyrna_struct_as_srna(data_type_py, false, error_prefix);
98 if (data_type == nullptr) {
99 return -1;
100 }
101 if (!PyUnicode_CheckExact(data_prop_py)) {
102 PyErr_Format(PyExc_TypeError, "%s: expected property to be a string", error_prefix);
103 return -1;
104 }
105 PointerRNA data_type_ptr{};
106 data_type_ptr.type = data_type;
107
108 const char *data_prop_str = PyUnicode_AsUTF8(data_prop_py);
109 PropertyRNA *data_prop = RNA_struct_find_property(&data_type_ptr, data_prop_str);
110
111 if (data_prop == nullptr) {
112 PyErr_Format(PyExc_TypeError,
113 "%s: struct %.200s does not contain property %.200s",
114 error_prefix,
115 RNA_struct_identifier(data_type),
116 data_prop_str);
117 return -1;
118 }
119
120 msg_key_params->ptr.type = data_type;
121 msg_key_params->prop = data_prop;
122 }
123 else {
124 PyErr_Format(PyExc_ValueError, "%s: Expected a pair (type, property_id)", error_prefix);
125 return -1;
126 }
127 }
128 return 0;
129}
130
133/* -------------------------------------------------------------------- */
137#define BPY_MSGBUS_USER_DATA_LEN 2
138
139/* Follow wmMsgNotifyFn spec */
141 wmMsgSubscribeKey * /*msg_key*/,
142 wmMsgSubscribeValue *msg_val)
143{
144 PyGILState_STATE gilstate;
145 bpy_context_set(C, &gilstate);
146
147 PyObject *user_data = static_cast<PyObject *>(msg_val->user_data);
148 BLI_assert(PyTuple_GET_SIZE(user_data) == BPY_MSGBUS_USER_DATA_LEN);
149
150 PyObject *callback_args = PyTuple_GET_ITEM(user_data, 0);
151 PyObject *callback_notify = PyTuple_GET_ITEM(user_data, 1);
152
153 const bool is_write_ok = pyrna_write_check();
154 if (!is_write_ok) {
155 pyrna_write_set(true);
156 }
157
158 PyObject *ret = PyObject_CallObject(callback_notify, callback_args);
159
160 if (ret == nullptr) {
161 PyC_Err_PrintWithFunc(callback_notify);
162 }
163 else {
164 if (ret != Py_None) {
165 PyErr_SetString(PyExc_ValueError, "the return value must be None");
166 PyC_Err_PrintWithFunc(callback_notify);
167 }
168 Py_DECREF(ret);
169 }
170
171 bpy_context_clear(C, &gilstate);
172
173 if (!is_write_ok) {
174 pyrna_write_set(false);
175 }
176}
177
178/* Follow wmMsgSubscribeValueFreeDataFn spec */
180 wmMsgSubscribeValue *msg_val)
181{
182 const PyGILState_STATE gilstate = PyGILState_Ensure();
183 Py_DECREF(msg_val->owner);
184 Py_DECREF(msg_val->user_data);
185 PyGILState_Release(gilstate);
186}
187
190/* -------------------------------------------------------------------- */
195 /* Wrap. */
196 bpy_msgbus_subscribe_rna_doc,
197 ".. function:: subscribe_rna(key, owner, args, notify, options=set())\n"
198 "\n"
199 " Register a message bus subscription. It will be cleared when another blend file is\n"
200 " loaded, or can be cleared explicitly via :func:`bpy.msgbus.clear_by_owner`.\n"
202 " :arg owner: Handle for this subscription (compared by identity).\n"
203 " :type owner: Any\n"
204 " :arg options: Change the behavior of the subscriber.\n"
205 "\n"
206 " - ``PERSISTENT`` when set, the subscriber will be kept when remapping ID data.\n"
207 "\n"
208 " :type options: set[str]\n"
209 "\n"
210 ".. note::\n"
211 "\n"
212 " All subscribers will be cleared on file-load. Subscribers can be re-registered on load,\n"
213 " see :mod:`bpy.app.handlers.load_post`.\n");
214static PyObject *bpy_msgbus_subscribe_rna(PyObject * /*self*/, PyObject *args, PyObject *kw)
215{
216 const char *error_prefix = "subscribe_rna";
217 PyObject *py_sub = nullptr;
218 PyObject *py_owner = nullptr;
219 PyObject *callback_args = nullptr;
220 PyObject *callback_notify = nullptr;
221
222 enum {
223 IS_PERSISTENT = (1 << 0),
224 };
225 PyObject *py_options = nullptr;
226 const EnumPropertyItem py_options_enum[] = {
227 {IS_PERSISTENT, "PERSISTENT", 0, ""},
228 {0, nullptr, 0, nullptr, nullptr},
229 };
230 int options = 0;
231
232 if (PyTuple_GET_SIZE(args) != 0) {
233 PyErr_Format(PyExc_TypeError, "%s: only keyword arguments are supported", error_prefix);
234 return nullptr;
235 }
236 static const char *_keywords[] = {
237 "key",
238 "owner",
239 "args",
240 "notify",
241 "options",
242 nullptr,
243 };
244 static _PyArg_Parser _parser = {
246 "O" /* `key` */
247 "O" /* `owner` */
248 "O!" /* `args` */
249 "O" /* `notify` */
250 "|$" /* Optional keyword only arguments. */
251 "O!" /* `options` */
252 ":subscribe_rna",
253 _keywords,
254 nullptr,
255 };
256 if (!_PyArg_ParseTupleAndKeywordsFast(args,
257 kw,
258 &_parser,
259 &py_sub,
260 &py_owner,
261 &PyTuple_Type,
262 &callback_args,
263 &callback_notify,
264 &PySet_Type,
265 &py_options))
266 {
267 return nullptr;
268 }
269
270 if (py_options &&
271 (pyrna_enum_bitfield_from_set(py_options_enum, py_options, &options, error_prefix) == -1))
272 {
273 return nullptr;
274 }
275
276 /* NOTE: we may want to have a way to pass this in. */
278 wmMsgBus *mbus = CTX_wm_message_bus(C);
279 wmMsgParams_RNA msg_key_params = {{nullptr}};
280
281 wmMsgSubscribeValue msg_val_params = {nullptr};
282
283 if (py_msgbus_rna_key_from_py(py_sub, &msg_key_params, error_prefix) == -1) {
284 return nullptr;
285 }
286
287 if (!PyFunction_Check(callback_notify)) {
288 PyErr_Format(PyExc_TypeError,
289 "notify expects a function, found %.200s",
290 Py_TYPE(callback_notify)->tp_name);
291 return nullptr;
292 }
293
294 if (options != 0) {
295 if (options & IS_PERSISTENT) {
296 msg_val_params.is_persistent = true;
297 }
298 }
299
300 /* owner can be anything. */
301 {
302 msg_val_params.owner = py_owner;
303 Py_INCREF(py_owner);
304 }
305
306 {
307 PyObject *user_data = PyTuple_New(2);
308 PyTuple_SET_ITEMS(user_data, Py_NewRef(callback_args), Py_NewRef(callback_notify));
309 msg_val_params.user_data = user_data;
310 }
311
312 msg_val_params.notify = bpy_msgbus_notify;
314
315 WM_msg_subscribe_rna_params(mbus, &msg_key_params, &msg_val_params, __func__);
316
317 if (false) { /* For debugging. */
318 WM_msg_dump(mbus, __func__);
319 }
320
321 Py_RETURN_NONE;
322}
323
325 /* Wrap. */
326 bpy_msgbus_publish_rna_doc,
327 ".. function:: publish_rna(key)\n"
329 "\n"
330 " Notify subscribers of changes to this property\n"
331 " (this typically doesn't need to be called explicitly since changes will automatically "
332 "publish updates).\n"
333 " In some cases it may be useful to publish changes explicitly using more general keys.\n");
334static PyObject *bpy_msgbus_publish_rna(PyObject * /*self*/, PyObject *args, PyObject *kw)
335{
336 const char *error_prefix = "publish_rna";
337 PyObject *py_sub = nullptr;
338
339 if (PyTuple_GET_SIZE(args) != 0) {
340 PyErr_Format(PyExc_TypeError, "%s: only keyword arguments are supported", error_prefix);
341 return nullptr;
342 }
343 static const char *_keywords[] = {
344 "key",
345 nullptr,
346 };
347 static _PyArg_Parser _parser = {
349 "O" /* `key` */
350 ":publish_rna",
351 _keywords,
352 nullptr,
353 };
354 if (!_PyArg_ParseTupleAndKeywordsFast(args, kw, &_parser, &py_sub)) {
355 return nullptr;
356 }
357
358 /* NOTE: we may want to have a way to pass this in. */
360 wmMsgBus *mbus = CTX_wm_message_bus(C);
361 wmMsgParams_RNA msg_key_params = {{nullptr}};
362
363 if (py_msgbus_rna_key_from_py(py_sub, &msg_key_params, error_prefix) == -1) {
364 return nullptr;
365 }
366
367 WM_msg_publish_rna_params(mbus, &msg_key_params);
368
369 Py_RETURN_NONE;
370}
371
373 /* Wrap. */
374 bpy_msgbus_clear_by_owner_doc,
375 ".. function:: clear_by_owner(owner)\n"
376 "\n"
377 " Clear all subscribers using this owner.\n");
378static PyObject *bpy_msgbus_clear_by_owner(PyObject * /*self*/, PyObject *py_owner)
379{
381 wmMsgBus *mbus = CTX_wm_message_bus(C);
382 WM_msgbus_clear_by_owner(mbus, py_owner);
383 Py_RETURN_NONE;
384}
385
386#if (defined(__GNUC__) && !defined(__clang__))
387# pragma GCC diagnostic push
388# pragma GCC diagnostic ignored "-Wcast-function-type"
389#endif
390
391static PyMethodDef BPy_msgbus_methods[] = {
392 {"subscribe_rna",
393 (PyCFunction)bpy_msgbus_subscribe_rna,
394 METH_VARARGS | METH_KEYWORDS,
395 bpy_msgbus_subscribe_rna_doc},
396 {"publish_rna",
397 (PyCFunction)bpy_msgbus_publish_rna,
398 METH_VARARGS | METH_KEYWORDS,
399 bpy_msgbus_publish_rna_doc},
400 {"clear_by_owner",
401 (PyCFunction)bpy_msgbus_clear_by_owner,
402 METH_O,
403 bpy_msgbus_clear_by_owner_doc},
404 {nullptr, nullptr, 0, nullptr},
405};
406
407#if (defined(__GNUC__) && !defined(__clang__))
408# pragma GCC diagnostic pop
409#endif
410
411static PyModuleDef _bpy_msgbus_def = {
412 /*m_base*/ PyModuleDef_HEAD_INIT,
413 /*m_name*/ "msgbus",
414 /*m_doc*/ nullptr,
415 /*m_size*/ 0,
416 /*m_methods*/ BPy_msgbus_methods,
417 /*m_slots*/ nullptr,
418 /*m_traverse*/ nullptr,
419 /*m_clear*/ nullptr,
420 /*m_free*/ nullptr,
421};
422
424{
425 PyObject *submodule;
426
427 submodule = PyModule_Create(&_bpy_msgbus_def);
428
429 return submodule;
430}
431
wmMsgBus * CTX_wm_message_bus(const bContext *C)
#define BLI_assert(a)
Definition BLI_assert.h:50
void bpy_context_clear(struct bContext *C, const PyGILState_STATE *gilstate)
void bpy_context_set(struct bContext *C, PyGILState_STATE *gilstate)
struct bContext * BPY_context_get(void)
static void bpy_msgbus_notify(bContext *C, wmMsgSubscribeKey *, wmMsgSubscribeValue *msg_val)
static void bpy_msgbus_subscribe_value_free_data(wmMsgSubscribeKey *, wmMsgSubscribeValue *msg_val)
static PyModuleDef _bpy_msgbus_def
#define BPY_MSGBUS_USER_DATA_LEN
static int py_msgbus_rna_key_from_py(PyObject *py_sub, wmMsgParams_RNA *msg_key_params, const char *error_prefix)
Definition bpy_msgbus.cc:57
static PyObject * bpy_msgbus_subscribe_rna(PyObject *, PyObject *args, PyObject *kw)
PyObject * BPY_msgbus_module()
static PyMethodDef BPy_msgbus_methods[]
PyDoc_STRVAR(bpy_msgbus_subscribe_rna_doc, ".. function:: subscribe_rna(key, owner, args, notify, options=set())\n" "\n" " Register a message bus subscription. It will be cleared when another blend file is\n" " loaded, or can be cleared explicitly via :func:`bpy.msgbus.clear_by_owner`.\n" "\n" BPY_MSGBUS_RNA_MSGKEY_DOC " :arg owner: Handle for this subscription (compared by identity).\n" " :type owner: Any\n" " :arg options: Change the behavior of the subscriber.\n" "\n" " - ``PERSISTENT`` when set, the subscriber will be kept when remapping ID data.\n" "\n" " :type options: set[str]\n" "\n" ".. note::\n" "\n" " All subscribers will be cleared on file-load. Subscribers can be re-registered on load,\n" " see :mod:`bpy.app.handlers.load_post`.\n")
#define BPY_MSGBUS_RNA_MSGKEY_DOC
Definition bpy_msgbus.cc:36
static PyObject * bpy_msgbus_clear_by_owner(PyObject *, PyObject *py_owner)
static PyObject * bpy_msgbus_publish_rna(PyObject *, PyObject *args, PyObject *kw)
StructRNA * pyrna_struct_as_srna(PyObject *self, const bool parent, const char *error_prefix)
Definition bpy_rna.cc:8124
bool pyrna_write_check()
Definition bpy_rna.cc:369
void pyrna_write_set(bool val)
Definition bpy_rna.cc:374
#define PYRNA_STRUCT_CHECK_INT(obj)
Definition bpy_rna.hh:79
#define PYRNA_PROP_CHECK_INT(obj)
Definition bpy_rna.hh:90
#define BPy_StructRNA_Check(v)
Definition bpy_rna.hh:69
#define BPy_PropertyRNA_Check(v)
Definition bpy_rna.hh:71
CCL_NAMESPACE_BEGIN struct Options options
#define BaseMathObject_CheckExact(v)
Definition mathutils.hh:70
int pyrna_enum_bitfield_from_set(const EnumPropertyItem *items, PyObject *value, int *r_value, const char *error_prefix)
void PyC_Err_PrintWithFunc(PyObject *py_func)
header-only compatibility defines.
#define PY_ARG_PARSER_HEAD_COMPAT()
header-only utilities
#define PyTuple_SET_ITEMS(op_arg,...)
return ret
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
const char * RNA_struct_identifier(const StructRNA *type)
PropertyRNA * prop
Definition bpy_rna.hh:143
PyObject_HEAD PointerRNA ptr
Definition bpy_rna.hh:142
PyObject_HEAD PointerRNA ptr
Definition bpy_rna.hh:124
StructRNA * type
Definition RNA_types.hh:41
const PropertyRNA * prop
wmMsgSubscribeValueFreeDataFn free_data
void WM_msgbus_clear_by_owner(wmMsgBus *mbus, void *owner)
void WM_msg_dump(wmMsgBus *mbus, const char *info_str)
void WM_msg_subscribe_rna_params(wmMsgBus *mbus, const wmMsgParams_RNA *msg_key_params, const wmMsgSubscribeValue *msg_val_params, const char *id_repr)
void WM_msg_publish_rna_params(wmMsgBus *mbus, const wmMsgParams_RNA *msg_key_params)