2 * ***** BEGIN GPL LICENSE BLOCK *****
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * Contributor(s): Campbell Barton
20 * ***** END GPL LICENSE BLOCK *****
23 /** \file blender/python/intern/bpy_app_handlers.c
24 * \ingroup pythonintern
26 * This file defines a 'PyStructSequence' accessed via 'bpy.app.handlers',
27 * which exposes various lists that the script author can add callback
28 * functions into (called via blenders generic BLI_cb api)
32 #include "BLI_utildefines.h"
33 #include "BLI_callbacks.h"
35 #include "RNA_types.h"
36 #include "RNA_access.h"
38 #include "bpy_app_handlers.h"
40 void bpy_app_generic_callback(struct Main *main, struct ID *id, void *arg);
42 static PyTypeObject BlenderAppCbType;
44 static PyStructSequence_Field app_cb_info_fields[]= {
45 {(char *)"frame_change_pre", (char *)"Callback list - on frame change for playback and rendering (before)"},
46 {(char *)"frame_change_post", (char *)"Callback list - on frame change for playback and rendering (after)"},
47 {(char *)"render_pre", (char *)"Callback list - on render (before)"},
48 {(char *)"render_post", (char *)"Callback list - on render (after)"},
49 {(char *)"render_stats", (char *)"Callback list - on printing render statistics"},
50 {(char *)"load_pre", (char *)"Callback list - on loading a new blend file (before)"},
51 {(char *)"load_post", (char *)"Callback list - on loading a new blend file (after)"},
52 {(char *)"save_pre", (char *)"Callback list - on saving a blend file (before)"},
53 {(char *)"save_post", (char *)"Callback list - on saving a blend file (after)"},
54 {(char *)"scene_update_pre", (char *)"Callback list - on updating the scenes data (before)"},
55 {(char *)"scene_update_post", (char *)"Callback list - on updating the scenes data (after)"},
57 /* sets the permanent tag */
58 # define APP_CB_OTHER_FIELDS 1
59 {(char *)"persistent", (char *)"Function decorator for callback functions not to be removed when loading new files"},
64 static PyStructSequence_Desc app_cb_info_desc= {
65 (char *)"bpy.app.handlers", /* name */
66 (char *)"This module contains callbacks", /* doc */
67 app_cb_info_fields, /* fields */
68 (sizeof(app_cb_info_fields)/sizeof(PyStructSequence_Field)) - 1
72 #if (BLI_CB_EVT_TOT != ((sizeof(app_cb_info_fields)/sizeof(PyStructSequence_Field))))
73 # error "Callbacks are out of sync"
77 /* --------------------------------------------------------------------------*/
78 /* permanent tagging code */
79 #define PERMINENT_CB_ID "_bpy_persistent"
81 static PyObject *bpy_app_handlers_persistent_new(PyTypeObject *UNUSED(type), PyObject *args, PyObject *UNUSED(kwds))
85 if(!PyArg_ParseTuple(args, "O:bpy.app.handlers.persistent", &value))
88 if (PyFunction_Check(value)) {
89 PyObject **dict_ptr= _PyObject_GetDictPtr(value);
90 if (dict_ptr == NULL) {
91 PyErr_SetString(PyExc_ValueError,
92 "bpy.app.handlers.persistent wasn't able to "
93 "get the dictionary from the function passed");
98 if (*dict_ptr == NULL) {
99 *dict_ptr= PyDict_New();
102 PyDict_SetItemString(*dict_ptr, PERMINENT_CB_ID, Py_None);
109 PyErr_SetString(PyExc_ValueError,
110 "bpy.app.handlers.persistent expected a function");
115 /* dummy type because decorators can't be PyCFunctions */
116 static PyTypeObject BPyPersistent_Type = {
118 #if defined(_MSC_VER) || defined(FREE_WINDOWS)
119 PyVarObject_HEAD_INIT(NULL, 0)
121 PyVarObject_HEAD_INIT(&PyType_Type, 0)
124 "persistent", /* tp_name */
125 0, /* tp_basicsize */
134 0, /* tp_as_number */
135 0, /* tp_as_sequence */
136 0, /* tp_as_mapping */
142 0, /* tp_as_buffer */
143 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
144 Py_TPFLAGS_BASETYPE, /* tp_flags */
148 0, /* tp_richcompare */
149 0, /* tp_weaklistoffset */
157 0, /* tp_descr_get */
158 0, /* tp_descr_set */
159 0, /* tp_dictoffset */
162 bpy_app_handlers_persistent_new, /* tp_new */
166 static PyObject *py_cb_array[BLI_CB_EVT_TOT]= {NULL};
168 static PyObject *make_app_cb_info(void)
170 PyObject *app_cb_info;
173 app_cb_info= PyStructSequence_New(&BlenderAppCbType);
174 if (app_cb_info == NULL) {
178 for (pos= 0; pos < BLI_CB_EVT_TOT; pos++) {
179 if (app_cb_info_fields[pos].name == NULL) {
180 Py_FatalError("invalid callback slots 1");
182 PyStructSequence_SET_ITEM(app_cb_info, pos, (py_cb_array[pos]= PyList_New(0)));
184 if (app_cb_info_fields[pos + APP_CB_OTHER_FIELDS].name != NULL) {
185 Py_FatalError("invalid callback slots 2");
188 /* custom function */
189 PyStructSequence_SET_ITEM(app_cb_info, pos++, (PyObject *)&BPyPersistent_Type);
194 PyObject *BPY_app_handlers_struct(void)
198 #if defined(_MSC_VER) || defined(FREE_WINDOWS)
199 BPyPersistent_Type.ob_base.ob_base.ob_type= &PyType_Type;
202 if (PyType_Ready(&BPyPersistent_Type) < 0) {
203 BLI_assert(!"error initializing 'bpy.app.handlers.persistent'");
206 PyStructSequence_InitType(&BlenderAppCbType, &app_cb_info_desc);
208 ret= make_app_cb_info();
210 /* prevent user from creating new instances */
211 BlenderAppCbType.tp_init= NULL;
212 BlenderAppCbType.tp_new= NULL;
213 BlenderAppCbType.tp_hash= (hashfunc)_Py_HashPointer; /* without this we can't do set(sys.modules) [#29635] */
215 /* assign the C callbacks */
217 static bCallbackFuncStore funcstore_array[BLI_CB_EVT_TOT]= {{NULL}};
218 bCallbackFuncStore *funcstore;
221 for (pos= 0; pos < BLI_CB_EVT_TOT; pos++) {
222 funcstore= &funcstore_array[pos];
223 funcstore->func= bpy_app_generic_callback;
225 funcstore->arg= SET_INT_IN_POINTER(pos);
226 BLI_add_cb(funcstore, pos);
233 void BPY_app_handlers_reset(const short do_all)
238 for (pos= 0; pos < BLI_CB_EVT_TOT; pos++) {
240 PyList_SetSlice(py_cb_array[pos], 0, PY_SSIZE_T_MAX, NULL);
244 /* save string conversion thrashing */
245 PyObject *perm_id_str= PyUnicode_FromString(PERMINENT_CB_ID);
247 for (pos= 0; pos < BLI_CB_EVT_TOT; pos++) {
248 /* clear only items without PERMINENT_CB_ID */
249 PyObject *ls= py_cb_array[pos];
255 for(i= PyList_GET_SIZE(ls) - 1; i >= 0; i--) {
257 if ( (PyFunction_Check((item= PyList_GET_ITEM(ls, i)))) &&
258 (dict_ptr= _PyObject_GetDictPtr(item)) &&
260 (PyDict_GetItem(*dict_ptr, perm_id_str) != NULL))
266 /* PySequence_DelItem(ls, i); */ /* more obvious buw slower */
267 PyList_SetSlice(ls, i, i + 1, NULL);
272 Py_DECREF(perm_id_str);
276 /* the actual callback - not necessarily called from py */
277 void bpy_app_generic_callback(struct Main *UNUSED(main), struct ID *id, void *arg)
279 PyObject *cb_list= py_cb_array[GET_INT_FROM_POINTER(arg)];
280 Py_ssize_t cb_list_len;
281 if ((cb_list_len= PyList_GET_SIZE(cb_list)) > 0) {
282 PyGILState_STATE gilstate= PyGILState_Ensure();
284 PyObject* args= PyTuple_New(1); // save python creating each call
289 /* setup arguments */
292 RNA_id_pointer_create(id, &id_ptr);
293 PyTuple_SET_ITEM(args, 0, pyrna_struct_CreatePyObject(&id_ptr));
296 PyTuple_SET_ITEM(args, 0, Py_None);
300 // Iterate the list and run the callbacks
301 for (pos=0; pos < cb_list_len; pos++) {
302 func= PyList_GET_ITEM(cb_list, pos);
303 ret= PyObject_Call(func, args, NULL);
315 PyGILState_Release(gilstate);