PyAPI: add undo redo handlers
[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 #include "../generic/python_utildefines.h"
41
42 #include "BPY_extern.h"
43
44 void bpy_app_generic_callback(struct Main *main, struct ID *id, void *arg);
45
46 static PyTypeObject BlenderAppCbType;
47
48 static PyStructSequence_Field app_cb_info_fields[] = {
49         {(char *)"frame_change_pre",  (char *)"on frame change for playback and rendering (before)"},
50         {(char *)"frame_change_post", (char *)"on frame change for playback and rendering (after)"},
51         {(char *)"render_pre",        (char *)"on render (before)"},
52         {(char *)"render_post",       (char *)"on render (after)"},
53         {(char *)"render_write",      (char *)"on writing a render frame (directly after the frame is written)"},
54         {(char *)"render_stats",      (char *)"on printing render statistics"},
55         {(char *)"render_init",       (char *)"on initialization of a render job"},
56         {(char *)"render_complete",   (char *)"on completion of render job"},
57         {(char *)"render_cancel",     (char *)"on canceling a render job"},
58         {(char *)"load_pre",          (char *)"on loading a new blend file (before)"},
59         {(char *)"load_post",         (char *)"on loading a new blend file (after)"},
60         {(char *)"save_pre",          (char *)"on saving a blend file (before)"},
61         {(char *)"save_post",         (char *)"on saving a blend file (after)"},
62         {(char *)"undo_pre",          (char *)"on loading an undo step (before)"},
63         {(char *)"undo_post",         (char *)"on loading an undo step (after)"},
64         {(char *)"redo_pre",          (char *)"on loading a redo step (before)"},
65         {(char *)"redo_post",         (char *)"on loading a redo step (after)"},
66         {(char *)"scene_update_pre",  (char *)"on every scene data update. Does not imply that anything changed in the "
67                                           "scene, just that the dependency graph is about to be reevaluated, and the "
68                                           "scene is about to be updated by Blender's animation system."},
69         {(char *)"scene_update_post",  (char *)"on every scene data update. Does not imply that anything changed in the "
70                                            "scene, just that the dependency graph was reevaluated, and the scene was "
71                                            "possibly updated by Blender's animation system."},
72         {(char *)"game_pre",          (char *)"on starting the game engine"},
73         {(char *)"game_post",         (char *)"on ending the game engine"},
74         {(char *)"version_update",    (char *)"on ending the versioning code"},
75
76         /* sets the permanent tag */
77 #   define APP_CB_OTHER_FIELDS 1
78         {(char *)"persistent",        (char *)"Function decorator for callback functions not to be removed when loading new files"},
79
80         {NULL}
81 };
82
83 static PyStructSequence_Desc app_cb_info_desc = {
84         (char *)"bpy.app.handlers",     /* name */
85         (char *)"This module contains callback lists",  /* doc */
86         app_cb_info_fields,    /* fields */
87         ARRAY_SIZE(app_cb_info_fields) - 1
88 };
89
90 #if 0
91 #  if (BLI_CB_EVT_TOT != ARRAY_SIZE(app_cb_info_fields))
92 #    error "Callbacks are out of sync"
93 #  endif
94 #endif
95
96 /* --------------------------------------------------------------------------*/
97 /* permanent tagging code */
98 #define PERMINENT_CB_ID "_bpy_persistent"
99
100 static PyObject *bpy_app_handlers_persistent_new(PyTypeObject *UNUSED(type), PyObject *args, PyObject *UNUSED(kwds))
101 {
102         PyObject *value;
103
104         if (!PyArg_ParseTuple(args, "O:bpy.app.handlers.persistent", &value))
105                 return NULL;
106
107         if (PyFunction_Check(value)) {
108                 PyObject **dict_ptr = _PyObject_GetDictPtr(value);
109                 if (dict_ptr == NULL) {
110                         PyErr_SetString(PyExc_ValueError,
111                                         "bpy.app.handlers.persistent wasn't able to "
112                                         "get the dictionary from the function passed");
113                         return NULL;
114                 }
115                 else {
116                         /* set id */
117                         if (*dict_ptr == NULL) {
118                                 *dict_ptr = PyDict_New();
119                         }
120
121                         PyDict_SetItemString(*dict_ptr, PERMINENT_CB_ID, Py_None);
122                 }
123
124                 Py_INCREF(value);
125                 return value;
126         }
127         else {
128                 PyErr_SetString(PyExc_ValueError,
129                                 "bpy.app.handlers.persistent expected a function");
130                 return NULL;
131         }
132 }
133
134 /* dummy type because decorators can't be PyCFunctions */
135 static PyTypeObject BPyPersistent_Type = {
136
137 #if defined(_MSC_VER)
138         PyVarObject_HEAD_INIT(NULL, 0)
139 #else
140         PyVarObject_HEAD_INIT(&PyType_Type, 0)
141 #endif
142
143         "persistent",                               /* tp_name */
144         0,                                          /* tp_basicsize */
145         0,                                          /* tp_itemsize */
146         /* methods */
147         0,                                          /* tp_dealloc */
148         0,                                          /* tp_print */
149         0,                                          /* tp_getattr */
150         0,                                          /* tp_setattr */
151         0,                                          /* tp_reserved */
152         0,                                          /* tp_repr */
153         0,                                          /* tp_as_number */
154         0,                                          /* tp_as_sequence */
155         0,                                          /* tp_as_mapping */
156         0,                                          /* tp_hash */
157         0,                                          /* tp_call */
158         0,                                          /* tp_str */
159         0,                                          /* tp_getattro */
160         0,                                          /* tp_setattro */
161         0,                                          /* tp_as_buffer */
162         Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
163         Py_TPFLAGS_BASETYPE,                        /* tp_flags */
164         0,                                          /* tp_doc */
165         0,                                          /* tp_traverse */
166         0,                                          /* tp_clear */
167         0,                                          /* tp_richcompare */
168         0,                                          /* tp_weaklistoffset */
169         0,                                          /* tp_iter */
170         0,                                          /* tp_iternext */
171         0,                                          /* tp_methods */
172         0,                                          /* tp_members */
173         0,                                          /* tp_getset */
174         0,                                          /* tp_base */
175         0,                                          /* tp_dict */
176         0,                                          /* tp_descr_get */
177         0,                                          /* tp_descr_set */
178         0,                                          /* tp_dictoffset */
179         0,                                          /* tp_init */
180         0,                                          /* tp_alloc */
181         bpy_app_handlers_persistent_new,            /* tp_new */
182         0,                                          /* tp_free */
183 };
184
185 static PyObject *py_cb_array[BLI_CB_EVT_TOT] = {NULL};
186
187 static PyObject *make_app_cb_info(void)
188 {
189         PyObject *app_cb_info;
190         int pos;
191
192         app_cb_info = PyStructSequence_New(&BlenderAppCbType);
193         if (app_cb_info == NULL) {
194                 return NULL;
195         }
196
197         for (pos = 0; pos < BLI_CB_EVT_TOT; pos++) {
198                 if (app_cb_info_fields[pos].name == NULL) {
199                         Py_FatalError("invalid callback slots 1");
200                 }
201                 PyStructSequence_SET_ITEM(app_cb_info, pos, (py_cb_array[pos] = PyList_New(0)));
202         }
203         if (app_cb_info_fields[pos + APP_CB_OTHER_FIELDS].name != NULL) {
204                 Py_FatalError("invalid callback slots 2");
205         }
206
207         /* custom function */
208         PyStructSequence_SET_ITEM(app_cb_info, pos++, (PyObject *)&BPyPersistent_Type);
209
210         return app_cb_info;
211 }
212
213 PyObject *BPY_app_handlers_struct(void)
214 {
215         PyObject *ret;
216
217 #if defined(_MSC_VER)
218         BPyPersistent_Type.ob_base.ob_base.ob_type = &PyType_Type;
219 #endif
220
221         if (PyType_Ready(&BPyPersistent_Type) < 0) {
222                 BLI_assert(!"error initializing 'bpy.app.handlers.persistent'");
223         }
224
225         PyStructSequence_InitType(&BlenderAppCbType, &app_cb_info_desc);
226
227         ret = make_app_cb_info();
228
229         /* prevent user from creating new instances */
230         BlenderAppCbType.tp_init = NULL;
231         BlenderAppCbType.tp_new = NULL;
232         BlenderAppCbType.tp_hash = (hashfunc)_Py_HashPointer; /* without this we can't do set(sys.modules) [#29635] */
233
234         /* assign the C callbacks */
235         if (ret) {
236                 static bCallbackFuncStore funcstore_array[BLI_CB_EVT_TOT] = {{NULL}};
237                 bCallbackFuncStore *funcstore;
238                 int pos = 0;
239
240                 for (pos = 0; pos < BLI_CB_EVT_TOT; pos++) {
241                         funcstore = &funcstore_array[pos];
242                         funcstore->func = bpy_app_generic_callback;
243                         funcstore->alloc = 0;
244                         funcstore->arg = SET_INT_IN_POINTER(pos);
245                         BLI_callback_add(funcstore, pos);
246                 }
247         }
248
249         return ret;
250 }
251
252 void BPY_app_handlers_reset(const short do_all)
253 {
254         PyGILState_STATE gilstate;
255         int pos = 0;
256
257         gilstate = PyGILState_Ensure();
258
259         if (do_all) {
260                 for (pos = 0; pos < BLI_CB_EVT_TOT; pos++) {
261                         /* clear list */
262                         PyList_SetSlice(py_cb_array[pos], 0, PY_SSIZE_T_MAX, NULL);
263                 }
264         }
265         else {
266                 /* save string conversion thrashing */
267                 PyObject *perm_id_str = PyUnicode_FromString(PERMINENT_CB_ID);
268
269                 for (pos = 0; pos < BLI_CB_EVT_TOT; pos++) {
270                         /* clear only items without PERMINENT_CB_ID */
271                         PyObject *ls = py_cb_array[pos];
272                         Py_ssize_t i;
273
274                         PyObject *item;
275                         PyObject **dict_ptr;
276
277                         for (i = PyList_GET_SIZE(ls) - 1; i >= 0; i--) {
278
279                                 if ((PyFunction_Check((item = PyList_GET_ITEM(ls, i)))) &&
280                                     (dict_ptr = _PyObject_GetDictPtr(item)) &&
281                                     (*dict_ptr) &&
282                                     (PyDict_GetItem(*dict_ptr, perm_id_str) != NULL))
283                                 {
284                                         /* keep */
285                                 }
286                                 else {
287                                         /* remove */
288                                         /* PySequence_DelItem(ls, i); */ /* more obvious buw slower */
289                                         PyList_SetSlice(ls, i, i + 1, NULL);
290                                 }
291                         }
292                 }
293
294                 Py_DECREF(perm_id_str);
295         }
296
297         PyGILState_Release(gilstate);
298 }
299
300 /* the actual callback - not necessarily called from py */
301 void bpy_app_generic_callback(struct Main *UNUSED(main), struct ID *id, void *arg)
302 {
303         PyObject *cb_list = py_cb_array[GET_INT_FROM_POINTER(arg)];
304         if (PyList_GET_SIZE(cb_list) > 0) {
305                 PyGILState_STATE gilstate = PyGILState_Ensure();
306
307                 PyObject *args = PyTuple_New(1);  /* save python creating each call */
308                 PyObject *func;
309                 PyObject *ret;
310                 Py_ssize_t pos;
311
312                 /* setup arguments */
313                 if (id) {
314                         PointerRNA id_ptr;
315                         RNA_id_pointer_create(id, &id_ptr);
316                         PyTuple_SET_ITEM(args, 0, pyrna_struct_CreatePyObject(&id_ptr));
317                 }
318                 else {
319                         PyTuple_SET_ITEM(args, 0, Py_INCREF_RET(Py_None));
320                 }
321
322                 /* Iterate the list and run the callbacks
323                  * note: don't store the list size since the scripts may remove themselves */
324                 for (pos = 0; pos < PyList_GET_SIZE(cb_list); pos++) {
325                         func = PyList_GET_ITEM(cb_list, pos);
326                         ret = PyObject_Call(func, args, NULL);
327                         if (ret == NULL) {
328                                 /* Don't set last system variables because they might cause some
329                                  * dangling pointers to external render engines (when exception
330                                  * happens during rendering) which will break logic of render pipeline
331                                  * which expects to be the only user of render engine when rendering
332                                  * is finished.
333                                  */
334                                 PyErr_PrintEx(0);
335                                 PyErr_Clear();
336                         }
337                         else {
338                                 Py_DECREF(ret);
339                         }
340                 }
341
342                 Py_DECREF(args);
343
344                 PyGILState_Release(gilstate);
345         }
346 }