ability to have permanent callbacks that stay active when new blend files are loaded.
[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
27 #include <Python.h>
28 #include "BLI_utildefines.h"
29 #include "BLI_callbacks.h"
30
31 #include "RNA_types.h"
32 #include "RNA_access.h"
33 #include "bpy_rna.h"
34 #include "bpy_app_handlers.h"
35
36 void bpy_app_generic_callback(struct Main *main, struct ID *id, void *arg);
37
38 static PyTypeObject BlenderAppCbType;
39
40 static PyStructSequence_Field app_cb_info_fields[]= {
41         {(char *)"frame_change_pre", NULL},
42         {(char *)"frame_change_post", NULL},
43         {(char *)"render_pre", NULL},
44         {(char *)"render_post", NULL},
45         {(char *)"render_stats", NULL},
46         {(char *)"load_pre", NULL},
47         {(char *)"load_post", NULL},
48         {(char *)"save_pre", NULL},
49         {(char *)"save_post", NULL},
50         {(char *)"scene_update_pre", NULL},
51         {(char *)"scene_update_post", NULL},
52
53         /* sets the permanent tag */
54 #   define APP_CB_OTHER_FIELDS 1
55         {(char *)"permanent_tag", NULL},
56
57         {NULL}
58 };
59
60 static PyStructSequence_Desc app_cb_info_desc= {
61         (char *)"bpy.app.handlers",     /* name */
62         (char *)"This module contains callbacks",    /* doc */
63         app_cb_info_fields,    /* fields */
64         (sizeof(app_cb_info_fields)/sizeof(PyStructSequence_Field)) - 1
65 };
66
67 /*
68 #if (BLI_CB_EVT_TOT != ((sizeof(app_cb_info_fields)/sizeof(PyStructSequence_Field))))
69 #  error "Callbacks are out of sync"
70 #endif
71 */
72
73 /* --------------------------------------------------------------------------*/
74 /* permanent tagging code */
75 #define PERMINENT_CB_ID "_bpy_permanent_tag"
76
77 PyDoc_STRVAR(bpy_app_handlers_permanent_tag_doc,
78 ".. function:: permanent_tag(func, state=True)\n"
79 "\n"
80 "   Set the function as being permanent so its not cleared when new blend files are loaded.\n"
81 "\n"
82 "   :arg func: The function  to set as permanent.\n"
83 "   :type func: function\n"
84 "   :arg state: Set the permanent state to True or False.\n"
85 "   :type state: bool\n"
86 "   :return: the function argument\n"
87 "   :rtype: function\n"
88 );
89
90 static PyObject *bpy_app_handlers_permanent_tag(PyObject *UNUSED(self), PyObject *args)
91 {
92         PyObject *value;
93         int state= 1;
94
95         if(!PyArg_ParseTuple(args, "O|i:permanent_tag", &value, &state))
96                 return NULL;
97
98         if (PyFunction_Check(value)) {
99                 PyObject **dict_ptr= _PyObject_GetDictPtr(value);
100                 if (dict_ptr == NULL) {
101                         PyErr_SetString(PyExc_ValueError,
102                                         "bpy.app.handlers.permanent_tag wasn't able to "
103                                         "get the dictionary from the function passed");
104                         return NULL;
105                 }
106                 else {
107                         if (state) {
108                                 /* set id */
109                                 if (*dict_ptr == NULL) {
110                                         *dict_ptr= PyDict_New();
111                                 }
112
113                                 PyDict_SetItemString(*dict_ptr, PERMINENT_CB_ID, Py_None);
114                         }
115                         else {
116                                 /* clear id */
117                                 if (*dict_ptr) {
118                                         PyDict_DelItemString(*dict_ptr, PERMINENT_CB_ID);
119                                 }
120                         }
121                 }
122
123                 Py_INCREF(value);
124                 return value;
125         }
126         else {
127                 PyErr_SetString(PyExc_ValueError,
128                                 "bpy.app.handlers.permanent_tag expected a function");
129                 return NULL;
130         }
131 }
132
133 static PyMethodDef meth_bpy_app_handlers_permanent_tag= {"permanent_tag", (PyCFunction)bpy_app_handlers_permanent_tag, METH_VARARGS, bpy_app_handlers_permanent_tag_doc};
134
135
136
137
138 static PyObject *py_cb_array[BLI_CB_EVT_TOT]= {NULL};
139
140 static PyObject *make_app_cb_info(void)
141 {
142         PyObject *app_cb_info;
143         int pos= 0;
144
145         app_cb_info= PyStructSequence_New(&BlenderAppCbType);
146         if (app_cb_info == NULL) {
147                 return NULL;
148         }
149
150         for (pos= 0; pos < BLI_CB_EVT_TOT; pos++) {
151                 if (app_cb_info_fields[pos].name == NULL) {
152                         Py_FatalError("invalid callback slots 1");
153                 }
154                 PyStructSequence_SET_ITEM(app_cb_info, pos, (py_cb_array[pos]= PyList_New(0)));
155         }
156         if (app_cb_info_fields[pos + APP_CB_OTHER_FIELDS].name != NULL) {
157                 Py_FatalError("invalid callback slots 2");
158         }
159
160         /* custom function */
161         PyStructSequence_SET_ITEM(app_cb_info, pos++, (PyObject *)PyCFunction_New(&meth_bpy_app_handlers_permanent_tag, NULL));
162
163         return app_cb_info;
164 }
165
166 PyObject *BPY_app_handlers_struct(void)
167 {
168         PyObject *ret;
169
170         PyStructSequence_InitType(&BlenderAppCbType, &app_cb_info_desc);
171
172         ret= make_app_cb_info();
173
174         /* prevent user from creating new instances */
175         BlenderAppCbType.tp_init= NULL;
176         BlenderAppCbType.tp_new= NULL;
177
178         /* assign the C callbacks */
179         if (ret) {
180                 static bCallbackFuncStore funcstore_array[BLI_CB_EVT_TOT]= {{NULL}};
181                 bCallbackFuncStore *funcstore;
182                 int pos= 0;
183
184                 for (pos= 0; pos < BLI_CB_EVT_TOT; pos++) {
185                         funcstore= &funcstore_array[pos];
186                         funcstore->func= bpy_app_generic_callback;
187                         funcstore->alloc= 0;
188                         funcstore->arg= SET_INT_IN_POINTER(pos);
189                         BLI_add_cb(funcstore, pos);
190                 }
191         }
192
193         return ret;
194 }
195
196 void BPY_app_handlers_reset(const short do_all)
197 {
198         int pos= 0;
199
200         if (do_all) {
201         for (pos= 0; pos < BLI_CB_EVT_TOT; pos++) {
202                         /* clear list */
203                         PyList_SetSlice(py_cb_array[pos], 0, PY_SSIZE_T_MAX, NULL);
204                 }
205         }
206         else {
207                 /* save string conversion thrashing */
208                 PyObject *perm_id_str= PyUnicode_FromString(PERMINENT_CB_ID);
209
210                 for (pos= 0; pos < BLI_CB_EVT_TOT; pos++) {
211                         /* clear only items without PERMINENT_CB_ID */
212                         PyObject *ls= py_cb_array[pos];
213                         Py_ssize_t i;
214
215                         PyObject *item;
216                         PyObject **dict_ptr;
217
218                         for(i= PyList_GET_SIZE(ls) - 1; i >= 0; i--) {
219
220                                 if (    (PyFunction_Check((item= PyList_GET_ITEM(ls, i)))) &&
221                                         (dict_ptr= _PyObject_GetDictPtr(item)) &&
222                                         (*dict_ptr) &&
223                                         (PyDict_GetItem(*dict_ptr, perm_id_str) != NULL))
224                                 {
225                                         /* keep */
226                                 }
227                                 else {
228                                         /* remove */
229                                         /* PySequence_DelItem(ls, i); */ /* more obvious buw slower */
230                                         PyList_SetSlice(ls, i, i + 1, NULL);
231                                 }
232                         }
233                 }
234
235                 Py_DECREF(perm_id_str);
236         }
237 }
238
239 /* the actual callback - not necessarily called from py */
240 void bpy_app_generic_callback(struct Main *UNUSED(main), struct ID *id, void *arg)
241 {
242         PyObject *cb_list= py_cb_array[GET_INT_FROM_POINTER(arg)];
243         Py_ssize_t cb_list_len;
244         if ((cb_list_len= PyList_GET_SIZE(cb_list)) > 0) {
245                 PyGILState_STATE gilstate= PyGILState_Ensure();
246
247                 PyObject* args= PyTuple_New(1); // save python creating each call
248                 PyObject* func;
249                 PyObject* ret;
250                 Py_ssize_t pos;
251
252                 /* setup arguments */
253                 if (id) {
254                         PointerRNA id_ptr;
255                         RNA_id_pointer_create(id, &id_ptr);
256                         PyTuple_SET_ITEM(args, 0, pyrna_struct_CreatePyObject(&id_ptr));
257                 }
258                 else {
259                         PyTuple_SET_ITEM(args, 0, Py_None);
260                         Py_INCREF(Py_None);
261                 }
262
263                 // Iterate the list and run the callbacks
264                 for (pos=0; pos < cb_list_len; pos++) {
265                         func= PyList_GET_ITEM(cb_list, pos);
266                         ret= PyObject_Call(func, args, NULL);
267                         if (ret==NULL) {
268                                 PyErr_Print();
269                                 PyErr_Clear();
270                         }
271                         else {
272                                 Py_DECREF(ret);
273                         }
274                 }
275
276                 Py_DECREF(args);
277
278                 PyGILState_Release(gilstate);
279         }
280 }