diff options
Diffstat (limited to 'source/blender/python/intern')
32 files changed, 2510 insertions, 1067 deletions
diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt index 357cb175769..bd7306cddf2 100644 --- a/source/blender/python/intern/CMakeLists.txt +++ b/source/blender/python/intern/CMakeLists.txt @@ -52,11 +52,13 @@ set(SRC bpy_app_build_options.c bpy_app_ffmpeg.c bpy_app_handlers.c + bpy_app_icons.c bpy_app_ocio.c bpy_app_oiio.c bpy_app_opensubdiv.c bpy_app_openvdb.c bpy_app_sdl.c + bpy_app_timers.c bpy_app_translations.c bpy_capi_utils.c bpy_driver.c @@ -65,6 +67,8 @@ set(SRC bpy_intern_string.c bpy_library_load.c bpy_library_write.c + bpy_gizmo_wrap.c + bpy_msgbus.c bpy_operator.c bpy_operator_wrap.c bpy_path.c @@ -75,11 +79,10 @@ set(SRC bpy_rna_callback.c bpy_rna_driver.c bpy_rna_id_collection.c + bpy_rna_gizmo.c bpy_traceback.c bpy_utils_previews.c bpy_utils_units.c - gpu.c - gpu_offscreen.c stubs.c bpy.h @@ -88,16 +91,20 @@ set(SRC bpy_app_build_options.h bpy_app_ffmpeg.h bpy_app_handlers.h + bpy_app_icons.h bpy_app_ocio.h bpy_app_oiio.h bpy_app_opensubdiv.h bpy_app_openvdb.h bpy_app_sdl.h + bpy_app_timers.h bpy_app_translations.h bpy_capi_utils.h bpy_driver.h bpy_intern_string.h bpy_library.h + bpy_gizmo_wrap.h + bpy_msgbus.h bpy_operator.h bpy_operator_wrap.h bpy_path.h @@ -107,10 +114,10 @@ set(SRC bpy_rna_callback.h bpy_rna_driver.h bpy_rna_id_collection.h + bpy_rna_gizmo.h bpy_traceback.h bpy_utils_previews.h bpy_utils_units.h - gpu.h ../BPY_extern.h ../BPY_extern_clog.h ) @@ -176,10 +183,6 @@ if(WITH_FREESTYLE) add_definitions(-DWITH_FREESTYLE) endif() -if(WITH_GAMEENGINE) - add_definitions(-DWITH_GAMEENGINE) -endif() - if(WITH_IMAGE_CINEON) add_definitions(-DWITH_CINEON) endif() @@ -188,10 +191,6 @@ if(WITH_IMAGE_DDS) add_definitions(-DWITH_DDS) endif() -if(WITH_IMAGE_FRAMESERVER) - add_definitions(-DWITH_FRAMESERVER) -endif() - if(WITH_IMAGE_HDR) add_definitions(-DWITH_HDR) endif() @@ -302,10 +301,6 @@ if(WITH_OPENSUBDIV) ) endif() -if(WITH_PLAYER) - add_definitions(-DWITH_PLAYER) -endif() - add_definitions(${GL_DEFINITIONS}) blender_add_lib(bf_python "${SRC}" "${INC}" "${INC_SYS}") diff --git a/source/blender/python/intern/bpy.c b/source/blender/python/intern/bpy.c index 82f710e4acf..07ad8274f11 100644 --- a/source/blender/python/intern/bpy.c +++ b/source/blender/python/intern/bpy.c @@ -46,6 +46,7 @@ #include "bpy_rna.h" #include "bpy_app.h" #include "bpy_rna_id_collection.h" +#include "bpy_rna_gizmo.h" #include "bpy_props.h" #include "bpy_library.h" #include "bpy_operator.h" @@ -57,6 +58,7 @@ /* external util modules */ #include "../generic/idprop_py_api.h" +#include "bpy_msgbus.h" #ifdef WITH_FREESTYLE # include "BPy_Freestyle.h" @@ -338,6 +340,8 @@ void BPy_init_modules(void) BPY_rna_id_collection_module(mod); + BPY_rna_gizmo_module(mod); + bpy_import_test("bpy_types"); PyModule_AddObject(mod, "data", BPY_rna_module()); /* imports bpy_types by running this */ bpy_import_test("bpy_types"); @@ -347,6 +351,7 @@ void BPy_init_modules(void) PyModule_AddObject(mod, "app", BPY_app_struct()); PyModule_AddObject(mod, "_utils_units", BPY_utils_units()); PyModule_AddObject(mod, "_utils_previews", BPY_utils_previews_module()); + PyModule_AddObject(mod, "msgbus", BPY_msgbus_module()); /* bpy context */ RNA_pointer_create(NULL, &RNA_Context, (void *)BPy_GetContext(), &ctx_ptr); @@ -371,6 +376,9 @@ void BPy_init_modules(void) PyModule_AddObject(mod, meth_bpy_register_class.ml_name, (PyObject *)PyCFunction_New(&meth_bpy_register_class, NULL)); PyModule_AddObject(mod, meth_bpy_unregister_class.ml_name, (PyObject *)PyCFunction_New(&meth_bpy_unregister_class, NULL)); + PyModule_AddObject(mod, meth_bpy_owner_id_get.ml_name, (PyObject *)PyCFunction_New(&meth_bpy_owner_id_get, NULL)); + PyModule_AddObject(mod, meth_bpy_owner_id_set.ml_name, (PyObject *)PyCFunction_New(&meth_bpy_owner_id_set, NULL)); + /* add our own modules dir, this is a python package */ bpy_package_py = bpy_import_test("bpy"); } diff --git a/source/blender/python/intern/bpy_app.c b/source/blender/python/intern/bpy_app.c index 928e14c09bf..fe8eb768b3a 100644 --- a/source/blender/python/intern/bpy_app.c +++ b/source/blender/python/intern/bpy_app.c @@ -47,11 +47,16 @@ #include "bpy_app_handlers.h" #include "bpy_driver.h" +/* modules */ +#include "bpy_app_icons.h" +#include "bpy_app_timers.h" + #include "BLI_utildefines.h" #include "BKE_appdir.h" #include "BKE_blender_version.h" #include "BKE_global.h" +#include "BKE_library_override.h" #include "DNA_ID.h" @@ -117,6 +122,10 @@ static PyStructSequence_Field app_info_fields[] = { {(char *)"build_options", (char *)"A set containing most important enabled optional build features"}, {(char *)"handlers", (char *)"Application handler callbacks"}, {(char *)"translations", (char *)"Application and addons internationalization API"}, + + /* Modules (not struct sequence). */ + {(char *)"icons", (char *)"Manage custom icons"}, + {(char *)"timers", (char *)"Manage timers"}, {NULL}, }; @@ -129,6 +138,8 @@ PyDoc_STRVAR(bpy_app_doc, " :maxdepth: 1\n" "\n" " bpy.app.handlers.rst\n" +" bpy.app.icons.rst\n" +" bpy.app.timers.rst\n" " bpy.app.translations.rst\n" ); @@ -210,6 +221,10 @@ static PyObject *make_app_info(void) SetObjItem(BPY_app_handlers_struct()); SetObjItem(BPY_app_translations_struct()); + /* modules */ + SetObjItem(BPY_app_icons_module()); + SetObjItem(BPY_app_timers_module()); + #undef SetIntItem #undef SetStrItem #undef SetBytesItem @@ -282,7 +297,7 @@ static PyObject *bpy_app_binary_path_python_get(PyObject *self, void *UNUSED(clo } PyDoc_STRVAR(bpy_app_debug_value_doc, -"Int, number which can be set to non-zero values for testing purposes" +"Short, number which can be set to non-zero values for testing purposes" ); static PyObject *bpy_app_debug_value_get(PyObject *UNUSED(self), void *UNUSED(closure)) { @@ -291,10 +306,12 @@ static PyObject *bpy_app_debug_value_get(PyObject *UNUSED(self), void *UNUSED(cl static int bpy_app_debug_value_set(PyObject *UNUSED(self), PyObject *value, void *UNUSED(closure)) { - int param = PyC_Long_AsI32(value); + short param = PyC_Long_AsI16(value); if (param == -1 && PyErr_Occurred()) { - PyErr_SetString(PyExc_TypeError, "bpy.app.debug_value can only be set to a whole number"); + PyC_Err_SetString_Prefix( + PyExc_TypeError, + "bpy.app.debug_value can only be set to a whole number"); return -1; } @@ -348,6 +365,29 @@ static PyObject *bpy_app_autoexec_fail_message_get(PyObject *UNUSED(self), void } +PyDoc_STRVAR(bpy_app_use_static_override_doc, +"Boolean, whether static override is exposed in UI or not." +); +static PyObject *bpy_app_use_static_override_get(PyObject *UNUSED(self), void *UNUSED(closure)) +{ + return PyBool_FromLong((long)BKE_override_static_is_enabled()); +} + +static int bpy_app_use_static_override_set(PyObject *UNUSED(self), PyObject *value, void *UNUSED(closure)) +{ + const int param = PyC_Long_AsBool(value); + + if (param == -1 && PyErr_Occurred()) { + PyErr_SetString(PyExc_TypeError, "bpy.app.use_static_override must be a boolean"); + return -1; + } + + BKE_override_static_enable((const bool)param); + + return 0; +} + + static PyGetSetDef bpy_app_getsets[] = { {(char *)"debug", bpy_app_debug_get, bpy_app_debug_set, (char *)bpy_app_debug_doc, (void *)G_DEBUG}, {(char *)"debug_ffmpeg", bpy_app_debug_get, bpy_app_debug_set, (char *)bpy_app_debug_doc, (void *)G_DEBUG_FFMPEG}, @@ -366,6 +406,8 @@ static PyGetSetDef bpy_app_getsets[] = { {(char *)"debug_gpumem", bpy_app_debug_get, bpy_app_debug_set, (char *)bpy_app_debug_doc, (void *)G_DEBUG_GPU_MEM}, {(char *)"debug_io", bpy_app_debug_get, bpy_app_debug_set, (char *)bpy_app_debug_doc, (void *)G_DEBUG_IO}, + {(char *)"use_static_override", bpy_app_use_static_override_get, bpy_app_use_static_override_set, (char *)bpy_app_use_static_override_doc, NULL}, + {(char *)"binary_path_python", bpy_app_binary_path_python_get, NULL, (char *)bpy_app_binary_path_python_doc, NULL}, {(char *)"debug_value", bpy_app_debug_value_get, bpy_app_debug_value_set, (char *)bpy_app_debug_value_doc, NULL}, diff --git a/source/blender/python/intern/bpy_app_build_options.c b/source/blender/python/intern/bpy_app_build_options.c index d5c325e4317..70c52b8860c 100644 --- a/source/blender/python/intern/bpy_app_build_options.c +++ b/source/blender/python/intern/bpy_app_build_options.c @@ -42,10 +42,8 @@ static PyStructSequence_Field app_builtopts_info_fields[] = { {(char *)"cycles", NULL}, {(char *)"cycles_osl", NULL}, {(char *)"freestyle", NULL}, - {(char *)"gameengine", NULL}, {(char *)"image_cineon", NULL}, {(char *)"image_dds", NULL}, - {(char *)"image_frameserver", NULL}, {(char *)"image_hdr", NULL}, {(char *)"image_openexr", NULL}, {(char *)"image_openjpeg", NULL}, @@ -64,7 +62,6 @@ static PyStructSequence_Field app_builtopts_info_fields[] = { {(char *)"mod_smoke", NULL}, {(char *)"collada", NULL}, {(char *)"opencolorio", NULL}, - {(char *)"player", NULL}, {(char *)"openmp", NULL}, {(char *)"openvdb", NULL}, {(char *)"alembic", NULL}, @@ -140,12 +137,6 @@ static PyObject *make_builtopts_info(void) SetObjIncref(Py_False); #endif -#ifdef WITH_GAMEENGINE - SetObjIncref(Py_True); -#else - SetObjIncref(Py_False); -#endif - #ifdef WITH_CINEON SetObjIncref(Py_True); #else @@ -158,12 +149,6 @@ static PyObject *make_builtopts_info(void) SetObjIncref(Py_False); #endif -#ifdef WITH_FRAMESERVER - SetObjIncref(Py_True); -#else - SetObjIncref(Py_False); -#endif - #ifdef WITH_HDR SetObjIncref(Py_True); #else @@ -272,12 +257,6 @@ static PyObject *make_builtopts_info(void) SetObjIncref(Py_False); #endif -#ifdef WITH_PLAYER - SetObjIncref(Py_True); -#else - SetObjIncref(Py_False); -#endif - #ifdef _OPENMP SetObjIncref(Py_True); #else diff --git a/source/blender/python/intern/bpy_app_handlers.c b/source/blender/python/intern/bpy_app_handlers.c index 8453808d6ec..d89b3bd01f5 100644 --- a/source/blender/python/intern/bpy_app_handlers.c +++ b/source/blender/python/intern/bpy_app_handlers.c @@ -46,35 +46,30 @@ 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", (char *)"on frame change for playback and rendering (before)"}, - {(char *)"frame_change_post", (char *)"on frame change for playback and rendering (after)"}, - {(char *)"render_pre", (char *)"on render (before)"}, - {(char *)"render_post", (char *)"on render (after)"}, - {(char *)"render_write", (char *)"on writing a render frame (directly after the frame is written)"}, - {(char *)"render_stats", (char *)"on printing render statistics"}, - {(char *)"render_init", (char *)"on initialization of a render job"}, - {(char *)"render_complete", (char *)"on completion of render job"}, - {(char *)"render_cancel", (char *)"on canceling a render job"}, - {(char *)"load_pre", (char *)"on loading a new blend file (before)"}, - {(char *)"load_post", (char *)"on loading a new blend file (after)"}, - {(char *)"save_pre", (char *)"on saving a blend file (before)"}, - {(char *)"save_post", (char *)"on saving a blend file (after)"}, - {(char *)"undo_pre", (char *)"on loading an undo step (before)"}, - {(char *)"undo_post", (char *)"on loading an undo step (after)"}, - {(char *)"redo_pre", (char *)"on loading a redo step (before)"}, - {(char *)"redo_post", (char *)"on loading a redo step (after)"}, - {(char *)"scene_update_pre", (char *)"on every scene data update. Does not imply that anything changed in the " - "scene, just that the dependency graph is about to be reevaluated, and the " - "scene is about to be updated by Blender's animation system."}, - {(char *)"scene_update_post", (char *)"on every scene data update. Does not imply that anything changed in the " - "scene, just that the dependency graph was reevaluated, and the scene was " - "possibly updated by Blender's animation system."}, - {(char *)"game_pre", (char *)"on starting the game engine"}, - {(char *)"game_post", (char *)"on ending the game engine"}, - {(char *)"version_update", (char *)"on ending the versioning code"}, + {(char *)"frame_change_pre", (char *)"on frame change for playback and rendering (before)"}, + {(char *)"frame_change_post", (char *)"on frame change for playback and rendering (after)"}, + {(char *)"render_pre", (char *)"on render (before)"}, + {(char *)"render_post", (char *)"on render (after)"}, + {(char *)"render_write", (char *)"on writing a render frame (directly after the frame is written)"}, + {(char *)"render_stats", (char *)"on printing render statistics"}, + {(char *)"render_init", (char *)"on initialization of a render job"}, + {(char *)"render_complete", (char *)"on completion of render job"}, + {(char *)"render_cancel", (char *)"on canceling a render job"}, + {(char *)"load_pre", (char *)"on loading a new blend file (before)"}, + {(char *)"load_post", (char *)"on loading a new blend file (after)"}, + {(char *)"save_pre", (char *)"on saving a blend file (before)"}, + {(char *)"save_post", (char *)"on saving a blend file (after)"}, + {(char *)"undo_pre", (char *)"on loading an undo step (before)"}, + {(char *)"undo_post", (char *)"on loading an undo step (after)"}, + {(char *)"redo_pre", (char *)"on loading a redo step (before)"}, + {(char *)"redo_post", (char *)"on loading a redo step (after)"}, + {(char *)"depsgraph_update_pre", (char *)"on depsgraph update (pre)"}, + {(char *)"depsgraph_update_post", (char *)"on depsgraph update (post)"}, + {(char *)"version_update", (char *)"on ending the versioning code"}, + {(char *)"load_factory_startup_post", (char *)"on loading factory startup (after)"}, /* sets the permanent tag */ -# define APP_CB_OTHER_FIELDS 1 +#define APP_CB_OTHER_FIELDS 1 {(char *)"persistent", (char *)"Function decorator for callback functions not to be removed when loading new files"}, {NULL} diff --git a/source/blender/python/intern/bpy_app_icons.c b/source/blender/python/intern/bpy_app_icons.c new file mode 100644 index 00000000000..12315a6e112 --- /dev/null +++ b/source/blender/python/intern/bpy_app_icons.c @@ -0,0 +1,187 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/python/intern/bpy_app_icons.c + * \ingroup pythonintern + * + * Runtime defined icons. + */ + +#include <Python.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_utildefines.h" + +#include "BKE_icons.h" + +#include "../generic/py_capi_utils.h" + +#include "bpy_app_icons.h" + +/* We may want to load direct from file. */ +PyDoc_STRVAR(bpy_app_icons_new_triangles_doc, +".. function:: new_triangles(range, coords, colors)" +"\n" +" Create a new icon from triangle geometry.\n" +"\n" +" :arg range: Pair of ints.\n" +" :type range: tuple.\n" +" :arg coords: Sequence of bytes (6 floats for one triangle) for (X, Y) coordinates.\n" +" :type coords: byte sequence.\n" +" :arg colors: Sequence of ints (12 for one triangles) for RGBA.\n" +" :type colors: byte sequence.\n" +" :return: Unique icon value (pass to interface ``icon_value`` argument).\n" +" :rtype: int\n" +); +static PyObject *bpy_app_icons_new_triangles(PyObject *UNUSED(self), PyObject *args, PyObject *kw) +{ + /* bytes */ + uchar coords_range[2]; + PyObject *py_coords, *py_colors; + + static const char *_keywords[] = {"range", "coords", "colors", NULL}; + static _PyArg_Parser _parser = {"(BB)SS:new_triangles", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast( + args, kw, &_parser, + &coords_range[0], &coords_range[1], &py_coords, &py_colors)) + { + return NULL; + } + + const int coords_len = PyBytes_GET_SIZE(py_coords); + const int tris_len = coords_len / 6; + if (tris_len * 6 != coords_len) { + PyErr_SetString(PyExc_ValueError, "coords must be multiple of 6"); + return NULL; + } + if (PyBytes_GET_SIZE(py_colors) != 2 * coords_len) { + PyErr_SetString(PyExc_ValueError, "colors must be twice size of coords"); + return NULL; + } + + int coords_size = sizeof(uchar[2]) * tris_len * 3; + int colors_size = sizeof(uchar[4]) * tris_len * 3; + uchar (*coords)[2] = MEM_mallocN(coords_size, __func__); + uchar (*colors)[4] = MEM_mallocN(colors_size, __func__); + + memcpy(coords, PyBytes_AS_STRING(py_coords), coords_size); + memcpy(colors, PyBytes_AS_STRING(py_colors), colors_size); + + struct Icon_Geom *geom = MEM_mallocN(sizeof(*geom), __func__); + geom->coords_len = tris_len; + geom->coords_range[0] = coords_range[0]; + geom->coords_range[1] = coords_range[1]; + geom->coords = coords; + geom->colors = colors; + geom->icon_id = 0; + int icon_id = BKE_icon_geom_ensure(geom); + return PyLong_FromLong(icon_id); +} + +PyDoc_STRVAR(bpy_app_icons_new_triangles_from_file_doc, +".. function:: new_triangles_from_file(filename)" +"\n" +" Create a new icon from triangle geometry.\n" +"\n" +" :arg filename: File path.\n" +" :type filename: string.\n" +" :return: Unique icon value (pass to interface ``icon_value`` argument).\n" +" :rtype: int\n" +); +static PyObject *bpy_app_icons_new_triangles_from_file(PyObject *UNUSED(self), PyObject *args, PyObject *kw) +{ + /* bytes */ + char *filename; + + static const char *_keywords[] = {"filename", NULL}; + static _PyArg_Parser _parser = {"s:new_triangles_from_file", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast( + args, kw, &_parser, + &filename)) + { + return NULL; + } + + struct Icon_Geom *geom = BKE_icon_geom_from_file(filename); + if (geom == NULL) { + PyErr_SetString(PyExc_ValueError, "Unable to load from file"); + return NULL; + } + int icon_id = BKE_icon_geom_ensure(geom); + return PyLong_FromLong(icon_id); +} + +PyDoc_STRVAR(bpy_app_icons_release_doc, +".. function:: release(icon_id)" +"\n" +" Release the icon.\n" +); +static PyObject *bpy_app_icons_release(PyObject *UNUSED(self), PyObject *args, PyObject *kw) +{ + int icon_id; + static const char *_keywords[] = {"icon_id", NULL}; + static _PyArg_Parser _parser = {"i:release", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast( + args, kw, &_parser, + &icon_id)) + { + return NULL; + } + + if (!BKE_icon_delete_unmanaged(icon_id)) { + PyErr_SetString(PyExc_ValueError, "invalid icon_id"); + return NULL; + } + Py_RETURN_NONE; +} + +static struct PyMethodDef M_AppIcons_methods[] = { + {"new_triangles", (PyCFunction)bpy_app_icons_new_triangles, + METH_VARARGS | METH_KEYWORDS, bpy_app_icons_new_triangles_doc}, + {"new_triangles_from_file", (PyCFunction)bpy_app_icons_new_triangles_from_file, + METH_VARARGS | METH_KEYWORDS, bpy_app_icons_new_triangles_from_file_doc}, + {"release", (PyCFunction)bpy_app_icons_release, + METH_VARARGS | METH_KEYWORDS, bpy_app_icons_release_doc}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef M_AppIcons_module_def = { + PyModuleDef_HEAD_INIT, + "bpy.app.icons", /* m_name */ + NULL, /* m_doc */ + 0, /* m_size */ + M_AppIcons_methods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ +}; + +PyObject *BPY_app_icons_module(void) +{ + PyObject *sys_modules = PyImport_GetModuleDict(); + + PyObject *mod = PyModule_Create(&M_AppIcons_module_def); + + PyDict_SetItem(sys_modules, PyModule_GetNameObject(mod), mod); + + return mod; +} diff --git a/source/blender/python/intern/bpy_app_icons.h b/source/blender/python/intern/bpy_app_icons.h new file mode 100644 index 00000000000..309c7ed222f --- /dev/null +++ b/source/blender/python/intern/bpy_app_icons.h @@ -0,0 +1,30 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/python/intern/bpy_app_icons.h + * \ingroup pythonintern + */ + +#ifndef __BPY_APP_ICONS_H__ +#define __BPY_APP_ICONS_H__ + +PyObject *BPY_app_icons_module(void); + +#endif /* __BPY_APP_ICONS_H__ */ diff --git a/source/blender/python/intern/bpy_app_timers.c b/source/blender/python/intern/bpy_app_timers.c new file mode 100644 index 00000000000..cd0e57200ec --- /dev/null +++ b/source/blender/python/intern/bpy_app_timers.c @@ -0,0 +1,199 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/python/intern/bpy_app_timers.c + * \ingroup pythonintern + */ + +#include <Python.h> +#include "BLI_utildefines.h" +#include "BLI_timer.h" +#include "PIL_time.h" + +#include "BPY_extern.h" +#include "bpy_app_timers.h" + +#include "../generic/py_capi_utils.h" +#include "../generic/python_utildefines.h" + + +static double handle_returned_value(PyObject *function, PyObject *ret) +{ + if (ret == NULL) { + PyErr_PrintEx(0); + PyErr_Clear(); + return -1; + } + + if (ret == Py_None) { + return -1; + } + + double value = PyFloat_AsDouble(ret); + if (value == -1.0f && PyErr_Occurred()) { + PyErr_Clear(); + printf("Error: 'bpy.app.timers' callback "); + PyObject_Print(function, stdout, Py_PRINT_RAW); + printf(" did not return None or float.\n"); + return -1; + } + + if (value < 0.0) { + value = 0.0; + } + + return value; +} + +static double py_timer_execute(uintptr_t UNUSED(uuid), void *user_data) +{ + PyObject *function = user_data; + + PyGILState_STATE gilstate; + gilstate = PyGILState_Ensure(); + + PyObject *py_ret = PyObject_CallObject(function, NULL); + double ret = handle_returned_value(function, py_ret); + + PyGILState_Release(gilstate); + + return ret; +} + +static void py_timer_free(uintptr_t UNUSED(uuid), void *user_data) +{ + PyObject *function = user_data; + + PyGILState_STATE gilstate; + gilstate = PyGILState_Ensure(); + + Py_DECREF(function); + + PyGILState_Release(gilstate); +} + + +PyDoc_STRVAR(bpy_app_timers_register_doc, +".. function:: register(function, first_interval=0, persistent=False)\n" +"\n" +" Add a new function that will be called after the specified amount of seconds.\n" +" The function gets no arguments and is expected to return either None or a float.\n" +" If ``None`` is returned, the timer will be unregistered.\n" +" A returned number specifies the delay until the function is called again.\n" +" ``functools.partial`` can be used to assign some parameters.\n" +"\n" +" :arg function: The function that should called.\n" +" :type function: Callable[[], Union[float, None]]\n" +" :arg first_interval: Seconds until the callback should be called the first time.\n" +" :type first_interval: float\n" +" :arg persistent: Don't remove timer when a new file is loaded.\n" +" :type persistent: bool\n" +); +static PyObject *bpy_app_timers_register(PyObject *UNUSED(self), PyObject *args, PyObject *kw) +{ + PyObject *function; + double first_interval = 0; + int persistent = false; + + static const char *_keywords[] = {"function", "first_interval", "persistent", NULL}; + static _PyArg_Parser _parser = {"O|$dp:register", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast( + args, kw, &_parser, + &function, &first_interval, &persistent)) + { + return NULL; + } + + if (!PyCallable_Check(function)) { + PyErr_SetString(PyExc_TypeError, "function is not callable"); + return NULL; + } + + Py_INCREF(function); + BLI_timer_register( + (intptr_t)function, + py_timer_execute, function, py_timer_free, + first_interval, persistent); + Py_RETURN_NONE; +} + +PyDoc_STRVAR(bpy_app_timers_unregister_doc, +".. function:: unregister(function)\n" +"\n" +" Unregister timer.\n" +"\n" +" :arg function: Function to unregister.\n" +" :type function: function\n" +); +static PyObject *bpy_app_timers_unregister(PyObject *UNUSED(self), PyObject *function) +{ + if (!BLI_timer_unregister((intptr_t)function)) { + PyErr_SetString(PyExc_ValueError, "Error: function is not registered"); + return NULL; + } + Py_RETURN_NONE; +} + +PyDoc_STRVAR(bpy_app_timers_is_registered_doc, +".. function:: is_registered(function)\n" +"\n" +" Check if this function is registered as a timer.\n" +"\n" +" :arg function: Function to check.\n" +" :type function: int\n" +" :return: True when this function is registered, otherwise False.\n" +" :rtype: bool\n" +); +static PyObject *bpy_app_timers_is_registered(PyObject *UNUSED(self), PyObject *function) +{ + bool ret = BLI_timer_is_registered((intptr_t)function); + return PyBool_FromLong(ret); +} + + +static struct PyMethodDef M_AppTimers_methods[] = { + {"register", (PyCFunction)bpy_app_timers_register, + METH_VARARGS | METH_KEYWORDS, bpy_app_timers_register_doc}, + {"unregister", (PyCFunction)bpy_app_timers_unregister, + METH_O, bpy_app_timers_unregister_doc}, + {"is_registered", (PyCFunction)bpy_app_timers_is_registered, + METH_O, bpy_app_timers_is_registered_doc}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef M_AppTimers_module_def = { + PyModuleDef_HEAD_INIT, + "bpy.app.timers", /* m_name */ + NULL, /* m_doc */ + 0, /* m_size */ + M_AppTimers_methods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ +}; + +PyObject *BPY_app_timers_module(void) +{ + PyObject *sys_modules = PyImport_GetModuleDict(); + PyObject *mod = PyModule_Create(&M_AppTimers_module_def); + PyDict_SetItem(sys_modules, PyModule_GetNameObject(mod), mod); + return mod; +} diff --git a/source/blender/python/intern/bpy_app_timers.h b/source/blender/python/intern/bpy_app_timers.h new file mode 100644 index 00000000000..ddf0e258e67 --- /dev/null +++ b/source/blender/python/intern/bpy_app_timers.h @@ -0,0 +1,30 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/python/intern/bpy_app_timers.h + * \ingroup pythonintern + */ + +#ifndef __BPY_APP_TIMERS_H__ +#define __BPY_APP_TIMERS_H__ + +PyObject *BPY_app_timers_module(void); + +#endif /* __BPY_APP_TIMERS_H__ */ diff --git a/source/blender/python/intern/bpy_driver.c b/source/blender/python/intern/bpy_driver.c index e7608e93f58..f098a28d679 100644 --- a/source/blender/python/intern/bpy_driver.c +++ b/source/blender/python/intern/bpy_driver.c @@ -48,6 +48,8 @@ #include "bpy_driver.h" +#include "BPY_extern.h" + extern void BPY_update_rna_module(void); #define USE_RNA_AS_PYOBJECT @@ -381,8 +383,12 @@ static bool bpy_driver_secure_bytecode_validate(PyObject *expr_code, PyObject *d * (new)note: checking if python is running is not threadsafe [#28114] * now release the GIL on python operator execution instead, using * PyEval_SaveThread() / PyEval_RestoreThread() so we don't lock up blender. + * + * For copy-on-write we always cache expressions and write errors in the + * original driver, otherwise these would get freed while editing. Due to + * the GIL this is thread-safe. */ -float BPY_driver_exec(struct PathResolvedRNA *anim_rna, ChannelDriver *driver, const float evaltime) +float BPY_driver_exec(struct PathResolvedRNA *anim_rna, ChannelDriver *driver, ChannelDriver *driver_orig, const float evaltime) { PyObject *driver_vars = NULL; PyObject *retval = NULL; @@ -398,7 +404,7 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, ChannelDriver *driver, c int i; /* get the py expression to be evaluated */ - expr = driver->expression; + expr = driver_orig->expression; if (expr[0] == '\0') return 0.0f; @@ -438,50 +444,50 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, ChannelDriver *driver, c /* update global namespace */ bpy_pydriver_namespace_update_frame(evaltime); - if (driver->flag & DRIVER_FLAG_USE_SELF) { + if (driver_orig->flag & DRIVER_FLAG_USE_SELF) { bpy_pydriver_namespace_update_self(anim_rna); } else { bpy_pydriver_namespace_clear_self(); } - if (driver->expr_comp == NULL) - driver->flag |= DRIVER_FLAG_RECOMPILE; + if (driver_orig->expr_comp == NULL) + driver_orig->flag |= DRIVER_FLAG_RECOMPILE; /* compile the expression first if it hasn't been compiled or needs to be rebuilt */ - if (driver->flag & DRIVER_FLAG_RECOMPILE) { - Py_XDECREF(driver->expr_comp); - driver->expr_comp = PyTuple_New(2); + if (driver_orig->flag & DRIVER_FLAG_RECOMPILE) { + Py_XDECREF(driver_orig->expr_comp); + driver_orig->expr_comp = PyTuple_New(2); expr_code = Py_CompileString(expr, "<bpy driver>", Py_eval_input); - PyTuple_SET_ITEM(((PyObject *)driver->expr_comp), 0, expr_code); + PyTuple_SET_ITEM(((PyObject *)driver_orig->expr_comp), 0, expr_code); - driver->flag &= ~DRIVER_FLAG_RECOMPILE; - driver->flag |= DRIVER_FLAG_RENAMEVAR; /* maybe this can be removed but for now best keep until were sure */ + driver_orig->flag &= ~DRIVER_FLAG_RECOMPILE; + driver_orig->flag |= DRIVER_FLAG_RENAMEVAR; /* maybe this can be removed but for now best keep until were sure */ #ifdef USE_BYTECODE_WHITELIST is_recompile = true; #endif } else { - expr_code = PyTuple_GET_ITEM(((PyObject *)driver->expr_comp), 0); + expr_code = PyTuple_GET_ITEM(((PyObject *)driver_orig->expr_comp), 0); } - if (driver->flag & DRIVER_FLAG_RENAMEVAR) { + if (driver_orig->flag & DRIVER_FLAG_RENAMEVAR) { /* may not be set */ - expr_vars = PyTuple_GET_ITEM(((PyObject *)driver->expr_comp), 1); + expr_vars = PyTuple_GET_ITEM(((PyObject *)driver_orig->expr_comp), 1); Py_XDECREF(expr_vars); - expr_vars = PyTuple_New(BLI_listbase_count(&driver->variables)); - PyTuple_SET_ITEM(((PyObject *)driver->expr_comp), 1, expr_vars); + expr_vars = PyTuple_New(BLI_listbase_count(&driver_orig->variables)); + PyTuple_SET_ITEM(((PyObject *)driver_orig->expr_comp), 1, expr_vars); - for (dvar = driver->variables.first, i = 0; dvar; dvar = dvar->next) { + for (dvar = driver_orig->variables.first, i = 0; dvar; dvar = dvar->next) { PyTuple_SET_ITEM(expr_vars, i++, PyUnicode_FromString(dvar->name)); } - driver->flag &= ~DRIVER_FLAG_RENAMEVAR; + driver_orig->flag &= ~DRIVER_FLAG_RENAMEVAR; } else { - expr_vars = PyTuple_GET_ITEM(((PyObject *)driver->expr_comp), 1); + expr_vars = PyTuple_GET_ITEM(((PyObject *)driver_orig->expr_comp), 1); } /* add target values to a dict that will be used as '__locals__' dict */ @@ -543,7 +549,7 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, ChannelDriver *driver, c } #ifdef USE_BYTECODE_WHITELIST - if (is_recompile) { + if (is_recompile && expr_code) { if (!(G.f & G_SCRIPT_AUTOEXEC)) { if (!bpy_driver_secure_bytecode_validate( expr_code, (PyObject *[]){ @@ -555,7 +561,7 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, ChannelDriver *driver, c { Py_DECREF(expr_code); expr_code = NULL; - PyTuple_SET_ITEM(((PyObject *)driver->expr_comp), 0, NULL); + PyTuple_SET_ITEM(((PyObject *)driver_orig->expr_comp), 0, NULL); } } } @@ -595,7 +601,7 @@ float BPY_driver_exec(struct PathResolvedRNA *anim_rna, ChannelDriver *driver, c return (float)result; } else { - fprintf(stderr, "\tBPY_driver_eval() - driver '%s' evaluates to '%f'\n", dvar->name, result); + fprintf(stderr, "\tBPY_driver_eval() - driver '%s' evaluates to '%f'\n", driver->expression, result); return 0.0f; } } diff --git a/source/blender/python/intern/bpy_driver.h b/source/blender/python/intern/bpy_driver.h index 017a6fe89c5..971cbdf901f 100644 --- a/source/blender/python/intern/bpy_driver.h +++ b/source/blender/python/intern/bpy_driver.h @@ -27,14 +27,7 @@ #ifndef __BPY_DRIVER_H__ #define __BPY_DRIVER_H__ -struct ChannelDriver; -struct PathResolvedRNA; - int bpy_pydriver_create_dict(void); extern PyObject *bpy_pydriver_Dict; -/* externals */ -float BPY_driver_exec(struct PathResolvedRNA *anim_rna, struct ChannelDriver *driver, const float evaltime); -void BPY_driver_reset(void); - #endif /* __BPY_DRIVER_H__ */ diff --git a/source/blender/python/intern/bpy_gizmo_wrap.c b/source/blender/python/intern/bpy_gizmo_wrap.c new file mode 100644 index 00000000000..411822ee4da --- /dev/null +++ b/source/blender/python/intern/bpy_gizmo_wrap.c @@ -0,0 +1,228 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/python/intern/bpy_gizmo_wrap.c + * \ingroup pythonintern + * + * This file is so Python can define widget-group's that C can call into. + * The generic callback functions for Python widget-group are defines in + * 'rna_wm.c', some calling into functions here to do python specific + * functionality. + * + * \note This follows 'bpy_operator_wrap.c' very closely. + * Keep in sync unless there is good reason not to! + */ + +#include <Python.h> + +#include "BLI_utildefines.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "bpy_rna.h" +#include "bpy_intern_string.h" +#include "bpy_gizmo_wrap.h" /* own include */ + +/* we may want to add, but not now */ + +/* -------------------------------------------------------------------- */ + +/** \name Gizmo + * \{ */ + + +static bool bpy_gizmotype_target_property_def( + wmGizmoType *gzt, PyObject *item) +{ + /* Note: names based on 'rna_rna.c' */ + PyObject *empty_tuple = PyTuple_New(0); + + struct { + char *id; + char *type_id; int type; + int array_length; + } params = { + .id = NULL, /* not optional */ + .type = PROP_FLOAT, + .type_id = NULL, + .array_length = 1, + }; + + static const char * const _keywords[] = {"id", "type", "array_length", NULL}; + static _PyArg_Parser _parser = {"|$ssi:register_class", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast( + empty_tuple, item, + &_parser, + ¶ms.id, + ¶ms.type_id, + ¶ms.array_length)) + { + goto fail; + } + + if (params.id == NULL) { + PyErr_SetString(PyExc_ValueError, "'id' argument not given"); + goto fail; + } + + if ((params.type_id != NULL) && + pyrna_enum_value_from_id( + rna_enum_property_type_items, params.type_id, ¶ms.type, "'type' enum value") == -1) + { + goto fail; + } + else { + params.type = rna_enum_property_type_items[params.type].value; + } + + if ((params.array_length < 1 || params.array_length > RNA_MAX_ARRAY_LENGTH)) { + PyErr_SetString(PyExc_ValueError, "'array_length' out of range"); + goto fail; + } + + WM_gizmotype_target_property_def(gzt, params.id, params.type, params.array_length); + Py_DECREF(empty_tuple); + return true; + +fail: + Py_DECREF(empty_tuple); + return false; +} + +static void gizmo_properties_init(wmGizmoType *gzt) +{ + PyTypeObject *py_class = gzt->ext.data; + RNA_struct_blender_type_set(gzt->ext.srna, gzt); + + /* only call this so pyrna_deferred_register_class gives a useful error + * WM_operatortype_append_ptr will call RNA_def_struct_identifier + * later */ + RNA_def_struct_identifier_no_struct_map(gzt->srna, gzt->idname); + + if (pyrna_deferred_register_class(gzt->srna, py_class) != 0) { + PyErr_Print(); /* failed to register operator props */ + PyErr_Clear(); + } + + /* Extract target property definitions from 'bl_target_properties' */ + { + /* picky developers will notice that 'bl_targets' won't work with inheritance + * get direct from the dict to avoid raising a load of attribute errors (yes this isnt ideal) - campbell */ + PyObject *py_class_dict = py_class->tp_dict; + PyObject *bl_target_properties = PyDict_GetItem(py_class_dict, bpy_intern_str_bl_target_properties); + + /* Some widgets may only exist to activate operators. */ + if (bl_target_properties != NULL) { + PyObject *bl_target_properties_fast; + if (!(bl_target_properties_fast = PySequence_Fast( + bl_target_properties, "bl_target_properties sequence"))) + { + /* PySequence_Fast sets the error */ + PyErr_Print(); + PyErr_Clear(); + return; + } + + const uint items_len = PySequence_Fast_GET_SIZE(bl_target_properties_fast); + PyObject **items = PySequence_Fast_ITEMS(bl_target_properties_fast); + + for (uint i = 0; i < items_len; i++) { + if (!bpy_gizmotype_target_property_def(gzt, items[i])) { + PyErr_Print(); + PyErr_Clear(); + break; + } + } + + Py_DECREF(bl_target_properties_fast); + } + } +} + +void BPY_RNA_gizmo_wrapper(wmGizmoType *gzt, void *userdata) +{ + /* take care not to overwrite anything set in + * WM_gizmomaptype_group_link_ptr before opfunc() is called */ + StructRNA *srna = gzt->srna; + *gzt = *((wmGizmoType *)userdata); + gzt->srna = srna; /* restore */ + + /* don't do translations here yet */ +#if 0 + /* Use i18n context from ext.srna if possible (py gizmogroups). */ + if (gt->ext.srna) { + RNA_def_struct_translation_context(gt->srna, RNA_struct_translation_context(gt->ext.srna)); + } +#endif + + gzt->struct_size = sizeof(wmGizmo); + + gizmo_properties_init(gzt); +} + +/** \} */ + + +/* -------------------------------------------------------------------- */ + +/** \name Gizmo Group + * \{ */ + +static void gizmogroup_properties_init(wmGizmoGroupType *gzgt) +{ + PyTypeObject *py_class = gzgt->ext.data; + RNA_struct_blender_type_set(gzgt->ext.srna, gzgt); + + /* only call this so pyrna_deferred_register_class gives a useful error + * WM_operatortype_append_ptr will call RNA_def_struct_identifier + * later */ + RNA_def_struct_identifier_no_struct_map(gzgt->srna, gzgt->idname); + + if (pyrna_deferred_register_class(gzgt->srna, py_class) != 0) { + PyErr_Print(); /* failed to register operator props */ + PyErr_Clear(); + } +} + +void BPY_RNA_gizmogroup_wrapper(wmGizmoGroupType *gzgt, void *userdata) +{ + /* take care not to overwrite anything set in + * WM_gizmomaptype_group_link_ptr before opfunc() is called */ + StructRNA *srna = gzgt->srna; + *gzgt = *((wmGizmoGroupType *)userdata); + gzgt->srna = srna; /* restore */ + + /* don't do translations here yet */ +#if 0 + /* Use i18n context from ext.srna if possible (py gizmogroups). */ + if (gzgt->ext.srna) { + RNA_def_struct_translation_context(gzgt->srna, RNA_struct_translation_context(gzgt->ext.srna)); + } +#endif + + gizmogroup_properties_init(gzgt); +} + +/** \} */ diff --git a/source/blender/python/intern/bpy_gizmo_wrap.h b/source/blender/python/intern/bpy_gizmo_wrap.h new file mode 100644 index 00000000000..96f15312a4e --- /dev/null +++ b/source/blender/python/intern/bpy_gizmo_wrap.h @@ -0,0 +1,35 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/python/intern/bpy_gizmo_wrap.h + * \ingroup pythonintern + */ + +#ifndef __BPY_GIZMO_WRAP_H__ +#define __BPY_GIZMO_WRAP_H__ + +struct wmGizmoType; +struct wmGizmoGroupType; + +/* exposed to rna/wm api */ +void BPY_RNA_gizmo_wrapper(struct wmGizmoType *gzt, void *userdata); +void BPY_RNA_gizmogroup_wrapper(struct wmGizmoGroupType *gzgt, void *userdata); + +#endif /* __BPY_GIZMO_WRAP_H__ */ diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c index 1ae3106aa76..e17e7562f2a 100644 --- a/source/blender/python/intern/bpy_interface.c +++ b/source/blender/python/intern/bpy_interface.c @@ -46,7 +46,6 @@ #include "RNA_types.h" #include "bpy.h" -#include "gpu.h" #include "bpy_rna.h" #include "bpy_path.h" #include "bpy_capi_utils.h" @@ -59,9 +58,9 @@ #include "BKE_appdir.h" #include "BKE_context.h" -#include "BKE_text.h" -#include "BKE_main.h" #include "BKE_global.h" /* only for script checking */ +#include "BKE_main.h" +#include "BKE_text.h" #include "CCL_api.h" @@ -75,6 +74,7 @@ #include "../generic/blf_py_api.h" #include "../generic/idprop_py_api.h" #include "../generic/imbuf_py_api.h" +#include "../gpu/gpu_py_api.h" #include "../bmesh/bmesh_py_api.h" #include "../mathutils/mathutils.h" @@ -233,7 +233,7 @@ static struct _inittab bpy_internal_modules[] = { #ifdef WITH_CYCLES {"_cycles", CCL_initPython}, #endif - {"gpu", GPU_initPython}, + {"gpu", BPyInit_gpu}, {"idprop", BPyInit_idprop}, {NULL, NULL} }; @@ -537,7 +537,8 @@ static bool python_script_exec( if (py_dict) { #ifdef PYMODULE_CLEAR_WORKAROUND - PyModuleObject *mmod = (PyModuleObject *)PyDict_GetItemString(PyImport_GetModuleDict(), "__main__"); + PyModuleObject *mmod = (PyModuleObject *)PyDict_GetItem( + PyImport_GetModuleDict(), bpy_intern_str___main__); PyObject *dict_back = mmod->md_dict; /* freeing the module will clear the namespace, * gives problems running classes defined in this namespace being used later. */ diff --git a/source/blender/python/intern/bpy_intern_string.c b/source/blender/python/intern/bpy_intern_string.c index ac0100fa75d..41276963fc9 100644 --- a/source/blender/python/intern/bpy_intern_string.c +++ b/source/blender/python/intern/bpy_intern_string.c @@ -34,21 +34,24 @@ #include "BLI_utildefines.h" -static PyObject *bpy_intern_str_arr[13]; +static PyObject *bpy_intern_str_arr[16]; -PyObject *bpy_intern_str_register; -PyObject *bpy_intern_str_unregister; -PyObject *bpy_intern_str_bl_rna; +PyObject *bpy_intern_str___annotations__; +PyObject *bpy_intern_str___doc__; +PyObject *bpy_intern_str___main__; +PyObject *bpy_intern_str___module__; +PyObject *bpy_intern_str___name__; +PyObject *bpy_intern_str___slots__; +PyObject *bpy_intern_str_attr; PyObject *bpy_intern_str_bl_property; +PyObject *bpy_intern_str_bl_rna; +PyObject *bpy_intern_str_bl_target_properties; PyObject *bpy_intern_str_bpy_types; -PyObject *bpy_intern_str_order; -PyObject *bpy_intern_str_attr; -PyObject *bpy_intern_str_self; PyObject *bpy_intern_str_frame; -PyObject *bpy_intern_str___slots__; -PyObject *bpy_intern_str___name__; -PyObject *bpy_intern_str___doc__; -PyObject *bpy_intern_str___module__; +PyObject *bpy_intern_str_properties; +PyObject *bpy_intern_str_register; +PyObject *bpy_intern_str_self; +PyObject *bpy_intern_str_unregister; void bpy_intern_string_init(void) { @@ -57,19 +60,22 @@ void bpy_intern_string_init(void) #define BPY_INTERN_STR(var, str) \ { var = bpy_intern_str_arr[i++] = PyUnicode_FromString(str); } (void)0 - BPY_INTERN_STR(bpy_intern_str_register, "register"); - BPY_INTERN_STR(bpy_intern_str_unregister, "unregister"); - BPY_INTERN_STR(bpy_intern_str_bl_rna, "bl_rna"); + BPY_INTERN_STR(bpy_intern_str___annotations__, "__annotations__"); + BPY_INTERN_STR(bpy_intern_str___doc__, "__doc__"); + BPY_INTERN_STR(bpy_intern_str___main__, "__main__"); + BPY_INTERN_STR(bpy_intern_str___module__, "__module__"); + BPY_INTERN_STR(bpy_intern_str___name__, "__name__"); + BPY_INTERN_STR(bpy_intern_str___slots__, "__slots__"); + BPY_INTERN_STR(bpy_intern_str_attr, "attr"); BPY_INTERN_STR(bpy_intern_str_bl_property, "bl_property"); + BPY_INTERN_STR(bpy_intern_str_bl_rna, "bl_rna"); + BPY_INTERN_STR(bpy_intern_str_bl_target_properties, "bl_target_properties"); BPY_INTERN_STR(bpy_intern_str_bpy_types, "bpy.types"); - BPY_INTERN_STR(bpy_intern_str_order, "order"); - BPY_INTERN_STR(bpy_intern_str_attr, "attr"); - BPY_INTERN_STR(bpy_intern_str_self, "self"); BPY_INTERN_STR(bpy_intern_str_frame, "frame"); - BPY_INTERN_STR(bpy_intern_str___slots__, "__slots__"); - BPY_INTERN_STR(bpy_intern_str___name__, "__name__"); - BPY_INTERN_STR(bpy_intern_str___doc__, "__doc__"); - BPY_INTERN_STR(bpy_intern_str___module__, "__module__"); + BPY_INTERN_STR(bpy_intern_str_properties, "properties"); + BPY_INTERN_STR(bpy_intern_str_register, "register"); + BPY_INTERN_STR(bpy_intern_str_self, "self"); + BPY_INTERN_STR(bpy_intern_str_unregister, "unregister"); #undef BPY_INTERN_STR diff --git a/source/blender/python/intern/bpy_intern_string.h b/source/blender/python/intern/bpy_intern_string.h index 394e84d89bd..41cd58f9c3d 100644 --- a/source/blender/python/intern/bpy_intern_string.h +++ b/source/blender/python/intern/bpy_intern_string.h @@ -30,18 +30,21 @@ void bpy_intern_string_init(void); void bpy_intern_string_exit(void); -extern PyObject *bpy_intern_str_register; -extern PyObject *bpy_intern_str_unregister; -extern PyObject *bpy_intern_str_bl_rna; +extern PyObject *bpy_intern_str___annotations__; +extern PyObject *bpy_intern_str___doc__; +extern PyObject *bpy_intern_str___main__; +extern PyObject *bpy_intern_str___module__; +extern PyObject *bpy_intern_str___name__; +extern PyObject *bpy_intern_str___slots__; +extern PyObject *bpy_intern_str_attr; extern PyObject *bpy_intern_str_bl_property; +extern PyObject *bpy_intern_str_bl_rna; +extern PyObject *bpy_intern_str_bl_target_properties; extern PyObject *bpy_intern_str_bpy_types; -extern PyObject *bpy_intern_str_order; -extern PyObject *bpy_intern_str_attr; -extern PyObject *bpy_intern_str_self; extern PyObject *bpy_intern_str_frame; -extern PyObject *bpy_intern_str___slots__; -extern PyObject *bpy_intern_str___name__; -extern PyObject *bpy_intern_str___doc__; -extern PyObject *bpy_intern_str___module__; +extern PyObject *bpy_intern_str_properties; +extern PyObject *bpy_intern_str_register; +extern PyObject *bpy_intern_str_self; +extern PyObject *bpy_intern_str_unregister; #endif /* __BPY_INTERN_STRING_H__ */ diff --git a/source/blender/python/intern/bpy_library_load.c b/source/blender/python/intern/bpy_library_load.c index e66b4e834dc..5c64a65fb58 100644 --- a/source/blender/python/intern/bpy_library_load.c +++ b/source/blender/python/intern/bpy_library_load.c @@ -38,16 +38,16 @@ #include "BLI_linklist.h" #include "BLI_path_util.h" -#include "BLO_readfile.h" - -#include "BKE_main.h" -#include "BKE_library.h" +#include "BKE_context.h" #include "BKE_idcode.h" +#include "BKE_library.h" +#include "BKE_main.h" #include "BKE_report.h" -#include "BKE_context.h" #include "DNA_space_types.h" /* FILE_LINK, FILE_RELPATH */ +#include "BLO_readfile.h" + #include "bpy_capi_utils.h" #include "bpy_library.h" @@ -332,6 +332,7 @@ static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *UNUSED(args)) Main *bmain = CTX_data_main(BPy_GetContext()); Main *mainl = NULL; int err = 0; + const bool do_append = ((self->flag & FILE_LINK) == 0); BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true); @@ -341,7 +342,7 @@ static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *UNUSED(args)) { int idcode_step = 0, idcode; while ((idcode = BKE_idcode_iter_step(&idcode_step))) { - if (BKE_idcode_is_linkable(idcode)) { + if (BKE_idcode_is_linkable(idcode) && (idcode != ID_WS || do_append)) { const char *name_plural = BKE_idcode_to_name_plural(idcode); PyObject *ls = PyDict_GetItemString(self->dict, name_plural); // printf("lib: %s\n", name_plural); @@ -402,7 +403,7 @@ static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *UNUSED(args)) } else { Library *lib = mainl->curlib; /* newly added lib, assign before append end */ - BLO_library_link_end(mainl, &(self->blo_handle), self->flag, NULL, NULL); + BLO_library_link_end(mainl, &(self->blo_handle), self->flag, NULL, NULL, NULL, NULL); BLO_blendhandle_close(self->blo_handle); self->blo_handle = NULL; @@ -414,7 +415,7 @@ static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *UNUSED(args)) BKE_main_lib_objects_recalc_all(bmain); /* append, rather than linking */ - if ((self->flag & FILE_LINK) == 0) { + if (do_append) { BKE_library_make_local(bmain, lib, old_to_new_ids, true, false); } } @@ -427,7 +428,7 @@ static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *UNUSED(args)) { int idcode_step = 0, idcode; while ((idcode = BKE_idcode_iter_step(&idcode_step))) { - if (BKE_idcode_is_linkable(idcode)) { + if (BKE_idcode_is_linkable(idcode) && (idcode != ID_WS || do_append)) { const char *name_plural = BKE_idcode_to_name_plural(idcode); PyObject *ls = PyDict_GetItemString(self->dict, name_plural); if (ls && PyList_Check(ls)) { diff --git a/source/blender/python/intern/bpy_library_write.c b/source/blender/python/intern/bpy_library_write.c index 76b7ccf72fa..69dcfdb9455 100644 --- a/source/blender/python/intern/bpy_library_write.c +++ b/source/blender/python/intern/bpy_library_write.c @@ -34,7 +34,6 @@ #include "BLI_string.h" #include "BLI_path_util.h" -#include "BKE_library.h" #include "BKE_blendfile.h" #include "BKE_global.h" #include "BKE_main.h" @@ -135,7 +134,7 @@ static PyObject *bpy_lib_write(PyObject *UNUSED(self), PyObject *args, PyObject if (!pyrna_id_FromPyObject(key, &id_store->id)) { PyErr_Format(PyExc_TypeError, - "Expected and ID type, not %.200s", + "Expected an ID type, not %.200s", Py_TYPE(key)->tp_name); ret = NULL; goto finally; diff --git a/source/blender/python/intern/bpy_msgbus.c b/source/blender/python/intern/bpy_msgbus.c new file mode 100644 index 00000000000..101cc8b41a3 --- /dev/null +++ b/source/blender/python/intern/bpy_msgbus.c @@ -0,0 +1,399 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/python/intern/bpy_msgbus.c + * \ingroup pythonintern + * This file defines '_bpy_msgbus' module, exposed as 'bpy.msgbus'. + */ + +#include <Python.h> + +#include "../generic/python_utildefines.h" +#include "../generic/py_capi_utils.h" +#include "../mathutils/mathutils.h" + +#include "BLI_utildefines.h" + +#include "BKE_context.h" + +#include "WM_api.h" +#include "WM_types.h" +#include "WM_message.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "bpy_capi_utils.h" +#include "bpy_rna.h" +#include "bpy_intern_string.h" +#include "bpy_gizmo_wrap.h" /* own include */ + + +#include "bpy_msgbus.h" /* own include */ + + +/* -------------------------------------------------------------------- */ +/** \name Internal Utils + * \{ */ + +#define BPY_MSGBUS_RNA_MSGKEY_DOC \ +" :arg key: Represents the type of data being subscribed to\n" \ +"\n" \ +" Arguments include\n" \ +" - :class:`bpy.types.Property` instance.\n" \ +" - :class:`bpy.types.Struct` type.\n" \ +" - (:class:`bpy.types.Struct`, str) type and property name.\n" \ +" :type key: Muliple\n" + +/** + * There are multiple ways we can get RNA from Python, + * it's also possible to register a type instead of an instance. + * + * This function handles converting Python to RNA subscription information. + * + * \param py_sub: See #BPY_MSGBUS_RNA_MSGKEY_DOC for description. + * \param msg_key_params: Message key with all members zeroed out. + * \return -1 on failure, 0 on success. + */ +static int py_msgbus_rna_key_from_py( + PyObject *py_sub, + wmMsgParams_RNA *msg_key_params, + const char *error_prefix) +{ + + /* Allow common case, object rotation, location - etc. */ + if (BaseMathObject_CheckExact(py_sub)) { + BaseMathObject *py_sub_math = (BaseMathObject *)py_sub; + if (py_sub_math->cb_user == NULL) { + PyErr_Format( + PyExc_TypeError, + "%s: math argument has no owner", + error_prefix); + return -1; + } + py_sub = py_sub_math->cb_user; + /* Common case will use BPy_PropertyRNA_Check below. */ + } + + if (BPy_PropertyRNA_Check(py_sub)) { + BPy_PropertyRNA *data_prop = (BPy_PropertyRNA *)py_sub; + PYRNA_PROP_CHECK_INT(data_prop); + msg_key_params->ptr = data_prop->ptr; + msg_key_params->prop = data_prop->prop; + } + else if (BPy_StructRNA_Check(py_sub)) { + /* note, this isn't typically used since we don't edit structs directly. */ + BPy_StructRNA *data_srna = (BPy_StructRNA *)py_sub; + PYRNA_STRUCT_CHECK_INT(data_srna); + msg_key_params->ptr = data_srna->ptr; + } + /* TODO - property / type, not instance. */ + else if (PyType_Check(py_sub)) { + StructRNA *data_type = pyrna_struct_as_srna(py_sub, false, error_prefix); + if (data_type == NULL) { + return -1; + } + msg_key_params->ptr.type = data_type; + } + else if (PyTuple_CheckExact(py_sub)) { + if (PyTuple_GET_SIZE(py_sub) == 2) { + PyObject *data_type_py = PyTuple_GET_ITEM(py_sub, 0); + PyObject *data_prop_py = PyTuple_GET_ITEM(py_sub, 1); + StructRNA *data_type = pyrna_struct_as_srna(data_type_py, false, error_prefix); + if (data_type == NULL) { + return -1; + } + if (!PyUnicode_CheckExact(data_prop_py)) { + PyErr_Format( + PyExc_TypeError, + "%s: expected property to be a string", + error_prefix); + return -1; + } + PointerRNA data_type_ptr = { .type = data_type, }; + const char *data_prop_str = _PyUnicode_AsString(data_prop_py); + PropertyRNA *data_prop = RNA_struct_find_property(&data_type_ptr, data_prop_str); + + if (data_prop == NULL) { + PyErr_Format( + PyExc_TypeError, + "%s: struct %.200s does not contain property %.200s", + error_prefix, + RNA_struct_identifier(data_type), + data_prop_str); + return -1; + } + + msg_key_params->ptr.type = data_type; + msg_key_params->prop = data_prop; + } + else { + PyErr_Format( + PyExc_ValueError, + "%s: Expected a pair (type, property_id)", + error_prefix); + return -1; + } + } + return 0; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal Callbacks + * \{ */ + +#define BPY_MSGBUS_USER_DATA_LEN 2 + +/* Follow wmMsgNotifyFn spec */ +static void bpy_msgbus_notify( + bContext *C, wmMsgSubscribeKey *UNUSED(msg_key), wmMsgSubscribeValue *msg_val) +{ + PyGILState_STATE gilstate; + bpy_context_set(C, &gilstate); + + PyObject *user_data = msg_val->user_data; + BLI_assert(PyTuple_GET_SIZE(user_data) == BPY_MSGBUS_USER_DATA_LEN); + + PyObject *callback_args = PyTuple_GET_ITEM(user_data, 0); + PyObject *callback_notify = PyTuple_GET_ITEM(user_data, 1); + + const bool is_write_ok = pyrna_write_check(); + if (!is_write_ok) { + pyrna_write_set(true); + } + + PyObject *ret = PyObject_CallObject(callback_notify, callback_args); + + if (ret == NULL) { + PyC_Err_PrintWithFunc(callback_notify); + } + else { + if (ret != Py_None) { + PyErr_SetString(PyExc_ValueError, "the return value must be None"); + PyC_Err_PrintWithFunc(callback_notify); + } + Py_DECREF(ret); + } + + bpy_context_clear(C, &gilstate); + + if (!is_write_ok) { + pyrna_write_set(false); + } +} + +/* Follow wmMsgSubscribeValueFreeDataFn spec */ +static void bpy_msgbus_subscribe_value_free_data( + struct wmMsgSubscribeKey *UNUSED(msg_key), struct wmMsgSubscribeValue *msg_val) +{ + PyGILState_STATE gilstate = PyGILState_Ensure(); + Py_DECREF(msg_val->owner); + Py_DECREF(msg_val->user_data); + PyGILState_Release(gilstate); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Public Message Bus API + * \{ */ + +PyDoc_STRVAR(bpy_msgbus_subscribe_rna_doc, +".. function:: subscribe_rna(data, owner, args, notify)\n" +"\n" +BPY_MSGBUS_RNA_MSGKEY_DOC +" :arg owner: Handle for this subscription (compared by identity).\n" +" :type owner: Any type.\n" +"\n" +" Returns a new vector int property definition.\n" +); +static PyObject *bpy_msgbus_subscribe_rna(PyObject *UNUSED(self), PyObject *args, PyObject *kw) +{ + const char *error_prefix = "subscribe_rna"; + PyObject *py_sub = NULL; + PyObject *py_owner = NULL; + PyObject *callback_args = NULL; + PyObject *callback_notify = NULL; + + enum { + IS_PERSISTENT = (1 << 0), + }; + PyObject *py_options = NULL; + EnumPropertyItem py_options_enum[] = { + {IS_PERSISTENT, "PERSISTENT", 0, ""}, + {0, NULL, 0, NULL, NULL} + }; + int options = 0; + + static const char *_keywords[] = { + "key", + "owner", + "args", + "notify", + "options", + NULL, + }; + static _PyArg_Parser _parser = {"$OOO!OO!:subscribe_rna", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast( + args, kw, &_parser, + &py_sub, &py_owner, + &PyTuple_Type, &callback_args, + &callback_notify, + &PySet_Type, &py_options)) + { + return NULL; + } + + if (py_options && + (pyrna_set_to_enum_bitfield(py_options_enum, py_options, &options, error_prefix)) == -1) + { + return NULL; + } + + /* Note: we may want to have a way to pass this in. */ + bContext *C = (bContext *)BPy_GetContext(); + struct wmMsgBus *mbus = CTX_wm_message_bus(C); + wmMsgParams_RNA msg_key_params = {{{0}}}; + + wmMsgSubscribeValue msg_val_params = {0}; + + if (py_msgbus_rna_key_from_py(py_sub, &msg_key_params, error_prefix) == -1) { + return NULL; + } + + if (!PyFunction_Check(callback_notify)) { + PyErr_Format( + PyExc_TypeError, + "notify expects a function, found %.200s", + Py_TYPE(callback_notify)->tp_name); + return NULL; + } + + if (options != 0) { + if (options & IS_PERSISTENT) { + msg_val_params.is_persistent = true; + } + } + + /* owner can be anything. */ + { + msg_val_params.owner = py_owner; + Py_INCREF(py_owner); + } + + { + PyObject *user_data = PyTuple_New(2); + PyTuple_SET_ITEMS( + user_data, + Py_INCREF_RET(callback_args), + Py_INCREF_RET(callback_notify)); + msg_val_params.user_data = user_data; + } + + msg_val_params.notify = bpy_msgbus_notify; + msg_val_params.free_data = bpy_msgbus_subscribe_value_free_data; + + WM_msg_subscribe_rna_params(mbus, &msg_key_params, &msg_val_params, __func__); + + WM_msg_dump(mbus, __func__); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(bpy_msgbus_publish_rna_doc, +".. function:: publish_rna(data, owner, args, notify)\n" +"\n" +BPY_MSGBUS_RNA_MSGKEY_DOC +"\n" +" Notify subscribers of changes to this property\n" +" (this typically doesn't need to be called explicitly since changes will automatically publish updates).\n" +" In some cases it may be useful to publish changes explicitly using more general keys.\n" +); +static PyObject *bpy_msgbus_publish_rna(PyObject *UNUSED(self), PyObject *args, PyObject *kw) +{ + const char *error_prefix = "publish_rna"; + PyObject *py_sub = NULL; + + static const char *_keywords[] = { + "key", + NULL, + }; + static _PyArg_Parser _parser = {"$O:publish_rna", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast( + args, kw, &_parser, + &py_sub)) + { + return NULL; + } + + /* Note: we may want to have a way to pass this in. */ + bContext *C = (bContext *)BPy_GetContext(); + struct wmMsgBus *mbus = CTX_wm_message_bus(C); + wmMsgParams_RNA msg_key_params = {{{0}}}; + + if (py_msgbus_rna_key_from_py(py_sub, &msg_key_params, error_prefix) == -1) { + return NULL; + } + + WM_msg_publish_rna_params(mbus, &msg_key_params); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(bpy_msgbus_clear_by_owner_doc, +".. function:: clear_by_owner(owner)\n" +"\n" +" Clear all subscribers using this owner.\n" +); +static PyObject *bpy_msgbus_clear_by_owner(PyObject *UNUSED(self), PyObject *py_owner) +{ + bContext *C = (bContext *)BPy_GetContext(); + struct wmMsgBus *mbus = CTX_wm_message_bus(C); + WM_msgbus_clear_by_owner(mbus, py_owner); + Py_RETURN_NONE; +} + +static struct PyMethodDef BPy_msgbus_methods[] = { + {"subscribe_rna", (PyCFunction)bpy_msgbus_subscribe_rna, METH_VARARGS | METH_KEYWORDS, bpy_msgbus_subscribe_rna_doc}, + {"publish_rna", (PyCFunction)bpy_msgbus_publish_rna, METH_VARARGS | METH_KEYWORDS, bpy_msgbus_publish_rna_doc}, + {"clear_by_owner", (PyCFunction)bpy_msgbus_clear_by_owner, METH_O, bpy_msgbus_clear_by_owner_doc}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef _bpy_msgbus_def = { + PyModuleDef_HEAD_INIT, + .m_name = "msgbus", + .m_methods = BPy_msgbus_methods, +}; + + +PyObject *BPY_msgbus_module(void) +{ + PyObject *submodule; + + submodule = PyModule_Create(&_bpy_msgbus_def); + + return submodule; +} + +/** \} */ diff --git a/source/blender/python/intern/bpy_msgbus.h b/source/blender/python/intern/bpy_msgbus.h new file mode 100644 index 00000000000..97b20e9b926 --- /dev/null +++ b/source/blender/python/intern/bpy_msgbus.h @@ -0,0 +1,30 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/python/intern/bpy_msgbus.h + * \ingroup pythonintern + */ + +#ifndef __BPY_MSGBUS_H__ +#define __BPY_MSGBUS_H__ + +PyObject *BPY_msgbus_module(void); + +#endif /* __BPY_MSGBUS_H__ */ diff --git a/source/blender/python/intern/bpy_props.c b/source/blender/python/intern/bpy_props.c index 0db2fc189c1..b7242680dca 100644 --- a/source/blender/python/intern/bpy_props.c +++ b/source/blender/python/intern/bpy_props.c @@ -1905,7 +1905,7 @@ static void bpy_prop_callback_assign_enum(struct PropertyRNA *prop, PyObject *ge #define BPY_PROPDEF_UNIT_DOC \ " :arg unit: Enumerator in ['NONE', 'LENGTH', 'AREA', 'VOLUME', 'ROTATION', 'TIME', 'VELOCITY', 'ACCELERATION', 'MASS', 'CAMERA'].\n" \ -" :type unit: string\n" \ +" :type unit: string\n" \ #define BPY_PROPDEF_NUM_MIN_DOC \ " :arg min: Hard minimum, trying to assign a value below will silently assign this minimum instead.\n" \ @@ -3083,9 +3083,10 @@ PyObject *BPy_CollectionProperty(PyObject *self, PyObject *args, PyObject *kw) return NULL; if (!RNA_struct_is_a(ptype, &RNA_PropertyGroup)) { - PyErr_Format(PyExc_TypeError, - "CollectionProperty(...) expected an RNA type derived from %.200s", - RNA_struct_ui_name(&RNA_ID), RNA_struct_ui_name(&RNA_PropertyGroup)); + PyErr_Format( + PyExc_TypeError, + "CollectionProperty(...) expected an RNA type derived from %.200s", + RNA_struct_ui_name(&RNA_ID), RNA_struct_ui_name(&RNA_PropertyGroup)); return NULL; } @@ -3203,11 +3204,6 @@ PyObject *BPY_rna_props(void) submodule = PyModule_Create(&props_module); PyDict_SetItemString(PyImport_GetModuleDict(), props_module.m_name, submodule); - /* INCREF since its its assumed that all these functions return the - * module with a new ref like PyDict_New, since they are passed to - * PyModule_AddObject which steals a ref */ - Py_INCREF(submodule); - /* api needs the PyObjects internally */ submodule_dict = PyModule_GetDict(submodule); diff --git a/source/blender/python/intern/bpy_props.h b/source/blender/python/intern/bpy_props.h index 614c1b4b708..fa2594f94d2 100644 --- a/source/blender/python/intern/bpy_props.h +++ b/source/blender/python/intern/bpy_props.h @@ -34,6 +34,6 @@ PyObject *BPy_PointerProperty(PyObject *self, PyObject *args, PyObject *kw); PyObject *BPy_CollectionProperty(PyObject *self, PyObject *args, PyObject *kw); StructRNA *pointer_type_from_py(PyObject *value, const char *error_prefix); -#define PYRNA_STACK_ARRAY 32 +#define PYRNA_STACK_ARRAY RNA_STACK_ARRAY #endif diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c index b36a6e06651..f25f007033f 100644 --- a/source/blender/python/intern/bpy_rna.c +++ b/source/blender/python/intern/bpy_rna.c @@ -315,7 +315,7 @@ static bool rna_id_write_error(PointerRNA *ptr, PyObject *key) ID *id = ptr->id.data; if (id) { const short idcode = GS(id->name); - if (!ELEM(idcode, ID_WM, ID_SCR)) { /* may need more added here */ + if (!ELEM(idcode, ID_WM, ID_SCR, ID_WS)) { /* may need more added here */ const char *idtype = BKE_idcode_to_name(idcode); const char *pyname; if (key && PyUnicode_Check(key)) pyname = _PyUnicode_AsString(key); @@ -1401,53 +1401,39 @@ static PyObject *pyrna_enum_to_py(PointerRNA *ptr, PropertyRNA *prop, int val) ret = PyUnicode_FromString(identifier); } else { + /* Static, no need to free. */ const EnumPropertyItem *enum_item; - bool free; + bool free_dummy; + RNA_property_enum_items_ex(NULL, ptr, prop, true, &enum_item, NULL, &free_dummy); + BLI_assert(!free_dummy); - /* don't throw error here, can't trust blender 100% to give the - * right values, python code should not generate error for that */ - RNA_property_enum_items(BPy_GetContext(), ptr, prop, &enum_item, NULL, &free); - if (enum_item && enum_item->identifier) { - ret = PyUnicode_FromString(enum_item->identifier); - } - else { - if (free) { - MEM_freeN((void *)enum_item); - } - RNA_property_enum_items(NULL, ptr, prop, &enum_item, NULL, &free); - - /* Do not print warning in case of DummyRNA_NULL_items, this one will never match any value... */ - if (enum_item != DummyRNA_NULL_items) { - const char *ptr_name = RNA_struct_name_get_alloc(ptr, NULL, 0, NULL); + /* Do not print warning in case of DummyRNA_NULL_items, this one will never match any value... */ + if (enum_item != DummyRNA_NULL_items) { + const char *ptr_name = RNA_struct_name_get_alloc(ptr, NULL, 0, NULL); - /* prefer not fail silently in case of api errors, maybe disable it later */ - CLOG_WARN(BPY_LOG_RNA, - "current value '%d' " - "matches no enum in '%s', '%s', '%s'", - val, RNA_struct_identifier(ptr->type), - ptr_name, RNA_property_identifier(prop)); + /* prefer not fail silently in case of api errors, maybe disable it later */ + CLOG_WARN(BPY_LOG_RNA, + "current value '%d' " + "matches no enum in '%s', '%s', '%s'", + val, RNA_struct_identifier(ptr->type), + ptr_name, RNA_property_identifier(prop)); #if 0 /* gives python decoding errors while generating docs :( */ - char error_str[256]; - BLI_snprintf(error_str, sizeof(error_str), - "RNA Warning: Current value \"%d\" " - "matches no enum in '%s', '%s', '%s'", - val, RNA_struct_identifier(ptr->type), - ptr_name, RNA_property_identifier(prop)); - - PyErr_Warn(PyExc_RuntimeWarning, error_str); + char error_str[256]; + BLI_snprintf(error_str, sizeof(error_str), + "RNA Warning: Current value \"%d\" " + "matches no enum in '%s', '%s', '%s'", + val, RNA_struct_identifier(ptr->type), + ptr_name, RNA_property_identifier(prop)); + + PyErr_Warn(PyExc_RuntimeWarning, error_str); #endif - if (ptr_name) - MEM_freeN((void *)ptr_name); - } - - ret = PyUnicode_FromString(""); + if (ptr_name) + MEM_freeN((void *)ptr_name); } - if (free) { - MEM_freeN((void *)enum_item); - } + ret = PyUnicode_FromString(""); #if 0 PyErr_Format(PyExc_AttributeError, "RNA Error: Current value \"%d\" matches no enum", val); @@ -1729,7 +1715,25 @@ static int pyrna_py_to_prop( const int subtype = RNA_property_subtype(prop); const char *param; - if (subtype == PROP_BYTESTRING) { + if (value == Py_None) { + if ((RNA_property_flag(prop) & PROP_NEVER_NULL) == 0) { + if (data) { + *((char **)data) = (char *)NULL; + } + else { + RNA_property_string_set(ptr, prop, NULL); + } + } + else { + PyC_Err_Format_Prefix( + PyExc_TypeError, + "%.200s %.200s.%.200s doesn't support None from string types", + error_prefix, RNA_struct_identifier(ptr->type), + RNA_property_identifier(prop)); + return -1; + } + } + else if (subtype == PROP_BYTESTRING) { /* Byte String */ @@ -1849,19 +1853,28 @@ static int pyrna_py_to_prop( * class mixing if this causes problems in the future it should be removed. */ if ((ptr_type == &RNA_AnyType) && - (BPy_StructRNA_Check(value)) && - (RNA_struct_is_a(((BPy_StructRNA *)value)->ptr.type, &RNA_Operator))) + (BPy_StructRNA_Check(value))) { - value = PyObject_GetAttrString(value, "properties"); - value_new = value; + const StructRNA *base_type = + RNA_struct_base_child_of(((const BPy_StructRNA *)value)->ptr.type, NULL); + if (ELEM(base_type, &RNA_Operator, &RNA_Gizmo)) { + value = PyObject_GetAttr(value, bpy_intern_str_properties); + value_new = value; + } } - - /* if property is an OperatorProperties pointer and value is a map, + /* if property is an OperatorProperties/GizmoProperties pointer and value is a map, * forward back to pyrna_pydict_to_props */ - if (RNA_struct_is_a(ptr_type, &RNA_OperatorProperties) && PyDict_Check(value)) { - PointerRNA opptr = RNA_property_pointer_get(ptr, prop); - return pyrna_pydict_to_props(&opptr, value, false, error_prefix); + if (PyDict_Check(value)) { + const StructRNA *base_type = RNA_struct_base_child_of(ptr_type, NULL); + if (base_type == &RNA_OperatorProperties) { + PointerRNA opptr = RNA_property_pointer_get(ptr, prop); + return pyrna_pydict_to_props(&opptr, value, false, error_prefix); + } + else if (base_type == &RNA_GizmoProperties) { + PointerRNA opptr = RNA_property_pointer_get(ptr, prop); + return pyrna_pydict_to_props(&opptr, value, false, error_prefix); + } } /* another exception, allow to pass a collection as an RNA property */ @@ -3500,6 +3513,68 @@ static PyObject *pyrna_struct_is_property_readonly(BPy_StructRNA *self, PyObject return PyBool_FromLong(!RNA_property_editable(&self->ptr, prop)); } + +PyDoc_STRVAR(pyrna_struct_is_property_overridable_static_doc, +".. method:: is_property_overridable_static(property)\n" +"\n" +" Check if a property is statically overridable.\n" +"\n" +" :return: True when the property is statically overridable.\n" +" :rtype: boolean\n" +); +static PyObject *pyrna_struct_is_property_overridable_static(BPy_StructRNA *self, PyObject *args) +{ + PropertyRNA *prop; + const char *name; + + PYRNA_STRUCT_CHECK_OBJ(self); + + if (!PyArg_ParseTuple(args, "s:is_property_overridable_static", &name)) { + return NULL; + } + + if ((prop = RNA_struct_find_property(&self->ptr, name)) == NULL) { + PyErr_Format(PyExc_TypeError, + "%.200s.is_property_overridable_static(\"%.200s\") not found", + RNA_struct_identifier(self->ptr.type), name); + return NULL; + } + + return PyBool_FromLong((long)RNA_property_overridable_get(&self->ptr, prop)); +} + +PyDoc_STRVAR(pyrna_struct_property_overridable_static_set_doc, +".. method:: property_overridable_static_set(property)\n" +"\n" +" Define a property as statically overridable or not (only for custom properties!).\n" +"\n" +" :return: True when the overridable status of the property was successfully set.\n" +" :rtype: boolean\n" +); +static PyObject *pyrna_struct_property_overridable_static_set(BPy_StructRNA *self, PyObject *args) +{ + PropertyRNA *prop; + const char *name; + int is_overridable; + + PYRNA_STRUCT_CHECK_OBJ(self); + + if (!PyArg_ParseTuple(args, "sp:property_overridable_static_set", &name, &is_overridable)) { + return NULL; + } + + if ((prop = RNA_struct_find_property(&self->ptr, name)) == NULL) { + PyErr_Format(PyExc_TypeError, + "%.200s.property_overridable_static_set(\"%.200s\") not found", + RNA_struct_identifier(self->ptr.type), name); + return NULL; + } + + return PyBool_FromLong((long)RNA_property_overridable_static_set(&self->ptr, prop, (bool)is_overridable)); +} + + + PyDoc_STRVAR(pyrna_struct_path_resolve_doc, ".. method:: path_resolve(path, coerce=True)\n" "\n" @@ -5172,6 +5247,8 @@ static struct PyMethodDef pyrna_struct_methods[] = { {"property_unset", (PyCFunction)pyrna_struct_property_unset, METH_VARARGS, pyrna_struct_property_unset_doc}, {"is_property_hidden", (PyCFunction)pyrna_struct_is_property_hidden, METH_VARARGS, pyrna_struct_is_property_hidden_doc}, {"is_property_readonly", (PyCFunction)pyrna_struct_is_property_readonly, METH_VARARGS, pyrna_struct_is_property_readonly_doc}, + {"is_property_overridable_static", (PyCFunction)pyrna_struct_is_property_overridable_static, METH_VARARGS, pyrna_struct_is_property_overridable_static_doc}, + {"property_overridable_static_set", (PyCFunction)pyrna_struct_property_overridable_static_set, METH_VARARGS, pyrna_struct_property_overridable_static_set_doc}, {"path_resolve", (PyCFunction)pyrna_struct_path_resolve, METH_VARARGS, pyrna_struct_path_resolve_doc}, {"path_from_id", (PyCFunction)pyrna_struct_path_from_id, METH_VARARGS, pyrna_struct_path_from_id_doc}, {"type_recast", (PyCFunction)pyrna_struct_type_recast, METH_NOARGS, pyrna_struct_type_recast_doc}, @@ -5581,6 +5658,17 @@ static PyObject *pyrna_func_call(BPy_FunctionRNA *self, PyObject *args, PyObject item = NULL; if (i < pyargs_len) { + /* New in 2.8x, optional arguments must be keywords. */ + if (UNLIKELY((flag_parameter & PARM_REQUIRED) == 0)) { + PyErr_Format(PyExc_TypeError, + "%.200s.%.200s(): required parameter \"%.200s\" to be a keyword argument!", + RNA_struct_identifier(self_ptr->type), + RNA_function_identifier(self_func), + RNA_property_identifier(parm)); + err = -1; + break; + } + item = PyTuple_GET_ITEM(args, i); kw_arg = false; } @@ -7353,29 +7441,40 @@ static int deferred_register_prop(StructRNA *srna, PyObject *key, PyObject *item static int pyrna_deferred_register_props(StructRNA *srna, PyObject *class_dict) { + PyObject *annotations_dict; PyObject *item, *key; - PyObject *order; Py_ssize_t pos = 0; int ret = 0; /* in both cases PyDict_CheckExact(class_dict) will be true even * though Operators have a metaclass dict namespace */ + if ((annotations_dict = PyDict_GetItem(class_dict, bpy_intern_str___annotations__)) && + PyDict_CheckExact(annotations_dict)) + { + while (PyDict_Next(annotations_dict, &pos, &key, &item)) { + ret = deferred_register_prop(srna, key, item); - if ((order = PyDict_GetItem(class_dict, bpy_intern_str_order)) && PyList_CheckExact(order)) { - for (pos = 0; pos < PyList_GET_SIZE(order); pos++) { - key = PyList_GET_ITEM(order, pos); - /* however unlikely its possible - * fails in py 3.3 beta with __qualname__ */ - if ((item = PyDict_GetItem(class_dict, key))) { - ret = deferred_register_prop(srna, key, item); - if (ret != 0) { - break; - } + if (ret != 0) { + break; } } } - else { + + { + /* This block can be removed once 2.8x is released and annotations are in use. */ + bool has_warning = false; while (PyDict_Next(class_dict, &pos, &key, &item)) { + if (pyrna_is_deferred_prop(item)) { + if (!has_warning) { + printf("Warning: class %.200s " + "contains a properties which should be an annotation!\n", + RNA_struct_identifier(srna)); + PyC_LineSpit(); + has_warning = true; + } + printf(" make annotation: %.200s.%.200s\n", + RNA_struct_identifier(srna), _PyUnicode_AsString(key)); + } ret = deferred_register_prop(srna, key, item); if (ret != 0) @@ -7658,7 +7757,8 @@ static int bpy_class_call(bContext *C, PointerRNA *ptr, FunctionRNA *func, Param PyGILState_STATE gilstate; #ifdef USE_PEDANTIC_WRITE - const bool is_readonly_init = !RNA_struct_is_a(ptr->type, &RNA_Operator); + const bool is_readonly_init = !(RNA_struct_is_a(ptr->type, &RNA_Operator) || + RNA_struct_is_a(ptr->type, &RNA_Gizmo)); // const char *func_id = RNA_function_identifier(func); /* UNUSED */ /* testing, for correctness, not operator and not draw function */ const bool is_readonly = !(RNA_function_flag(func) & FUNC_ALLOW_WRITE); @@ -8316,6 +8416,43 @@ static PyObject *pyrna_unregister_class(PyObject *UNUSED(self), PyObject *py_cla Py_RETURN_NONE; } +/* Access to 'owner_id' internal global. */ + +static PyObject *pyrna_bl_owner_id_get(PyObject *UNUSED(self)) +{ + const char *name = RNA_struct_state_owner_get(); + if (name) { + return PyUnicode_FromString(name); + } + Py_RETURN_NONE; +} + +static PyObject *pyrna_bl_owner_id_set(PyObject *UNUSED(self), PyObject *value) +{ + const char *name; + if (value == Py_None) { + name = NULL; + } + else if (PyUnicode_Check(value)) { + name = _PyUnicode_AsString(value); + } + else { + PyErr_Format(PyExc_ValueError, + "owner_set(...): " + "expected None or a string, not '%.200s'", Py_TYPE(value)->tp_name); + return NULL; + } + RNA_struct_state_owner_set(name); + Py_RETURN_NONE; +} + +PyMethodDef meth_bpy_owner_id_get = { + "_bl_owner_id_get", (PyCFunction)pyrna_bl_owner_id_get, METH_NOARGS, NULL, +}; +PyMethodDef meth_bpy_owner_id_set = { + "_bl_owner_id_set", (PyCFunction)pyrna_bl_owner_id_set, METH_O, NULL, +}; + /* currently this is fairly limited, we would need to make some way to split up * pyrna_callback_classmethod_... if we want more than one callback per type */ typedef struct BPyRNA_CallBack { @@ -8323,9 +8460,46 @@ typedef struct BPyRNA_CallBack { StructRNA *bpy_srna; } PyRNA_CallBack; +PyDoc_STRVAR(pyrna_draw_handler_add_doc, +".. method:: draw_handler_add(callback, args, region_type, draw_type)\n" +"\n" +" Add a new draw handler to this space type.\n" +" It will be called every time the specified region in the space type will be drawn.\n" +" Note: All arguments are positional only for now.\n" +"\n" +" :param callback:\n" +" A function that will be called when the region is drawn.\n" +" It gets the specified arguments as input.\n" +" :type callback: function\n" +" :param args: Arguments that will be passed to the callback.\n" +" :type args: tuple\n" +" :param region_type: The region type the callback draws in; usually `'WINDOW'`. (:class:`bpy.types.Region.type`)\n" +" :type region_type: str\n" +" :param draw_type: Usually `POST_PIXEL` for 2D drawing and `POST_VIEW` for 3D drawing. In some cases `PRE_VIEW` can be used.\n" +" :type draw_type: str\n" +" :return: Handler that can be removed later on.\n" +" :rtype: object" +); + +PyDoc_STRVAR(pyrna_draw_handler_remove_doc, +".. method:: draw_handler_remove(handler, region_type)\n" +"\n" +" Remove a draw handler that was added previously.\n" +"\n" +" :param handler: The draw handler that should be removed.\n" +" :type handler: object\n" +" :param region_type: Region type the callback was added to.\n" +" :type region_type: str\n" +); + static struct BPyRNA_CallBack pyrna_cb_methods[] = { - {{"draw_handler_add", (PyCFunction)pyrna_callback_classmethod_add, METH_VARARGS | METH_STATIC, ""}, &RNA_Space}, - {{"draw_handler_remove", (PyCFunction)pyrna_callback_classmethod_remove, METH_VARARGS | METH_STATIC, ""}, &RNA_Space}, + {{"draw_handler_add", (PyCFunction)pyrna_callback_classmethod_add, + METH_VARARGS | METH_STATIC, pyrna_draw_handler_add_doc}, &RNA_Space}, + {{"draw_handler_remove", (PyCFunction)pyrna_callback_classmethod_remove, + METH_VARARGS | METH_STATIC, pyrna_draw_handler_remove_doc}, &RNA_Space}, + + {{"draw_cursor_add", (PyCFunction)pyrna_callback_classmethod_add, METH_VARARGS | METH_STATIC, ""}, &RNA_WindowManager}, + {{"draw_cursor_remove", (PyCFunction)pyrna_callback_classmethod_remove, METH_VARARGS | METH_STATIC, ""}, &RNA_WindowManager}, {{NULL, NULL, 0, NULL}, NULL} }; diff --git a/source/blender/python/intern/bpy_rna.h b/source/blender/python/intern/bpy_rna.h index f666294666e..32a63acde40 100644 --- a/source/blender/python/intern/bpy_rna.h +++ b/source/blender/python/intern/bpy_rna.h @@ -225,4 +225,8 @@ int pyrna_prop_validity_check(BPy_PropertyRNA *self); extern PyMethodDef meth_bpy_register_class; extern PyMethodDef meth_bpy_unregister_class; +/* bpy.utils._bl_owner_(get/set) */ +extern PyMethodDef meth_bpy_owner_id_set; +extern PyMethodDef meth_bpy_owner_id_get; + #endif diff --git a/source/blender/python/intern/bpy_rna_anim.c b/source/blender/python/intern/bpy_rna_anim.c index 73809e96560..2727b81e1cf 100644 --- a/source/blender/python/intern/bpy_rna_anim.c +++ b/source/blender/python/intern/bpy_rna_anim.c @@ -33,6 +33,7 @@ #include "BLI_utildefines.h" #include "BLI_string.h" +#include "BLI_string_utils.h" #include "DNA_scene_types.h" #include "DNA_anim_types.h" @@ -61,9 +62,9 @@ #include "../generic/python_utildefines.h" /* for keyframes and drivers */ -static int pyrna_struct_anim_args_parse( +static int pyrna_struct_anim_args_parse_ex( PointerRNA *ptr, const char *error_prefix, const char *path, - const char **path_full, int *index) + const char **r_path_full, int *r_index, bool *r_path_no_validate) { const bool is_idbase = RNA_struct_is_ID(ptr->type); PropertyRNA *prop; @@ -78,11 +79,11 @@ static int pyrna_struct_anim_args_parse( /* full paths can only be given from ID base */ if (is_idbase) { - int r_index = -1; - if (RNA_path_resolve_property_full(ptr, path, &r_ptr, &prop, &r_index) == false) { + int path_index = -1; + if (RNA_path_resolve_property_full(ptr, path, &r_ptr, &prop, &path_index) == false) { prop = NULL; } - else if (r_index != -1) { + else if (path_index != -1) { PyErr_Format(PyExc_ValueError, "%.200s path includes index, must be a separate argument", error_prefix, path); @@ -101,86 +102,167 @@ static int pyrna_struct_anim_args_parse( } if (prop == NULL) { + if (r_path_no_validate) { + *r_path_no_validate = true; + return -1; + } PyErr_Format(PyExc_TypeError, "%.200s property \"%s\" not found", error_prefix, path); return -1; } - if (!RNA_property_animateable(&r_ptr, prop)) { - PyErr_Format(PyExc_TypeError, - "%.200s property \"%s\" not animatable", - error_prefix, path); - return -1; + if (r_path_no_validate) { + /* Don't touch the index. */ } + else { + if (!RNA_property_animateable(&r_ptr, prop)) { + PyErr_Format(PyExc_TypeError, + "%.200s property \"%s\" not animatable", + error_prefix, path); + return -1; + } - if (RNA_property_array_check(prop) == 0) { - if ((*index) == -1) { - *index = 0; + if (RNA_property_array_check(prop) == 0) { + if ((*r_index) == -1) { + *r_index = 0; + } + else { + PyErr_Format(PyExc_TypeError, + "%.200s index %d was given while property \"%s\" is not an array", + error_prefix, *r_index, path); + return -1; + } } else { - PyErr_Format(PyExc_TypeError, - "%.200s index %d was given while property \"%s\" is not an array", - error_prefix, *index, path); - return -1; + int array_len = RNA_property_array_length(&r_ptr, prop); + if ((*r_index) < -1 || (*r_index) >= array_len) { + PyErr_Format(PyExc_TypeError, + "%.200s index out of range \"%s\", given %d, array length is %d", + error_prefix, path, *r_index, array_len); + return -1; + } } } + + if (is_idbase) { + *r_path_full = BLI_strdup(path); + } else { - int array_len = RNA_property_array_length(&r_ptr, prop); - if ((*index) < -1 || (*index) >= array_len) { + *r_path_full = RNA_path_from_ID_to_property(&r_ptr, prop); + + if (*r_path_full == NULL) { PyErr_Format(PyExc_TypeError, - "%.200s index out of range \"%s\", given %d, array length is %d", - error_prefix, path, *index, array_len); + "%.200s could not make path to \"%s\"", + error_prefix, path); return -1; } } + return 0; +} + +static int pyrna_struct_anim_args_parse( + PointerRNA *ptr, const char *error_prefix, const char *path, + const char **r_path_full, int *r_index) +{ + return pyrna_struct_anim_args_parse_ex(ptr, error_prefix, path, r_path_full, r_index, NULL); +} + +/** + * Unlike #pyrna_struct_anim_args_parse \a r_path_full may be copied from \a path. + */ +static int pyrna_struct_anim_args_parse_no_resolve( + PointerRNA *ptr, const char *error_prefix, const char *path, + const char **r_path_full) +{ + const bool is_idbase = RNA_struct_is_ID(ptr->type); if (is_idbase) { - *path_full = BLI_strdup(path); + *r_path_full = path; + return 0; } else { - *path_full = RNA_path_from_ID_to_property(&r_ptr, prop); - - if (*path_full == NULL) { + char *path_prefix = RNA_path_from_ID_to_struct(ptr); + if (path_prefix == NULL) { PyErr_Format(PyExc_TypeError, - "%.200s could not make path to \"%s\"", - error_prefix, path); + "%.200s could not make path for type %s", + error_prefix, RNA_struct_identifier(ptr->type)); return -1; } + + if (*path == '[') { + *r_path_full = BLI_string_joinN(path_prefix, path); + } + else { + *r_path_full = BLI_string_join_by_sep_charN('.', path_prefix, path); + } + MEM_freeN(path_prefix); } + return 0; +} +static int pyrna_struct_anim_args_parse_no_resolve_fallback( + PointerRNA *ptr, const char *error_prefix, const char *path, + const char **r_path_full, int *r_index) +{ + bool path_unresolved = false; + if (pyrna_struct_anim_args_parse_ex( + ptr, error_prefix, path, + r_path_full, r_index, &path_unresolved) == -1) + { + if (path_unresolved == true) { + if (pyrna_struct_anim_args_parse_no_resolve( + ptr, error_prefix, path, r_path_full) == -1) + { + return -1; + } + } + else { + return -1; + } + } return 0; } /* internal use for insert and delete */ static int pyrna_struct_keyframe_parse( PointerRNA *ptr, PyObject *args, PyObject *kw, const char *parse_str, const char *error_prefix, - const char **path_full, int *index, float *cfra, const char **group_name, int *options) /* return values */ + /* return values */ + const char **r_path_full, int *r_index, float *r_cfra, const char **r_group_name, int *r_options) { static const char *kwlist[] = {"data_path", "index", "frame", "group", "options", NULL}; PyObject *pyoptions = NULL; const char *path; /* note, parse_str MUST start with 's|ifsO!' */ - if (!PyArg_ParseTupleAndKeywords(args, kw, parse_str, (char **)kwlist, &path, index, cfra, group_name, - &PySet_Type, &pyoptions)) + if (!PyArg_ParseTupleAndKeywords( + args, kw, parse_str, (char **)kwlist, &path, r_index, r_cfra, r_group_name, + &PySet_Type, &pyoptions)) { return -1; } - if (pyrna_struct_anim_args_parse(ptr, error_prefix, path, path_full, index) == -1) + if (pyrna_struct_anim_args_parse( + ptr, error_prefix, path, + r_path_full, r_index) == -1) + { return -1; + } - if (*cfra == FLT_MAX) - *cfra = CTX_data_scene(BPy_GetContext())->r.cfra; + if (*r_cfra == FLT_MAX) { + *r_cfra = CTX_data_scene(BPy_GetContext())->r.cfra; + } /* flag may be null (no option currently for remove keyframes e.g.). */ - if (options) { - if (pyoptions && (pyrna_set_to_enum_bitfield(rna_enum_keying_flag_items, pyoptions, options, error_prefix) == -1)) { + if (r_options) { + if (pyoptions && + (pyrna_set_to_enum_bitfield( + rna_enum_keying_flag_items, pyoptions, r_options, error_prefix) == -1)) + { return -1; } - *options |= INSERTKEY_NO_USERPREF; + *r_options |= INSERTKEY_NO_USERPREF; } return 0; /* success */ @@ -222,9 +304,10 @@ PyObject *pyrna_struct_keyframe_insert(BPy_StructRNA *self, PyObject *args, PyOb PYRNA_STRUCT_CHECK_OBJ(self); - if (pyrna_struct_keyframe_parse(&self->ptr, args, kw, - "s|ifsO!:bpy_struct.keyframe_insert()", "bpy_struct.keyframe_insert()", - &path_full, &index, &cfra, &group_name, &options) == -1) + if (pyrna_struct_keyframe_parse( + &self->ptr, args, kw, + "s|ifsO!:bpy_struct.keyframe_insert()", "bpy_struct.keyframe_insert()", + &path_full, &index, &cfra, &group_name, &options) == -1) { return NULL; } @@ -233,6 +316,7 @@ PyObject *pyrna_struct_keyframe_insert(BPy_StructRNA *self, PyObject *args, PyOb * strips themselves. These are stored separately or else the properties will * not have any effect. */ + struct Depsgraph *depsgraph = CTX_data_depsgraph(BPy_GetContext()); ReportList reports; short result = 0; @@ -254,7 +338,7 @@ PyObject *pyrna_struct_keyframe_insert(BPy_StructRNA *self, PyObject *args, PyOb NlaStrip *strip = (NlaStrip *)ptr.data; FCurve *fcu = list_find_fcurve(&strip->fcurves, RNA_property_identifier(prop), index); - result = insert_keyframe_direct(&reports, ptr, prop, fcu, cfra, keytype, options); + result = insert_keyframe_direct(depsgraph, &reports, ptr, prop, fcu, cfra, keytype, NULL, options); } else { BKE_reportf(&reports, RPT_ERROR, "Could not resolve path (%s)", path_full); @@ -268,13 +352,14 @@ PyObject *pyrna_struct_keyframe_insert(BPy_StructRNA *self, PyObject *args, PyOb } else { ID *id = self->ptr.id.data; + struct Depsgraph *depsgraph = CTX_data_depsgraph(BPy_GetContext()); ReportList reports; short result; BKE_reports_init(&reports, RPT_STORE); - BLI_assert(BKE_id_is_in_gobal_main(id)); - result = insert_keyframe(G_MAIN, &reports, id, NULL, group_name, path_full, index, cfra, keytype, options); + BLI_assert(BKE_id_is_in_global_main(id)); + result = insert_keyframe(G_MAIN, depsgraph, &reports, id, NULL, group_name, path_full, index, cfra, keytype, NULL, options); MEM_freeN((void *)path_full); if (BPy_reports_to_error(&reports, PyExc_RuntimeError, true) == -1) @@ -384,7 +469,7 @@ PyObject *pyrna_struct_keyframe_delete(BPy_StructRNA *self, PyObject *args, PyOb BKE_reports_init(&reports, RPT_STORE); - result = delete_keyframe(&reports, (ID *)self->ptr.id.data, NULL, group_name, path_full, index, cfra, 0); + result = delete_keyframe(G.main, &reports, (ID *)self->ptr.id.data, NULL, group_name, path_full, index, cfra, 0); MEM_freeN((void *)path_full); if (BPy_reports_to_error(&reports, PyExc_RuntimeError, true) == -1) @@ -417,7 +502,10 @@ PyObject *pyrna_struct_driver_add(BPy_StructRNA *self, PyObject *args) if (!PyArg_ParseTuple(args, "s|i:driver_add", &path, &index)) return NULL; - if (pyrna_struct_anim_args_parse(&self->ptr, "bpy_struct.driver_add():", path, &path_full, &index) == -1) { + if (pyrna_struct_anim_args_parse( + &self->ptr, "bpy_struct.driver_add():", path, + &path_full, &index) == -1) + { return NULL; } else { @@ -488,10 +576,14 @@ PyObject *pyrna_struct_driver_remove(BPy_StructRNA *self, PyObject *args) PYRNA_STRUCT_CHECK_OBJ(self); - if (!PyArg_ParseTuple(args, "s|i:driver_remove", &path, &index)) + if (!PyArg_ParseTuple(args, "s|i:driver_remove", &path, &index)) { return NULL; + } - if (pyrna_struct_anim_args_parse(&self->ptr, "bpy_struct.driver_remove():", path, &path_full, &index) == -1) { + if (pyrna_struct_anim_args_parse_no_resolve_fallback( + &self->ptr, "bpy_struct.driver_remove():", path, + &path_full, &index) == -1) + { return NULL; } else { @@ -502,7 +594,9 @@ PyObject *pyrna_struct_driver_remove(BPy_StructRNA *self, PyObject *args) result = ANIM_remove_driver(&reports, (ID *)self->ptr.id.data, path_full, index, 0); - MEM_freeN((void *)path_full); + if (path != path_full) { + MEM_freeN((void *)path_full); + } if (BPy_reports_to_error(&reports, PyExc_RuntimeError, true) == -1) return NULL; diff --git a/source/blender/python/intern/bpy_rna_callback.c b/source/blender/python/intern/bpy_rna_callback.c index fbdcc03ddd0..ed29aa795e6 100644 --- a/source/blender/python/intern/bpy_rna_callback.c +++ b/source/blender/python/intern/bpy_rna_callback.c @@ -33,6 +33,7 @@ #include "RNA_types.h" #include "BLI_utildefines.h" +#include "BLI_listbase.h" #include "bpy_rna.h" #include "bpy_rna_callback.h" @@ -47,8 +48,12 @@ #include "BKE_context.h" #include "BKE_screen.h" +#include "WM_api.h" + #include "ED_space_api.h" +#include "../generic/python_utildefines.h" + /* use this to stop other capsules from being mis-used */ #define RNA_CAPSULE_ID "RNA_HANDLE" #define RNA_CAPSULE_ID_INVALID "RNA_HANDLE_REMOVED" @@ -82,6 +87,53 @@ static void cb_region_draw(const bContext *C, ARegion *UNUSED(ar), void *customd bpy_context_clear((bContext *)C, &gilstate); } +/* We could make generic utility */ +static PyObject *PyC_Tuple_CopySized(PyObject *src, int len_dst) +{ + PyObject *dst = PyTuple_New(len_dst); + int len_src = PyTuple_GET_SIZE(src); + BLI_assert(len_src <= len_dst); + for (int i = 0; i < len_src; i++) { + PyObject *item = PyTuple_GET_ITEM(src, i); + PyTuple_SET_ITEM(dst, i, item); + Py_INCREF(item); + } + return dst; +} + +static void cb_wm_cursor_draw(bContext *C, int x, int y, void *customdata) +{ + PyObject *cb_func, *cb_args, *result; + PyGILState_STATE gilstate; + + bpy_context_set((bContext *)C, &gilstate); + + cb_func = PyTuple_GET_ITEM((PyObject *)customdata, 1); + cb_args = PyTuple_GET_ITEM((PyObject *)customdata, 2); + + const int cb_args_len = PyTuple_GET_SIZE(cb_args); + + PyObject *cb_args_xy = PyTuple_New(2); + PyTuple_SET_ITEMS(cb_args_xy, PyLong_FromLong(x), PyLong_FromLong(y)); + + PyObject *cb_args_with_xy = PyC_Tuple_CopySized(cb_args, cb_args_len + 1); + PyTuple_SET_ITEM(cb_args_with_xy, cb_args_len, cb_args_xy); + + result = PyObject_CallObject(cb_func, cb_args_with_xy); + + Py_DECREF(cb_args_with_xy); + + if (result) { + Py_DECREF(result); + } + else { + PyErr_Print(); + PyErr_Clear(); + } + + bpy_context_clear((bContext *)C, &gilstate); +} + #if 0 PyObject *pyrna_callback_add(BPy_StructRNA *self, PyObject *args) { @@ -171,11 +223,9 @@ static eSpace_Type rna_Space_refine_reverse(StructRNA *srna) if (srna == &RNA_SpaceTextEditor) return SPACE_TEXT; if (srna == &RNA_SpaceDopeSheetEditor) return SPACE_ACTION; if (srna == &RNA_SpaceNLA) return SPACE_NLA; - if (srna == &RNA_SpaceTimeline) return SPACE_TIME; if (srna == &RNA_SpaceNodeEditor) return SPACE_NODE; - if (srna == &RNA_SpaceLogicEditor) return SPACE_LOGIC; if (srna == &RNA_SpaceConsole) return SPACE_CONSOLE; - if (srna == &RNA_SpaceUserPreferences) return SPACE_USERPREF; + if (srna == &RNA_SpacePreferences) return SPACE_USERPREF; if (srna == &RNA_SpaceClipEditor) return SPACE_CLIP; return SPACE_EMPTY; } @@ -185,10 +235,6 @@ PyObject *pyrna_callback_classmethod_add(PyObject *UNUSED(self), PyObject *args) void *handle; PyObject *cls; PyObject *cb_func, *cb_args; - const char *cb_regiontype_str; - const char *cb_event_str; - int cb_event; - int cb_regiontype; StructRNA *srna; if (PyTuple_GET_SIZE(args) < 2) { @@ -207,23 +253,78 @@ PyObject *pyrna_callback_classmethod_add(PyObject *UNUSED(self), PyObject *args) } /* class specific callbacks */ - if (RNA_struct_is_a(srna, &RNA_Space)) { - if (!PyArg_ParseTuple(args, "OOO!ss:Space.draw_handler_add", - &cls, &cb_func, /* already assigned, no matter */ - &PyTuple_Type, &cb_args, &cb_regiontype_str, &cb_event_str)) + + if (srna == &RNA_WindowManager) { + const char *error_prefix = "WindowManager.draw_cursor_add"; + struct { + const char *space_type_str; + const char *region_type_str; + + int space_type; + int region_type; + } params = { + .space_type_str = NULL, + .region_type_str = NULL, + .space_type = SPACE_TYPE_ANY, + .region_type = RGN_TYPE_ANY, + }; + + if (!PyArg_ParseTuple( + args, "OOO!|ss:WindowManager.draw_cursor_add", + &cls, &cb_func, /* already assigned, no matter */ + &PyTuple_Type, &cb_args, ¶ms.space_type_str, ¶ms.region_type_str)) + { + return NULL; + } + + if (params.space_type_str && pyrna_enum_value_from_id( + rna_enum_space_type_items, params.space_type_str, + ¶ms.space_type, error_prefix) == -1) + { + return NULL; + } + else if (params.region_type_str && pyrna_enum_value_from_id( + rna_enum_region_type_items, params.region_type_str, + ¶ms.region_type, error_prefix) == -1) + { + return NULL; + } + + bContext *C = BPy_GetContext(); + struct wmWindowManager *wm = CTX_wm_manager(C); + handle = WM_paint_cursor_activate( + wm, + params.space_type, params.region_type, + NULL, cb_wm_cursor_draw, (void *)args); + } + else if (RNA_struct_is_a(srna, &RNA_Space)) { + const char *error_prefix = "Space.draw_handler_add"; + struct { + const char *region_type_str; + const char *event_str; + + int region_type; + int event; + } params; + + if (!PyArg_ParseTuple( + args, "OOO!ss:Space.draw_handler_add", + &cls, &cb_func, /* already assigned, no matter */ + &PyTuple_Type, &cb_args, + ¶ms.region_type_str, ¶ms.event_str)) { return NULL; } if (pyrna_enum_value_from_id( - region_draw_mode_items, cb_event_str, - &cb_event, "bpy_struct.callback_add()") == -1) + region_draw_mode_items, params.event_str, + ¶ms.event, error_prefix) == -1) { return NULL; } else if (pyrna_enum_value_from_id( - rna_enum_region_type_items, cb_regiontype_str, - &cb_regiontype, "bpy_struct.callback_add()") == -1) + rna_enum_region_type_items, params.region_type_str, + ¶ms.region_type, error_prefix) == -1) { return NULL; } @@ -235,13 +336,12 @@ PyObject *pyrna_callback_classmethod_add(PyObject *UNUSED(self), PyObject *args) } else { SpaceType *st = BKE_spacetype_from_id(spaceid); - ARegionType *art = BKE_regiontype_from_id(st, cb_regiontype); + ARegionType *art = BKE_regiontype_from_id(st, params.region_type); if (art == NULL) { - PyErr_Format(PyExc_TypeError, "region type '%.200s' not in space", cb_regiontype_str); + PyErr_Format(PyExc_TypeError, "region type '%.200s' not in space", params.region_type_str); return NULL; } - handle = ED_region_draw_cb_activate(art, cb_region_draw, (void *)args, cb_event); - Py_INCREF(args); + handle = ED_region_draw_cb_activate(art, cb_region_draw, (void *)args, params.event); } } } @@ -250,7 +350,14 @@ PyObject *pyrna_callback_classmethod_add(PyObject *UNUSED(self), PyObject *args) return NULL; } - return PyCapsule_New((void *)handle, RNA_CAPSULE_ID, NULL); + PyObject *ret = PyCapsule_New((void *)handle, RNA_CAPSULE_ID, NULL); + + /* Store 'args' in context as well as the handler custom-data, + * because the handle may be freed by Blender (new file, new window... etc) */ + PyCapsule_SetContext(ret, args); + Py_INCREF(args); + + return ret; } PyObject *pyrna_callback_classmethod_remove(PyObject *UNUSED(self), PyObject *args) @@ -258,10 +365,8 @@ PyObject *pyrna_callback_classmethod_remove(PyObject *UNUSED(self), PyObject *ar PyObject *cls; PyObject *py_handle; void *handle; - void *customdata; StructRNA *srna; - const char *cb_regiontype_str; - int cb_regiontype; + bool capsule_clear = false; if (PyTuple_GET_SIZE(args) < 2) { PyErr_SetString(PyExc_ValueError, "callback_remove(handler): expected at least 2 args"); @@ -278,21 +383,39 @@ PyObject *pyrna_callback_classmethod_remove(PyObject *UNUSED(self), PyObject *ar PyErr_SetString(PyExc_ValueError, "callback_remove(handler): NULL handler given, invalid or already removed"); return NULL; } + PyObject *handle_args = PyCapsule_GetContext(py_handle); - if (RNA_struct_is_a(srna, &RNA_Space)) { - if (!PyArg_ParseTuple(args, "OO!s:Space.draw_handler_remove", - &cls, &PyCapsule_Type, &py_handle, /* already assigned, no matter */ - &cb_regiontype_str)) + if (srna == &RNA_WindowManager) { + if (!PyArg_ParseTuple( + args, "OO!:WindowManager.draw_cursor_remove", + &cls, &PyCapsule_Type, &py_handle)) + { + return NULL; + } + bContext *C = BPy_GetContext(); + struct wmWindowManager *wm = CTX_wm_manager(C); + WM_paint_cursor_end(wm, handle); + capsule_clear = true; + } + else if (RNA_struct_is_a(srna, &RNA_Space)) { + const char *error_prefix = "Space.draw_handler_remove"; + struct { + const char *region_type_str; + + int region_type; + } params; + + if (!PyArg_ParseTuple( + args, "OO!s:Space.draw_handler_remove", + &cls, &PyCapsule_Type, &py_handle, /* already assigned, no matter */ + ¶ms.region_type_str)) { return NULL; } - - customdata = ED_region_draw_cb_customdata(handle); - Py_DECREF((PyObject *)customdata); if (pyrna_enum_value_from_id( - rna_enum_region_type_items, cb_regiontype_str, - &cb_regiontype, "bpy_struct.callback_remove()") == -1) + rna_enum_region_type_items, params.region_type_str, + ¶ms.region_type, error_prefix) == -1) { return NULL; } @@ -304,12 +427,13 @@ PyObject *pyrna_callback_classmethod_remove(PyObject *UNUSED(self), PyObject *ar } else { SpaceType *st = BKE_spacetype_from_id(spaceid); - ARegionType *art = BKE_regiontype_from_id(st, cb_regiontype); + ARegionType *art = BKE_regiontype_from_id(st, params.region_type); if (art == NULL) { - PyErr_Format(PyExc_TypeError, "region type '%.200s' not in space", cb_regiontype_str); + PyErr_Format(PyExc_TypeError, "region type '%.200s' not in space", params.region_type_str); return NULL; } ED_region_draw_cb_exit(art, handle); + capsule_clear = true; } } } @@ -319,7 +443,10 @@ PyObject *pyrna_callback_classmethod_remove(PyObject *UNUSED(self), PyObject *ar } /* don't allow reuse */ - PyCapsule_SetName(py_handle, RNA_CAPSULE_ID_INVALID); + if (capsule_clear) { + Py_DECREF(handle_args); + PyCapsule_SetName(py_handle, RNA_CAPSULE_ID_INVALID); + } Py_RETURN_NONE; } diff --git a/source/blender/python/intern/bpy_rna_gizmo.c b/source/blender/python/intern/bpy_rna_gizmo.c new file mode 100644 index 00000000000..8189431dfc9 --- /dev/null +++ b/source/blender/python/intern/bpy_rna_gizmo.c @@ -0,0 +1,564 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/python/intern/bpy_rna_gizmo.c + * \ingroup pythonintern + * + * . + */ + +#include <Python.h> +#include <stddef.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_utildefines.h" +#include "BLI_alloca.h" + +#include "BKE_main.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "bpy_capi_utils.h" +#include "bpy_rna_gizmo.h" + +#include "../generic/py_capi_utils.h" +#include "../generic/python_utildefines.h" + +#include "RNA_access.h" +#include "RNA_types.h" +#include "RNA_enum_types.h" + +#include "bpy_rna.h" + + +/* -------------------------------------------------------------------- */ +/** \name Gizmo Target Property Define API + * \{ */ + +enum { + BPY_GIZMO_FN_SLOT_GET = 0, + BPY_GIZMO_FN_SLOT_SET, + BPY_GIZMO_FN_SLOT_RANGE_GET, +}; +#define BPY_GIZMO_FN_SLOT_LEN (BPY_GIZMO_FN_SLOT_RANGE_GET + 1) + +struct BPyGizmoHandlerUserData { + + PyObject *fn_slots[BPY_GIZMO_FN_SLOT_LEN]; +}; + +static void py_rna_gizmo_handler_get_cb( + const wmGizmo *UNUSED(gz), wmGizmoProperty *gz_prop, + void *value_p) +{ + PyGILState_STATE gilstate = PyGILState_Ensure(); + + struct BPyGizmoHandlerUserData *data = gz_prop->custom_func.user_data; + PyObject *ret = PyObject_CallObject(data->fn_slots[BPY_GIZMO_FN_SLOT_GET], NULL); + if (ret == NULL) { + goto fail; + } + + if (gz_prop->type->data_type == PROP_FLOAT) { + float *value = value_p; + if (gz_prop->type->array_length == 1) { + if ((*value = PyFloat_AsDouble(ret)) == -1.0f && PyErr_Occurred()) { + goto fail; + } + } + else { + if (PyC_AsArray(value, ret, gz_prop->type->array_length, &PyFloat_Type, false, + "Gizmo get callback: ") == -1) + { + goto fail; + } + } + } + else { + PyErr_SetString(PyExc_AttributeError, "internal error, unsupported type"); + goto fail; + } + + Py_DECREF(ret); + + PyGILState_Release(gilstate); + return; + +fail: + PyErr_Print(); + PyErr_Clear(); + + PyGILState_Release(gilstate); +} + +static void py_rna_gizmo_handler_set_cb( + const wmGizmo *UNUSED(gz), wmGizmoProperty *gz_prop, + const void *value_p) +{ + PyGILState_STATE gilstate = PyGILState_Ensure(); + + struct BPyGizmoHandlerUserData *data = gz_prop->custom_func.user_data; + + PyObject *args = PyTuple_New(1); + + if (gz_prop->type->data_type == PROP_FLOAT) { + const float *value = value_p; + PyObject *py_value; + if (gz_prop->type->array_length == 1) { + py_value = PyFloat_FromDouble(*value); + } + else { + py_value = PyC_Tuple_PackArray_F32(value, gz_prop->type->array_length); + } + if (py_value == NULL) { + goto fail; + } + PyTuple_SET_ITEM(args, 0, py_value); + } + else { + PyErr_SetString(PyExc_AttributeError, "internal error, unsupported type"); + goto fail; + } + + PyObject *ret = PyObject_CallObject(data->fn_slots[BPY_GIZMO_FN_SLOT_SET], args); + if (ret == NULL) { + goto fail; + } + Py_DECREF(ret); + + PyGILState_Release(gilstate); + return; + +fail: + PyErr_Print(); + PyErr_Clear(); + + Py_DECREF(args); + + PyGILState_Release(gilstate); +} + +static void py_rna_gizmo_handler_range_get_cb( + const wmGizmo *UNUSED(gz), wmGizmoProperty *gz_prop, + void *value_p) +{ + struct BPyGizmoHandlerUserData *data = gz_prop->custom_func.user_data; + + PyGILState_STATE gilstate = PyGILState_Ensure(); + + PyObject *ret = PyObject_CallObject(data->fn_slots[BPY_GIZMO_FN_SLOT_RANGE_GET], NULL); + if (ret == NULL) { + goto fail; + } + + if (!PyTuple_Check(ret)) { + PyErr_Format(PyExc_TypeError, + "Expected a tuple, not %.200s", + Py_TYPE(ret)->tp_name); + goto fail; + } + + if (PyTuple_GET_SIZE(ret) != 2) { + PyErr_Format(PyExc_TypeError, + "Expected a tuple of size 2, not %d", + PyTuple_GET_SIZE(ret)); + goto fail; + } + + if (gz_prop->type->data_type == PROP_FLOAT) { + float range[2]; + for (int i = 0; i < 2; i++) { + if (((range[i] = PyFloat_AsDouble(PyTuple_GET_ITEM(ret, i))) == -1.0f && PyErr_Occurred()) == 0) { + /* pass */ + } + else { + goto fail; + } + } + memcpy(value_p, range, sizeof(range)); + } + else { + PyErr_SetString(PyExc_AttributeError, "internal error, unsupported type"); + goto fail; + } + + Py_DECREF(ret); + PyGILState_Release(gilstate); + return; + +fail: + Py_XDECREF(ret); + + PyErr_Print(); + PyErr_Clear(); + + PyGILState_Release(gilstate); +} + +static void py_rna_gizmo_handler_free_cb( + const wmGizmo *UNUSED(gz), wmGizmoProperty *gz_prop) +{ + struct BPyGizmoHandlerUserData *data = gz_prop->custom_func.user_data; + + PyGILState_STATE gilstate = PyGILState_Ensure(); + for (int i = 0; i < BPY_GIZMO_FN_SLOT_LEN; i++) { + Py_XDECREF(data->fn_slots[i]); + } + PyGILState_Release(gilstate); + + MEM_freeN(data); + +} + +PyDoc_STRVAR(bpy_gizmo_target_set_handler_doc, +".. method:: target_set_handler(target, get, set, range=None):\n" +"\n" +" Assigns callbacks to a gizmos property.\n" +"\n" +" :arg get: Function that returns the value for this property (single value or sequence).\n" +" :type get: callable\n" +" :arg set: Function that takes a single value argument and applies it.\n" +" :type set: callable\n" +" :arg range: Function that returns a (min, max) tuple for gizmos that use a range.\n" +" :type range: callable\n" +); +static PyObject *bpy_gizmo_target_set_handler(PyObject *UNUSED(self), PyObject *args, PyObject *kw) +{ + PyGILState_STATE gilstate = PyGILState_Ensure(); + + struct { + PyObject *self; + char *target; + PyObject *py_fn_slots[BPY_GIZMO_FN_SLOT_LEN]; + } params = { + .self = NULL, + .target = NULL, + .py_fn_slots = {NULL}, + }; + + /* Note: this is a counter-part to functions: + * 'Gizmo.target_set_prop & target_set_operator' + * (see: rna_wm_gizmo_api.c). conventions should match. */ + static const char * const _keywords[] = {"self", "target", "get", "set", "range", NULL}; + static _PyArg_Parser _parser = {"Os|$OOO:target_set_handler", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast( + args, kw, &_parser, + ¶ms.self, + ¶ms.target, + ¶ms.py_fn_slots[BPY_GIZMO_FN_SLOT_GET], + ¶ms.py_fn_slots[BPY_GIZMO_FN_SLOT_SET], + ¶ms.py_fn_slots[BPY_GIZMO_FN_SLOT_RANGE_GET])) + { + goto fail; + } + + wmGizmo *gz = ((BPy_StructRNA *)params.self)->ptr.data; + + const wmGizmoPropertyType *gz_prop_type = + WM_gizmotype_target_property_find(gz->type, params.target); + if (gz_prop_type == NULL) { + PyErr_Format(PyExc_ValueError, + "Gizmo target property '%s.%s' not found", + gz->type->idname, params.target); + goto fail; + } + + { + const int slots_required = 2; + const int slots_start = 2; + for (int i = 0; i < BPY_GIZMO_FN_SLOT_LEN; i++) { + if (params.py_fn_slots[i] == NULL) { + if (i < slots_required) { + PyErr_Format(PyExc_ValueError, "Argument '%s' not given", _keywords[slots_start + i]); + goto fail; + } + } + else if (!PyCallable_Check(params.py_fn_slots[i])) { + PyErr_Format(PyExc_ValueError, "Argument '%s' not callable", _keywords[slots_start + i]); + goto fail; + } + } + } + + struct BPyGizmoHandlerUserData *data = MEM_callocN(sizeof(*data), __func__); + + for (int i = 0; i < BPY_GIZMO_FN_SLOT_LEN; i++) { + data->fn_slots[i] = params.py_fn_slots[i]; + Py_XINCREF(params.py_fn_slots[i]); + } + + WM_gizmo_target_property_def_func_ptr( + gz, gz_prop_type, + &(const struct wmGizmoPropertyFnParams) { + .value_get_fn = py_rna_gizmo_handler_get_cb, + .value_set_fn = py_rna_gizmo_handler_set_cb, + .range_get_fn = py_rna_gizmo_handler_range_get_cb, + .free_fn = py_rna_gizmo_handler_free_cb, + .user_data = data, + }); + + PyGILState_Release(gilstate); + + Py_RETURN_NONE; + +fail: + PyGILState_Release(gilstate); + return NULL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Gizmo Target Property Access API + * \{ */ + +PyDoc_STRVAR(bpy_gizmo_target_get_value_doc, +".. method:: target_get_value(target):\n" +"\n" +" Get the value of this target property.\n" +"\n" +" :arg target: Target property name.\n" +" :type target: string\n" +" :return: The value of the target property.\n" +" :rtype: Single value or array based on the target type\n" +); +static PyObject *bpy_gizmo_target_get_value(PyObject *UNUSED(self), PyObject *args, PyObject *kw) +{ + struct { + PyObject *self; + char *target; + } params = { + .self = NULL, + .target = NULL, + }; + + static const char * const _keywords[] = {"self", "target", NULL}; + static _PyArg_Parser _parser = {"Os:target_get_value", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast( + args, kw, &_parser, + ¶ms.self, + ¶ms.target)) + { + goto fail; + } + + wmGizmo *gz = ((BPy_StructRNA *)params.self)->ptr.data; + + wmGizmoProperty *gz_prop = + WM_gizmo_target_property_find(gz, params.target); + if (gz_prop == NULL) { + PyErr_Format(PyExc_ValueError, + "Gizmo target property '%s.%s' not found", + gz->type->idname, params.target); + goto fail; + } + + const int array_len = WM_gizmo_target_property_array_length(gz, gz_prop); + switch (gz_prop->type->data_type) { + case PROP_FLOAT: + { + if (array_len != 0) { + float *value = BLI_array_alloca(value, array_len); + WM_gizmo_target_property_float_get_array(gz, gz_prop, value); + return PyC_Tuple_PackArray_F32(value, array_len); + } + else { + float value = WM_gizmo_target_property_float_get(gz, gz_prop); + return PyFloat_FromDouble(value); + } + break; + } + default: + { + PyErr_SetString(PyExc_RuntimeError, "Not yet supported type"); + goto fail; + } + } + +fail: + return NULL; +} + +PyDoc_STRVAR(bpy_gizmo_target_set_value_doc, +".. method:: target_set_value(target):\n" +"\n" +" Set the value of this target property.\n" +"\n" +" :arg target: Target property name.\n" +" :type target: string\n" +); +static PyObject *bpy_gizmo_target_set_value(PyObject *UNUSED(self), PyObject *args, PyObject *kw) +{ + struct { + PyObject *self; + char *target; + PyObject *value; + } params = { + .self = NULL, + .target = NULL, + .value = NULL, + }; + + static const char * const _keywords[] = {"self", "target", "value", NULL}; + static _PyArg_Parser _parser = {"OsO:target_set_value", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast( + args, kw, &_parser, + ¶ms.self, + ¶ms.target, + ¶ms.value)) + { + goto fail; + } + + wmGizmo *gz = ((BPy_StructRNA *)params.self)->ptr.data; + + wmGizmoProperty *gz_prop = + WM_gizmo_target_property_find(gz, params.target); + if (gz_prop == NULL) { + PyErr_Format(PyExc_ValueError, + "Gizmo target property '%s.%s' not found", + gz->type->idname, params.target); + goto fail; + } + + const int array_len = WM_gizmo_target_property_array_length(gz, gz_prop); + switch (gz_prop->type->data_type) { + case PROP_FLOAT: + { + if (array_len != 0) { + float *value = BLI_array_alloca(value, array_len); + if (PyC_AsArray(value, params.value, gz_prop->type->array_length, &PyFloat_Type, false, + "Gizmo target property array") == -1) + { + goto fail; + } + WM_gizmo_target_property_float_set_array(BPy_GetContext(), gz, gz_prop, value); + } + else { + float value; + if ((value = PyFloat_AsDouble(params.value)) == -1.0f && PyErr_Occurred()) { + goto fail; + } + WM_gizmo_target_property_float_set(BPy_GetContext(), gz, gz_prop, value); + } + Py_RETURN_NONE; + } + default: + { + PyErr_SetString(PyExc_RuntimeError, "Not yet supported type"); + goto fail; + } + } + +fail: + return NULL; +} + + +PyDoc_STRVAR(bpy_gizmo_target_get_range_doc, +".. method:: target_get_range(target):\n" +"\n" +" Get the range for this target property.\n" +"\n" +" :arg target: Target property name.\n" +" :return: The range of this property (min, max).\n" +" :rtype: tuple pair.\n" +); +static PyObject *bpy_gizmo_target_get_range(PyObject *UNUSED(self), PyObject *args, PyObject *kw) +{ + struct { + PyObject *self; + char *target; + } params = { + .self = NULL, + .target = NULL, + }; + + static const char * const _keywords[] = {"self", "target", NULL}; + static _PyArg_Parser _parser = {"Os:target_get_range", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast( + args, kw, &_parser, + ¶ms.self, + ¶ms.target)) + { + goto fail; + } + + wmGizmo *gz = ((BPy_StructRNA *)params.self)->ptr.data; + + wmGizmoProperty *gz_prop = + WM_gizmo_target_property_find(gz, params.target); + if (gz_prop == NULL) { + PyErr_Format(PyExc_ValueError, + "Gizmo target property '%s.%s' not found", + gz->type->idname, params.target); + goto fail; + } + + switch (gz_prop->type->data_type) { + case PROP_FLOAT: + { + float range[2]; + WM_gizmo_target_property_float_range_get(gz, gz_prop, range); + return PyC_Tuple_PackArray_F32(range, 2); + } + default: + { + PyErr_SetString(PyExc_RuntimeError, "Not yet supported type"); + goto fail; + } + } + +fail: + return NULL; +} + +/** \} */ + +int BPY_rna_gizmo_module(PyObject *mod_par) +{ + static PyMethodDef method_def_array[] = { + /* Gizmo Target Property Define API */ + {"target_set_handler", (PyCFunction)bpy_gizmo_target_set_handler, + METH_VARARGS | METH_KEYWORDS, bpy_gizmo_target_set_handler_doc}, + /* Gizmo Target Property Access API */ + {"target_get_value", (PyCFunction)bpy_gizmo_target_get_value, + METH_VARARGS | METH_KEYWORDS, bpy_gizmo_target_get_value_doc}, + {"target_set_value", (PyCFunction)bpy_gizmo_target_set_value, + METH_VARARGS | METH_KEYWORDS, bpy_gizmo_target_set_value_doc}, + {"target_get_range", (PyCFunction)bpy_gizmo_target_get_range, + METH_VARARGS | METH_KEYWORDS, bpy_gizmo_target_get_range_doc}, + /* no sentinel needed. */ + }; + + for (int i = 0; i < ARRAY_SIZE(method_def_array); i++) { + PyMethodDef *m = &method_def_array[i]; + PyObject *func = PyCFunction_New(m, NULL); + PyObject *func_inst = PyInstanceMethod_New(func); + char name_prefix[128]; + PyOS_snprintf(name_prefix, sizeof(name_prefix), "_rna_gizmo_%s", m->ml_name); + /* TODO, return a type that binds nearly to a method. */ + PyModule_AddObject(mod_par, name_prefix, func_inst); + } + + return 0; +} diff --git a/source/blender/python/intern/gpu.h b/source/blender/python/intern/bpy_rna_gizmo.h index 0da44a4eb87..68d8c1e052a 100644 --- a/source/blender/python/intern/gpu.h +++ b/source/blender/python/intern/bpy_rna_gizmo.h @@ -15,27 +15,18 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * The Original Code is Copyright (C) 2005 Blender Foundation. - * All rights reserved. - * - * The Original Code is: all of this file. - * - * Contributor(s): Benoit Bolsee. + * Contributor(s): Bastien Montagne * * ***** END GPL LICENSE BLOCK ***** */ -/** \file blender/python/intern/gpu.h +/** \file blender/python/intern/bpy_rna_gizmo.h * \ingroup pythonintern - * - * Initializes the gpu Python module. */ -#ifndef __GPU_H__ -#define __GPU_H__ - -PyObject *GPU_initPython(void); +#ifndef __BPY_RNA_GIZMO_H__ +#define __BPY_RNA_GIZMO_H__ -PyObject *BPyInit_gpu_offscreen(void); +int BPY_rna_gizmo_module(PyObject *); -#endif /* __GPU_H__ */ +#endif /* __BPY_RNA_GIZMO_H__ */ diff --git a/source/blender/python/intern/bpy_rna_id_collection.c b/source/blender/python/intern/bpy_rna_id_collection.c index 4806c2266ba..3fc12d4cc54 100644 --- a/source/blender/python/intern/bpy_rna_id_collection.c +++ b/source/blender/python/intern/bpy_rna_id_collection.c @@ -35,9 +35,9 @@ #include "BLI_bitmap.h" #include "BKE_global.h" -#include "BKE_main.h" #include "BKE_library.h" #include "BKE_library_query.h" +#include "BKE_main.h" #include "DNA_ID.h" /* Those folowing are only to support hack of not listing some internal 'backward' pointers in generated user_map... */ @@ -97,11 +97,11 @@ static int foreach_libblock_id_user_map_callback( } if ((GS(self_id->name) == ID_OB) && (id_p == (ID **)&((Object *)self_id)->proxy_from)) { - /* We skip proxy_from here, since it some internal pointer which is not irrelevant info for py/API level. */ + /* We skip proxy_from here, since it's some internal pointer which is not relevant info for py/API level. */ return IDWALK_RET_NOP; } else if ((GS(self_id->name) == ID_KE) && (id_p == (ID **)&((Key *)self_id)->from)) { - /* We skip from here, since it some internal pointer which is not irrelevant info for py/API level. */ + /* We skip from here, since it's some internal pointer which is not relevant info for py/API level. */ return IDWALK_RET_NOP; } @@ -290,6 +290,79 @@ error: } +PyDoc_STRVAR(bpy_batch_remove_doc, +".. method:: batch_remove(ids=(id1, id2, ...))\n" +"\n" +" Remove (delete) several IDs at once.\n" +"\n" +" WARNING: Considered experimental feature currently.\n" +"\n" +" Note that this function is quicker than individual calls to :func:`remove()` (from :class:`bpy.types.BlendData`\n" +" ID collections), but less safe/versatile (it can break Blender, e.g. by removing all scenes...).\n" +"\n" +" :arg ids: Iterables of IDs (types can be mixed).\n" +" :type subset: sequence\n" +); +static PyObject *bpy_batch_remove(PyObject *UNUSED(self), PyObject *args, PyObject *kwds) +{ +#if 0 /* If someone knows how to get a proper 'self' in that case... */ + BPy_StructRNA *pyrna = (BPy_StructRNA *)self; + Main *bmain = pyrna->ptr.data; +#else + Main *bmain = G_MAIN; /* XXX Ugly, but should work! */ +#endif + + PyObject *ids = NULL; + + PyObject *ret = NULL; + + static const char *_keywords[] = {"ids", NULL}; + static _PyArg_Parser _parser = {"O:user_map", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast( + args, kwds, &_parser, + &ids)) + { + return ret; + } + + if (ids) { + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + + PyObject *ids_fast = PySequence_Fast(ids, "batch_remove"); + if (ids_fast == NULL) { + goto error; + } + + PyObject **ids_array = PySequence_Fast_ITEMS(ids_fast); + Py_ssize_t ids_len = PySequence_Fast_GET_SIZE(ids_fast); + + for (; ids_len; ids_array++, ids_len--) { + ID *id; + if (!pyrna_id_FromPyObject(*ids_array, &id)) { + PyErr_Format(PyExc_TypeError, + "Expected an ID type, not %.200s", + Py_TYPE(*ids_array)->tp_name); + Py_DECREF(ids_fast); + goto error; + } + + id->tag |= LIB_TAG_DOIT; + } + Py_DECREF(ids_fast); + + BKE_id_multi_tagged_delete(bmain); + } + else { + goto error; + } + + Py_INCREF(Py_None); + ret = Py_None; + +error: + return ret; +} + int BPY_rna_id_collection_module(PyObject *mod_par) { static PyMethodDef user_map = { @@ -297,5 +370,10 @@ int BPY_rna_id_collection_module(PyObject *mod_par) PyModule_AddObject(mod_par, "_rna_id_collection_user_map", PyCFunction_New(&user_map, NULL)); + static PyMethodDef batch_remove = { + "batch_remove", (PyCFunction)bpy_batch_remove, METH_VARARGS | METH_KEYWORDS, bpy_batch_remove_doc}; + + PyModule_AddObject(mod_par, "_rna_id_collection_batch_remove", PyCFunction_New(&batch_remove, NULL)); + return 0; } diff --git a/source/blender/python/intern/bpy_utils_units.c b/source/blender/python/intern/bpy_utils_units.c index cbd8b67d1ce..1d1108e1af0 100644 --- a/source/blender/python/intern/bpy_utils_units.c +++ b/source/blender/python/intern/bpy_utils_units.c @@ -323,7 +323,6 @@ PyObject *BPY_utils_units(void) submodule = PyModule_Create(&bpyunits_module); PyDict_SetItemString(PyImport_GetModuleDict(), bpyunits_module.m_name, submodule); - Py_INCREF(submodule); /* Finalize our unit systems and types structseq definitions! */ diff --git a/source/blender/python/intern/gpu.c b/source/blender/python/intern/gpu.c deleted file mode 100644 index d902b6838f4..00000000000 --- a/source/blender/python/intern/gpu.c +++ /dev/null @@ -1,342 +0,0 @@ -/* - * ***** BEGIN GPL LICENSE BLOCK ***** - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 2006 Blender Foundation. - * All rights reserved. - * - * The Original Code is: all of this file. - * - * Contributor(s): Benoit Bolsee. - * - * ***** END GPL LICENSE BLOCK ***** - */ - -/** \file blender/python/intern/gpu.c - * \ingroup pythonintern - * - * This file defines the 'gpu' module, used to get GLSL shader code and data - * from blender materials. - */ - -#include <Python.h> - -#include "DNA_scene_types.h" -#include "DNA_material_types.h" -#include "DNA_ID.h" -#include "DNA_customdata_types.h" - -#include "BLI_listbase.h" -#include "BLI_utildefines.h" - -#include "RNA_access.h" - -#include "bpy_rna.h" - -#include "../generic/py_capi_utils.h" - -#include "GPU_material.h" - -#include "gpu.h" - -#define PY_MODULE_ADD_CONSTANT(module, name) PyModule_AddIntConstant(module, # name, name) - -PyDoc_STRVAR(M_gpu_doc, -"This module provides access to the GLSL shader and Offscreen rendering functionalities." -); -static struct PyModuleDef gpumodule = { - PyModuleDef_HEAD_INIT, - "gpu", /* name of module */ - M_gpu_doc, /* module documentation */ - -1, /* size of per-interpreter state of the module, - * or -1 if the module keeps state in global variables. */ - NULL, NULL, NULL, NULL, NULL -}; - -static PyObject *PyInit_gpu(void) -{ - PyObject *m; - - m = PyModule_Create(&gpumodule); - if (m == NULL) - return NULL; - - - /* Take care to update docs when editing: 'doc/python_api/rst/gpu.rst' */ - - - /* -------------------------------------------------------------------- */ - /* GPUDynamicType */ - - /* device constant groups */ - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_GROUP_MISC); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_GROUP_LAMP); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_GROUP_OBJECT); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_GROUP_SAMPLER); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_GROUP_MIST); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_GROUP_WORLD); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_GROUP_MAT); - - /* device constants */ - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_NONE); - /* GPU_DYNAMIC_GROUP_OBJECT */ - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_OBJECT_VIEWMAT); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_OBJECT_MAT); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_OBJECT_VIEWIMAT); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_OBJECT_IMAT); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_OBJECT_LOCTOVIEWMAT); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_OBJECT_LOCTOVIEWIMAT); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_OBJECT_COLOR); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_OBJECT_AUTOBUMPSCALE); - /* GPU_DYNAMIC_GROUP_LAMP */ - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_LAMP_DYNVEC); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_LAMP_DYNCO); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_LAMP_DYNIMAT); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_LAMP_DYNPERSMAT); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_LAMP_DYNENERGY); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_LAMP_DYNCOL); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_LAMP_ATT1); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_LAMP_ATT2); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_LAMP_DISTANCE); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_LAMP_SPOTSIZE); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_LAMP_SPOTBLEND); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_LAMP_SPOTSCALE); - /* GPU_DYNAMIC_GROUP_SAMPLER */ - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_SAMPLER_2DBUFFER); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_SAMPLER_2DIMAGE); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_SAMPLER_2DSHADOW); - /* GPU_DYNAMIC_GROUP_MIST */ - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_MIST_ENABLE); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_MIST_START); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_MIST_DISTANCE); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_MIST_INTENSITY); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_MIST_TYPE); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_MIST_COLOR); - /* GPU_DYNAMIC_GROUP_WORLD */ - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_HORIZON_COLOR); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_AMBIENT_COLOR); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_ZENITH_COLOR); - /* GPU_DYNAMIC_GROUP_MAT */ - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_MAT_DIFFRGB); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_MAT_REF); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_MAT_SPECRGB); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_MAT_SPEC); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_MAT_HARD); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_MAT_EMIT); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_MAT_AMB); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_MAT_ALPHA); - PY_MODULE_ADD_CONSTANT(m, GPU_DYNAMIC_MAT_MIR); - - - /* -------------------------------------------------------------------- */ - /* GPUDataType */ - - PY_MODULE_ADD_CONSTANT(m, GPU_DATA_1I); - PY_MODULE_ADD_CONSTANT(m, GPU_DATA_1F); - PY_MODULE_ADD_CONSTANT(m, GPU_DATA_2F); - PY_MODULE_ADD_CONSTANT(m, GPU_DATA_3F); - PY_MODULE_ADD_CONSTANT(m, GPU_DATA_4F); - PY_MODULE_ADD_CONSTANT(m, GPU_DATA_9F); - PY_MODULE_ADD_CONSTANT(m, GPU_DATA_16F); - PY_MODULE_ADD_CONSTANT(m, GPU_DATA_4UB); - - - /* -------------------------------------------------------------------- */ - /* CustomDataType - * - * Intentionally only include the subset used by the GPU API. - */ - PY_MODULE_ADD_CONSTANT(m, CD_MTFACE); - PY_MODULE_ADD_CONSTANT(m, CD_ORCO); - PY_MODULE_ADD_CONSTANT(m, CD_TANGENT); - PY_MODULE_ADD_CONSTANT(m, CD_MCOL); - return m; -} - -#define PY_DICT_ADD_STRING(d, s, f) \ - val = PyUnicode_FromString(s->f); \ - PyDict_SetItemString(d, # f, val); \ - Py_DECREF(val) - -#define PY_DICT_ADD_LONG(d, s, f) \ - val = PyLong_FromLong(s->f); \ - PyDict_SetItemString(d, # f, val); \ - Py_DECREF(val) - -#define PY_DICT_ADD_ID(d, s, f) \ - RNA_id_pointer_create((struct ID *)s->f, &tptr); \ - val = pyrna_struct_CreatePyObject(&tptr); \ - PyDict_SetItemString(d, # f, val); \ - Py_DECREF(val) - -#if 0 /* UNUSED */ -#define PY_OBJ_ADD_ID(d, s, f) \ - val = PyUnicode_FromString(&s->f->id.name[2]); \ - PyObject_SetAttrString(d, # f, val); \ - Py_DECREF(val) - -#define PY_OBJ_ADD_LONG(d, s, f) \ - val = PyLong_FromLong(s->f); \ - PyObject_SetAttrString(d, # f, val); \ - Py_DECREF(val) - -#define PY_OBJ_ADD_STRING(d, s, f) \ - val = PyUnicode_FromString(s->f); \ - PyObject_SetAttrString(d, # f, val); \ - Py_DECREF(val) -#endif - -PyDoc_STRVAR(GPU_export_shader_doc, -"export_shader(scene, material)\n" -"\n" -" Returns the GLSL shader that produces the visual effect of material in scene.\n" -"\n" -" :return: Dictionary defining the shader, uniforms and attributes.\n" -" :rtype: Dict" -); -static PyObject *GPU_export_shader(PyObject *UNUSED(self), PyObject *args, PyObject *kw) -{ - PyObject *pyscene; - PyObject *pymat; - PyObject *result; - PyObject *dict; - PyObject *val; - PyObject *seq; - - int i; - Scene *scene; - PointerRNA tptr; - Material *material; - GPUShaderExport *shader; - GPUInputUniform *uniform; - GPUInputAttribute *attribute; - - static const char *_keywords[] = {"scene", "material", NULL}; - static _PyArg_Parser _parser = {"OO:export_shader", _keywords, 0}; - if (!_PyArg_ParseTupleAndKeywordsFast( - args, kw, &_parser, - &pyscene, &pymat)) - { - return NULL; - } - scene = (Scene *)PyC_RNA_AsPointer(pyscene, "Scene"); - if (scene == NULL) { - return NULL; - } - - material = (Material *)PyC_RNA_AsPointer(pymat, "Material"); - if (material == NULL) { - return NULL; - } - - /* we can call our internal function at last: */ - shader = GPU_shader_export(scene, material); - if (!shader) { - PyErr_SetString(PyExc_RuntimeError, "cannot export shader"); - return NULL; - } - /* build a dictionary */ - result = PyDict_New(); - if (shader->fragment) { - PY_DICT_ADD_STRING(result, shader, fragment); - } - if (shader->vertex) { - PY_DICT_ADD_STRING(result, shader, vertex); - } - seq = PyList_New(BLI_listbase_count(&shader->uniforms)); - for (i = 0, uniform = shader->uniforms.first; uniform; uniform = uniform->next, i++) { - dict = PyDict_New(); - PY_DICT_ADD_STRING(dict, uniform, varname); - PY_DICT_ADD_LONG(dict, uniform, datatype); - PY_DICT_ADD_LONG(dict, uniform, type); - if (uniform->lamp) { - PY_DICT_ADD_ID(dict, uniform, lamp); - } - if (uniform->material) { - PY_DICT_ADD_ID(dict, uniform, material); - } - if (uniform->image) { - PY_DICT_ADD_ID(dict, uniform, image); - } - if (uniform->type == GPU_DYNAMIC_SAMPLER_2DBUFFER || - uniform->type == GPU_DYNAMIC_SAMPLER_2DIMAGE || - uniform->type == GPU_DYNAMIC_SAMPLER_2DSHADOW) - { - PY_DICT_ADD_LONG(dict, uniform, texnumber); - } - if (uniform->texpixels) { - val = PyByteArray_FromStringAndSize((const char *)uniform->texpixels, uniform->texsize * 4); - PyDict_SetItemString(dict, "texpixels", val); - Py_DECREF(val); - PY_DICT_ADD_LONG(dict, uniform, texsize); - } - PyList_SET_ITEM(seq, i, dict); - } - PyDict_SetItemString(result, "uniforms", seq); - Py_DECREF(seq); - - seq = PyList_New(BLI_listbase_count(&shader->attributes)); - for (i = 0, attribute = shader->attributes.first; attribute; attribute = attribute->next, i++) { - dict = PyDict_New(); - PY_DICT_ADD_STRING(dict, attribute, varname); - PY_DICT_ADD_LONG(dict, attribute, datatype); - PY_DICT_ADD_LONG(dict, attribute, type); - PY_DICT_ADD_LONG(dict, attribute, number); - if (attribute->name) { - if (attribute->name[0] != 0) { - PY_DICT_ADD_STRING(dict, attribute, name); - } - else { - val = PyLong_FromLong(0); - PyDict_SetItemString(dict, "name", val); - Py_DECREF(val); - } - } - PyList_SET_ITEM(seq, i, dict); - } - PyDict_SetItemString(result, "attributes", seq); - Py_DECREF(seq); - - GPU_free_shader_export(shader); - - return result; -} - -static PyMethodDef meth_export_shader[] = { - {"export_shader", (PyCFunction)GPU_export_shader, METH_VARARGS | METH_KEYWORDS, GPU_export_shader_doc} -}; - -/* -------------------------------------------------------------------- */ -/* Initialize Module */ - -PyObject *GPU_initPython(void) -{ - PyObject *module; - PyObject *submodule; - PyObject *sys_modules = PyImport_GetModuleDict(); - - module = PyInit_gpu(); - - PyModule_AddObject(module, "export_shader", (PyObject *)PyCFunction_New(meth_export_shader, NULL)); - - /* gpu.offscreen */ - PyModule_AddObject(module, "offscreen", (submodule = BPyInit_gpu_offscreen())); - PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule); - Py_INCREF(submodule); - - PyDict_SetItem(sys_modules, PyModule_GetNameObject(module), module); - return module; -} diff --git a/source/blender/python/intern/gpu_offscreen.c b/source/blender/python/intern/gpu_offscreen.c deleted file mode 100644 index 3b9b3c70ead..00000000000 --- a/source/blender/python/intern/gpu_offscreen.c +++ /dev/null @@ -1,408 +0,0 @@ -/* - * ***** BEGIN GPL LICENSE BLOCK ***** - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * Copyright 2015, Blender Foundation. - * - * ***** END GPL LICENSE BLOCK ***** - */ - -/** \file blender/python/intern/gpu_offscreen.c - * \ingroup pythonintern - * - * This file defines the offscreen functionalities of the 'gpu' module - * used for off-screen OpenGL rendering. - */ - -#include <Python.h> - -#include "MEM_guardedalloc.h" - -#include "BLI_utildefines.h" - -#include "WM_types.h" - -#include "BKE_global.h" -#include "BKE_library.h" - -#include "ED_screen.h" - -#include "GPU_compositing.h" -#include "GPU_framebuffer.h" - -#include "../mathutils/mathutils.h" - -#include "../generic/py_capi_utils.h" - -#include "gpu.h" - -#include "ED_view3d.h" - -/* -------------------------------------------------------------------- */ -/* GPU Offscreen PyObject */ - -typedef struct { - PyObject_HEAD - GPUOffScreen *ofs; -} BPy_GPUOffScreen; - -static int bpy_gpu_offscreen_valid_check(BPy_GPUOffScreen *py_gpu_ofs) -{ - if (UNLIKELY(py_gpu_ofs->ofs == NULL)) { - PyErr_SetString(PyExc_ReferenceError, "GPU offscreen was freed, no further access is valid"); - return -1; - } - return 0; -} - -#define BPY_GPU_OFFSCREEN_CHECK_OBJ(pygpu) { \ - if (UNLIKELY(bpy_gpu_offscreen_valid_check(pygpu) == -1)) { \ - return NULL; \ - } \ -} ((void)0) - -PyDoc_STRVAR(pygpu_offscreen_width_doc, "Texture width.\n\n:type: int"); -static PyObject *pygpu_offscreen_width_get(BPy_GPUOffScreen *self, void *UNUSED(type)) -{ - BPY_GPU_OFFSCREEN_CHECK_OBJ(self); - return PyLong_FromLong(GPU_offscreen_width(self->ofs)); -} - -PyDoc_STRVAR(pygpu_offscreen_height_doc, "Texture height.\n\n:type: int"); -static PyObject *pygpu_offscreen_height_get(BPy_GPUOffScreen *self, void *UNUSED(type)) -{ - BPY_GPU_OFFSCREEN_CHECK_OBJ(self); - return PyLong_FromLong(GPU_offscreen_height(self->ofs)); -} - -PyDoc_STRVAR(pygpu_offscreen_color_texture_doc, "Color texture.\n\n:type: int"); -static PyObject *pygpu_offscreen_color_texture_get(BPy_GPUOffScreen *self, void *UNUSED(type)) -{ - BPY_GPU_OFFSCREEN_CHECK_OBJ(self); - return PyLong_FromLong(GPU_offscreen_color_texture(self->ofs)); -} - -PyDoc_STRVAR(pygpu_offscreen_bind_doc, -"bind(save=True)\n" -"\n" -" Bind the offscreen object.\n" -"\n" -" :param save: save OpenGL current states.\n" -" :type save: bool\n" -); -static PyObject *pygpu_offscreen_bind(BPy_GPUOffScreen *self, PyObject *args, PyObject *kwds) -{ - static const char *kwlist[] = {"save", NULL}; - bool save = true; - - BPY_GPU_OFFSCREEN_CHECK_OBJ(self); - - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "|O&:bind", (char **)(kwlist), - PyC_ParseBool, &save)) - { - return NULL; - } - - GPU_offscreen_bind(self->ofs, save); - Py_RETURN_NONE; -} - -PyDoc_STRVAR(pygpu_offscreen_unbind_doc, -"unbind(restore=True)\n" -"\n" -" Unbind the offscreen object.\n" -"\n" -" :param restore: restore OpenGL previous states.\n" -" :type restore: bool\n" -); -static PyObject *pygpu_offscreen_unbind(BPy_GPUOffScreen *self, PyObject *args, PyObject *kwds) -{ - static const char *kwlist[] = {"restore", NULL}; - bool restore = true; - - BPY_GPU_OFFSCREEN_CHECK_OBJ(self); - - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "|O&:unbind", (char **)(kwlist), - PyC_ParseBool, &restore)) - { - return NULL; - } - - GPU_offscreen_unbind(self->ofs, restore); - Py_RETURN_NONE; -} - -PyDoc_STRVAR(pygpu_offscreen_draw_view3d_doc, -"draw_view3d(scene, view3d, region, modelview_matrix, projection_matrix)\n" -"\n" -" Draw the 3d viewport in the offscreen object.\n" -"\n" -" :param scene: Scene to draw.\n" -" :type scene: :class:`bpy.types.Scene`\n" -" :param view3d: 3D View to get the drawing settings from.\n" -" :type view3d: :class:`bpy.types.SpaceView3D`\n" -" :param region: Region of the 3D View.\n" -" :type region: :class:`bpy.types.Region`\n" -" :param modelview_matrix: ModelView Matrix.\n" -" :type modelview_matrix: :class:`mathutils.Matrix`\n" -" :param projection_matrix: Projection Matrix.\n" -" :type projection_matrix: :class:`mathutils.Matrix`\n" -); -static PyObject *pygpu_offscreen_draw_view3d(BPy_GPUOffScreen *self, PyObject *args, PyObject *kwds) -{ - static const char *kwlist[] = {"scene", "view3d", "region", "projection_matrix", "modelview_matrix", NULL}; - - MatrixObject *py_mat_modelview, *py_mat_projection; - PyObject *py_scene, *py_region, *py_view3d; - - struct Main *bmain = G_MAIN; /* XXX UGLY! */ - Scene *scene; - View3D *v3d; - ARegion *ar; - GPUFX *fx; - GPUFXSettings fx_settings; - struct RV3DMatrixStore *rv3d_mats; - - BPY_GPU_OFFSCREEN_CHECK_OBJ(self); - - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "OOOO&O&:draw_view3d", (char **)(kwlist), - &py_scene, &py_view3d, &py_region, - Matrix_Parse4x4, &py_mat_projection, - Matrix_Parse4x4, &py_mat_modelview) || - (!(scene = PyC_RNA_AsPointer(py_scene, "Scene")) || - !(v3d = PyC_RNA_AsPointer(py_view3d, "SpaceView3D")) || - !(ar = PyC_RNA_AsPointer(py_region, "Region")))) - { - return NULL; - } - - BLI_assert(BKE_id_is_in_gobal_main(&scene->id)); - - fx = GPU_fx_compositor_create(); - - fx_settings = v3d->fx_settings; /* full copy */ - - ED_view3d_draw_offscreen_init(bmain, scene, v3d); - - rv3d_mats = ED_view3d_mats_rv3d_backup(ar->regiondata); - - GPU_offscreen_bind(self->ofs, true); /* bind */ - - ED_view3d_draw_offscreen( - bmain, scene, v3d, ar, GPU_offscreen_width(self->ofs), GPU_offscreen_height(self->ofs), - (float(*)[4])py_mat_modelview->matrix, (float(*)[4])py_mat_projection->matrix, - false, true, true, "", - fx, &fx_settings, - self->ofs); - - GPU_fx_compositor_destroy(fx); - GPU_offscreen_unbind(self->ofs, true); /* unbind */ - - ED_view3d_mats_rv3d_restore(ar->regiondata, rv3d_mats); - MEM_freeN(rv3d_mats); - - Py_RETURN_NONE; -} - -PyDoc_STRVAR(pygpu_offscreen_free_doc, -"free()\n" -"\n" -" Free the offscreen object\n" -" The framebuffer, texture and render objects will no longer be accessible.\n" -); -static PyObject *pygpu_offscreen_free(BPy_GPUOffScreen *self) -{ - BPY_GPU_OFFSCREEN_CHECK_OBJ(self); - - GPU_offscreen_free(self->ofs); - self->ofs = NULL; - Py_RETURN_NONE; -} - -static void BPy_GPUOffScreen__tp_dealloc(BPy_GPUOffScreen *self) -{ - if (self->ofs) - GPU_offscreen_free(self->ofs); - Py_TYPE(self)->tp_free((PyObject *)self); -} - -static PyGetSetDef bpy_gpu_offscreen_getseters[] = { - {(char *)"color_texture", (getter)pygpu_offscreen_color_texture_get, (setter)NULL, pygpu_offscreen_color_texture_doc, NULL}, - {(char *)"width", (getter)pygpu_offscreen_width_get, (setter)NULL, pygpu_offscreen_width_doc, NULL}, - {(char *)"height", (getter)pygpu_offscreen_height_get, (setter)NULL, pygpu_offscreen_height_doc, NULL}, - {NULL, NULL, NULL, NULL, NULL} /* Sentinel */ -}; - -static struct PyMethodDef bpy_gpu_offscreen_methods[] = { - {"bind", (PyCFunction)pygpu_offscreen_bind, METH_VARARGS | METH_KEYWORDS, pygpu_offscreen_bind_doc}, - {"unbind", (PyCFunction)pygpu_offscreen_unbind, METH_VARARGS | METH_KEYWORDS, pygpu_offscreen_unbind_doc}, - {"draw_view3d", (PyCFunction)pygpu_offscreen_draw_view3d, METH_VARARGS | METH_KEYWORDS, pygpu_offscreen_draw_view3d_doc}, - {"free", (PyCFunction)pygpu_offscreen_free, METH_NOARGS, pygpu_offscreen_free_doc}, - {NULL, NULL, 0, NULL} -}; - -PyDoc_STRVAR(py_gpu_offscreen_doc, -".. class:: GPUOffscreen" -"\n" -" This object gives access to off screen buffers.\n" -); -static PyTypeObject BPy_GPUOffScreen_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "GPUOffScreen", /* tp_name */ - sizeof(BPy_GPUOffScreen), /* tp_basicsize */ - 0, /* tp_itemsize */ - /* methods */ - (destructor)BPy_GPUOffScreen__tp_dealloc, /* tp_dealloc */ - NULL, /* tp_print */ - NULL, /* tp_getattr */ - NULL, /* tp_setattr */ - NULL, /* tp_compare */ - NULL, /* tp_repr */ - NULL, /* tp_as_number */ - NULL, /* tp_as_sequence */ - NULL, /* tp_as_mapping */ - NULL, /* tp_hash */ - NULL, /* tp_call */ - NULL, /* tp_str */ - NULL, /* tp_getattro */ - NULL, /* tp_setattro */ - NULL, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ - py_gpu_offscreen_doc, /* Documentation string */ - NULL, /* tp_traverse */ - NULL, /* tp_clear */ - NULL, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - NULL, /* tp_iter */ - NULL, /* tp_iternext */ - bpy_gpu_offscreen_methods, /* tp_methods */ - NULL, /* tp_members */ - bpy_gpu_offscreen_getseters, /* tp_getset */ - NULL, /* tp_base */ - NULL, /* tp_dict */ - NULL, /* tp_descr_get */ - NULL, /* tp_descr_set */ - 0, /* tp_dictoffset */ - 0, /* tp_init */ - NULL, /* tp_alloc */ - NULL, /* tp_new */ - (freefunc)0, /* tp_free */ - NULL, /* tp_is_gc */ - NULL, /* tp_bases */ - NULL, /* tp_mro */ - NULL, /* tp_cache */ - NULL, /* tp_subclasses */ - NULL, /* tp_weaklist */ - (destructor) NULL /* tp_del */ -}; - -/* -------------------------------------------------------------------- */ -/* GPU offscreen methods */ - -static PyObject *BPy_GPU_OffScreen_CreatePyObject(GPUOffScreen *ofs) -{ - BPy_GPUOffScreen *self; - self = PyObject_New(BPy_GPUOffScreen, &BPy_GPUOffScreen_Type); - self->ofs = ofs; - return (PyObject *)self; -} - -PyDoc_STRVAR(pygpu_offscreen_new_doc, -"new(width, height, samples=0)\n" -"\n" -" Return a GPUOffScreen.\n" -"\n" -" :param width: Horizontal dimension of the buffer.\n" -" :type width: int`\n" -" :param height: Vertical dimension of the buffer.\n" -" :type height: int`\n" -" :param samples: OpenGL samples to use for MSAA or zero to disable.\n" -" :type samples: int\n" -" :return: Newly created off-screen buffer.\n" -" :rtype: :class:`gpu.GPUOffscreen`\n" -); -static PyObject *pygpu_offscreen_new(PyObject *UNUSED(self), PyObject *args, PyObject *kwds) -{ - static const char *kwlist[] = {"width", "height", "samples", NULL}; - - GPUOffScreen *ofs; - int width, height, samples = 0; - char err_out[256]; - - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "ii|i:new", (char **)(kwlist), - &width, &height, &samples)) - { - return NULL; - } - - ofs = GPU_offscreen_create(width, height, samples, err_out); - - if (ofs == NULL) { - PyErr_Format(PyExc_RuntimeError, - "gpu.offscreen.new(...) failed with '%s'", - err_out[0] ? err_out : "unknown error"); - return NULL; - } - - return BPy_GPU_OffScreen_CreatePyObject(ofs); -} - -static struct PyMethodDef BPy_GPU_offscreen_methods[] = { - {"new", (PyCFunction)pygpu_offscreen_new, METH_VARARGS | METH_KEYWORDS, pygpu_offscreen_new_doc}, - {NULL, NULL, 0, NULL} -}; - -PyDoc_STRVAR(BPy_GPU_offscreen_doc, -"This module provides access to offscreen rendering functions." -); -static PyModuleDef BPy_GPU_offscreen_module_def = { - PyModuleDef_HEAD_INIT, - "gpu.offscreen", /* m_name */ - BPy_GPU_offscreen_doc, /* m_doc */ - 0, /* m_size */ - BPy_GPU_offscreen_methods, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL, /* m_free */ -}; - -PyObject *BPyInit_gpu_offscreen(void) -{ - PyObject *submodule; - - /* Register the 'GPUOffscreen' class */ - if (PyType_Ready(&BPy_GPUOffScreen_Type)) { - return NULL; - } - - submodule = PyModule_Create(&BPy_GPU_offscreen_module_def); - -#define MODULE_TYPE_ADD(s, t) \ - PyModule_AddObject(s, t.tp_name, (PyObject *)&t); Py_INCREF((PyObject *)&t) - - MODULE_TYPE_ADD(submodule, BPy_GPUOffScreen_Type); - -#undef MODULE_TYPE_ADD - - return submodule; -} - -#undef BPY_GPU_OFFSCREEN_CHECK_OBJ |