LibLink: New Append code.

This commit fully refactors the way linked IDs are made local when
appended.

Previously, `BKE_library_make_local` was (ab)used for this task, but it
was missing some contextual data and doing complex processing to try to
work around this, with limited sucess. Further more, it was nearly
impossibe to extend (e.g. to get new append behaviors necessary for the
asset project).

The new code is a dedicated append step in WM linking process.

NOTE: BPY API (`libray.load()` context manager) uses its own code here,
which still relies on `BKE_library_make_local` for appending.
Unfortunately, merging those two different code paths is not trivial so
for now this API will remain unchanged.

Fix T55629: Append already linked Data is impossible.
This commit is contained in:
Bastien Montagne 2021-09-14 17:49:57 +02:00
parent cddb792021
commit 3be5ce4aad
Notes: blender-bot 2023-02-14 06:32:27 +01:00
Referenced by commit 4a009b54bc, Fix broken WorkSpace use after recent refactor of append code.
Referenced by commit b6d890672c, Fix crash in 'drag asset' case in new append code from yesterday.
Referenced by issue #99166, Regression: Append/Link of Object can create extra Scene
Referenced by issue #98072, Regression: When appending a Scene, the Collections that are turned off get instanced into Current Scene
Referenced by issue #93190, Appending grease pencil brushes creates "fake user" unintentionally
Referenced by issue #92688, It is not possible to append objects which are part of linked collection
Referenced by issue #92347, Append function excludes Collections that do not contain Objects directly
Referenced by issue #92224, Handling of appending collections that contain linked materials has changed from 2.93 to 3.0, no longer works as expected
Referenced by issue #91834, Appending objects with shape keys into new file is broken.
Referenced by issue #91414, Unify link/append between WM operators and BPY context manager API, and cleanup usages of `BKE_library_make_local`
Referenced by issue #90545, ID Deduplication on Append - Technical Implementation
Referenced by issue #55629, Append already linked Data
3 changed files with 636 additions and 47 deletions

View File

@ -3416,7 +3416,7 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
case SPACE_FILE: {
SpaceFile *sfile = (SpaceFile *)sl;
if (sfile->params) {
sfile->params->flag &= ~(FILE_PARAMS_FLAG_UNUSED_1 | FILE_PARAMS_FLAG_UNUSED_6 |
sfile->params->flag &= ~(FILE_APPEND_SET_FAKEUSER | FILE_APPEND_RECURSIVE |
FILE_OBDATA_INSTANCE);
}
break;

View File

@ -936,13 +936,13 @@ typedef enum eFileSel_Action {
* (WM and BLO code area, see #eBLOLibLinkFlags in BLO_readfile.h).
*/
typedef enum eFileSel_Params_Flag {
FILE_PARAMS_FLAG_UNUSED_1 = (1 << 0), /* cleared */
FILE_APPEND_SET_FAKEUSER = (1 << 0),
FILE_RELPATH = (1 << 1),
FILE_LINK = (1 << 2),
FILE_HIDE_DOT = (1 << 3),
FILE_AUTOSELECT = (1 << 4),
FILE_ACTIVE_COLLECTION = (1 << 5),
FILE_PARAMS_FLAG_UNUSED_6 = (1 << 6), /* cleared */
FILE_APPEND_RECURSIVE = (1 << 6),
FILE_DIRSEL_ONLY = (1 << 7),
FILE_FILTER = (1 << 8),
FILE_OBDATA_INSTANCE = (1 << 9),

View File

@ -35,7 +35,9 @@
#include "MEM_guardedalloc.h"
#include "DNA_ID.h"
#include "DNA_collection_types.h"
#include "DNA_key_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_windowmanager_types.h"
@ -50,15 +52,21 @@
#include "BLO_readfile.h"
#include "BKE_armature.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_key.h"
#include "BKE_layer.h"
#include "BKE_lib_id.h"
#include "BKE_lib_override.h"
#include "BKE_lib_query.h"
#include "BKE_lib_remap.h"
#include "BKE_main.h"
#include "BKE_material.h"
#include "BKE_object.h"
#include "BKE_report.h"
#include "BKE_rigidbody.h"
#include "BKE_scene.h"
#include "BKE_idtype.h"
@ -137,6 +145,14 @@ static short wm_link_append_flag(wmOperator *op)
if (RNA_boolean_get(op->ptr, "link")) {
flag |= FILE_LINK;
}
else {
if (RNA_boolean_get(op->ptr, "use_recursive")) {
flag |= FILE_APPEND_RECURSIVE;
}
if (RNA_boolean_get(op->ptr, "set_fake")) {
flag |= FILE_APPEND_SET_FAKEUSER;
}
}
if (RNA_boolean_get(op->ptr, "instance_collections")) {
flag |= FILE_COLLECTION_INSTANCE;
}
@ -153,6 +169,10 @@ typedef struct WMLinkAppendDataItem {
*libraries; /* All libs (from WMLinkAppendData.libraries) to try to load this ID from. */
short idcode;
/** Type of action to do to append this item, and other append-specific information. */
char append_action;
char append_tag;
ID *new_id;
void *customdata;
} WMLinkAppendDataItem;
@ -167,10 +187,32 @@ typedef struct WMLinkAppendData {
*/
int flag;
/** Allows to easily find an existing items from an ID pointer. Used by append code. */
GHash *new_id_to_item;
/* Internal 'private' data */
MemArena *memarena;
} WMLinkAppendData;
typedef struct WMLinkAppendDataCallBack {
WMLinkAppendData *lapp_data;
WMLinkAppendDataItem *item;
ReportList *reports;
} WMLinkAppendDataCallBack;
enum {
WM_APPEND_ACT_UNSET = 0,
WM_APPEND_ACT_KEEP_LINKED,
WM_APPEND_ACT_REUSE_LOCAL,
WM_APPEND_ACT_MAKE_LOCAL,
WM_APPEND_ACT_COPY_LOCAL,
};
enum {
WM_APPEND_TAG_INDIRECT = 1 << 0,
};
static WMLinkAppendData *wm_link_append_data_new(const int flag)
{
MemArena *ma = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
@ -184,6 +226,10 @@ static WMLinkAppendData *wm_link_append_data_new(const int flag)
static void wm_link_append_data_free(WMLinkAppendData *lapp_data)
{
if (lapp_data->new_id_to_item != NULL) {
BLI_ghash_free(lapp_data->new_id_to_item, NULL, NULL);
}
BLI_memarena_free(lapp_data->memarena);
}
@ -213,6 +259,7 @@ static WMLinkAppendDataItem *wm_link_append_data_item_add(WMLinkAppendData *lapp
item->libraries = BLI_BITMAP_NEW_MEMARENA(lapp_data->memarena, lapp_data->num_libraries);
item->new_id = NULL;
item->append_action = WM_APPEND_ACT_UNSET;
item->customdata = customdata;
BLI_linklist_append_arena(&lapp_data->items, item, lapp_data->memarena);
@ -221,6 +268,567 @@ static WMLinkAppendDataItem *wm_link_append_data_item_add(WMLinkAppendData *lapp
return item;
}
/* -------------------------------------------------------------------- */
/** \name Library appending helper functions.
*
* FIXME: Deduplicate code with similar one in readfile.c
* \{ */
static bool object_in_any_scene(Main *bmain, Object *ob)
{
LISTBASE_FOREACH (Scene *, sce, &bmain->scenes) {
if (BKE_scene_object_find(sce, ob)) {
return true;
}
}
return false;
}
static bool object_in_any_collection(Main *bmain, Object *ob)
{
LISTBASE_FOREACH (Collection *, collection, &bmain->collections) {
if (BKE_collection_has_object(collection, ob)) {
return true;
}
}
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
if (scene->master_collection != NULL &&
BKE_collection_has_object(scene->master_collection, ob)) {
return true;
}
}
return false;
}
/**
* Shared operations to perform on the object's base after adding it to the scene.
*/
static void wm_append_loose_data_instantiate_object_base_instance_init(
Object *ob, bool set_selected, bool set_active, ViewLayer *view_layer, const View3D *v3d)
{
Base *base = BKE_view_layer_base_find(view_layer, ob);
if (v3d != NULL) {
base->local_view_bits |= v3d->local_view_uuid;
}
if (set_selected) {
if (base->flag & BASE_SELECTABLE) {
base->flag |= BASE_SELECTED;
}
}
if (set_active) {
view_layer->basact = base;
}
BKE_scene_object_base_flag_sync_from_base(base);
}
static ID *wm_append_loose_data_instantiate_process_check(WMLinkAppendDataItem *item)
{
/* We consider that if we either kept it linked, or re-used already local data, instantiation
* status of those should not be modified. */
if (!ELEM(item->append_action, WM_APPEND_ACT_COPY_LOCAL, WM_APPEND_ACT_MAKE_LOCAL)) {
return NULL;
}
ID *id = item->new_id;
if (id == NULL) {
return NULL;
}
if (item->append_action == WM_APPEND_ACT_COPY_LOCAL) {
BLI_assert(ID_IS_LINKED(id));
id = id->newid;
if (id == NULL) {
return NULL;
}
BLI_assert(!ID_IS_LINKED(id));
return id;
}
BLI_assert(!ID_IS_LINKED(id));
return id;
}
static void wm_append_loose_data_instantiate_ensure_active_collection(
WMLinkAppendData *lapp_data,
Main *bmain,
Scene *scene,
ViewLayer *view_layer,
Collection **r_active_collection)
{
/* Find or add collection as needed. */
if (*r_active_collection == NULL) {
if (lapp_data->flag & FILE_ACTIVE_COLLECTION) {
LayerCollection *lc = BKE_layer_collection_get_active(view_layer);
*r_active_collection = lc->collection;
}
else {
*r_active_collection = BKE_collection_add(bmain, scene->master_collection, NULL);
}
}
}
/* TODO: De-duplicate this code with the one in readfile.c, think we need some utils code for that
* in BKE. */
static void wm_append_loose_data_instantiate(WMLinkAppendData *lapp_data,
Main *bmain,
Scene *scene,
ViewLayer *view_layer,
const View3D *v3d)
{
LinkNode *itemlink;
Collection *active_collection = NULL;
const bool do_obdata = (lapp_data->flag & FILE_OBDATA_INSTANCE) != 0;
const bool object_set_selected = (lapp_data->flag & FILE_AUTOSELECT) != 0;
/* Do NOT make base active here! screws up GUI stuff,
* if you want it do it at the editor level. */
const bool object_set_active = false;
/* First pass on obdata to enable their instantiation by default, then do a second pass on
* objects to clear it for any obdata already in use. */
if (do_obdata) {
for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
WMLinkAppendDataItem *item = itemlink->link;
ID *id = wm_append_loose_data_instantiate_process_check(item);
if (id == NULL) {
continue;
}
const ID_Type idcode = GS(id->name);
if (!OB_DATA_SUPPORT_ID(idcode)) {
continue;
}
id->tag |= LIB_TAG_DOIT;
}
for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
WMLinkAppendDataItem *item = itemlink->link;
ID *id = item->new_id;
if (id == NULL || GS(id->name) != ID_OB) {
continue;
}
Object *ob = (Object *)id;
Object *new_ob = (Object *)id->newid;
if (ob->data != NULL) {
((ID *)(ob->data))->tag &= ~LIB_TAG_DOIT;
}
if (new_ob != NULL && new_ob->data != NULL) {
((ID *)(new_ob->data))->tag &= ~LIB_TAG_DOIT;
}
}
}
/* First do collections, then objects, then obdata. */
/* NOTE: For collections we only view_layer-instantiate duplicated collections that have
* non-instantiated objects in them. */
for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
WMLinkAppendDataItem *item = itemlink->link;
ID *id = wm_append_loose_data_instantiate_process_check(item);
if (id == NULL || GS(id->name) != ID_GR) {
continue;
}
/* We do not want to force instantiation of indirectly appended collections. Users can now
* easily instantiate collections (and their objects) as needed by themselves. See T67032. */
/* We need to check that objects in that collections are already instantiated in a scene.
* Otherwise, it's better to add the collection to the scene's active collection, than to
* instantiate its objects in active scene's collection directly. See T61141.
*
* NOTE: We only check object directly into that collection, not recursively into its
* children.
*/
Collection *collection = (Collection *)id;
bool do_add_collection = false;
LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) {
Object *ob = coll_ob->ob;
if (!object_in_any_scene(bmain, ob)) {
do_add_collection = true;
break;
}
}
if (do_add_collection) {
wm_append_loose_data_instantiate_ensure_active_collection(
lapp_data, bmain, scene, view_layer, &active_collection);
/* In case user requested instantiation of collections as empties, we do so for the one they
* explicitely selected (originally directly linked IDs). */
if ((lapp_data->flag & FILE_COLLECTION_INSTANCE) != 0 &&
(item->append_tag & WM_APPEND_TAG_INDIRECT) == 0) {
/* BKE_object_add(...) messes with the selection. */
Object *ob = BKE_object_add_only_object(bmain, OB_EMPTY, collection->id.name + 2);
ob->type = OB_EMPTY;
ob->empty_drawsize = U.collection_instance_empty_size;
BKE_collection_object_add(bmain, active_collection, ob);
const bool set_selected = (lapp_data->flag & FILE_AUTOSELECT) != 0;
/* TODO: why is it OK to make this active here but not in other situations?
* See other callers of #object_base_instance_init */
const bool set_active = set_selected;
wm_append_loose_data_instantiate_object_base_instance_init(
ob, set_selected, set_active, view_layer, v3d);
/* Assign the collection. */
ob->instance_collection = collection;
id_us_plus(&collection->id);
ob->transflag |= OB_DUPLICOLLECTION;
copy_v3_v3(ob->loc, scene->cursor.location);
}
else {
/* Add collection as child of active collection. */
BKE_collection_child_add(bmain, active_collection, collection);
if ((lapp_data->flag & FILE_AUTOSELECT) != 0) {
LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) {
Object *ob = coll_ob->ob;
Base *base = BKE_view_layer_base_find(view_layer, ob);
if (base) {
base->flag |= BASE_SELECTED;
BKE_scene_object_base_flag_sync_from_base(base);
}
}
}
}
}
}
/* NOTE: For objects we only view_layer-instantiate duplicated objects that are not yet used
* anywhere. */
for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
WMLinkAppendDataItem *item = itemlink->link;
ID *id = wm_append_loose_data_instantiate_process_check(item);
if (id == NULL || GS(id->name) != ID_OB) {
continue;
}
Object *ob = (Object *)id;
if (object_in_any_collection(bmain, ob)) {
continue;
}
wm_append_loose_data_instantiate_ensure_active_collection(
lapp_data, bmain, scene, view_layer, &active_collection);
CLAMP_MIN(ob->id.us, 0);
ob->mode = OB_MODE_OBJECT;
BKE_collection_object_add(bmain, active_collection, ob);
wm_append_loose_data_instantiate_object_base_instance_init(
ob, object_set_selected, object_set_active, view_layer, v3d);
}
if (!do_obdata) {
return;
}
for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
WMLinkAppendDataItem *item = itemlink->link;
ID *id = wm_append_loose_data_instantiate_process_check(item);
if (id == NULL) {
continue;
}
const ID_Type idcode = GS(id->name);
if (!OB_DATA_SUPPORT_ID(idcode)) {
continue;
}
if ((id->tag & LIB_TAG_DOIT) == 0) {
continue;
}
wm_append_loose_data_instantiate_ensure_active_collection(
lapp_data, bmain, scene, view_layer, &active_collection);
const int type = BKE_object_obdata_to_type(id);
BLI_assert(type != -1);
Object *ob = BKE_object_add_only_object(bmain, type, id->name + 2);
ob->data = id;
id_us_plus(id);
BKE_object_materials_test(bmain, ob, ob->data);
BKE_collection_object_add(bmain, active_collection, ob);
wm_append_loose_data_instantiate_object_base_instance_init(
ob, object_set_selected, object_set_active, view_layer, v3d);
copy_v3_v3(ob->loc, scene->cursor.location);
}
}
/* \} */
static int foreach_libblock_append_callback(LibraryIDLinkCallbackData *cb_data)
{
if (cb_data->cb_flag & (IDWALK_CB_EMBEDDED | IDWALK_CB_INTERNAL | IDWALK_CB_LOOPBACK)) {
return IDWALK_RET_NOP;
}
WMLinkAppendDataCallBack *data = cb_data->user_data;
ID *id = *cb_data->id_pointer;
if (id == NULL) {
return IDWALK_RET_NOP;
}
if (!BKE_idtype_idcode_is_linkable(GS(id->name))) {
return IDWALK_RET_NOP;
}
WMLinkAppendDataItem *item = BLI_ghash_lookup(data->lapp_data->new_id_to_item, id);
if (item == NULL) {
item = wm_link_append_data_item_add(data->lapp_data, id->name, GS(id->name), NULL);
item->new_id = id;
/* Since we did not have an item for that ID yet, we now user did not selected it explicitely,
* it was rather linked indirectly. This info is important for instantiation of collections. */
item->append_tag |= WM_APPEND_TAG_INDIRECT;
BLI_ghash_insert(data->lapp_data->new_id_to_item, id, item);
}
/* NOTE: currently there is no need to do anything else here, but in the future this would be
* the place to add specific per-usage decisions on how to append an ID. */
return IDWALK_RET_NOP;
}
/* Perform append operation, using modern ID usage looper to detect which ID should be kept linked,
* made local, duplicated as local, re-used from local etc.
*
* TODO: Expose somehow this logic to the two other parts of code performing actual append (i.e.
* copy/paste and bpy link/append API). Then we can heavily simplify `BKE_library_make_local()`. */
static void wm_append_do(WMLinkAppendData *lapp_data,
ReportList *reports,
Main *bmain,
Scene *scene,
ViewLayer *view_layer,
const View3D *v3d)
{
BLI_assert((lapp_data->flag & FILE_LINK) == 0);
const bool do_recursive = (lapp_data->flag & FILE_APPEND_RECURSIVE) != 0;
const bool set_fakeuser = (lapp_data->flag & FILE_APPEND_SET_FAKEUSER) != 0;
LinkNode *itemlink;
/* Generate a mapping between newly linked IDs and their items. */
lapp_data->new_id_to_item = BLI_ghash_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__);
for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
WMLinkAppendDataItem *item = itemlink->link;
ID *id = item->new_id;
if (id == NULL) {
continue;
}
BLI_ghash_insert(lapp_data->new_id_to_item, id, item);
}
/* Note: Since we append items for IDs not already listed (i.e. implicitely linked indirect
* dependencies), this list will grow and we will process those IDs later, leading to a flatten
* recursive processing of all the linked dependencies. */
for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
WMLinkAppendDataItem *item = itemlink->link;
ID *id = item->new_id;
if (id == NULL) {
continue;
}
BLI_assert(item->customdata == NULL);
/* Clear tag previously used to mark IDs needing post-processing (instanciation of loose
* objects etc.). */
id->tag &= ~LIB_TAG_DOIT;
if (item->append_action != WM_APPEND_ACT_UNSET) {
/* Already set, pass. */
}
if (GS(id->name) == ID_OB && ((Object *)id)->proxy_from != NULL) {
CLOG_INFO(&LOG, 3, "Appended ID '%s' is proxified, keeping it linked...", id->name);
item->append_action = WM_APPEND_ACT_KEEP_LINKED;
}
else if (id->tag & LIB_TAG_PRE_EXISTING) {
CLOG_INFO(&LOG, 3, "Appended ID '%s' was already linked, need to copy it...", id->name);
item->append_action = WM_APPEND_ACT_COPY_LOCAL;
}
else {
/* In future we could search for already existing matching local ID etc. */
CLOG_INFO(&LOG, 3, "Appended ID '%s' will be made local...", id->name);
item->append_action = WM_APPEND_ACT_MAKE_LOCAL;
}
/* Only check dependencies if we are not keeping linked data, nor re-using existing local data.
*/
if (do_recursive &&
!ELEM(item->append_action, WM_APPEND_ACT_KEEP_LINKED, WM_APPEND_ACT_REUSE_LOCAL)) {
WMLinkAppendDataCallBack cb_data = {
.lapp_data = lapp_data, .item = item, .reports = reports};
BKE_library_foreach_ID_link(
bmain, id, foreach_libblock_append_callback, &cb_data, IDWALK_NOP);
}
}
/* Effectively perform required operation on every linked ID. */
for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
WMLinkAppendDataItem *item = itemlink->link;
ID *id = item->new_id;
if (id == NULL) {
continue;
}
switch (item->append_action) {
case WM_APPEND_ACT_COPY_LOCAL: {
BKE_lib_id_make_local(
bmain, id, false, LIB_ID_MAKELOCAL_FULL_LIBRARY | LIB_ID_MAKELOCAL_FORCE_COPY);
if (id->newid != NULL) {
if (GS(id->newid->name) == ID_OB) {
BKE_rigidbody_ensure_local_object(bmain, (Object *)id->newid);
}
if (set_fakeuser) {
if (!ELEM(GS(id->name), ID_OB, ID_GR)) {
/* Do not set fake user on objects nor collections (instancing). */
id_fake_user_set(id->newid);
}
}
}
break;
}
case WM_APPEND_ACT_MAKE_LOCAL:
BKE_lib_id_make_local(bmain,
id,
false,
LIB_ID_MAKELOCAL_FULL_LIBRARY | LIB_ID_MAKELOCAL_FORCE_LOCAL |
LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING);
BLI_assert(id->newid == NULL);
if (GS(id->name) == ID_OB) {
BKE_rigidbody_ensure_local_object(bmain, (Object *)id);
}
if (set_fakeuser) {
if (!ELEM(GS(id->name), ID_OB, ID_GR)) {
/* Do not set fake user on objects nor collections (instancing). */
id_fake_user_set(id);
}
}
break;
case WM_APPEND_ACT_KEEP_LINKED:
/* Nothing to do here. */
break;
case WM_APPEND_ACT_REUSE_LOCAL:
/* We only need to set `newid` to ID found in previous loop, for proper remapping. */
ID_NEW_SET(id->newid, item->customdata);
/* Do not set again fake user in case we reuse existing local ID. */
break;
case WM_APPEND_ACT_UNSET:
CLOG_ERROR(
&LOG, "Unexpected unset append action for '%s' ID, assuming 'keep link'", id->name);
break;
default:
BLI_assert(0);
}
}
/* Remap IDs as needed. */
for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
WMLinkAppendDataItem *item = itemlink->link;
if (item->append_action == WM_APPEND_ACT_KEEP_LINKED) {
continue;
}
ID *id = item->new_id;
if (id == NULL) {
continue;
}
if (item->append_action == WM_APPEND_ACT_COPY_LOCAL) {
BLI_assert(ID_IS_LINKED(id));
id = id->newid;
if (id == NULL) {
continue;
}
}
BLI_assert(!ID_IS_LINKED(id));
BKE_libblock_relink_to_newid_new(bmain, id);
}
/* Instantiate newly created (duplicated) IDs as needed. */
wm_append_loose_data_instantiate(lapp_data, bmain, scene, view_layer, v3d);
/* Attempt to deal with object proxies.
*
* NOTE: Copied from `BKE_library_make_local`, but this is not really working (as in, not
* producing any useful result in any known use case), neither here nor in
* `BKE_library_make_local` currently.
* Proxies are end of life anyway, so not worth spending time on this. */
for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
WMLinkAppendDataItem *item = itemlink->link;
if (item->append_action != WM_APPEND_ACT_COPY_LOCAL) {
continue;
}
ID *id = item->new_id;
if (id == NULL) {
continue;
}
BLI_assert(ID_IS_LINKED(id));
/* Attempt to re-link copied proxy objects. This allows appending of an entire scene
* from another blend file into this one, even when that blend file contains proxified
* armatures that have local references. Since the proxified object needs to be linked
* (not local), this will only work when the "Localize all" checkbox is disabled.
* TL;DR: this is a dirty hack on top of an already weak feature (proxies). */
if (GS(id->name) == ID_OB && ((Object *)id)->proxy != NULL) {
Object *ob = (Object *)id;
Object *ob_new = (Object *)id->newid;
bool is_local = false, is_lib = false;
/* Proxies only work when the proxified object is linked-in from a library. */
if (!ID_IS_LINKED(ob->proxy)) {
CLOG_WARN(&LOG,
"Proxy object %s will lose its link to %s, because the "
"proxified object is local",
id->newid->name,
ob->proxy->id.name);
continue;
}
BKE_library_ID_test_usages(bmain, id, &is_local, &is_lib);
/* We can only switch the proxy'ing to a made-local proxy if it is no longer
* referred to from a library. Not checking for local use; if new local proxy
* was not used locally would be a nasty bug! */
if (is_local || is_lib) {
CLOG_WARN(&LOG,
"Made-local proxy object %s will lose its link to %s, "
"because the linked-in proxy is referenced (is_local=%i, is_lib=%i)",
id->newid->name,
ob->proxy->id.name,
is_local,
is_lib);
}
else {
/* we can switch the proxy'ing from the linked-in to the made-local proxy.
* BKE_object_make_proxy() shouldn't be used here, as it allocates memory that
* was already allocated by object_make_local() (which called BKE_object_copy). */
ob_new->proxy = ob->proxy;
ob_new->proxy_group = ob->proxy_group;
ob_new->proxy_from = ob->proxy_from;
ob_new->proxy->proxy_from = ob_new;
ob->proxy = ob->proxy_from = ob->proxy_group = NULL;
}
}
}
BKE_main_id_newptr_and_tag_clear(bmain);
}
static void wm_link_do(WMLinkAppendData *lapp_data,
ReportList *reports,
Main *bmain,
@ -263,6 +871,11 @@ static void wm_link_do(WMLinkAppendData *lapp_data,
struct LibraryLink_Params liblink_params;
BLO_library_link_params_init_with_context(
&liblink_params, bmain, flag, id_tag_extra, scene, view_layer, v3d);
/* In case of append, do not handle instantiation in linking process, but during append phase
* (see #wm_append_loose_data_instantiate ). */
if ((flag & FILE_LINK) == 0) {
liblink_params.flag &= ~BLO_LIBLINK_NEEDS_ID_TAG_DOIT;
}
mainl = BLO_library_link_begin(&bh, libname, &liblink_params);
lib = mainl->curlib;
@ -501,28 +1114,7 @@ static int wm_link_append_exec(bContext *C, wmOperator *op)
/* append, rather than linking */
if (do_append) {
const bool set_fake = RNA_boolean_get(op->ptr, "set_fake");
const bool use_recursive = RNA_boolean_get(op->ptr, "use_recursive");
if (use_recursive) {
BKE_library_make_local(bmain, NULL, NULL, true, set_fake);
}
else {
LinkNode *itemlink;
GSet *done_libraries = BLI_gset_new_ex(
BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__, lapp_data->num_libraries);
for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
ID *new_id = ((WMLinkAppendDataItem *)(itemlink->link))->new_id;
if (new_id && !BLI_gset_haskey(done_libraries, new_id->lib)) {
BKE_library_make_local(bmain, new_id->lib, NULL, true, set_fake);
BLI_gset_insert(done_libraries, new_id->lib);
}
}
BLI_gset_free(done_libraries, NULL);
}
wm_append_do(lapp_data, op->reports, bmain, scene, view_layer, CTX_wm_view3d(C));
}
wm_link_append_data_free(lapp_data);
@ -652,20 +1244,20 @@ void WM_OT_append(wmOperatorType *ot)
*
* \{ */
static ID *wm_file_link_datablock_ex(Main *bmain,
Scene *scene,
ViewLayer *view_layer,
View3D *v3d,
const char *filepath,
const short id_code,
const char *id_name,
bool clear_pre_existing_flag)
static ID *wm_file_link_append_datablock_ex(Main *bmain,
Scene *scene,
ViewLayer *view_layer,
View3D *v3d,
const char *filepath,
const short id_code,
const char *id_name,
const bool do_append)
{
/* Tag everything so we can make local only the new datablock. */
BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true);
/* Define working data, with just the one item we want to link. */
WMLinkAppendData *lapp_data = wm_link_append_data_new(0);
WMLinkAppendData *lapp_data = wm_link_append_data_new(do_append ? FILE_APPEND_RECURSIVE : 0);
wm_link_append_data_library_add(lapp_data, filepath);
WMLinkAppendDataItem *item = wm_link_append_data_item_add(lapp_data, id_name, id_code, NULL);
@ -676,11 +1268,14 @@ static ID *wm_file_link_datablock_ex(Main *bmain,
/* Get linked datablock and free working data. */
ID *id = item->new_id;
if (do_append) {
wm_append_do(lapp_data, NULL, bmain, scene, view_layer, v3d);
}
wm_link_append_data_free(lapp_data);
if (clear_pre_existing_flag) {
BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false);
}
BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false);
return id;
}
@ -693,8 +1288,8 @@ ID *WM_file_link_datablock(Main *bmain,
const short id_code,
const char *id_name)
{
return wm_file_link_datablock_ex(
bmain, scene, view_layer, v3d, filepath, id_code, id_name, true);
return wm_file_link_append_datablock_ex(
bmain, scene, view_layer, v3d, filepath, id_code, id_name, false);
}
ID *WM_file_append_datablock(Main *bmain,
@ -705,14 +1300,8 @@ ID *WM_file_append_datablock(Main *bmain,
const short id_code,
const char *id_name)
{
ID *id = wm_file_link_datablock_ex(
bmain, scene, view_layer, v3d, filepath, id_code, id_name, false);
/* Make datablock local. */
BKE_library_make_local(bmain, NULL, NULL, true, false);
/* Clear pre existing tag. */
BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false);
ID *id = wm_file_link_append_datablock_ex(
bmain, scene, view_layer, v3d, filepath, id_code, id_name, true);
return id;
}