29 const int pointers_num,
34#define FILEPATH_SAVE_ARG \
35 "Accepts one argument: " \
36 "the file being saved, an empty string for the startup-file."
37#define FILEPATH_LOAD_ARG \
38 "Accepts one argument: " \
39 "the file being loaded, an empty string for the startup-file."
40#define RENDER_STATS_ARG \
41 "Accepts one argument: " \
42 "the render stats (render/saving time plus in background mode frame/used [peak] memory)."
43#define DEPSGRAPH_UPDATE_ARG \
44 "Accepts two arguments: " \
45 "The scene datablock and the dependency graph being updated"
47 "Accepts one argument: " \
48 "the scene datablock being rendered"
49#define OBJECT_BAKE_ARG \
50 "Accepts one argument: " \
51 "the object datablock being baked"
52#define COMPOSITE_ARG \
53 "Accepts one argument: " \
55#define ANNOTATION_ARG \
56 "Accepts two arguments: " \
57 "the annotation datablock and dependency graph"
58#define BLENDIMPORT_ARG \
59 "Accepts one argument: " \
60 "a BlendImportContext"
67 "Called after frame change for playback and rendering, before any data is evaluated for the "
68 "new frame. This makes it possible to change data and relations (for example swap an object "
69 "to another mesh) for the new frame. Note that this handler is **not** to be used as 'before "
70 "the frame changes' event. The dependency graph is not available in this handler, as data "
71 "and relations may have been altered and the dependency graph has not yet been updated for "
74 "Called after frame change for playback and rendering, after the data has been evaluated "
76 {
"render_pre",
"on render (before)"},
77 {
"render_post",
"on render (after)"},
78 {
"render_write",
"on writing a render frame (directly after the frame is written)"},
80 {
"render_init",
"on initialization of a render job. " RENDER_ARG},
81 {
"render_complete",
"on completion of render job. " RENDER_ARG},
82 {
"render_cancel",
"on canceling a render job. " RENDER_ARG},
86 {
"load_post_fail",
"on failure to load a new blend file (after). " FILEPATH_LOAD_ARG},
90 {
"save_post_fail",
"on failure to save a blend file (after). " FILEPATH_SAVE_ARG},
92 {
"undo_pre",
"on loading an undo step (before)"},
93 {
"undo_post",
"on loading an undo step (after)"},
94 {
"redo_pre",
"on loading a redo step (before)"},
95 {
"redo_post",
"on loading a redo step (after)"},
98 {
"version_update",
"on ending the versioning code"},
99 {
"load_factory_preferences_post",
"on loading factory preferences (after)"},
100 {
"load_factory_startup_post",
"on loading factory startup (after)"},
101 {
"xr_session_start_pre",
"on starting an xr session (before)"},
102 {
"annotation_pre",
"on drawing an annotation (before)"},
103 {
"annotation_post",
"on drawing an annotation (after)"},
105 {
"object_bake_complete",
106 "on completing a bake job; will be called in the main thread. " OBJECT_BAKE_ARG},
107 {
"object_bake_cancel",
108 "on canceling a bake job; will be called in the main thread. " OBJECT_BAKE_ARG},
109 {
"composite_pre",
"on a compositing background job (before). " COMPOSITE_ARG},
110 {
"composite_post",
"on a compositing background job (after). " COMPOSITE_ARG},
111 {
"composite_cancel",
"on a compositing background job (cancel). " COMPOSITE_ARG},
114 {
"translation_update_post",
"on translation settings update"},
118 {
"_extension_repos_update_pre",
"on changes to extension repos (before)"},
119 {
"_extension_repos_update_post",
"on changes to extension repos (after)"},
120 {
"_extension_repos_sync",
"on creating or synchronizing the active repository"},
121 {
"_extension_repos_files_clear",
122 "remove files from the repository directory (uses as a string argument)"},
124 "on linking or appending data (before), get a single `BlendImportContext` parameter"},
125 {
"blend_import_post",
126 "on linking or appending data (after), get a single `BlendImportContext` parameter"},
129#define APP_CB_OTHER_FIELDS 1
131 "Function decorator for callback functions not to be removed when loading new files"},
138 "This module contains callback lists",
144# if (BKE_CB_EVT_TOT != ARRAY_SIZE(app_cb_info_fields))
145# error "Callbacks are out of sync"
153#define PERMINENT_CB_ID "_bpy_persistent"
161 if (!PyArg_ParseTuple(args,
"O:bpy.app.handlers.persistent", &value)) {
165 if (PyFunction_Check(value)) {
166 PyObject **dict_ptr = _PyObject_GetDictPtr(value);
167 if (dict_ptr ==
nullptr) {
168 PyErr_SetString(PyExc_ValueError,
169 "bpy.app.handlers.persistent wasn't able to "
170 "get the dictionary from the function passed");
175 if (*dict_ptr ==
nullptr) {
176 *dict_ptr = PyDict_New();
185 PyErr_SetString(PyExc_ValueError,
"bpy.app.handlers.persistent expected a function");
192 PyVarObject_HEAD_INIT(
nullptr, 0)
194 PyVarObject_HEAD_INIT(&PyType_Type, 0)
214 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
252 PyObject *app_cb_info;
256 if (app_cb_info ==
nullptr) {
262 Py_FatalError(
"invalid callback slots 1");
267 Py_FatalError(
"invalid callback slots 2");
285 BLI_assert_msg(0,
"error initializing 'bpy.app.handlers.persistent'");
305 funcstore = &funcstore_array[
pos];
307 funcstore->
alloc = 0;
318 PyGILState_STATE gilstate = PyGILState_Ensure();
336 for (
i = PyList_GET_SIZE(ls) - 1;
i >= 0;
i--) {
337 PyObject *item = PyList_GET_ITEM(ls,
i);
339 if (PyMethod_Check(item)) {
340 PyObject *item_test = PyMethod_GET_FUNCTION(item);
347 if (PyFunction_Check(item) && (dict_ptr = _PyObject_GetDictPtr(item)) && (*dict_ptr) &&
348 (PyDict_GetItem(*dict_ptr, perm_id_str) !=
nullptr))
355 PyList_SetSlice(ls,
i,
i + 1,
nullptr);
360 Py_DECREF(perm_id_str);
363 PyGILState_Release(gilstate);
366static PyObject *
choose_arguments(PyObject *func, PyObject *args_all, PyObject *args_single)
368 if (!PyFunction_Check(func)) {
371 PyCodeObject *code = (PyCodeObject *)PyFunction_GetCode(func);
372 if (code->co_argcount == 1) {
381 const int pointers_num,
385 if (PyList_GET_SIZE(cb_list) > 0) {
386 const PyGILState_STATE gilstate = PyGILState_Ensure();
388 const int num_arguments = 2;
389 PyObject *args_all = PyTuple_New(num_arguments);
390 PyObject *args_single = PyTuple_New(1);
396 for (
int i = 0;
i < pointers_num; ++
i) {
400 for (
int i = pointers_num;
i < num_arguments; ++
i) {
401 PyTuple_SET_ITEM(args_all,
i, Py_NewRef(Py_None));
404 if (pointers_num == 0) {
405 PyTuple_SET_ITEM(args_single, 0, Py_NewRef(Py_None));
414 for (
pos = 0;
pos < PyList_GET_SIZE(cb_list);
pos++) {
415 func = PyList_GET_ITEM(cb_list,
pos);
417 ret = PyObject_Call(func, args,
nullptr);
418 if (
ret ==
nullptr) {
426 PySys_WriteStderr(
"Error in bpy.app.handlers.%s[%d]:\n",
438 Py_DECREF(args_single);
440 PyGILState_Release(gilstate);
void BKE_callback_add(bCallbackFuncStore *funcstore, eCbEvent evt)
#define BLI_assert_msg(a, msg)
#define POINTER_FROM_INT(i)
#define POINTER_AS_INT(i)
PyObject * BPY_app_handlers_struct()
static PyStructSequence_Desc app_cb_info_desc
#define FILEPATH_SAVE_ARG
#define APP_CB_OTHER_FIELDS
static PyTypeObject BPyPersistent_Type
#define FILEPATH_LOAD_ARG
#define DEPSGRAPH_UPDATE_ARG
static PyObject * make_app_cb_info()
void BPY_app_handlers_reset(const bool do_all)
static PyObject * bpy_app_handlers_persistent_new(PyTypeObject *, PyObject *args, PyObject *)
static PyStructSequence_Field app_cb_info_fields[]
static PyObject * py_cb_array[BKE_CB_EVT_TOT]
static PyTypeObject BlenderAppCbType
static PyObject * choose_arguments(PyObject *func, PyObject *args_all, PyObject *args_single)
void bpy_app_generic_callback(Main *main, PointerRNA **pointers, const int pointers_num, void *arg)
PyObject * pyrna_struct_CreatePyObject_with_primitive_support(PointerRNA *ptr)
header-only compatibility defines.
void(* func)(Main *, PointerRNA **, int num_pointers, void *arg)