LibLink/Append: Port `bpy.data.libraries.load` to new `BKE_blendfile_link_append` module.

Note that this fully replaces the 'PyCapsule' storage of linked/appended items
in the python API code by the generic storage of items in the
`BlendfileLinkAppendContext` data.

Maniphest Tasks: T91414

Differential Revision: https://developer.blender.org/D13331
This commit is contained in:
Bastien Montagne 2021-11-23 12:12:28 +01:00
parent 8cf0d15b60
commit 0704570721
Notes: blender-bot 2023-02-14 08:39:23 +01:00
Referenced by issue #94338, bpy.data.libraries.load does not return the right thing (Geometry Nodes, Collections, Shader nodes are fine)
Referenced by issue #91414, Unify link/append between WM operators and BPY context manager API, and cleanup usages of `BKE_library_make_local`
1 changed files with 130 additions and 111 deletions

View File

@ -34,6 +34,7 @@
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "BKE_blendfile_link_append.h"
#include "BKE_context.h"
#include "BKE_idtype.h"
#include "BKE_lib_id.h"
@ -346,11 +347,63 @@ static void bpy_lib_exit_warn_type(BPy_Library *self, PyObject *item)
PyErr_Restore(exc, val, tb);
}
struct LibExitLappContextItemsIterData {
short idcode;
BPy_Library *py_library;
PyObject *py_list;
Py_ssize_t py_list_size;
};
static bool bpy_lib_exit_lapp_context_items_cb(BlendfileLinkAppendContext *lapp_context,
BlendfileLinkAppendContextItem *item,
void *userdata)
{
struct LibExitLappContextItemsIterData *data = userdata;
/* Since `bpy_lib_exit` loops over all ID types, all items in `lapp_context` end up being looped
* over for each ID type, so when it does not match the item can simply be skipped: it either has
* already been processed, or will be processed in a later loop. */
if (BKE_blendfile_link_append_context_item_idcode_get(lapp_context, item) != data->idcode) {
return true;
}
const int py_list_index = POINTER_AS_INT(
BKE_blendfile_link_append_context_item_userdata_get(lapp_context, item));
ID *new_id = BKE_blendfile_link_append_context_item_newid_get(lapp_context, item);
BLI_assert(py_list_index < data->py_list_size);
/* Fully invalid items (which got set to `Py_None` already in first loop of `bpy_lib_exit`)
* should never be accessed here, since their index should never be set to any item in
* `lapp_context`. */
PyObject *item_src = PyList_GET_ITEM(data->py_list, py_list_index);
BLI_assert(item_src != Py_None);
PyObject *py_item;
if (new_id != NULL) {
PointerRNA newid_ptr;
RNA_id_pointer_create(new_id, &newid_ptr);
py_item = pyrna_struct_CreatePyObject(&newid_ptr);
}
else {
const char *item_idname = PyUnicode_AsUTF8(item_src);
const char *idcode_name_plural = BKE_idtype_idcode_to_name_plural(data->idcode);
bpy_lib_exit_warn_idname(data->py_library, idcode_name_plural, item_idname);
py_item = Py_INCREF_RET(Py_None);
}
PyList_SET_ITEM(data->py_list, py_list_index, py_item);
Py_DECREF(item_src);
return true;
}
static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *UNUSED(args))
{
Main *bmain = self->bmain;
Main *mainl = NULL;
const int err = 0;
const bool do_append = ((self->flag & FILE_LINK) == 0);
BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true);
@ -360,134 +413,100 @@ static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *UNUSED(args))
struct LibraryLink_Params liblink_params;
BLO_library_link_params_init(&liblink_params, bmain, self->flag, id_tag_extra);
mainl = BLO_library_link_begin(&(self->blo_handle), self->relpath, &liblink_params);
BlendfileLinkAppendContext *lapp_context = BKE_blendfile_link_append_context_new(
&liblink_params);
BKE_blendfile_link_append_context_library_add(lapp_context, self->abspath, self->blo_handle);
{
int idcode_step = 0, idcode;
while ((idcode = BKE_idtype_idcode_iter_step(&idcode_step))) {
if (BKE_idtype_idcode_is_linkable(idcode) && (idcode != ID_WS || do_append)) {
const char *name_plural = BKE_idtype_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 */
const Py_ssize_t size = PyList_GET_SIZE(ls);
Py_ssize_t i;
int idcode_step = 0;
short idcode;
while ((idcode = BKE_idtype_idcode_iter_step(&idcode_step))) {
if (!BKE_idtype_idcode_is_linkable(idcode) || (idcode == ID_WS && !do_append)) {
continue;
}
for (i = 0; i < size; i++) {
PyObject *item_src = PyList_GET_ITEM(ls, i);
PyObject *item_dst; /* must be set below */
const char *item_idname = PyUnicode_AsUTF8(item_src);
const char *name_plural = BKE_idtype_idcode_to_name_plural(idcode);
PyObject *ls = PyDict_GetItemString(self->dict, name_plural);
// printf("lib: %s\n", name_plural);
if (ls == NULL || !PyList_Check(ls)) {
continue;
}
// printf(" %s\n", item_idname);
const Py_ssize_t size = PyList_GET_SIZE(ls);
if (size == 0) {
continue;
}
if (item_idname) {
ID *id = BLO_library_link_named_part(
mainl, &(self->blo_handle), idcode, item_idname, &liblink_params);
if (id) {
/* loop */
for (Py_ssize_t i = 0; i < size; i++) {
PyObject *item_src = PyList_GET_ITEM(ls, i);
const char *item_idname = PyUnicode_AsUTF8(item_src);
if (self->bmain_is_temp) {
/* If this fails, #LibraryLink_Params.id_tag_extra is not being applied. */
BLI_assert(id->tag & LIB_TAG_TEMP_MAIN);
}
// printf(" %s\n", item_idname);
/* NOTE: index of item in py list is stored in userdata pointer, so that it can be found
* later on to replace the ID name by the actual ID pointer. */
if (item_idname != NULL) {
BlendfileLinkAppendContextItem *item = BKE_blendfile_link_append_context_item_add(
lapp_context, item_idname, idcode, POINTER_FROM_INT(i));
BKE_blendfile_link_append_context_item_library_index_enable(lapp_context, item, 0);
}
else {
/* XXX, could complain about this */
bpy_lib_exit_warn_type(self, item_src);
PyErr_Clear();
#ifdef USE_RNA_DATABLOCKS
/* swap name for pointer to the id */
item_dst = PyCapsule_New((void *)id, NULL, NULL);
#else
/* leave as is */
continue;
/* We can replace the item immediately with `None`. */
PyObject *py_item = Py_INCREF_RET(Py_None);
PyList_SET_ITEM(ls, i, py_item);
Py_DECREF(item_src);
#endif
}
else {
bpy_lib_exit_warn_idname(self, name_plural, item_idname);
/* just warn for now */
/* err = -1; */
item_dst = Py_INCREF_RET(Py_None);
}
/* ID or None */
}
else {
/* XXX, could complain about this */
bpy_lib_exit_warn_type(self, item_src);
PyErr_Clear();
item_dst = Py_INCREF_RET(Py_None);
}
/* item_dst must be new or already incref'd */
Py_DECREF(item_src);
PyList_SET_ITEM(ls, i, item_dst);
}
}
}
}
}
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;
BKE_blendfile_link(lapp_context, NULL);
if (do_append) {
BKE_blendfile_append(lapp_context, NULL);
}
Library *lib = mainl->curlib; /* newly added lib, assign before append end */
BLO_library_link_end(mainl, &(self->blo_handle), &liblink_params);
/* If enabled, replace named items in given lists by the final matching new ID pointer. */
#ifdef USE_RNA_DATABLOCKS
idcode_step = 0;
while ((idcode = BKE_idtype_idcode_iter_step(&idcode_step))) {
if (!BKE_idtype_idcode_is_linkable(idcode) || (idcode == ID_WS && !do_append)) {
continue;
}
const char *name_plural = BKE_idtype_idcode_to_name_plural(idcode);
PyObject *ls = PyDict_GetItemString(self->dict, name_plural);
// printf("lib: %s\n", name_plural);
if (ls == NULL || !PyList_Check(ls)) {
continue;
}
const Py_ssize_t size = PyList_GET_SIZE(ls);
if (size == 0) {
continue;
}
/* Loop over linked items in `lapp_context` to find matching python one in the list, and
* replace them with proper ID pointer. */
struct LibExitLappContextItemsIterData iter_data = {
.idcode = idcode, .py_library = self, .py_list = ls, .py_list_size = size};
BKE_blendfile_link_append_context_item_foreach(
lapp_context,
bpy_lib_exit_lapp_context_items_cb,
BKE_BLENDFILE_LINK_APPEND_FOREACH_ITEM_FLAG_DO_DIRECT,
&iter_data);
}
#endif // USE_RNA_DATABLOCKS
BLO_blendhandle_close(self->blo_handle);
self->blo_handle = NULL;
GHash *old_to_new_ids = BLI_ghash_ptr_new(__func__);
/* copied from wm_operator.c */
{
/* mark all library linked objects to be updated */
BKE_main_lib_objects_recalc_all(bmain);
/* append, rather than linking */
if (do_append) {
BKE_library_make_local(bmain, lib, old_to_new_ids, true, false);
}
}
BKE_blendfile_link_append_context_free(lapp_context);
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 */
#ifdef USE_RNA_DATABLOCKS
{
int idcode_step = 0, idcode;
while ((idcode = BKE_idtype_idcode_iter_step(&idcode_step))) {
if (BKE_idtype_idcode_is_linkable(idcode) && (idcode != ID_WS || do_append)) {
const char *name_plural = BKE_idtype_idcode_to_name_plural(idcode);
PyObject *ls = PyDict_GetItemString(self->dict, name_plural);
if (ls && PyList_Check(ls)) {
const 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);
id = BLI_ghash_lookup_default(old_to_new_ids, id, id);
Py_DECREF(item);
RNA_id_pointer_create(id, &id_ptr);
item = pyrna_struct_CreatePyObject(&id_ptr);
PyList_SET_ITEM(ls, i, item);
}
}
}
}
}
}
#endif /* USE_RNA_DATABLOCKS */
BLI_ghash_free(old_to_new_ids, NULL, NULL);
Py_RETURN_NONE;
}