Blender  V3.3
bpy_msgbus.c
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
8 #include <Python.h>
9 
10 #include "../generic/py_capi_rna.h"
11 #include "../generic/py_capi_utils.h"
12 #include "../generic/python_utildefines.h"
13 #include "../mathutils/mathutils.h"
14 
15 #include "BLI_utildefines.h"
16 
17 #include "BKE_context.h"
18 
19 #include "WM_api.h"
20 #include "WM_message.h"
21 #include "WM_types.h"
22 
23 #include "RNA_access.h"
24 #include "RNA_define.h"
25 #include "RNA_enum_types.h"
26 
27 #include "bpy_capi_utils.h"
28 #include "bpy_gizmo_wrap.h" /* own include */
29 #include "bpy_intern_string.h"
30 #include "bpy_rna.h"
31 
32 #include "bpy_msgbus.h" /* own include */
33 
34 /* -------------------------------------------------------------------- */
38 #define BPY_MSGBUS_RNA_MSGKEY_DOC \
39  " :arg key: Represents the type of data being subscribed to\n" \
40  "\n" \
41  " Arguments include\n" \
42  " - :class:`bpy.types.Property` instance.\n" \
43  " - :class:`bpy.types.Struct` type.\n" \
44  " - (:class:`bpy.types.Struct`, str) type and property name.\n" \
45  " :type key: Muliple\n"
46 
57 static 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 == NULL) {
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 == NULL) {
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 == NULL) {
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  .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 == NULL) {
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 *UNUSED(msg_key),
142  wmMsgSubscribeValue *msg_val)
143 {
144  PyGILState_STATE gilstate;
145  bpy_context_set(C, &gilstate);
146 
147  PyObject *user_data = 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 == NULL) {
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  struct 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  bpy_msgbus_subscribe_rna_doc,
196  ".. function:: subscribe_rna(key, owner, args, notify, options=set())\n"
197  "\n"
198  " Register a message bus subscription. It will be cleared when another blend file is\n"
199  " loaded, or can be cleared explicitly via :func:`bpy.msgbus.clear_by_owner`.\n"
201  " :arg owner: Handle for this subscription (compared by identity).\n"
202  " :type owner: Any type.\n"
203  " :arg options: Change the behavior of the subscriber.\n"
204  "\n"
205  " - ``PERSISTENT`` when set, the subscriber will be kept when remapping ID data.\n"
206  "\n"
207  " :type options: set of str.\n"
208  "\n"
209  ".. note::\n"
210  "\n"
211  " All subscribers will be cleared on file-load. Subscribers can be re-registered on load,\n"
212  " see :mod:`bpy.app.handlers.load_post`.\n");
213 static PyObject *bpy_msgbus_subscribe_rna(PyObject *UNUSED(self), PyObject *args, PyObject *kw)
214 {
215  const char *error_prefix = "subscribe_rna";
216  PyObject *py_sub = NULL;
217  PyObject *py_owner = NULL;
218  PyObject *callback_args = NULL;
219  PyObject *callback_notify = NULL;
220 
221  enum {
222  IS_PERSISTENT = (1 << 0),
223  };
224  PyObject *py_options = NULL;
225  EnumPropertyItem py_options_enum[] = {
226  {IS_PERSISTENT, "PERSISTENT", 0, ""},
227  {0, NULL, 0, NULL, NULL},
228  };
229  int options = 0;
230 
231  if (PyTuple_GET_SIZE(args) != 0) {
232  PyErr_Format(PyExc_TypeError, "%s: only keyword arguments are supported", error_prefix);
233  return NULL;
234  }
235  static const char *_keywords[] = {
236  "key",
237  "owner",
238  "args",
239  "notify",
240  "options",
241  NULL,
242  };
243  static _PyArg_Parser _parser = {
244  "O" /* `key` */
245  "O" /* `owner` */
246  "O!" /* `args` */
247  "O" /* `notify` */
248  "|$" /* Optional keyword only arguments. */
249  "O!" /* `options` */
250  ":subscribe_rna",
251  _keywords,
252  0,
253  };
254  if (!_PyArg_ParseTupleAndKeywordsFast(args,
255  kw,
256  &_parser,
257  &py_sub,
258  &py_owner,
259  &PyTuple_Type,
260  &callback_args,
261  &callback_notify,
262  &PySet_Type,
263  &py_options)) {
264  return NULL;
265  }
266 
267  if (py_options &&
268  (pyrna_enum_bitfield_from_set(py_options_enum, py_options, &options, error_prefix)) == -1) {
269  return NULL;
270  }
271 
272  /* NOTE: we may want to have a way to pass this in. */
274  struct wmMsgBus *mbus = CTX_wm_message_bus(C);
275  wmMsgParams_RNA msg_key_params = {{0}};
276 
277  wmMsgSubscribeValue msg_val_params = {0};
278 
279  if (py_msgbus_rna_key_from_py(py_sub, &msg_key_params, error_prefix) == -1) {
280  return NULL;
281  }
282 
283  if (!PyFunction_Check(callback_notify)) {
284  PyErr_Format(PyExc_TypeError,
285  "notify expects a function, found %.200s",
286  Py_TYPE(callback_notify)->tp_name);
287  return NULL;
288  }
289 
290  if (options != 0) {
291  if (options & IS_PERSISTENT) {
292  msg_val_params.is_persistent = true;
293  }
294  }
295 
296  /* owner can be anything. */
297  {
298  msg_val_params.owner = py_owner;
299  Py_INCREF(py_owner);
300  }
301 
302  {
303  PyObject *user_data = PyTuple_New(2);
304  PyTuple_SET_ITEMS(user_data, Py_INCREF_RET(callback_args), Py_INCREF_RET(callback_notify));
305  msg_val_params.user_data = user_data;
306  }
307 
308  msg_val_params.notify = bpy_msgbus_notify;
310 
311  WM_msg_subscribe_rna_params(mbus, &msg_key_params, &msg_val_params, __func__);
312 
313  if (0) { /* For debugging. */
314  WM_msg_dump(mbus, __func__);
315  }
316 
317  Py_RETURN_NONE;
318 }
319 
321  bpy_msgbus_publish_rna_doc,
322  ".. function:: publish_rna(key)\n"
324  "\n"
325  " Notify subscribers of changes to this property\n"
326  " (this typically doesn't need to be called explicitly since changes will automatically "
327  "publish updates).\n"
328  " In some cases it may be useful to publish changes explicitly using more general keys.\n");
329 static PyObject *bpy_msgbus_publish_rna(PyObject *UNUSED(self), PyObject *args, PyObject *kw)
330 {
331  const char *error_prefix = "publish_rna";
332  PyObject *py_sub = NULL;
333 
334  if (PyTuple_GET_SIZE(args) != 0) {
335  PyErr_Format(PyExc_TypeError, "%s: only keyword arguments are supported", error_prefix);
336  return NULL;
337  }
338  static const char *_keywords[] = {
339  "key",
340  NULL,
341  };
342  static _PyArg_Parser _parser = {
343  "O" /* `key` */
344  ":publish_rna",
345  _keywords,
346  0,
347  };
348  if (!_PyArg_ParseTupleAndKeywordsFast(args, kw, &_parser, &py_sub)) {
349  return NULL;
350  }
351 
352  /* NOTE: we may want to have a way to pass this in. */
354  struct wmMsgBus *mbus = CTX_wm_message_bus(C);
355  wmMsgParams_RNA msg_key_params = {{0}};
356 
357  if (py_msgbus_rna_key_from_py(py_sub, &msg_key_params, error_prefix) == -1) {
358  return NULL;
359  }
360 
361  WM_msg_publish_rna_params(mbus, &msg_key_params);
362 
363  Py_RETURN_NONE;
364 }
365 
366 PyDoc_STRVAR(bpy_msgbus_clear_by_owner_doc,
367  ".. function:: clear_by_owner(owner)\n"
368  "\n"
369  " Clear all subscribers using this owner.\n");
370 static PyObject *bpy_msgbus_clear_by_owner(PyObject *UNUSED(self), PyObject *py_owner)
371 {
373  struct wmMsgBus *mbus = CTX_wm_message_bus(C);
374  WM_msgbus_clear_by_owner(mbus, py_owner);
375  Py_RETURN_NONE;
376 }
377 
378 static struct PyMethodDef BPy_msgbus_methods[] = {
379  {"subscribe_rna",
380  (PyCFunction)bpy_msgbus_subscribe_rna,
381  METH_VARARGS | METH_KEYWORDS,
382  bpy_msgbus_subscribe_rna_doc},
383  {"publish_rna",
384  (PyCFunction)bpy_msgbus_publish_rna,
385  METH_VARARGS | METH_KEYWORDS,
386  bpy_msgbus_publish_rna_doc},
387  {"clear_by_owner",
388  (PyCFunction)bpy_msgbus_clear_by_owner,
389  METH_O,
390  bpy_msgbus_clear_by_owner_doc},
391  {NULL, NULL, 0, NULL},
392 };
393 
394 static struct PyModuleDef _bpy_msgbus_def = {
395  PyModuleDef_HEAD_INIT,
396  .m_name = "msgbus",
397  .m_methods = BPy_msgbus_methods,
398 };
399 
400 PyObject *BPY_msgbus_module(void)
401 {
402  PyObject *submodule;
403 
404  submodule = PyModule_Create(&_bpy_msgbus_def);
405 
406  return submodule;
407 }
408 
struct wmMsgBus * CTX_wm_message_bus(const bContext *C)
Definition: context.c:770
#define BLI_assert(a)
Definition: BLI_assert.h:46
#define UNUSED(x)
#define C
Definition: RandGen.cpp:25
struct bContext * BPY_context_get(void)
void bpy_context_clear(struct bContext *C, const PyGILState_STATE *gilstate)
void bpy_context_set(struct bContext *C, PyGILState_STATE *gilstate)
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 type.\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 of 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")
static struct PyMethodDef BPy_msgbus_methods[]
Definition: bpy_msgbus.c:378
#define BPY_MSGBUS_USER_DATA_LEN
Definition: bpy_msgbus.c:137
static PyObject * bpy_msgbus_clear_by_owner(PyObject *UNUSED(self), PyObject *py_owner)
Definition: bpy_msgbus.c:370
static int py_msgbus_rna_key_from_py(PyObject *py_sub, wmMsgParams_RNA *msg_key_params, const char *error_prefix)
Definition: bpy_msgbus.c:57
static PyObject * bpy_msgbus_subscribe_rna(PyObject *UNUSED(self), PyObject *args, PyObject *kw)
Definition: bpy_msgbus.c:213
static PyObject * bpy_msgbus_publish_rna(PyObject *UNUSED(self), PyObject *args, PyObject *kw)
Definition: bpy_msgbus.c:329
static void bpy_msgbus_notify(bContext *C, wmMsgSubscribeKey *UNUSED(msg_key), wmMsgSubscribeValue *msg_val)
Definition: bpy_msgbus.c:140
static void bpy_msgbus_subscribe_value_free_data(struct wmMsgSubscribeKey *UNUSED(msg_key), struct wmMsgSubscribeValue *msg_val)
Definition: bpy_msgbus.c:179
#define BPY_MSGBUS_RNA_MSGKEY_DOC
Definition: bpy_msgbus.c:38
static struct PyModuleDef _bpy_msgbus_def
Definition: bpy_msgbus.c:394
PyObject * BPY_msgbus_module(void)
Definition: bpy_msgbus.c:400
bool pyrna_write_check(void)
Definition: bpy_rna.c:344
StructRNA * pyrna_struct_as_srna(PyObject *self, const bool parent, const char *error_prefix)
Definition: bpy_rna.c:7907
void pyrna_write_set(bool val)
Definition: bpy_rna.c:349
#define PYRNA_STRUCT_CHECK_INT(obj)
Definition: bpy_rna.h:76
#define PYRNA_PROP_CHECK_INT(obj)
Definition: bpy_rna.h:87
#define BPy_StructRNA_Check(v)
Definition: bpy_rna.h:66
#define BPy_PropertyRNA_Check(v)
Definition: bpy_rna.h:68
CCL_NAMESPACE_BEGIN struct Options options
void * user_data
#define BaseMathObject_CheckExact(v)
Definition: mathutils.h:65
int pyrna_enum_bitfield_from_set(const EnumPropertyItem *items, PyObject *value, int *r_value, const char *error_prefix)
Definition: py_capi_rna.c:133
void PyC_Err_PrintWithFunc(PyObject *py_func)
#define PyTuple_SET_ITEMS(op_arg,...)
return ret
const char * RNA_struct_identifier(const StructRNA *type)
Definition: rna_access.c:586
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
Definition: rna_access.c:717
PropertyRNA * prop
Definition: bpy_rna.h:129
PyObject_HEAD PointerRNA ptr
Definition: bpy_rna.h:128
PyObject_HEAD PointerRNA ptr
Definition: bpy_rna.h:111
struct StructRNA * type
Definition: RNA_types.h:37
const PropertyRNA * prop
wmMsgSubscribeValueFreeDataFn free_data
wmMsgNotifyFn notify
void WM_msgbus_clear_by_owner(struct wmMsgBus *mbus, void *owner)
void WM_msg_dump(struct wmMsgBus *mbus, const char *info_str)
void WM_msg_subscribe_rna_params(struct wmMsgBus *mbus, const wmMsgParams_RNA *msg_key_params, const wmMsgSubscribeValue *msg_val_params, const char *id_repr)
void WM_msg_publish_rna_params(struct wmMsgBus *mbus, const wmMsgParams_RNA *msg_key_params)