From 723e129252f82cc81faa8834a68c79e4439ee8fa Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 2 Nov 2011 20:56:52 +0000 Subject: Depsgraph/Python: callbacks and properties to detect datablock changes * Adds two new python handlers: scene_update_pre() and scene_update_post() These run before and after Blender does a scene update on making modifications to the scene. * Datablocks now have an is_updated property. This will be set to true in the above callbacks if the datablock was tagged to be updated. This works for the most common datablocks used for rendering: object, material, world, lamsp, texture, mesh, curve. * Datablock collections also have an is_updated property. If this is set, it means one datablock of this type was added, removed or modified. It's also useful as a quick check to avoid looping over all datablocks. * RenderEngine.view_update() can also check these properties, for interactive viewport rendering. http://wiki.blender.org/index.php/Dev:2.6/Source/Render/UpdateAPI --- source/blender/python/intern/bpy_app_handlers.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'source/blender/python') diff --git a/source/blender/python/intern/bpy_app_handlers.c b/source/blender/python/intern/bpy_app_handlers.c index 7008aeda074..e7f74569f1a 100644 --- a/source/blender/python/intern/bpy_app_handlers.c +++ b/source/blender/python/intern/bpy_app_handlers.c @@ -47,6 +47,8 @@ static PyStructSequence_Field app_cb_info_fields[]= { {(char *)"load_post", NULL}, {(char *)"save_pre", NULL}, {(char *)"save_post", NULL}, + {(char *)"scene_update_pre", NULL}, + {(char *)"scene_update_post", NULL}, {NULL} }; -- cgit v1.2.3 From e2393d11090765caf987e5ef8e2e688d80c0e64d Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 3 Nov 2011 06:53:52 +0000 Subject: ability to have permanent callbacks that stay active when new blend files are loaded. this works by tagging functions, eg: def my_func(scene): pass bpy.app.handlers.permanent_tag(my_func, True) # <-- important bit bpy.app.handlers.frame_change_pre.append(my_func) --- source/blender/python/BPY_extern.h | 2 +- source/blender/python/intern/bpy_app_handlers.c | 113 +++++++++++++++++++++++- 2 files changed, 111 insertions(+), 4 deletions(-) (limited to 'source/blender/python') diff --git a/source/blender/python/BPY_extern.h b/source/blender/python/BPY_extern.h index d8ddcd593a8..29e0185c2f2 100644 --- a/source/blender/python/BPY_extern.h +++ b/source/blender/python/BPY_extern.h @@ -71,7 +71,7 @@ void BPY_text_free_code(struct Text *text); void BPY_modules_update(struct bContext *C); // XXX - annoying, need this for pointers that get out of date void BPY_modules_load_user(struct bContext *C); -void BPY_app_handlers_reset(void); +void BPY_app_handlers_reset(const short do_all); void BPY_driver_reset(void); float BPY_driver_exec(struct ChannelDriver *driver); diff --git a/source/blender/python/intern/bpy_app_handlers.c b/source/blender/python/intern/bpy_app_handlers.c index e7f74569f1a..9ca0e71e71d 100644 --- a/source/blender/python/intern/bpy_app_handlers.c +++ b/source/blender/python/intern/bpy_app_handlers.c @@ -49,6 +49,11 @@ static PyStructSequence_Field app_cb_info_fields[]= { {(char *)"save_post", NULL}, {(char *)"scene_update_pre", NULL}, {(char *)"scene_update_post", NULL}, + + /* sets the permanent tag */ +# define APP_CB_OTHER_FIELDS 1 + {(char *)"permanent_tag", NULL}, + {NULL} }; @@ -65,6 +70,71 @@ static PyStructSequence_Desc app_cb_info_desc= { #endif */ +/* --------------------------------------------------------------------------*/ +/* permanent tagging code */ +#define PERMINENT_CB_ID "_bpy_permanent_tag" + +PyDoc_STRVAR(bpy_app_handlers_permanent_tag_doc, +".. function:: permanent_tag(func, state=True)\n" +"\n" +" Set the function as being permanent so its not cleared when new blend files are loaded.\n" +"\n" +" :arg func: The function to set as permanent.\n" +" :type func: function\n" +" :arg state: Set the permanent state to True or False.\n" +" :type state: bool\n" +" :return: the function argument\n" +" :rtype: function\n" +); + +static PyObject *bpy_app_handlers_permanent_tag(PyObject *UNUSED(self), PyObject *args) +{ + PyObject *value; + int state= 1; + + if(!PyArg_ParseTuple(args, "O|i:permanent_tag", &value, &state)) + return NULL; + + if (PyFunction_Check(value)) { + PyObject **dict_ptr= _PyObject_GetDictPtr(value); + if (dict_ptr == NULL) { + PyErr_SetString(PyExc_ValueError, + "bpy.app.handlers.permanent_tag wasn't able to " + "get the dictionary from the function passed"); + return NULL; + } + else { + if (state) { + /* set id */ + if (*dict_ptr == NULL) { + *dict_ptr= PyDict_New(); + } + + PyDict_SetItemString(*dict_ptr, PERMINENT_CB_ID, Py_None); + } + else { + /* clear id */ + if (*dict_ptr) { + PyDict_DelItemString(*dict_ptr, PERMINENT_CB_ID); + } + } + } + + Py_INCREF(value); + return value; + } + else { + PyErr_SetString(PyExc_ValueError, + "bpy.app.handlers.permanent_tag expected a function"); + return NULL; + } +} + +static PyMethodDef meth_bpy_app_handlers_permanent_tag= {"permanent_tag", (PyCFunction)bpy_app_handlers_permanent_tag, METH_VARARGS, bpy_app_handlers_permanent_tag_doc}; + + + + static PyObject *py_cb_array[BLI_CB_EVT_TOT]= {NULL}; static PyObject *make_app_cb_info(void) @@ -83,10 +153,13 @@ static PyObject *make_app_cb_info(void) } PyStructSequence_SET_ITEM(app_cb_info, pos, (py_cb_array[pos]= PyList_New(0))); } - if (app_cb_info_fields[pos].name != NULL) { + if (app_cb_info_fields[pos + APP_CB_OTHER_FIELDS].name != NULL) { Py_FatalError("invalid callback slots 2"); } + /* custom function */ + PyStructSequence_SET_ITEM(app_cb_info, pos++, (PyObject *)PyCFunction_New(&meth_bpy_app_handlers_permanent_tag, NULL)); + return app_cb_info; } @@ -120,12 +193,46 @@ PyObject *BPY_app_handlers_struct(void) return ret; } -void BPY_app_handlers_reset(void) +void BPY_app_handlers_reset(const short do_all) { int pos= 0; + if (do_all) { for (pos= 0; pos < BLI_CB_EVT_TOT; pos++) { - PyList_SetSlice(py_cb_array[pos], 0, PY_SSIZE_T_MAX, NULL); + /* clear list */ + PyList_SetSlice(py_cb_array[pos], 0, PY_SSIZE_T_MAX, NULL); + } + } + else { + /* save string conversion thrashing */ + PyObject *perm_id_str= PyUnicode_FromString(PERMINENT_CB_ID); + + for (pos= 0; pos < BLI_CB_EVT_TOT; pos++) { + /* clear only items without PERMINENT_CB_ID */ + PyObject *ls= py_cb_array[pos]; + Py_ssize_t i; + + PyObject *item; + PyObject **dict_ptr; + + for(i= PyList_GET_SIZE(ls) - 1; i >= 0; i--) { + + if ( (PyFunction_Check((item= PyList_GET_ITEM(ls, i)))) && + (dict_ptr= _PyObject_GetDictPtr(item)) && + (*dict_ptr) && + (PyDict_GetItem(*dict_ptr, perm_id_str) != NULL)) + { + /* keep */ + } + else { + /* remove */ + /* PySequence_DelItem(ls, i); */ /* more obvious buw slower */ + PyList_SetSlice(ls, i, i + 1, NULL); + } + } + } + + Py_DECREF(perm_id_str); } } -- cgit v1.2.3 From 5eef9374362eb1089f08d4ab5f397843d2301d7a Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 3 Nov 2011 09:13:47 +0000 Subject: modify previous api feature to tag functions as permanent, use nicer decorator style, eg: # -------- import bpy from bpy.app.handlers import persistent @persistent def my_func(scene): pass bpy.app.handlers.frame_change_pre.append(my_func) --- source/blender/python/intern/bpy_app_handlers.c | 100 +++++++++++++++--------- 1 file changed, 61 insertions(+), 39 deletions(-) (limited to 'source/blender/python') diff --git a/source/blender/python/intern/bpy_app_handlers.c b/source/blender/python/intern/bpy_app_handlers.c index 9ca0e71e71d..cf90443e212 100644 --- a/source/blender/python/intern/bpy_app_handlers.c +++ b/source/blender/python/intern/bpy_app_handlers.c @@ -52,7 +52,7 @@ static PyStructSequence_Field app_cb_info_fields[]= { /* sets the permanent tag */ # define APP_CB_OTHER_FIELDS 1 - {(char *)"permanent_tag", NULL}, + {(char *)"persistent", NULL}, {NULL} }; @@ -72,52 +72,30 @@ static PyStructSequence_Desc app_cb_info_desc= { /* --------------------------------------------------------------------------*/ /* permanent tagging code */ -#define PERMINENT_CB_ID "_bpy_permanent_tag" - -PyDoc_STRVAR(bpy_app_handlers_permanent_tag_doc, -".. function:: permanent_tag(func, state=True)\n" -"\n" -" Set the function as being permanent so its not cleared when new blend files are loaded.\n" -"\n" -" :arg func: The function to set as permanent.\n" -" :type func: function\n" -" :arg state: Set the permanent state to True or False.\n" -" :type state: bool\n" -" :return: the function argument\n" -" :rtype: function\n" -); - -static PyObject *bpy_app_handlers_permanent_tag(PyObject *UNUSED(self), PyObject *args) +#define PERMINENT_CB_ID "_bpy_persistent" + +static PyObject *bpy_app_handlers_persistent_new(PyTypeObject *UNUSED(type), PyObject *args, PyObject *UNUSED(kwds)) { PyObject *value; - int state= 1; - if(!PyArg_ParseTuple(args, "O|i:permanent_tag", &value, &state)) + if(!PyArg_ParseTuple(args, "O:bpy.app.handlers.persistent", &value)) return NULL; if (PyFunction_Check(value)) { PyObject **dict_ptr= _PyObject_GetDictPtr(value); if (dict_ptr == NULL) { PyErr_SetString(PyExc_ValueError, - "bpy.app.handlers.permanent_tag wasn't able to " + "bpy.app.handlers.persistent wasn't able to " "get the dictionary from the function passed"); return NULL; } else { - if (state) { - /* set id */ - if (*dict_ptr == NULL) { - *dict_ptr= PyDict_New(); - } - - PyDict_SetItemString(*dict_ptr, PERMINENT_CB_ID, Py_None); - } - else { - /* clear id */ - if (*dict_ptr) { - PyDict_DelItemString(*dict_ptr, PERMINENT_CB_ID); - } + /* set id */ + if (*dict_ptr == NULL) { + *dict_ptr= PyDict_New(); } + + PyDict_SetItemString(*dict_ptr, PERMINENT_CB_ID, Py_None); } Py_INCREF(value); @@ -125,15 +103,55 @@ static PyObject *bpy_app_handlers_permanent_tag(PyObject *UNUSED(self), PyObject } else { PyErr_SetString(PyExc_ValueError, - "bpy.app.handlers.permanent_tag expected a function"); + "bpy.app.handlers.persistent expected a function"); return NULL; } } -static PyMethodDef meth_bpy_app_handlers_permanent_tag= {"permanent_tag", (PyCFunction)bpy_app_handlers_permanent_tag, METH_VARARGS, bpy_app_handlers_permanent_tag_doc}; - - - +/* dummy type because decorators can't be PyCFunctions */ +static PyTypeObject BPyPersistent_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "persistent", /* tp_name */ + 0, /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + bpy_app_handlers_persistent_new, /* tp_new */ + 0, /* tp_free */ +}; static PyObject *py_cb_array[BLI_CB_EVT_TOT]= {NULL}; @@ -158,7 +176,7 @@ static PyObject *make_app_cb_info(void) } /* custom function */ - PyStructSequence_SET_ITEM(app_cb_info, pos++, (PyObject *)PyCFunction_New(&meth_bpy_app_handlers_permanent_tag, NULL)); + PyStructSequence_SET_ITEM(app_cb_info, pos++, (PyObject *)&BPyPersistent_Type); return app_cb_info; } @@ -167,6 +185,10 @@ PyObject *BPY_app_handlers_struct(void) { PyObject *ret; + if (PyType_Ready(&BPyPersistent_Type) < 0) { + BLI_assert(!"error initializing 'bpy.app.handlers.persistent'"); + } + PyStructSequence_InitType(&BlenderAppCbType, &app_cb_info_desc); ret= make_app_cb_info(); -- cgit v1.2.3 From dff93ddeccadf4e4ccc78bacf8073de1f8e64434 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 3 Nov 2011 12:01:18 +0000 Subject: hopefully fix msvc build error --- source/blender/python/intern/bpy_app_handlers.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'source/blender/python') diff --git a/source/blender/python/intern/bpy_app_handlers.c b/source/blender/python/intern/bpy_app_handlers.c index cf90443e212..5b8c20bb12d 100644 --- a/source/blender/python/intern/bpy_app_handlers.c +++ b/source/blender/python/intern/bpy_app_handlers.c @@ -110,7 +110,13 @@ static PyObject *bpy_app_handlers_persistent_new(PyTypeObject *UNUSED(type), PyO /* dummy type because decorators can't be PyCFunctions */ static PyTypeObject BPyPersistent_Type = { + +#if defined(_MSC_VER) || defined(FREE_WINDOWS) + PyVarObject_HEAD_INIT(NULL, 0) +#else PyVarObject_HEAD_INIT(&PyType_Type, 0) +#endif + "persistent", /* tp_name */ 0, /* tp_basicsize */ 0, /* tp_itemsize */ @@ -185,6 +191,10 @@ PyObject *BPY_app_handlers_struct(void) { PyObject *ret; +#if defined(_MSC_VER) || defined(FREE_WINDOWS) + BPyPersistent_Type.ob_base.ob_base.ob_type= &PyType_Type; +#endif + if (PyType_Ready(&BPyPersistent_Type) < 0) { BLI_assert(!"error initializing 'bpy.app.handlers.persistent'"); } -- cgit v1.2.3 From 665f602f152984f5a6f4ea05a2a90382acbf700a Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 3 Nov 2011 14:09:18 +0000 Subject: python string conversion - use _PyUnicode_AsStringAndSize where possible - use %R for PyErr_Format(...) rather then running repr on the object explicitly - use const char --- source/blender/python/generic/IDProp.c | 39 +++++++++++++++++---------- source/blender/python/generic/IDProp.h | 2 +- source/blender/python/generic/py_capi_utils.c | 2 +- source/blender/python/intern/bpy_operator.c | 4 +-- source/blender/python/intern/bpy_props.c | 2 +- 5 files changed, 30 insertions(+), 19 deletions(-) (limited to 'source/blender/python') diff --git a/source/blender/python/generic/IDProp.c b/source/blender/python/generic/IDProp.c index 076b4811d07..6d869a7eb1f 100644 --- a/source/blender/python/generic/IDProp.c +++ b/source/blender/python/generic/IDProp.c @@ -195,19 +195,22 @@ static PyObject *BPy_IDGroup_GetName(BPy_IDProperty *self, void *UNUSED(closure) static int BPy_IDGroup_SetName(BPy_IDProperty *self, PyObject *value, void *UNUSED(closure)) { - char *st; + const char *name; + Py_ssize_t name_size; + if (!PyUnicode_Check(value)) { PyErr_SetString(PyExc_TypeError, "expected a string!"); return -1; } - st = _PyUnicode_AsString(value); - if (BLI_strnlen(st, MAX_IDPROP_NAME) == MAX_IDPROP_NAME) { + name = _PyUnicode_AsStringAndSize(value, &name_size); + + if (name_size > MAX_IDPROP_NAME) { PyErr_SetString(PyExc_TypeError, "string length cannot exceed 31 characters!"); return -1; } - BLI_strncpy(self->prop->name, st, sizeof(self->prop->name)); + memcpy(self->prop->name, name, name_size); return 0; } @@ -236,7 +239,7 @@ static Py_ssize_t BPy_IDGroup_Map_Len(BPy_IDProperty *self) static PyObject *BPy_IDGroup_Map_GetItem(BPy_IDProperty *self, PyObject *item) { IDProperty *idprop; - char *name; + const char *name; if (self->prop->type != IDP_GROUP) { PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); @@ -301,14 +304,22 @@ static int idp_sequence_type(PyObject *seq) return type; } -/* note: group can be a pointer array or a group */ -const char *BPy_IDProperty_Map_ValidateAndCreate(const char *name, IDProperty *group, PyObject *ob) +/* note: group can be a pointer array or a group. + * assume we already checked key is a string. */ +const char *BPy_IDProperty_Map_ValidateAndCreate(PyObject *name_obj, IDProperty *group, PyObject *ob) { IDProperty *prop = NULL; IDPropertyTemplate val = {0}; - if (strlen(name) >= sizeof(group->name)) - return "the length of IDProperty names is limited to 31 characters"; + const char *name= ""; + + if (name_obj) { + Py_ssize_t name_size; + name = _PyUnicode_AsStringAndSize(name_obj, &name_size); + if (name_size > MAX_IDPROP_NAME) { + return "the length of IDProperty names is limited to 31 characters"; + } + } if (PyFloat_Check(ob)) { val.d = PyFloat_AsDouble(ob); @@ -364,7 +375,7 @@ const char *BPy_IDProperty_Map_ValidateAndCreate(const char *name, IDProperty *g for (i=0; i Date: Fri, 4 Nov 2011 04:27:46 +0000 Subject: - added docs and examples for bpy.app.handlers - correct error in own last commit for BKE_screen_find_big_area() --- source/blender/python/intern/bpy_app_handlers.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'source/blender/python') diff --git a/source/blender/python/intern/bpy_app_handlers.c b/source/blender/python/intern/bpy_app_handlers.c index 5b8c20bb12d..f130a4bcc5c 100644 --- a/source/blender/python/intern/bpy_app_handlers.c +++ b/source/blender/python/intern/bpy_app_handlers.c @@ -38,21 +38,21 @@ void bpy_app_generic_callback(struct Main *main, struct ID *id, void *arg); static PyTypeObject BlenderAppCbType; static PyStructSequence_Field app_cb_info_fields[]= { - {(char *)"frame_change_pre", NULL}, - {(char *)"frame_change_post", NULL}, - {(char *)"render_pre", NULL}, - {(char *)"render_post", NULL}, - {(char *)"render_stats", NULL}, - {(char *)"load_pre", NULL}, - {(char *)"load_post", NULL}, - {(char *)"save_pre", NULL}, - {(char *)"save_post", NULL}, - {(char *)"scene_update_pre", NULL}, - {(char *)"scene_update_post", NULL}, + {(char *)"frame_change_pre", (char *)"Callback list - on frame change for playback and rendering (before)"}, + {(char *)"frame_change_post", (char *)"Callback list - on frame change for playback and rendering (after)"}, + {(char *)"render_pre", (char *)"Callback list - on render (before)"}, + {(char *)"render_post", (char *)"Callback list - on render (after)"}, + {(char *)"render_stats", (char *)"Callback list - on printing render statistics"}, + {(char *)"load_pre", (char *)"Callback list - on loading a new blend file (before)"}, + {(char *)"load_post", (char *)"Callback list - on loading a new blend file (after)"}, + {(char *)"save_pre", (char *)"Callback list - on saving a blend file (before)"}, + {(char *)"save_post", (char *)"Callback list - on saving a blend file (after)"}, + {(char *)"scene_update_pre", (char *)"Callback list - on updating the scenes data (before)"}, + {(char *)"scene_update_post", (char *)"Callback list - on updating the scenes data (after)"}, /* sets the permanent tag */ # define APP_CB_OTHER_FIELDS 1 - {(char *)"persistent", NULL}, + {(char *)"persistent", (char *)"Function decorator for callback functions not to be removed when loading new files"}, {NULL} }; -- cgit v1.2.3