From 3e5414e490a67adecb7364a53d09767d718c9025 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 3 Mar 2016 13:11:07 +1100 Subject: PyAPI: API for selectively writing data-blocks Useful for writing asset-libraries to a file, eg. `bpy.data.libraries.write(filepath, datablocks, relative_remap=False, fake_user=False)` --- source/blender/python/intern/CMakeLists.txt | 3 +- source/blender/python/intern/bpy.c | 4 +- source/blender/python/intern/bpy_library.c | 482 ----------------------- source/blender/python/intern/bpy_library.h | 3 +- source/blender/python/intern/bpy_library_load.c | 481 ++++++++++++++++++++++ source/blender/python/intern/bpy_library_write.c | 211 ++++++++++ 6 files changed, 699 insertions(+), 485 deletions(-) delete mode 100644 source/blender/python/intern/bpy_library.c create mode 100644 source/blender/python/intern/bpy_library_load.c create mode 100644 source/blender/python/intern/bpy_library_write.c (limited to 'source') diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt index 8ce94b8006f..fa7c94025f7 100644 --- a/source/blender/python/intern/CMakeLists.txt +++ b/source/blender/python/intern/CMakeLists.txt @@ -61,7 +61,8 @@ set(SRC bpy_interface.c bpy_interface_atexit.c bpy_intern_string.c - bpy_library.c + bpy_library_load.c + bpy_library_write.c bpy_operator.c bpy_operator_wrap.c bpy_path.c diff --git a/source/blender/python/intern/bpy.c b/source/blender/python/intern/bpy.c index 930950a6600..c09b39139c2 100644 --- a/source/blender/python/intern/bpy.c +++ b/source/blender/python/intern/bpy.c @@ -322,7 +322,9 @@ void BPy_init_modules(void) PyModule_AddObject(mod, "types", BPY_rna_types()); /* needs to be first so bpy_types can run */ - BPY_library_module(mod); + BPY_library_load_module(mod); + BPY_library_write_module(mod); + BPY_rna_id_collection_module(mod); bpy_import_test("bpy_types"); diff --git a/source/blender/python/intern/bpy_library.c b/source/blender/python/intern/bpy_library.c deleted file mode 100644 index dd1be67946b..00000000000 --- a/source/blender/python/intern/bpy_library.c +++ /dev/null @@ -1,482 +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. - * - * Contributor(s): Campbell Barton - * - * ***** END GPL LICENSE BLOCK ***** - */ - -/** \file blender/python/intern/bpy_library.c - * \ingroup pythonintern - * - * This file exposed blend file library appending/linking to python, typically - * this would be done via RNA api but in this case a hand written python api - * allows us to use pythons context manager (__enter__ and __exit__). - * - * Everything here is exposed via bpy.data.libraries.load(...) which returns - * a context manager. - */ - -#include -#include - -#include "BLI_utildefines.h" -#include "BLI_string.h" -#include "BLI_linklist.h" -#include "BLI_path_util.h" - -#include "BLO_readfile.h" - -#include "BKE_global.h" -#include "BKE_main.h" -#include "BKE_library.h" -#include "BKE_idcode.h" -#include "BKE_report.h" -#include "BKE_context.h" - -#include "DNA_space_types.h" /* FILE_LINK, FILE_RELPATH */ - -#include "bpy_util.h" -#include "bpy_library.h" - -#include "../generic/py_capi_utils.h" -#include "../generic/python_utildefines.h" - -/* nifty feature. swap out strings for RNA data */ -#define USE_RNA_DATABLOCKS - -#ifdef USE_RNA_DATABLOCKS -# include "bpy_rna.h" -# include "RNA_access.h" -#endif - -typedef struct { - PyObject_HEAD /* required python macro */ - /* collection iterator specific parts */ - char relpath[FILE_MAX]; - char abspath[FILE_MAX]; /* absolute path */ - BlendHandle *blo_handle; - int flag; - PyObject *dict; -} BPy_Library; - -static PyObject *bpy_lib_load(PyObject *self, PyObject *args, PyObject *kwds); -static PyObject *bpy_lib_enter(BPy_Library *self, PyObject *args); -static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *args); -static PyObject *bpy_lib_dir(BPy_Library *self); - -static PyMethodDef bpy_lib_methods[] = { - {"__enter__", (PyCFunction)bpy_lib_enter, METH_NOARGS}, - {"__exit__", (PyCFunction)bpy_lib_exit, METH_VARARGS}, - {"__dir__", (PyCFunction)bpy_lib_dir, METH_NOARGS}, - {NULL} /* sentinel */ -}; - -static void bpy_lib_dealloc(BPy_Library *self) -{ - Py_XDECREF(self->dict); - Py_TYPE(self)->tp_free(self); -} - - -static PyTypeObject bpy_lib_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "bpy_lib", /* tp_name */ - sizeof(BPy_Library), /* tp_basicsize */ - 0, /* tp_itemsize */ - /* methods */ - (destructor)bpy_lib_dealloc, /* tp_dealloc */ - NULL, /* printfunc tp_print; */ - NULL, /* getattrfunc tp_getattr; */ - NULL, /* setattrfunc tp_setattr; */ - NULL, /* tp_compare */ /* DEPRECATED in python 3.0! */ - NULL, /* tp_repr */ - - /* Method suites for standard classes */ - - NULL, /* PyNumberMethods *tp_as_number; */ - NULL, /* PySequenceMethods *tp_as_sequence; */ - NULL, /* PyMappingMethods *tp_as_mapping; */ - - /* More standard operations (here for binary compatibility) */ - - NULL, /* hashfunc tp_hash; */ - NULL, /* ternaryfunc tp_call; */ - NULL, /* reprfunc tp_str; */ - - /* will only use these if this is a subtype of a py class */ - NULL /*PyObject_GenericGetAttr is assigned later */, /* getattrofunc tp_getattro; */ - NULL, /* setattrofunc tp_setattro; */ - - /* Functions to access object as input/output buffer */ - NULL, /* PyBufferProcs *tp_as_buffer; */ - - /*** Flags to define presence of optional/expanded features ***/ - Py_TPFLAGS_DEFAULT, /* long tp_flags; */ - - NULL, /* char *tp_doc; Documentation string */ - /*** Assigned meaning in release 2.0 ***/ - /* call function for all accessible objects */ - NULL, /* traverseproc tp_traverse; */ - - /* delete references to contained objects */ - NULL, /* inquiry tp_clear; */ - - /*** Assigned meaning in release 2.1 ***/ - /*** rich comparisons ***/ - NULL, /* subclassed */ /* richcmpfunc tp_richcompare; */ - - /*** weak reference enabler ***/ - 0, - /*** Added in release 2.2 ***/ - /* Iterators */ - NULL, /* getiterfunc tp_iter; */ - NULL, /* iternextfunc tp_iternext; */ - - /*** Attribute descriptor and subclassing stuff ***/ - bpy_lib_methods, /* struct PyMethodDef *tp_methods; */ - NULL, /* struct PyMemberDef *tp_members; */ - NULL, /* struct PyGetSetDef *tp_getset; */ - NULL, /* struct _typeobject *tp_base; */ - NULL, /* PyObject *tp_dict; */ - NULL, /* descrgetfunc tp_descr_get; */ - NULL, /* descrsetfunc tp_descr_set; */ - offsetof(BPy_Library, dict), /* long tp_dictoffset; */ - NULL, /* initproc tp_init; */ - NULL, /* allocfunc tp_alloc; */ - NULL, /* newfunc tp_new; */ - /* Low-level free-memory routine */ - NULL, /* freefunc tp_free; */ - /* For PyObject_IS_GC */ - NULL, /* inquiry tp_is_gc; */ - NULL, /* PyObject *tp_bases; */ - /* method resolution order */ - NULL, /* PyObject *tp_mro; */ - NULL, /* PyObject *tp_cache; */ - NULL, /* PyObject *tp_subclasses; */ - NULL, /* PyObject *tp_weaklist; */ - NULL -}; - -PyDoc_STRVAR(bpy_lib_load_doc, -".. method:: load(filepath, link=False, relative=False)\n" -"\n" -" Returns a context manager which exposes 2 library objects on entering.\n" -" Each object has attributes matching bpy.data which are lists of strings to be linked.\n" -"\n" -" :arg filepath: The path to a blend file.\n" -" :type filepath: string\n" -" :arg link: When False reference to the original file is lost.\n" -" :type link: bool\n" -" :arg relative: When True the path is stored relative to the open blend file.\n" -" :type relative: bool\n" -); -static PyObject *bpy_lib_load(PyObject *UNUSED(self), PyObject *args, PyObject *kwds) -{ - static const char *kwlist[] = {"filepath", "link", "relative", NULL}; - BPy_Library *ret; - const char *filename = NULL; - bool is_rel = false, is_link = false; - - if (!PyArg_ParseTupleAndKeywords( - args, kwds, - "s|O&O&:load", (char **)kwlist, - &filename, - PyC_ParseBool, &is_link, - PyC_ParseBool, &is_rel)) - { - return NULL; - } - - ret = PyObject_New(BPy_Library, &bpy_lib_Type); - - BLI_strncpy(ret->relpath, filename, sizeof(ret->relpath)); - BLI_strncpy(ret->abspath, filename, sizeof(ret->abspath)); - BLI_path_abs(ret->abspath, G.main->name); - - ret->blo_handle = NULL; - ret->flag = ((is_link ? FILE_LINK : 0) | - (is_rel ? FILE_RELPATH : 0)); - - ret->dict = PyDict_New(); - - return (PyObject *)ret; -} - -static PyObject *_bpy_names(BPy_Library *self, int blocktype) -{ - PyObject *list; - LinkNode *l, *names; - int totnames; - - names = BLO_blendhandle_get_datablock_names(self->blo_handle, blocktype, &totnames); - - if (names) { - int counter = 0; - list = PyList_New(totnames); - for (l = names; l; l = l->next) { - PyList_SET_ITEM(list, counter, PyUnicode_FromString((char *)l->link)); - counter++; - } - BLI_linklist_free(names, free); /* free linklist *and* each node's data */ - } - else { - list = PyList_New(0); - } - - return list; -} - -static PyObject *bpy_lib_enter(BPy_Library *self, PyObject *UNUSED(args)) -{ - PyObject *ret; - BPy_Library *self_from; - PyObject *from_dict = PyDict_New(); - ReportList reports; - - BKE_reports_init(&reports, RPT_STORE); - - self->blo_handle = BLO_blendhandle_from_file(self->abspath, &reports); - - if (self->blo_handle == NULL) { - if (BPy_reports_to_error(&reports, PyExc_IOError, true) != -1) { - PyErr_Format(PyExc_IOError, - "load: %s failed to open blend file", - self->abspath); - } - return NULL; - } - else { - int i = 0, code; - while ((code = BKE_idcode_iter_step(&i))) { - if (BKE_idcode_is_linkable(code)) { - const char *name_plural = BKE_idcode_to_name_plural(code); - PyObject *str = PyUnicode_FromString(name_plural); - PyDict_SetItem(self->dict, str, PyList_New(0)); - PyDict_SetItem(from_dict, str, _bpy_names(self, code)); - Py_DECREF(str); - } - } - } - - /* create a dummy */ - self_from = PyObject_New(BPy_Library, &bpy_lib_Type); - BLI_strncpy(self_from->relpath, self->relpath, sizeof(self_from->relpath)); - BLI_strncpy(self_from->abspath, self->abspath, sizeof(self_from->abspath)); - - self_from->blo_handle = NULL; - self_from->flag = 0; - self_from->dict = from_dict; /* owns the dict */ - - /* return pair */ - ret = PyTuple_New(2); - PyTuple_SET_ITEMS(ret, - (PyObject *)self_from, - (PyObject *)self); - Py_INCREF(self); - - BKE_reports_clear(&reports); - - return ret; -} - -static void bpy_lib_exit_warn_idname(BPy_Library *self, const char *name_plural, const char *idname) -{ - PyObject *exc, *val, *tb; - PyErr_Fetch(&exc, &val, &tb); - if (PyErr_WarnFormat(PyExc_UserWarning, 1, - "load: '%s' does not contain %s[\"%s\"]", - self->abspath, name_plural, idname)) - { - /* Spurious errors can appear at shutdown */ - if (PyErr_ExceptionMatches(PyExc_Warning)) { - PyErr_WriteUnraisable((PyObject *)self); - } - } - PyErr_Restore(exc, val, tb); -} - -static void bpy_lib_exit_warn_type(BPy_Library *self, PyObject *item) -{ - PyObject *exc, *val, *tb; - PyErr_Fetch(&exc, &val, &tb); - if (PyErr_WarnFormat(PyExc_UserWarning, 1, - "load: '%s' expected a string type, not a %.200s", - self->abspath, Py_TYPE(item)->tp_name)) - { - /* Spurious errors can appear at shutdown */ - if (PyErr_ExceptionMatches(PyExc_Warning)) { - PyErr_WriteUnraisable((PyObject *)self); - } - } - PyErr_Restore(exc, val, tb); -} - -static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *UNUSED(args)) -{ - Main *bmain = CTX_data_main(BPy_GetContext()); - Main *mainl = NULL; - int err = 0; - - BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true); - - /* here appending/linking starts */ - mainl = BLO_library_link_begin(bmain, &(self->blo_handle), self->relpath); - - { - int idcode_step = 0, idcode; - while ((idcode = BKE_idcode_iter_step(&idcode_step))) { - if (BKE_idcode_is_linkable(idcode)) { - const char *name_plural = BKE_idcode_to_name_plural(idcode); - PyObject *ls = PyDict_GetItemString(self->dict, name_plural); - // printf("lib: %s\n", name_plural); - if (ls && PyList_Check(ls)) { - /* loop */ - Py_ssize_t size = PyList_GET_SIZE(ls); - Py_ssize_t i; - PyObject *item; - const char *item_str; - - for (i = 0; i < size; i++) { - item = PyList_GET_ITEM(ls, i); - item_str = _PyUnicode_AsString(item); - - // printf(" %s\n", item_str); - - if (item_str) { - ID *id = BLO_library_link_named_part(mainl, &(self->blo_handle), idcode, item_str); - if (id) { -#ifdef USE_RNA_DATABLOCKS - /* swap name for pointer to the id */ - Py_DECREF(item); - item = PyCapsule_New((void *)id, NULL, NULL); -#endif - } - else { - bpy_lib_exit_warn_idname(self, name_plural, item_str); - /* just warn for now */ - /* err = -1; */ -#ifdef USE_RNA_DATABLOCKS - item = Py_INCREF_RET(Py_None); -#endif - } - - /* ID or None */ - } - else { - /* XXX, could complain about this */ - bpy_lib_exit_warn_type(self, item); - PyErr_Clear(); - -#ifdef USE_RNA_DATABLOCKS - item = Py_INCREF_RET(Py_None); -#endif - } - -#ifdef USE_RNA_DATABLOCKS - PyList_SET_ITEM(ls, i, item); -#endif - } - } - } - } - } - - if (err == -1) { - /* exception raised above, XXX, this leaks some memory */ - BLO_blendhandle_close(self->blo_handle); - self->blo_handle = NULL; - BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); - return NULL; - } - 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_blendhandle_close(self->blo_handle); - self->blo_handle = NULL; - - /* copied from wm_operator.c */ - { - /* mark all library linked objects to be updated */ - BKE_main_lib_objects_recalc_all(G.main); - - /* append, rather than linking */ - if ((self->flag & FILE_LINK) == 0) { - BKE_library_make_local(bmain, lib, true, false); - } - } - - BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); - - /* finally swap the capsules for real bpy objects - * important since BLO_library_append_end initializes NodeTree types used by srna->refine */ - { - int idcode_step = 0, idcode; - while ((idcode = BKE_idcode_iter_step(&idcode_step))) { - if (BKE_idcode_is_linkable(idcode)) { - const char *name_plural = BKE_idcode_to_name_plural(idcode); - PyObject *ls = PyDict_GetItemString(self->dict, name_plural); - if (ls && PyList_Check(ls)) { - Py_ssize_t size = PyList_GET_SIZE(ls); - Py_ssize_t i; - PyObject *item; - - for (i = 0; i < size; i++) { - item = PyList_GET_ITEM(ls, i); - if (PyCapsule_CheckExact(item)) { - PointerRNA id_ptr; - ID *id; - - id = PyCapsule_GetPointer(item, NULL); - Py_DECREF(item); - - RNA_id_pointer_create(id, &id_ptr); - item = pyrna_struct_CreatePyObject(&id_ptr); - PyList_SET_ITEM(ls, i, item); - } - } - } - } - } - } - - Py_RETURN_NONE; - } -} - -static PyObject *bpy_lib_dir(BPy_Library *self) -{ - return PyDict_Keys(self->dict); -} - - -int BPY_library_module(PyObject *mod_par) -{ - static PyMethodDef load_meth = {"load", (PyCFunction)bpy_lib_load, - METH_STATIC | METH_VARARGS | METH_KEYWORDS, - bpy_lib_load_doc}; - - PyModule_AddObject(mod_par, "_library_load", PyCFunction_New(&load_meth, NULL)); - - /* some compilers don't like accessing this directly, delay assignment */ - bpy_lib_Type.tp_getattro = PyObject_GenericGetAttr; - - if (PyType_Ready(&bpy_lib_Type) < 0) - return -1; - - return 0; -} diff --git a/source/blender/python/intern/bpy_library.h b/source/blender/python/intern/bpy_library.h index 1b68007b704..c381a4206fa 100644 --- a/source/blender/python/intern/bpy_library.h +++ b/source/blender/python/intern/bpy_library.h @@ -27,6 +27,7 @@ #ifndef __BPY_LIBRARY_H__ #define __BPY_LIBRARY_H__ -int BPY_library_module(PyObject *); +int BPY_library_load_module(PyObject *mod_par); +int BPY_library_write_module(PyObject *mod_par); #endif /* __BPY_LIBRARY_H__ */ diff --git a/source/blender/python/intern/bpy_library_load.c b/source/blender/python/intern/bpy_library_load.c new file mode 100644 index 00000000000..a120e4886e0 --- /dev/null +++ b/source/blender/python/intern/bpy_library_load.c @@ -0,0 +1,481 @@ +/* + * ***** 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_library_load.c + * \ingroup pythonintern + * + * This file exposed blend file library appending/linking to python, typically + * this would be done via RNA api but in this case a hand written python api + * allows us to use pythons context manager (__enter__ and __exit__). + * + * Everything here is exposed via bpy.data.libraries.load(...) which returns + * a context manager. + */ + +#include +#include + +#include "BLI_utildefines.h" +#include "BLI_string.h" +#include "BLI_linklist.h" +#include "BLI_path_util.h" + +#include "BLO_readfile.h" + +#include "BKE_global.h" +#include "BKE_main.h" +#include "BKE_library.h" +#include "BKE_idcode.h" +#include "BKE_report.h" +#include "BKE_context.h" + +#include "DNA_space_types.h" /* FILE_LINK, FILE_RELPATH */ + +#include "bpy_util.h" +#include "bpy_library.h" + +#include "../generic/py_capi_utils.h" +#include "../generic/python_utildefines.h" + +/* nifty feature. swap out strings for RNA data */ +#define USE_RNA_DATABLOCKS + +#ifdef USE_RNA_DATABLOCKS +# include "bpy_rna.h" +# include "RNA_access.h" +#endif + +typedef struct { + PyObject_HEAD /* required python macro */ + /* collection iterator specific parts */ + char relpath[FILE_MAX]; + char abspath[FILE_MAX]; /* absolute path */ + BlendHandle *blo_handle; + int flag; + PyObject *dict; +} BPy_Library; + +static PyObject *bpy_lib_load(PyObject *self, PyObject *args, PyObject *kwds); +static PyObject *bpy_lib_enter(BPy_Library *self, PyObject *args); +static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *args); +static PyObject *bpy_lib_dir(BPy_Library *self); + +static PyMethodDef bpy_lib_methods[] = { + {"__enter__", (PyCFunction)bpy_lib_enter, METH_NOARGS}, + {"__exit__", (PyCFunction)bpy_lib_exit, METH_VARARGS}, + {"__dir__", (PyCFunction)bpy_lib_dir, METH_NOARGS}, + {NULL} /* sentinel */ +}; + +static void bpy_lib_dealloc(BPy_Library *self) +{ + Py_XDECREF(self->dict); + Py_TYPE(self)->tp_free(self); +} + + +static PyTypeObject bpy_lib_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "bpy_lib", /* tp_name */ + sizeof(BPy_Library), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)bpy_lib_dealloc, /* tp_dealloc */ + NULL, /* printfunc tp_print; */ + NULL, /* getattrfunc tp_getattr; */ + NULL, /* setattrfunc tp_setattr; */ + NULL, /* tp_compare */ /* DEPRECATED in python 3.0! */ + NULL, /* tp_repr */ + + /* Method suites for standard classes */ + + NULL, /* PyNumberMethods *tp_as_number; */ + NULL, /* PySequenceMethods *tp_as_sequence; */ + NULL, /* PyMappingMethods *tp_as_mapping; */ + + /* More standard operations (here for binary compatibility) */ + + NULL, /* hashfunc tp_hash; */ + NULL, /* ternaryfunc tp_call; */ + NULL, /* reprfunc tp_str; */ + + /* will only use these if this is a subtype of a py class */ + NULL /*PyObject_GenericGetAttr is assigned later */, /* getattrofunc tp_getattro; */ + NULL, /* setattrofunc tp_setattro; */ + + /* Functions to access object as input/output buffer */ + NULL, /* PyBufferProcs *tp_as_buffer; */ + + /*** Flags to define presence of optional/expanded features ***/ + Py_TPFLAGS_DEFAULT, /* long tp_flags; */ + + NULL, /* char *tp_doc; Documentation string */ + /*** Assigned meaning in release 2.0 ***/ + /* call function for all accessible objects */ + NULL, /* traverseproc tp_traverse; */ + + /* delete references to contained objects */ + NULL, /* inquiry tp_clear; */ + + /*** Assigned meaning in release 2.1 ***/ + /*** rich comparisons ***/ + NULL, /* subclassed */ /* richcmpfunc tp_richcompare; */ + + /*** weak reference enabler ***/ + 0, + /*** Added in release 2.2 ***/ + /* Iterators */ + NULL, /* getiterfunc tp_iter; */ + NULL, /* iternextfunc tp_iternext; */ + + /*** Attribute descriptor and subclassing stuff ***/ + bpy_lib_methods, /* struct PyMethodDef *tp_methods; */ + NULL, /* struct PyMemberDef *tp_members; */ + NULL, /* struct PyGetSetDef *tp_getset; */ + NULL, /* struct _typeobject *tp_base; */ + NULL, /* PyObject *tp_dict; */ + NULL, /* descrgetfunc tp_descr_get; */ + NULL, /* descrsetfunc tp_descr_set; */ + offsetof(BPy_Library, dict), /* long tp_dictoffset; */ + NULL, /* initproc tp_init; */ + NULL, /* allocfunc tp_alloc; */ + NULL, /* newfunc tp_new; */ + /* Low-level free-memory routine */ + NULL, /* freefunc tp_free; */ + /* For PyObject_IS_GC */ + NULL, /* inquiry tp_is_gc; */ + NULL, /* PyObject *tp_bases; */ + /* method resolution order */ + NULL, /* PyObject *tp_mro; */ + NULL, /* PyObject *tp_cache; */ + NULL, /* PyObject *tp_subclasses; */ + NULL, /* PyObject *tp_weaklist; */ + NULL +}; + +PyDoc_STRVAR(bpy_lib_load_doc, +".. method:: load(filepath, link=False, relative=False)\n" +"\n" +" Returns a context manager which exposes 2 library objects on entering.\n" +" Each object has attributes matching bpy.data which are lists of strings to be linked.\n" +"\n" +" :arg filepath: The path to a blend file.\n" +" :type filepath: string\n" +" :arg link: When False reference to the original file is lost.\n" +" :type link: bool\n" +" :arg relative: When True the path is stored relative to the open blend file.\n" +" :type relative: bool\n" +); +static PyObject *bpy_lib_load(PyObject *UNUSED(self), PyObject *args, PyObject *kwds) +{ + static const char *kwlist[] = {"filepath", "link", "relative", NULL}; + BPy_Library *ret; + const char *filename = NULL; + bool is_rel = false, is_link = false; + + if (!PyArg_ParseTupleAndKeywords( + args, kwds, + "s|O&O&:load", (char **)kwlist, + &filename, + PyC_ParseBool, &is_link, + PyC_ParseBool, &is_rel)) + { + return NULL; + } + + ret = PyObject_New(BPy_Library, &bpy_lib_Type); + + BLI_strncpy(ret->relpath, filename, sizeof(ret->relpath)); + BLI_strncpy(ret->abspath, filename, sizeof(ret->abspath)); + BLI_path_abs(ret->abspath, G.main->name); + + ret->blo_handle = NULL; + ret->flag = ((is_link ? FILE_LINK : 0) | + (is_rel ? FILE_RELPATH : 0)); + + ret->dict = PyDict_New(); + + return (PyObject *)ret; +} + +static PyObject *_bpy_names(BPy_Library *self, int blocktype) +{ + PyObject *list; + LinkNode *l, *names; + int totnames; + + names = BLO_blendhandle_get_datablock_names(self->blo_handle, blocktype, &totnames); + + if (names) { + int counter = 0; + list = PyList_New(totnames); + for (l = names; l; l = l->next) { + PyList_SET_ITEM(list, counter, PyUnicode_FromString((char *)l->link)); + counter++; + } + BLI_linklist_free(names, free); /* free linklist *and* each node's data */ + } + else { + list = PyList_New(0); + } + + return list; +} + +static PyObject *bpy_lib_enter(BPy_Library *self, PyObject *UNUSED(args)) +{ + PyObject *ret; + BPy_Library *self_from; + PyObject *from_dict = PyDict_New(); + ReportList reports; + + BKE_reports_init(&reports, RPT_STORE); + + self->blo_handle = BLO_blendhandle_from_file(self->abspath, &reports); + + if (self->blo_handle == NULL) { + if (BPy_reports_to_error(&reports, PyExc_IOError, true) != -1) { + PyErr_Format(PyExc_IOError, + "load: %s failed to open blend file", + self->abspath); + } + return NULL; + } + else { + int i = 0, code; + while ((code = BKE_idcode_iter_step(&i))) { + if (BKE_idcode_is_linkable(code)) { + const char *name_plural = BKE_idcode_to_name_plural(code); + PyObject *str = PyUnicode_FromString(name_plural); + PyDict_SetItem(self->dict, str, PyList_New(0)); + PyDict_SetItem(from_dict, str, _bpy_names(self, code)); + Py_DECREF(str); + } + } + } + + /* create a dummy */ + self_from = PyObject_New(BPy_Library, &bpy_lib_Type); + BLI_strncpy(self_from->relpath, self->relpath, sizeof(self_from->relpath)); + BLI_strncpy(self_from->abspath, self->abspath, sizeof(self_from->abspath)); + + self_from->blo_handle = NULL; + self_from->flag = 0; + self_from->dict = from_dict; /* owns the dict */ + + /* return pair */ + ret = PyTuple_New(2); + PyTuple_SET_ITEMS(ret, + (PyObject *)self_from, + (PyObject *)self); + Py_INCREF(self); + + BKE_reports_clear(&reports); + + return ret; +} + +static void bpy_lib_exit_warn_idname(BPy_Library *self, const char *name_plural, const char *idname) +{ + PyObject *exc, *val, *tb; + PyErr_Fetch(&exc, &val, &tb); + if (PyErr_WarnFormat(PyExc_UserWarning, 1, + "load: '%s' does not contain %s[\"%s\"]", + self->abspath, name_plural, idname)) + { + /* Spurious errors can appear at shutdown */ + if (PyErr_ExceptionMatches(PyExc_Warning)) { + PyErr_WriteUnraisable((PyObject *)self); + } + } + PyErr_Restore(exc, val, tb); +} + +static void bpy_lib_exit_warn_type(BPy_Library *self, PyObject *item) +{ + PyObject *exc, *val, *tb; + PyErr_Fetch(&exc, &val, &tb); + if (PyErr_WarnFormat(PyExc_UserWarning, 1, + "load: '%s' expected a string type, not a %.200s", + self->abspath, Py_TYPE(item)->tp_name)) + { + /* Spurious errors can appear at shutdown */ + if (PyErr_ExceptionMatches(PyExc_Warning)) { + PyErr_WriteUnraisable((PyObject *)self); + } + } + PyErr_Restore(exc, val, tb); +} + +static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *UNUSED(args)) +{ + Main *bmain = CTX_data_main(BPy_GetContext()); + Main *mainl = NULL; + int err = 0; + + BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true); + + /* here appending/linking starts */ + mainl = BLO_library_link_begin(bmain, &(self->blo_handle), self->relpath); + + { + int idcode_step = 0, idcode; + while ((idcode = BKE_idcode_iter_step(&idcode_step))) { + if (BKE_idcode_is_linkable(idcode)) { + const char *name_plural = BKE_idcode_to_name_plural(idcode); + PyObject *ls = PyDict_GetItemString(self->dict, name_plural); + // printf("lib: %s\n", name_plural); + if (ls && PyList_Check(ls)) { + /* loop */ + Py_ssize_t size = PyList_GET_SIZE(ls); + Py_ssize_t i; + PyObject *item; + const char *item_str; + + for (i = 0; i < size; i++) { + item = PyList_GET_ITEM(ls, i); + item_str = _PyUnicode_AsString(item); + + // printf(" %s\n", item_str); + + if (item_str) { + ID *id = BLO_library_link_named_part(mainl, &(self->blo_handle), idcode, item_str); + if (id) { +#ifdef USE_RNA_DATABLOCKS + /* swap name for pointer to the id */ + Py_DECREF(item); + item = PyCapsule_New((void *)id, NULL, NULL); +#endif + } + else { + bpy_lib_exit_warn_idname(self, name_plural, item_str); + /* just warn for now */ + /* err = -1; */ +#ifdef USE_RNA_DATABLOCKS + item = Py_INCREF_RET(Py_None); +#endif + } + + /* ID or None */ + } + else { + /* XXX, could complain about this */ + bpy_lib_exit_warn_type(self, item); + PyErr_Clear(); + +#ifdef USE_RNA_DATABLOCKS + item = Py_INCREF_RET(Py_None); +#endif + } + +#ifdef USE_RNA_DATABLOCKS + PyList_SET_ITEM(ls, i, item); +#endif + } + } + } + } + } + + if (err == -1) { + /* exception raised above, XXX, this leaks some memory */ + BLO_blendhandle_close(self->blo_handle); + self->blo_handle = NULL; + BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); + return NULL; + } + 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_blendhandle_close(self->blo_handle); + self->blo_handle = NULL; + + /* copied from wm_operator.c */ + { + /* mark all library linked objects to be updated */ + BKE_main_lib_objects_recalc_all(G.main); + + /* append, rather than linking */ + if ((self->flag & FILE_LINK) == 0) { + BKE_library_make_local(bmain, lib, true, false); + } + } + + BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); + + /* finally swap the capsules for real bpy objects + * important since BLO_library_append_end initializes NodeTree types used by srna->refine */ + { + int idcode_step = 0, idcode; + while ((idcode = BKE_idcode_iter_step(&idcode_step))) { + if (BKE_idcode_is_linkable(idcode)) { + const char *name_plural = BKE_idcode_to_name_plural(idcode); + PyObject *ls = PyDict_GetItemString(self->dict, name_plural); + if (ls && PyList_Check(ls)) { + Py_ssize_t size = PyList_GET_SIZE(ls); + Py_ssize_t i; + PyObject *item; + + for (i = 0; i < size; i++) { + item = PyList_GET_ITEM(ls, i); + if (PyCapsule_CheckExact(item)) { + PointerRNA id_ptr; + ID *id; + + id = PyCapsule_GetPointer(item, NULL); + Py_DECREF(item); + + RNA_id_pointer_create(id, &id_ptr); + item = pyrna_struct_CreatePyObject(&id_ptr); + PyList_SET_ITEM(ls, i, item); + } + } + } + } + } + } + + Py_RETURN_NONE; + } +} + +static PyObject *bpy_lib_dir(BPy_Library *self) +{ + return PyDict_Keys(self->dict); +} + + +int BPY_library_load_module(PyObject *mod_par) +{ + static PyMethodDef load_meth = { + "load", (PyCFunction)bpy_lib_load, + METH_STATIC | METH_VARARGS | METH_KEYWORDS, + bpy_lib_load_doc, + }; + PyModule_AddObject(mod_par, "_library_load", PyCFunction_New(&load_meth, NULL)); + + /* some compilers don't like accessing this directly, delay assignment */ + bpy_lib_Type.tp_getattro = PyObject_GenericGetAttr; + + if (PyType_Ready(&bpy_lib_Type) < 0) + return -1; + + return 0; +} diff --git a/source/blender/python/intern/bpy_library_write.c b/source/blender/python/intern/bpy_library_write.c new file mode 100644 index 00000000000..255bb82ea10 --- /dev/null +++ b/source/blender/python/intern/bpy_library_write.c @@ -0,0 +1,211 @@ +/* + * ***** 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_library_write.c + * \ingroup pythonintern + * + * Python API for writing a set of data-blocks into a file. + * Useful for writing out asset-libraries, defines: `bpy.data.libraries.write(...)`. + */ + +#include +#include + +#include "MEM_guardedalloc.h" + +#include "BLI_utildefines.h" +#include "BLI_string.h" +#include "BLI_path_util.h" + +#include "BKE_library.h" +#include "BKE_blender.h" +#include "BKE_global.h" +#include "BKE_main.h" +#include "BKE_report.h" + +#include "RNA_types.h" + +#include "bpy_rna.h" +#include "bpy_util.h" +#include "bpy_library.h" + +#include "../generic/py_capi_utils.h" + + +PyDoc_STRVAR(bpy_lib_write_doc, +".. method:: write(filepath, datablocks, relative_remap=False, fake_user=False)\n" +"\n" +" Write data-blocks into a blend file.\n" +"\n" +" :arg filepath: The path to write the blend-file.\n" +" :type filepath: string\n" +" :arg datablocks: data-blocks (:class:`bpy.types.ID` instances).\n" +" :type datablocks: sequence\n" +" :arg relative_remap: When Truem, remap the paths relative to the current blend-file.\n" +" :type relative_remap: bool\n" +" :arg fake_user: When True, data-blocks will be written with fake-user flag enabled.\n" +" :type fake_user: bool\n" +); +static PyObject *bpy_lib_write(PyObject *UNUSED(self), PyObject *args, PyObject *kwds) +{ + static const char *kwlist[] = { + "filepath", "datablocks", + /* optional */ + "relative_remap", "fake_user", + NULL, + }; + + /* args */ + const char *filepath; + char filepath_abs[FILE_MAX]; + PyObject *datablocks = NULL; + bool use_relative_remap = false, use_fake_user = false; + + if (!PyArg_ParseTupleAndKeywords( + args, kwds, + "sO!|$O&O&:write", (char **)kwlist, + &filepath, + &PySet_Type, &datablocks, + PyC_ParseBool, &use_relative_remap, + PyC_ParseBool, &use_fake_user)) + { + return NULL; + } + + Main *bmain_src = G.main; + int write_flags = 0; + + if (use_relative_remap) { + write_flags |= G_FILE_RELATIVE_REMAP; + } + + BLI_strncpy(filepath_abs, filepath, FILE_MAX); + BLI_path_abs(filepath_abs, G.main->name); + + BKE_blendfile_write_partial_begin(bmain_src); + + /* array of ID's and backup any data we modify */ + struct { + ID *id; + /* original values */ + short id_flag; + short id_us; + } *id_store_array, *id_store; + int id_store_len = 0; + + PyObject *ret; + + /* collect all id data from the set and store in 'id_store_array' */ + { + Py_ssize_t pos, hash; + PyObject *key; + + id_store_array = MEM_mallocN(sizeof(*id_store_array) * PySet_Size(datablocks), __func__); + id_store = id_store_array; + + pos = hash = 0; + while (_PySet_NextEntry(datablocks, &pos, &key, &hash)) { + + if (!pyrna_id_FromPyObject(key, &id_store->id)) { + PyErr_Format(PyExc_TypeError, + "Expected and ID type, not %.200s", + Py_TYPE(key)->tp_name); + ret = NULL; + goto finally; + } + else { + id_store->id_flag = id_store->id->flag; + id_store->id_us = id_store->id->us; + + if (use_fake_user) { + id_store->id->flag |= LIB_FAKEUSER; + } + id_store->id->us = 1; + + BKE_blendfile_write_partial_tag_ID(id_store->id, true); + + id_store_len += 1; + id_store++; + } + } + } + + /* write blend */ + int retval = 0; + ReportList reports; + + BKE_reports_init(&reports, RPT_STORE); + + retval = BKE_blendfile_write_partial(bmain_src, filepath_abs, write_flags, &reports); + + /* cleanup state */ + BKE_blendfile_write_partial_end(bmain_src); + + if (retval) { + BKE_reports_print(&reports, RPT_ERROR_ALL); + BKE_reports_clear(&reports); + ret = Py_None; + Py_INCREF(ret); + } + else { + if (BPy_reports_to_error(&reports, PyExc_RuntimeError, true) == 0) { + PyErr_SetString(PyExc_RuntimeError, "Unknown error writing library data"); + } + ret = NULL; + } + + +finally: + + /* clear all flags for ID's added to the store (may run on error too) */ + id_store = id_store_array; + + for (int i = 0; i < id_store_len; id_store++, i++) { + + + if (use_fake_user) { + if ((id_store->id_flag & LIB_FAKEUSER) == 0) { + id_store->id->flag &= ~LIB_FAKEUSER; + } + } + + id_store->id->us = id_store->id_us; + + BKE_blendfile_write_partial_tag_ID(id_store->id, false); + } + + MEM_freeN(id_store_array); + + return ret; +} + + +int BPY_library_write_module(PyObject *mod_par) +{ + static PyMethodDef write_meth = { + "write", (PyCFunction)bpy_lib_write, + METH_STATIC | METH_VARARGS | METH_KEYWORDS, + bpy_lib_write_doc, + }; + + PyModule_AddObject(mod_par, "_library_write", PyCFunction_New(&write_meth, NULL)); + + return 0; +} -- cgit v1.2.3