diff --git a/release/scripts/modules/bpy_types.py b/release/scripts/modules/bpy_types.py index e21b34adc4e..d0607cbe675 100644 --- a/release/scripts/modules/bpy_types.py +++ b/release/scripts/modules/bpy_types.py @@ -26,6 +26,7 @@ StructMetaPropGroup = bpy_types.bpy_struct_meta_idprop # StructRNA = bpy_types.Struct bpy_types.BlendDataLibraries.load = _bpy._library_load +bpy_types.BlendDataLibraries.write = _bpy._library_write bpy_types.BlendData.user_map = _bpy._rna_id_collection_user_map 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.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.c b/source/blender/python/intern/bpy_library_load.c similarity index 97% rename from source/blender/python/intern/bpy_library.c rename to source/blender/python/intern/bpy_library_load.c index dd1be67946b..a120e4886e0 100644 --- a/source/blender/python/intern/bpy_library.c +++ b/source/blender/python/intern/bpy_library_load.c @@ -15,12 +15,10 @@ * 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 +/** \file blender/python/intern/bpy_library_load.c * \ingroup pythonintern * * This file exposed blend file library appending/linking to python, typically @@ -464,12 +462,13 @@ static PyObject *bpy_lib_dir(BPy_Library *self) } -int BPY_library_module(PyObject *mod_par) +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}; - + 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 */ 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; +}