documentation - brief descriptions for bpy api files.
[blender.git] / source / blender / python / intern / bpy_app_handlers.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
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.
8  *
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.
13  *
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.
17  *
18  * Contributor(s): Campbell Barton
19  *
20  * ***** END GPL LICENSE BLOCK *****
21  */
22
23 /** \file blender/python/intern/bpy_app_handlers.c
24  *  \ingroup pythonintern
25  *
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)
29  */
30
31 #include <Python.h>
32 #include "BLI_utildefines.h"
33 #include "BLI_callbacks.h"
34
35 #include "RNA_types.h"
36 #include "RNA_access.h"
37 #include "bpy_rna.h"
38 #include "bpy_app_handlers.h"
39
40 void bpy_app_generic_callback(struct Main *main, struct ID *id, void *arg);
41
42 static PyTypeObject BlenderAppCbType;
43
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)"},
56
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"},
60
61         {NULL}
62 };
63
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
69 };
70
71 /*
72 #if (BLI_CB_EVT_TOT != ((sizeof(app_cb_info_fields)/sizeof(PyStructSequence_Field))))
73 #  error "Callbacks are out of sync"
74 #endif
75 */
76
77 /* --------------------------------------------------------------------------*/
78 /* permanent tagging code */
79 #define PERMINENT_CB_ID "_bpy_persistent"
80
81 static PyObject *bpy_app_handlers_persistent_new(PyTypeObject *UNUSED(type), PyObject *args, PyObject *UNUSED(kwds))
82 {
83         PyObject *value;
84
85         if(!PyArg_ParseTuple(args, "O:bpy.app.handlers.persistent", &value))
86                 return NULL;
87
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");
94                         return NULL;
95                 }
96                 else {
97                         /* set id */
98                         if (*dict_ptr == NULL) {
99                                 *dict_ptr= PyDict_New();
100                         }
101
102                         PyDict_SetItemString(*dict_ptr, PERMINENT_CB_ID, Py_None);
103                 }
104
105                 Py_INCREF(value);
106                 return value;
107         }
108         else {
109                 PyErr_SetString(PyExc_ValueError,
110                                 "bpy.app.handlers.persistent expected a function");
111                 return NULL;
112         }
113 }
114
115 /* dummy type because decorators can't be PyCFunctions */
116 static PyTypeObject BPyPersistent_Type = {
117
118 #if defined(_MSC_VER) || defined(FREE_WINDOWS)
119     PyVarObject_HEAD_INIT(NULL, 0)
120 #else
121     PyVarObject_HEAD_INIT(&PyType_Type, 0)
122 #endif
123
124     "persistent",                               /* tp_name */
125     0,                                          /* tp_basicsize */
126     0,                                          /* tp_itemsize */
127     /* methods */
128     0,                                          /* tp_dealloc */
129     0,                                          /* tp_print */
130     0,                                          /* tp_getattr */
131     0,                                          /* tp_setattr */
132     0,                                          /* tp_reserved */
133     0,                                          /* tp_repr */
134     0,                                          /* tp_as_number */
135     0,                                          /* tp_as_sequence */
136     0,                                          /* tp_as_mapping */
137     0,                                          /* tp_hash */
138     0,                                          /* tp_call */
139     0,                                          /* tp_str */
140     0,                                          /* tp_getattro */
141     0,                                          /* tp_setattro */
142     0,                                          /* tp_as_buffer */
143     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
144         Py_TPFLAGS_BASETYPE,                    /* tp_flags */
145     0,                                          /* tp_doc */
146     0,                                          /* tp_traverse */
147     0,                                          /* tp_clear */
148     0,                                          /* tp_richcompare */
149     0,                                          /* tp_weaklistoffset */
150     0,                                          /* tp_iter */
151     0,                                          /* tp_iternext */
152     0,                                          /* tp_methods */
153     0,                                          /* tp_members */
154     0,                                          /* tp_getset */
155     0,                                          /* tp_base */
156     0,                                          /* tp_dict */
157     0,                                          /* tp_descr_get */
158     0,                                          /* tp_descr_set */
159     0,                                          /* tp_dictoffset */
160     0,                                          /* tp_init */
161     0,                                          /* tp_alloc */
162     bpy_app_handlers_persistent_new,            /* tp_new */
163     0,                                          /* tp_free */
164 };
165
166 static PyObject *py_cb_array[BLI_CB_EVT_TOT]= {NULL};
167
168 static PyObject *make_app_cb_info(void)
169 {
170         PyObject *app_cb_info;
171         int pos= 0;
172
173         app_cb_info= PyStructSequence_New(&BlenderAppCbType);
174         if (app_cb_info == NULL) {
175                 return NULL;
176         }
177
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");
181                 }
182                 PyStructSequence_SET_ITEM(app_cb_info, pos, (py_cb_array[pos]= PyList_New(0)));
183         }
184         if (app_cb_info_fields[pos + APP_CB_OTHER_FIELDS].name != NULL) {
185                 Py_FatalError("invalid callback slots 2");
186         }
187
188         /* custom function */
189         PyStructSequence_SET_ITEM(app_cb_info, pos++, (PyObject *)&BPyPersistent_Type);
190
191         return app_cb_info;
192 }
193
194 PyObject *BPY_app_handlers_struct(void)
195 {
196         PyObject *ret;
197
198 #if defined(_MSC_VER) || defined(FREE_WINDOWS)
199         BPyPersistent_Type.ob_base.ob_base.ob_type= &PyType_Type;
200 #endif
201
202         if (PyType_Ready(&BPyPersistent_Type) < 0) {
203                 BLI_assert(!"error initializing 'bpy.app.handlers.persistent'");
204         }
205
206         PyStructSequence_InitType(&BlenderAppCbType, &app_cb_info_desc);
207
208         ret= make_app_cb_info();
209
210         /* prevent user from creating new instances */
211         BlenderAppCbType.tp_init= NULL;
212         BlenderAppCbType.tp_new= NULL;
213
214         /* assign the C callbacks */
215         if (ret) {
216                 static bCallbackFuncStore funcstore_array[BLI_CB_EVT_TOT]= {{NULL}};
217                 bCallbackFuncStore *funcstore;
218                 int pos= 0;
219
220                 for (pos= 0; pos < BLI_CB_EVT_TOT; pos++) {
221                         funcstore= &funcstore_array[pos];
222                         funcstore->func= bpy_app_generic_callback;
223                         funcstore->alloc= 0;
224                         funcstore->arg= SET_INT_IN_POINTER(pos);
225                         BLI_add_cb(funcstore, pos);
226                 }
227         }
228
229         return ret;
230 }
231
232 void BPY_app_handlers_reset(const short do_all)
233 {
234         int pos= 0;
235
236         if (do_all) {
237         for (pos= 0; pos < BLI_CB_EVT_TOT; pos++) {
238                         /* clear list */
239                         PyList_SetSlice(py_cb_array[pos], 0, PY_SSIZE_T_MAX, NULL);
240                 }
241         }
242         else {
243                 /* save string conversion thrashing */
244                 PyObject *perm_id_str= PyUnicode_FromString(PERMINENT_CB_ID);
245
246                 for (pos= 0; pos < BLI_CB_EVT_TOT; pos++) {
247                         /* clear only items without PERMINENT_CB_ID */
248                         PyObject *ls= py_cb_array[pos];
249                         Py_ssize_t i;
250
251                         PyObject *item;
252                         PyObject **dict_ptr;
253
254                         for(i= PyList_GET_SIZE(ls) - 1; i >= 0; i--) {
255
256                                 if (    (PyFunction_Check((item= PyList_GET_ITEM(ls, i)))) &&
257                                         (dict_ptr= _PyObject_GetDictPtr(item)) &&
258                                         (*dict_ptr) &&
259                                         (PyDict_GetItem(*dict_ptr, perm_id_str) != NULL))
260                                 {
261                                         /* keep */
262                                 }
263                                 else {
264                                         /* remove */
265                                         /* PySequence_DelItem(ls, i); */ /* more obvious buw slower */
266                                         PyList_SetSlice(ls, i, i + 1, NULL);
267                                 }
268                         }
269                 }
270
271                 Py_DECREF(perm_id_str);
272         }
273 }
274
275 /* the actual callback - not necessarily called from py */
276 void bpy_app_generic_callback(struct Main *UNUSED(main), struct ID *id, void *arg)
277 {
278         PyObject *cb_list= py_cb_array[GET_INT_FROM_POINTER(arg)];
279         Py_ssize_t cb_list_len;
280         if ((cb_list_len= PyList_GET_SIZE(cb_list)) > 0) {
281                 PyGILState_STATE gilstate= PyGILState_Ensure();
282
283                 PyObject* args= PyTuple_New(1); // save python creating each call
284                 PyObject* func;
285                 PyObject* ret;
286                 Py_ssize_t pos;
287
288                 /* setup arguments */
289                 if (id) {
290                         PointerRNA id_ptr;
291                         RNA_id_pointer_create(id, &id_ptr);
292                         PyTuple_SET_ITEM(args, 0, pyrna_struct_CreatePyObject(&id_ptr));
293                 }
294                 else {
295                         PyTuple_SET_ITEM(args, 0, Py_None);
296                         Py_INCREF(Py_None);
297                 }
298
299                 // Iterate the list and run the callbacks
300                 for (pos=0; pos < cb_list_len; pos++) {
301                         func= PyList_GET_ITEM(cb_list, pos);
302                         ret= PyObject_Call(func, args, NULL);
303                         if (ret==NULL) {
304                                 PyErr_Print();
305                                 PyErr_Clear();
306                         }
307                         else {
308                                 Py_DECREF(ret);
309                         }
310                 }
311
312                 Py_DECREF(args);
313
314                 PyGILState_Release(gilstate);
315         }
316 }