5063 lines
200 KiB
C++
5063 lines
200 KiB
C++
/* SPDX-FileCopyrightText: 2016 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*/
|
|
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <queue>
|
|
|
|
#include "CLG_log.h"
|
|
|
|
#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 "DEG_depsgraph.hh"
|
|
#include "DEG_depsgraph_build.hh"
|
|
|
|
#include "BKE_anim_data.h"
|
|
#include "BKE_armature.hh"
|
|
#include "BKE_blender.h"
|
|
#include "BKE_collection.h"
|
|
#include "BKE_fcurve.h"
|
|
#include "BKE_global.h"
|
|
#include "BKE_idtype.h"
|
|
#include "BKE_key.h"
|
|
#include "BKE_layer.h"
|
|
#include "BKE_lib_id.h"
|
|
#include "BKE_lib_override.hh"
|
|
#include "BKE_lib_query.h"
|
|
#include "BKE_lib_remap.h"
|
|
#include "BKE_main.h"
|
|
#include "BKE_main_namemap.h"
|
|
#include "BKE_node.hh"
|
|
#include "BKE_report.h"
|
|
#include "BKE_scene.h"
|
|
|
|
#include "BLO_readfile.h"
|
|
|
|
#include "BLI_ghash.h"
|
|
#include "BLI_linklist.h"
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_memarena.h"
|
|
#include "BLI_set.hh"
|
|
#include "BLI_string.h"
|
|
#include "BLI_task.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "PIL_time.h"
|
|
|
|
#include "RNA_access.hh"
|
|
#include "RNA_path.hh"
|
|
#include "RNA_prototypes.h"
|
|
#include "RNA_types.hh"
|
|
|
|
#include "atomic_ops.h"
|
|
|
|
#include "lib_intern.h"
|
|
|
|
#define OVERRIDE_AUTO_CHECK_DELAY 0.2 /* 200ms between auto-override checks. */
|
|
//#define DEBUG_OVERRIDE_TIMEIT
|
|
|
|
#ifdef DEBUG_OVERRIDE_TIMEIT
|
|
# include "PIL_time_utildefines.h"
|
|
#endif
|
|
|
|
static CLG_LogRef LOG = {"bke.liboverride"};
|
|
static CLG_LogRef LOG_RESYNC = {"bke.liboverride_resync"};
|
|
|
|
static void lib_override_library_property_copy(IDOverrideLibraryProperty *op_dst,
|
|
IDOverrideLibraryProperty *op_src);
|
|
static void lib_override_library_property_operation_copy(
|
|
IDOverrideLibraryPropertyOperation *opop_dst, IDOverrideLibraryPropertyOperation *opop_src);
|
|
|
|
static void lib_override_library_property_clear(IDOverrideLibraryProperty *op);
|
|
static void lib_override_library_property_operation_clear(
|
|
IDOverrideLibraryPropertyOperation *opop);
|
|
|
|
BLI_INLINE IDOverrideLibraryRuntime *override_library_runtime_ensure(
|
|
IDOverrideLibrary *liboverride)
|
|
{
|
|
if (liboverride->runtime == nullptr) {
|
|
liboverride->runtime = MEM_cnew<IDOverrideLibraryRuntime>(__func__);
|
|
}
|
|
return liboverride->runtime;
|
|
}
|
|
|
|
/** Helper to preserve Pose mode on override objects.
|
|
* A bit annoying to have this special case, but not much to be done here currently, since the
|
|
* matching RNA property is read-only. */
|
|
BLI_INLINE void lib_override_object_posemode_transfer(ID *id_dst, ID *id_src)
|
|
{
|
|
if (GS(id_src->name) == ID_OB && GS(id_dst->name) == ID_OB) {
|
|
Object *ob_src = reinterpret_cast<Object *>(id_src);
|
|
Object *ob_dst = reinterpret_cast<Object *>(id_dst);
|
|
if (ob_src->type == OB_ARMATURE && (ob_src->mode & OB_MODE_POSE) != 0) {
|
|
ob_dst->restore_mode = ob_dst->mode;
|
|
ob_dst->mode |= OB_MODE_POSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Get override data for a given ID. Needed because of our beloved shape keys snowflake. */
|
|
BLI_INLINE const IDOverrideLibrary *BKE_lib_override_library_get(const Main * /*bmain*/,
|
|
const ID *id,
|
|
const ID * /*owner_id_hint*/,
|
|
const ID **r_owner_id)
|
|
{
|
|
if (id->flag & LIB_EMBEDDED_DATA_LIB_OVERRIDE) {
|
|
const ID *owner_id = BKE_id_owner_get(const_cast<ID *>(id));
|
|
BLI_assert_msg(owner_id != nullptr, "Liboverride-embedded ID with no owner");
|
|
if (r_owner_id != nullptr) {
|
|
*r_owner_id = owner_id;
|
|
}
|
|
return owner_id->override_library;
|
|
}
|
|
|
|
if (r_owner_id != nullptr) {
|
|
*r_owner_id = id;
|
|
}
|
|
return id->override_library;
|
|
}
|
|
|
|
IDOverrideLibrary *BKE_lib_override_library_get(Main *bmain,
|
|
ID *id,
|
|
ID *owner_id_hint,
|
|
ID **r_owner_id)
|
|
{
|
|
/* Reuse the implementation of the const access function, which does not change the arguments.
|
|
* Add const explicitly to make it clear to the compiler to avoid just calling this function. */
|
|
return const_cast<IDOverrideLibrary *>(
|
|
BKE_lib_override_library_get(const_cast<const Main *>(bmain),
|
|
const_cast<const ID *>(id),
|
|
const_cast<const ID *>(owner_id_hint),
|
|
const_cast<const ID **>(r_owner_id)));
|
|
}
|
|
|
|
IDOverrideLibrary *BKE_lib_override_library_init(ID *local_id, ID *reference_id)
|
|
{
|
|
/* If reference_id is nullptr, we are creating an override template for purely local data.
|
|
* Else, reference *must* be linked data. */
|
|
BLI_assert(reference_id == nullptr || ID_IS_LINKED(reference_id));
|
|
BLI_assert(local_id->override_library == nullptr);
|
|
|
|
ID *ancestor_id;
|
|
for (ancestor_id = reference_id;
|
|
ancestor_id != nullptr && ancestor_id->override_library != nullptr &&
|
|
ancestor_id->override_library->reference != nullptr;
|
|
ancestor_id = ancestor_id->override_library->reference)
|
|
{
|
|
/* pass */
|
|
}
|
|
|
|
if (ancestor_id != nullptr && ancestor_id->override_library != nullptr) {
|
|
/* Original ID has a template, use it! */
|
|
BKE_lib_override_library_copy(local_id, ancestor_id, true);
|
|
if (local_id->override_library->reference != reference_id) {
|
|
id_us_min(local_id->override_library->reference);
|
|
local_id->override_library->reference = reference_id;
|
|
id_us_plus(local_id->override_library->reference);
|
|
}
|
|
return local_id->override_library;
|
|
}
|
|
|
|
/* Else, generate new empty override. */
|
|
local_id->override_library = MEM_cnew<IDOverrideLibrary>(__func__);
|
|
local_id->override_library->reference = reference_id;
|
|
id_us_plus(local_id->override_library->reference);
|
|
local_id->tag &= ~LIB_TAG_LIBOVERRIDE_REFOK;
|
|
/* By default initialized liboverrides are 'system overrides', higher-level code is responsible
|
|
* to unset this flag for specific IDs. */
|
|
local_id->override_library->flag |= LIBOVERRIDE_FLAG_SYSTEM_DEFINED;
|
|
/* TODO: do we want to add tag or flag to referee to mark it as such? */
|
|
return local_id->override_library;
|
|
}
|
|
|
|
void BKE_lib_override_library_copy(ID *dst_id, const ID *src_id, const bool do_full_copy)
|
|
{
|
|
BLI_assert(ID_IS_OVERRIDE_LIBRARY(src_id) || ID_IS_OVERRIDE_LIBRARY_TEMPLATE(src_id));
|
|
|
|
if (dst_id->override_library != nullptr) {
|
|
if (src_id->override_library == nullptr) {
|
|
BKE_lib_override_library_free(&dst_id->override_library, true);
|
|
return;
|
|
}
|
|
|
|
BKE_lib_override_library_clear(dst_id->override_library, true);
|
|
}
|
|
else if (src_id->override_library == nullptr) {
|
|
/* Virtual overrides of embedded data does not require any extra work. */
|
|
return;
|
|
}
|
|
else {
|
|
BKE_lib_override_library_init(dst_id, nullptr);
|
|
}
|
|
|
|
/* If source is already overriding data, we copy it but reuse its reference for destination ID.
|
|
* Otherwise, source is only an override template, it then becomes reference of destination ID.
|
|
*/
|
|
dst_id->override_library->reference = src_id->override_library->reference ?
|
|
src_id->override_library->reference :
|
|
const_cast<ID *>(src_id);
|
|
id_us_plus(dst_id->override_library->reference);
|
|
|
|
dst_id->override_library->hierarchy_root = src_id->override_library->hierarchy_root;
|
|
dst_id->override_library->flag = src_id->override_library->flag;
|
|
|
|
if (do_full_copy) {
|
|
BLI_duplicatelist(&dst_id->override_library->properties,
|
|
&src_id->override_library->properties);
|
|
for (IDOverrideLibraryProperty *op_dst = static_cast<IDOverrideLibraryProperty *>(
|
|
dst_id->override_library->properties.first),
|
|
*op_src = static_cast<IDOverrideLibraryProperty *>(
|
|
src_id->override_library->properties.first);
|
|
op_dst;
|
|
op_dst = op_dst->next, op_src = op_src->next)
|
|
{
|
|
lib_override_library_property_copy(op_dst, op_src);
|
|
}
|
|
}
|
|
|
|
dst_id->tag &= ~LIB_TAG_LIBOVERRIDE_REFOK;
|
|
}
|
|
|
|
void BKE_lib_override_library_clear(IDOverrideLibrary *liboverride, const bool do_id_user)
|
|
{
|
|
BLI_assert(liboverride != nullptr);
|
|
|
|
if (!ELEM(nullptr, liboverride->runtime, liboverride->runtime->rna_path_to_override_properties))
|
|
{
|
|
BLI_ghash_clear(liboverride->runtime->rna_path_to_override_properties, nullptr, nullptr);
|
|
}
|
|
|
|
LISTBASE_FOREACH (IDOverrideLibraryProperty *, op, &liboverride->properties) {
|
|
lib_override_library_property_clear(op);
|
|
}
|
|
BLI_freelistN(&liboverride->properties);
|
|
|
|
if (do_id_user) {
|
|
id_us_min(liboverride->reference);
|
|
/* override->storage should never be refcounted... */
|
|
}
|
|
}
|
|
|
|
void BKE_lib_override_library_free(IDOverrideLibrary **liboverride, const bool do_id_user)
|
|
{
|
|
BLI_assert(*liboverride != nullptr);
|
|
|
|
if ((*liboverride)->runtime != nullptr) {
|
|
if ((*liboverride)->runtime->rna_path_to_override_properties != nullptr) {
|
|
BLI_ghash_free((*liboverride)->runtime->rna_path_to_override_properties, nullptr, nullptr);
|
|
}
|
|
MEM_SAFE_FREE((*liboverride)->runtime);
|
|
}
|
|
|
|
BKE_lib_override_library_clear(*liboverride, do_id_user);
|
|
MEM_freeN(*liboverride);
|
|
*liboverride = nullptr;
|
|
}
|
|
|
|
static ID *lib_override_library_create_from(Main *bmain,
|
|
Library *owner_library,
|
|
ID *reference_id,
|
|
const int lib_id_copy_flags)
|
|
{
|
|
/* NOTE: We do not want to copy possible override data from reference here (whether it is an
|
|
* override template, or already an override of some other ref data). */
|
|
ID *local_id = BKE_id_copy_ex(bmain,
|
|
reference_id,
|
|
nullptr,
|
|
LIB_ID_COPY_DEFAULT | LIB_ID_COPY_NO_LIB_OVERRIDE |
|
|
lib_id_copy_flags);
|
|
|
|
/* In case we could not get an override ID with the exact same name as its linked reference,
|
|
* ensure we at least get a uniquely named override ID over the whole current Main data, to
|
|
* reduce potential name collisions with other reference IDs.
|
|
*
|
|
* While in normal cases this would not be an issue, when files start to get heavily broken and
|
|
* not sound, such conflicts can become a source of problems. */
|
|
if (!STREQ(local_id->name + 2, reference_id->name + 2)) {
|
|
BLI_strncpy(local_id->name + 2, reference_id->name + 2, MAX_ID_NAME - 2);
|
|
BKE_main_namemap_get_name(bmain, local_id, local_id->name + 2, true);
|
|
id_sort_by_name(which_libbase(bmain, GS(local_id->name)), local_id, nullptr);
|
|
}
|
|
|
|
if (local_id == nullptr) {
|
|
return nullptr;
|
|
}
|
|
id_us_min(local_id);
|
|
|
|
/* TODO: Handle this properly in LIB_NO_MAIN case as well (i.e. resync case). Or offload to
|
|
* generic ID copy code? Would probably be better to have a version of #BKE_id_copy_ex that takes
|
|
* an extra `target_lib` parameter. */
|
|
local_id->lib = owner_library;
|
|
if ((lib_id_copy_flags & LIB_ID_CREATE_NO_MAIN) != 0 && owner_library == nullptr) {
|
|
lib_id_copy_ensure_local(bmain, reference_id, local_id, 0);
|
|
}
|
|
|
|
BKE_lib_override_library_init(local_id, reference_id);
|
|
|
|
/* NOTE: From liboverride perspective (and RNA one), shape keys are considered as local embedded
|
|
* data-blocks, just like root node trees or master collections. Therefore, we never need to
|
|
* create overrides for them. We need a way to mark them as overrides though. */
|
|
Key *reference_key;
|
|
if ((reference_key = BKE_key_from_id(reference_id)) != nullptr) {
|
|
Key *local_key = BKE_key_from_id(local_id);
|
|
BLI_assert(local_key != nullptr);
|
|
local_key->id.flag |= LIB_EMBEDDED_DATA_LIB_OVERRIDE;
|
|
}
|
|
|
|
return local_id;
|
|
}
|
|
|
|
/* TODO: This could be simplified by storing a flag in #IDOverrideLibrary
|
|
* during the diffing process? */
|
|
bool BKE_lib_override_library_is_user_edited(const ID *id)
|
|
{
|
|
|
|
if (!ID_IS_OVERRIDE_LIBRARY(id)) {
|
|
return false;
|
|
}
|
|
|
|
/* A bit weird, but those embedded IDs are handled by their owner ID anyway, so we can just
|
|
* assume they are never user-edited, actual proper detection will happen from their owner check.
|
|
*/
|
|
if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
|
|
return false;
|
|
}
|
|
|
|
LISTBASE_FOREACH (const IDOverrideLibraryProperty *, op, &id->override_library->properties) {
|
|
LISTBASE_FOREACH (const IDOverrideLibraryPropertyOperation *, opop, &op->operations) {
|
|
if ((opop->flag & LIBOVERRIDE_OP_FLAG_IDPOINTER_MATCH_REFERENCE) != 0) {
|
|
continue;
|
|
}
|
|
if (opop->operation == LIBOVERRIDE_OP_NOOP) {
|
|
continue;
|
|
}
|
|
/* If an operation does not match the filters above, it is considered as a user-editing one,
|
|
* therefore this override is user-edited. */
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BKE_lib_override_library_is_system_defined(const Main *bmain, const ID *id)
|
|
{
|
|
if (ID_IS_OVERRIDE_LIBRARY(id)) {
|
|
const ID *override_owner_id;
|
|
BKE_lib_override_library_get(bmain, id, nullptr, &override_owner_id);
|
|
return (override_owner_id->override_library->flag & LIBOVERRIDE_FLAG_SYSTEM_DEFINED) != 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BKE_lib_override_library_property_is_animated(
|
|
const ID *id,
|
|
const IDOverrideLibraryProperty *liboverride_prop,
|
|
const PropertyRNA *override_rna_prop,
|
|
const int rnaprop_index)
|
|
{
|
|
AnimData *anim_data = BKE_animdata_from_id(id);
|
|
if (anim_data != nullptr) {
|
|
FCurve *fcurve;
|
|
char *index_token_start = const_cast<char *>(
|
|
RNA_path_array_index_token_find(liboverride_prop->rna_path, override_rna_prop));
|
|
if (index_token_start != nullptr) {
|
|
const char index_token_start_backup = *index_token_start;
|
|
*index_token_start = '\0';
|
|
fcurve = BKE_animadata_fcurve_find_by_rna_path(
|
|
anim_data, liboverride_prop->rna_path, rnaprop_index, nullptr, nullptr);
|
|
*index_token_start = index_token_start_backup;
|
|
}
|
|
else {
|
|
fcurve = BKE_animadata_fcurve_find_by_rna_path(
|
|
anim_data, liboverride_prop->rna_path, 0, nullptr, nullptr);
|
|
}
|
|
if (fcurve != nullptr) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int foreachid_is_hierarchy_leaf_fn(LibraryIDLinkCallbackData *cb_data)
|
|
{
|
|
ID *id_owner = cb_data->owner_id;
|
|
ID *id = *cb_data->id_pointer;
|
|
bool *is_leaf = static_cast<bool *>(cb_data->user_data);
|
|
|
|
if (cb_data->cb_flag & IDWALK_CB_LOOPBACK) {
|
|
return IDWALK_RET_NOP;
|
|
}
|
|
|
|
if (id != nullptr && ID_IS_OVERRIDE_LIBRARY_REAL(id) &&
|
|
id->override_library->hierarchy_root == id_owner->override_library->hierarchy_root)
|
|
{
|
|
*is_leaf = false;
|
|
return IDWALK_RET_STOP_ITER;
|
|
}
|
|
return IDWALK_RET_NOP;
|
|
}
|
|
|
|
bool BKE_lib_override_library_is_hierarchy_leaf(Main *bmain, ID *id)
|
|
{
|
|
if (ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
|
|
bool is_leaf = true;
|
|
BKE_library_foreach_ID_link(
|
|
bmain, id, foreachid_is_hierarchy_leaf_fn, &is_leaf, IDWALK_READONLY);
|
|
return is_leaf;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void BKE_lib_override_id_tag_on_deg_tag_from_user(ID *id)
|
|
{
|
|
/* Only local liboverrides need to be tagged for refresh, linked ones should not be editable. */
|
|
if (ID_IS_LINKED(id) || !ID_IS_OVERRIDE_LIBRARY(id)) {
|
|
return;
|
|
}
|
|
/* NOTE: Valid relationships between IDs here (especially the beloved ObData <-> ShapeKey special
|
|
* case) cannot be always expected when ID get tagged. So now, embedded IDs and similar also get
|
|
* tagged, and the 'liboverride refresh' code is responsible to properly progagate the update to
|
|
* the owner ID when needed (see #BKE_lib_override_library_main_operations_create). */
|
|
id->tag |= LIB_TAG_LIBOVERRIDE_AUTOREFRESH;
|
|
}
|
|
|
|
ID *BKE_lib_override_library_create_from_id(Main *bmain,
|
|
ID *reference_id,
|
|
const bool do_tagged_remap)
|
|
{
|
|
BLI_assert(reference_id != nullptr);
|
|
BLI_assert(ID_IS_LINKED(reference_id));
|
|
|
|
ID *local_id = lib_override_library_create_from(bmain, nullptr, reference_id, 0);
|
|
/* We cannot allow automatic hierarchy resync on this ID, it is highly likely to generate a giant
|
|
* mess in case there are a lot of hidden, non-instantiated, non-properly organized dependencies.
|
|
* Ref #94650. */
|
|
local_id->override_library->flag |= LIBOVERRIDE_FLAG_NO_HIERARCHY;
|
|
local_id->override_library->flag &= ~LIBOVERRIDE_FLAG_SYSTEM_DEFINED;
|
|
local_id->override_library->hierarchy_root = local_id;
|
|
|
|
if (do_tagged_remap) {
|
|
Key *reference_key, *local_key = nullptr;
|
|
if ((reference_key = BKE_key_from_id(reference_id)) != nullptr) {
|
|
local_key = BKE_key_from_id(local_id);
|
|
BLI_assert(local_key != nullptr);
|
|
}
|
|
|
|
ID *other_id;
|
|
FOREACH_MAIN_ID_BEGIN (bmain, other_id) {
|
|
if ((other_id->tag & LIB_TAG_DOIT) != 0 && !ID_IS_LINKED(other_id)) {
|
|
/* Note that using ID_REMAP_SKIP_INDIRECT_USAGE below is superfluous, as we only remap
|
|
* local IDs usages anyway. */
|
|
BKE_libblock_relink_ex(bmain,
|
|
other_id,
|
|
reference_id,
|
|
local_id,
|
|
ID_REMAP_SKIP_INDIRECT_USAGE | ID_REMAP_SKIP_OVERRIDE_LIBRARY);
|
|
if (reference_key != nullptr) {
|
|
BKE_libblock_relink_ex(bmain,
|
|
other_id,
|
|
&reference_key->id,
|
|
&local_key->id,
|
|
ID_REMAP_SKIP_INDIRECT_USAGE | ID_REMAP_SKIP_OVERRIDE_LIBRARY);
|
|
}
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
}
|
|
|
|
/* Cleanup global namemap, to avoid extra processing with regular ID name management. Better to
|
|
* re-create the global namemap on demand. */
|
|
BKE_main_namemap_destroy(&bmain->name_map_global);
|
|
|
|
return local_id;
|
|
}
|
|
|
|
static void lib_override_prefill_newid_from_existing_overrides(Main *bmain, ID *id_hierarchy_root)
|
|
{
|
|
ID *id_iter;
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
|
|
ID *id = id_iter;
|
|
if (GS(id_iter->name) == ID_KE) {
|
|
id = reinterpret_cast<Key *>(id_iter)->from;
|
|
BLI_assert(id != nullptr);
|
|
}
|
|
if (ID_IS_OVERRIDE_LIBRARY_REAL(id) &&
|
|
id->override_library->hierarchy_root == id_hierarchy_root) {
|
|
id->override_library->reference->newid = id;
|
|
if (GS(id_iter->name) == ID_KE) {
|
|
Key *reference_key = BKE_key_from_id(id->override_library->reference);
|
|
if (reference_key != nullptr) {
|
|
reference_key->id.newid = id_iter;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
}
|
|
|
|
static void lib_override_remapper_overrides_add(IDRemapper *id_remapper,
|
|
ID *reference_id,
|
|
ID *local_id)
|
|
{
|
|
BKE_id_remapper_add(id_remapper, reference_id, local_id);
|
|
|
|
Key *reference_key, *local_key = nullptr;
|
|
if ((reference_key = BKE_key_from_id(reference_id)) != nullptr) {
|
|
if (reference_id->newid != nullptr) {
|
|
local_key = BKE_key_from_id(reference_id->newid);
|
|
BLI_assert(local_key != nullptr);
|
|
}
|
|
|
|
BKE_id_remapper_add(id_remapper, &reference_key->id, &local_key->id);
|
|
}
|
|
}
|
|
|
|
/* TODO: Make this static local function instead? API is becoming complex, and it's not used
|
|
* outside of this file anyway. */
|
|
bool BKE_lib_override_library_create_from_tag(Main *bmain,
|
|
Library *owner_library,
|
|
const ID *id_root_reference,
|
|
ID *id_hierarchy_root,
|
|
const ID *id_hierarchy_root_reference,
|
|
const bool do_no_main,
|
|
const bool do_fully_editable)
|
|
{
|
|
BLI_assert(id_root_reference != nullptr && ID_IS_LINKED(id_root_reference));
|
|
/* If we do not have any hierarchy root given, then the root reference must be tagged for
|
|
* override. */
|
|
BLI_assert(id_hierarchy_root != nullptr || id_hierarchy_root_reference != nullptr ||
|
|
(id_root_reference->tag & LIB_TAG_DOIT) != 0);
|
|
/* At least one of the hierarchy root pointers must be nullptr, passing both is useless and can
|
|
* create confusion. */
|
|
BLI_assert(ELEM(nullptr, id_hierarchy_root, id_hierarchy_root_reference));
|
|
|
|
if (id_hierarchy_root != nullptr) {
|
|
/* If the hierarchy root is given, it must be a valid existing override (used during partial
|
|
* resync process mainly). */
|
|
BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root) &&
|
|
id_hierarchy_root->override_library->reference->lib == id_root_reference->lib);
|
|
|
|
if (!do_no_main) {
|
|
/* When processing within Main, set existing overrides in given hierarchy as 'newid' of their
|
|
* linked reference. This allows to re-use existing overrides instead of creating new ones in
|
|
* partial override cases. */
|
|
lib_override_prefill_newid_from_existing_overrides(bmain, id_hierarchy_root);
|
|
}
|
|
}
|
|
if (!ELEM(id_hierarchy_root_reference, nullptr, id_root_reference)) {
|
|
/* If the reference hierarchy root is given, it must be from the same library as the reference
|
|
* root, and also tagged for override. */
|
|
BLI_assert(id_hierarchy_root_reference->lib == id_root_reference->lib &&
|
|
(id_hierarchy_root_reference->tag & LIB_TAG_DOIT) != 0);
|
|
}
|
|
|
|
const Library *reference_library = id_root_reference->lib;
|
|
|
|
ID *reference_id;
|
|
bool success = true;
|
|
|
|
ListBase todo_ids = {nullptr};
|
|
LinkData *todo_id_iter;
|
|
|
|
/* Get all IDs we want to override. */
|
|
FOREACH_MAIN_ID_BEGIN (bmain, reference_id) {
|
|
if ((reference_id->tag & LIB_TAG_DOIT) != 0 && reference_id->lib == reference_library &&
|
|
BKE_idtype_idcode_is_linkable(GS(reference_id->name)))
|
|
{
|
|
todo_id_iter = MEM_cnew<LinkData>(__func__);
|
|
todo_id_iter->data = reference_id;
|
|
BLI_addtail(&todo_ids, todo_id_iter);
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
/* Override the IDs. */
|
|
for (todo_id_iter = static_cast<LinkData *>(todo_ids.first); todo_id_iter != nullptr;
|
|
todo_id_iter = todo_id_iter->next)
|
|
{
|
|
reference_id = static_cast<ID *>(todo_id_iter->data);
|
|
|
|
/* If `newid` is already set, assume it has been handled by calling code.
|
|
* Only current use case: re-using proxy ID when converting to liboverride. */
|
|
if (reference_id->newid == nullptr) {
|
|
/* NOTE: `no main` case is used during resync procedure, to support recursive resync.
|
|
* This requires extra care further down the resync process,
|
|
* see: #BKE_lib_override_library_resync. */
|
|
reference_id->newid = lib_override_library_create_from(
|
|
bmain, owner_library, reference_id, do_no_main ? LIB_ID_CREATE_NO_MAIN : 0);
|
|
if (reference_id->newid == nullptr) {
|
|
success = false;
|
|
break;
|
|
}
|
|
if (do_fully_editable) {
|
|
reference_id->newid->override_library->flag &= ~LIBOVERRIDE_FLAG_SYSTEM_DEFINED;
|
|
}
|
|
}
|
|
/* We also tag the new IDs so that in next step we can remap their pointers too. */
|
|
reference_id->newid->tag |= LIB_TAG_DOIT;
|
|
|
|
Key *reference_key;
|
|
if ((reference_key = BKE_key_from_id(reference_id)) != nullptr) {
|
|
reference_key->id.tag |= LIB_TAG_DOIT;
|
|
|
|
Key *local_key = BKE_key_from_id(reference_id->newid);
|
|
BLI_assert(local_key != nullptr);
|
|
reference_key->id.newid = &local_key->id;
|
|
/* We also tag the new IDs so that in next step we can remap their pointers too. */
|
|
local_key->id.tag |= LIB_TAG_DOIT;
|
|
}
|
|
}
|
|
|
|
/* Only remap new local ID's pointers, we don't want to force our new overrides onto our whole
|
|
* existing linked IDs usages. */
|
|
if (success) {
|
|
/* If a valid liboverride hierarchy root was given, only remap non-liboverride data and
|
|
* liboverrides belonging to that hierarchy. Avoids having other liboverride hierarchies of
|
|
* the same reference data also remapped to the newly created liboverride. */
|
|
const bool do_remap_liboverride_hierarchy_only = (id_hierarchy_root != nullptr && !do_no_main);
|
|
|
|
if (id_hierarchy_root_reference != nullptr) {
|
|
id_hierarchy_root = id_hierarchy_root_reference->newid;
|
|
}
|
|
else if (id_root_reference->newid != nullptr &&
|
|
(id_hierarchy_root == nullptr ||
|
|
id_hierarchy_root->override_library->reference == id_root_reference))
|
|
{
|
|
id_hierarchy_root = id_root_reference->newid;
|
|
}
|
|
BLI_assert(id_hierarchy_root != nullptr);
|
|
|
|
LinkNode *relinked_ids = nullptr;
|
|
IDRemapper *id_remapper = BKE_id_remapper_create();
|
|
/* Still checking the whole Main, that way we can tag other local IDs as needing to be
|
|
* remapped to use newly created overriding IDs, if needed. */
|
|
ID *id;
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
ID *other_id;
|
|
/* In case we created new overrides as 'no main', they are not accessible directly in this
|
|
* loop, but we can get to them through their reference's `newid` pointer. */
|
|
if (do_no_main && id->lib == id_root_reference->lib && id->newid != nullptr) {
|
|
other_id = id->newid;
|
|
/* Otherwise we cannot properly distinguish between IDs that are actually from the
|
|
* linked library (and should not be remapped), and IDs that are overrides re-generated
|
|
* from the reference from the linked library, and must therefore be remapped.
|
|
*
|
|
* This is reset afterwards at the end of this loop. */
|
|
other_id->lib = nullptr;
|
|
}
|
|
else {
|
|
other_id = id;
|
|
}
|
|
|
|
/* If other ID is a linked one, but not from the same library as our reference, then we
|
|
* consider we should also relink it, as part of recursive resync. */
|
|
if ((other_id->tag & LIB_TAG_DOIT) != 0 && other_id->lib != id_root_reference->lib) {
|
|
ID *owner_id;
|
|
BKE_lib_override_library_get(bmain, other_id, nullptr, &owner_id);
|
|
|
|
/* When the root of the current liboverride hierarchy is known, only remap liboverrides if
|
|
* they belong to that hierarchy. */
|
|
if (!do_remap_liboverride_hierarchy_only ||
|
|
(!ID_IS_OVERRIDE_LIBRARY_REAL(owner_id) ||
|
|
owner_id->override_library->hierarchy_root == id_hierarchy_root))
|
|
{
|
|
BLI_linklist_prepend(&relinked_ids, other_id);
|
|
}
|
|
|
|
if (ID_IS_OVERRIDE_LIBRARY_REAL(other_id) &&
|
|
other_id->override_library->hierarchy_root == id_hierarchy_root)
|
|
{
|
|
reference_id = other_id->override_library->reference;
|
|
ID *local_id = reference_id->newid;
|
|
if (other_id == local_id) {
|
|
lib_override_remapper_overrides_add(id_remapper, reference_id, local_id);
|
|
}
|
|
}
|
|
}
|
|
if (other_id != id) {
|
|
other_id->lib = id_root_reference->lib;
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
for (todo_id_iter = static_cast<LinkData *>(todo_ids.first); todo_id_iter != nullptr;
|
|
todo_id_iter = todo_id_iter->next)
|
|
{
|
|
reference_id = static_cast<ID *>(todo_id_iter->data);
|
|
ID *local_id = reference_id->newid;
|
|
|
|
if (local_id == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
local_id->override_library->hierarchy_root = id_hierarchy_root;
|
|
|
|
lib_override_remapper_overrides_add(id_remapper, reference_id, local_id);
|
|
}
|
|
|
|
BKE_libblock_relink_multiple(bmain,
|
|
relinked_ids,
|
|
ID_REMAP_TYPE_REMAP,
|
|
id_remapper,
|
|
ID_REMAP_SKIP_OVERRIDE_LIBRARY | ID_REMAP_FORCE_USER_REFCOUNT);
|
|
|
|
BKE_id_remapper_free(id_remapper);
|
|
BLI_linklist_free(relinked_ids, nullptr);
|
|
}
|
|
else {
|
|
/* We need to cleanup potentially already created data. */
|
|
for (todo_id_iter = static_cast<LinkData *>(todo_ids.first); todo_id_iter != nullptr;
|
|
todo_id_iter = todo_id_iter->next)
|
|
{
|
|
reference_id = static_cast<ID *>(todo_id_iter->data);
|
|
BKE_id_delete(bmain, reference_id->newid);
|
|
reference_id->newid = nullptr;
|
|
}
|
|
}
|
|
|
|
BLI_freelistN(&todo_ids);
|
|
|
|
return success;
|
|
}
|
|
|
|
struct LibOverrideGroupTagData {
|
|
Main *bmain;
|
|
Scene *scene;
|
|
ID *id_root;
|
|
ID *hierarchy_root_id;
|
|
uint tag;
|
|
uint missing_tag;
|
|
/* Whether we are looping on override data, or their references (linked) one. */
|
|
bool is_override;
|
|
/* Whether we are creating new override, or resyncing existing one. */
|
|
bool is_resync;
|
|
|
|
/* Mapping linked objects to all their instantiating collections (as a linked list).
|
|
* Avoids calling #BKE_collection_object_find over and over, this function is very expansive. */
|
|
GHash *linked_object_to_instantiating_collections;
|
|
MemArena *mem_arena;
|
|
};
|
|
|
|
static void lib_override_group_tag_data_object_to_collection_init_collection_process(
|
|
LibOverrideGroupTagData *data, Collection *collection)
|
|
{
|
|
LISTBASE_FOREACH (CollectionObject *, collection_object, &collection->gobject) {
|
|
Object *ob = collection_object->ob;
|
|
if (!ID_IS_LINKED(ob)) {
|
|
continue;
|
|
}
|
|
|
|
LinkNodePair **collections_linkedlist_p;
|
|
if (!BLI_ghash_ensure_p(data->linked_object_to_instantiating_collections,
|
|
ob,
|
|
reinterpret_cast<void ***>(&collections_linkedlist_p)))
|
|
{
|
|
*collections_linkedlist_p = static_cast<LinkNodePair *>(
|
|
BLI_memarena_calloc(data->mem_arena, sizeof(**collections_linkedlist_p)));
|
|
}
|
|
BLI_linklist_append_arena(*collections_linkedlist_p, collection, data->mem_arena);
|
|
}
|
|
}
|
|
|
|
/* Initialize complex data, `data` is expected to be already initialized with basic pointers and
|
|
* other simple data.
|
|
*
|
|
* NOTE: Currently creates a mapping from linked object to all of their instantiating collections
|
|
* (as returned by #BKE_collection_object_find). */
|
|
static void lib_override_group_tag_data_object_to_collection_init(LibOverrideGroupTagData *data)
|
|
{
|
|
data->mem_arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
|
|
|
|
data->linked_object_to_instantiating_collections = BLI_ghash_new(
|
|
BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__);
|
|
if (data->scene != nullptr) {
|
|
lib_override_group_tag_data_object_to_collection_init_collection_process(
|
|
data, data->scene->master_collection);
|
|
}
|
|
LISTBASE_FOREACH (Collection *, collection, &data->bmain->collections) {
|
|
lib_override_group_tag_data_object_to_collection_init_collection_process(data, collection);
|
|
}
|
|
}
|
|
|
|
static void lib_override_group_tag_data_clear(LibOverrideGroupTagData *data)
|
|
{
|
|
BLI_ghash_free(data->linked_object_to_instantiating_collections, nullptr, nullptr);
|
|
BLI_memarena_free(data->mem_arena);
|
|
memset(data, 0, sizeof(*data));
|
|
}
|
|
|
|
static void lib_override_hierarchy_dependencies_recursive_tag_from(LibOverrideGroupTagData *data)
|
|
{
|
|
Main *bmain = data->bmain;
|
|
ID *id = data->id_root;
|
|
const bool is_override = data->is_override;
|
|
|
|
if ((*reinterpret_cast<uint *>(&id->tag) & data->tag) == 0) {
|
|
/* This ID is not tagged, no reason to proceed further to its parents. */
|
|
return;
|
|
}
|
|
|
|
MainIDRelationsEntry *entry = static_cast<MainIDRelationsEntry *>(
|
|
BLI_ghash_lookup(bmain->relations->relations_from_pointers, id));
|
|
BLI_assert(entry != nullptr);
|
|
|
|
if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED_FROM) {
|
|
/* This ID has already been processed. */
|
|
return;
|
|
}
|
|
/* This way we won't process again that ID, should we encounter it again through another
|
|
* relationship hierarchy. */
|
|
entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED_FROM;
|
|
|
|
for (MainIDRelationsEntryItem *from_id_entry = entry->from_ids; from_id_entry != nullptr;
|
|
from_id_entry = from_id_entry->next)
|
|
{
|
|
if ((from_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) {
|
|
/* Never consider non-overridable relationships ('from', 'parents', 'owner' etc. pointers)
|
|
* as actual dependencies. */
|
|
continue;
|
|
}
|
|
/* We only consider IDs from the same library. */
|
|
ID *from_id = from_id_entry->id_pointer.from;
|
|
if (from_id == nullptr || from_id->lib != id->lib ||
|
|
(is_override && !ID_IS_OVERRIDE_LIBRARY(from_id)))
|
|
{
|
|
/* IDs from different libraries, or non-override IDs in case we are processing overrides,
|
|
* are both barriers of dependency. */
|
|
continue;
|
|
}
|
|
from_id->tag |= data->tag;
|
|
LibOverrideGroupTagData sub_data = *data;
|
|
sub_data.id_root = from_id;
|
|
lib_override_hierarchy_dependencies_recursive_tag_from(&sub_data);
|
|
}
|
|
}
|
|
|
|
/* Tag all IDs in dependency relationships within an override hierarchy/group.
|
|
*
|
|
* Requires existing `Main.relations`.
|
|
*
|
|
* NOTE: This is typically called to complete `lib_override_linked_group_tag()`.
|
|
*/
|
|
static bool lib_override_hierarchy_dependencies_recursive_tag(LibOverrideGroupTagData *data)
|
|
{
|
|
Main *bmain = data->bmain;
|
|
ID *id = data->id_root;
|
|
const bool is_override = data->is_override;
|
|
const bool is_resync = data->is_resync;
|
|
|
|
MainIDRelationsEntry *entry = static_cast<MainIDRelationsEntry *>(
|
|
BLI_ghash_lookup(bmain->relations->relations_from_pointers, id));
|
|
BLI_assert(entry != nullptr);
|
|
|
|
if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED_TO) {
|
|
/* This ID has already been processed. */
|
|
return (*reinterpret_cast<uint *>(&id->tag) & data->tag) != 0;
|
|
}
|
|
/* This way we won't process again that ID, should we encounter it again through another
|
|
* relationship hierarchy. */
|
|
entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED_TO;
|
|
|
|
for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != nullptr;
|
|
to_id_entry = to_id_entry->next)
|
|
{
|
|
if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) {
|
|
/* Never consider non-overridable relationships ('from', 'parents', 'owner' etc. pointers) as
|
|
* actual dependencies. */
|
|
continue;
|
|
}
|
|
/* We only consider IDs from the same library. */
|
|
ID *to_id = *to_id_entry->id_pointer.to;
|
|
if (to_id == nullptr || to_id->lib != id->lib ||
|
|
(is_override && !ID_IS_OVERRIDE_LIBRARY(to_id))) {
|
|
/* IDs from different libraries, or non-override IDs in case we are processing overrides, are
|
|
* both barriers of dependency. */
|
|
continue;
|
|
}
|
|
LibOverrideGroupTagData sub_data = *data;
|
|
sub_data.id_root = to_id;
|
|
if (lib_override_hierarchy_dependencies_recursive_tag(&sub_data)) {
|
|
id->tag |= data->tag;
|
|
}
|
|
}
|
|
|
|
/* If the current ID is/has been tagged for override above, then check its reversed dependencies
|
|
* (i.e. IDs that depend on the current one).
|
|
*
|
|
* This will cover e.g. the case where user override an armature, and would expect the mesh
|
|
* object deformed by that armature to also be overridden. */
|
|
if ((*reinterpret_cast<uint *>(&id->tag) & data->tag) != 0 && !is_resync) {
|
|
lib_override_hierarchy_dependencies_recursive_tag_from(data);
|
|
}
|
|
|
|
return (*reinterpret_cast<uint *>(&id->tag) & data->tag) != 0;
|
|
}
|
|
|
|
static void lib_override_linked_group_tag_recursive(LibOverrideGroupTagData *data)
|
|
{
|
|
Main *bmain = data->bmain;
|
|
ID *id_owner = data->id_root;
|
|
BLI_assert(ID_IS_LINKED(id_owner));
|
|
BLI_assert(!data->is_override);
|
|
const uint tag = data->tag;
|
|
const uint missing_tag = data->missing_tag;
|
|
|
|
MainIDRelationsEntry *entry = static_cast<MainIDRelationsEntry *>(
|
|
BLI_ghash_lookup(bmain->relations->relations_from_pointers, id_owner));
|
|
BLI_assert(entry != nullptr);
|
|
|
|
if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) {
|
|
/* This ID has already been processed. */
|
|
return;
|
|
}
|
|
/* This way we won't process again that ID, should we encounter it again through another
|
|
* relationship hierarchy. */
|
|
entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED;
|
|
|
|
for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != nullptr;
|
|
to_id_entry = to_id_entry->next)
|
|
{
|
|
if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) {
|
|
/* Never consider non-overridable relationships as actual dependencies. */
|
|
continue;
|
|
}
|
|
|
|
ID *to_id = *to_id_entry->id_pointer.to;
|
|
if (ELEM(to_id, nullptr, id_owner)) {
|
|
continue;
|
|
}
|
|
/* We only consider IDs from the same library. */
|
|
if (to_id->lib != id_owner->lib) {
|
|
continue;
|
|
}
|
|
BLI_assert(ID_IS_LINKED(to_id));
|
|
|
|
/* We tag all collections and objects for override. And we also tag all other data-blocks which
|
|
* would use one of those.
|
|
* NOTE: missing IDs (aka placeholders) are never overridden. */
|
|
if (ELEM(GS(to_id->name), ID_OB, ID_GR)) {
|
|
if (to_id->tag & LIB_TAG_MISSING) {
|
|
to_id->tag |= missing_tag;
|
|
}
|
|
else {
|
|
to_id->tag |= tag;
|
|
}
|
|
}
|
|
|
|
/* Recursively process the dependencies. */
|
|
LibOverrideGroupTagData sub_data = *data;
|
|
sub_data.id_root = to_id;
|
|
lib_override_linked_group_tag_recursive(&sub_data);
|
|
}
|
|
}
|
|
|
|
static bool lib_override_linked_group_tag_collections_keep_tagged_check_recursive(
|
|
LibOverrideGroupTagData *data, Collection *collection)
|
|
{
|
|
/* NOTE: Collection's object cache (using bases, as returned by #BKE_collection_object_cache_get)
|
|
* is not usable here, as it may have become invalid from some previous operation and it should
|
|
* not be updated here. So instead only use collections' reliable 'raw' data to check if some
|
|
* object in the hierarchy of the given collection is still tagged for override. */
|
|
for (CollectionObject *collection_object =
|
|
static_cast<CollectionObject *>(collection->gobject.first);
|
|
collection_object != nullptr;
|
|
collection_object = collection_object->next)
|
|
{
|
|
Object *object = collection_object->ob;
|
|
if (object == nullptr) {
|
|
continue;
|
|
}
|
|
if ((object->id.tag & data->tag) != 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (CollectionChild *collection_child =
|
|
static_cast<CollectionChild *>(collection->children.first);
|
|
collection_child != nullptr;
|
|
collection_child = collection_child->next)
|
|
{
|
|
if (lib_override_linked_group_tag_collections_keep_tagged_check_recursive(
|
|
data, collection_child->collection))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void lib_override_linked_group_tag_clear_boneshapes_objects(LibOverrideGroupTagData *data)
|
|
{
|
|
Main *bmain = data->bmain;
|
|
ID *id_root = data->id_root;
|
|
|
|
/* Remove (untag) bone shape objects, they shall never need to be to directly/explicitly
|
|
* overridden. */
|
|
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
|
|
if (ob->type == OB_ARMATURE && ob->pose != nullptr && (ob->id.tag & data->tag)) {
|
|
for (bPoseChannel *pchan = static_cast<bPoseChannel *>(ob->pose->chanbase.first);
|
|
pchan != nullptr;
|
|
pchan = pchan->next)
|
|
{
|
|
if (pchan->custom != nullptr && &pchan->custom->id != id_root) {
|
|
pchan->custom->id.tag &= ~data->tag;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Remove (untag) collections if they do not own any tagged object (either themselves, or in
|
|
* their children collections). */
|
|
LISTBASE_FOREACH (Collection *, collection, &bmain->collections) {
|
|
if ((collection->id.tag & data->tag) == 0 || &collection->id == id_root) {
|
|
continue;
|
|
}
|
|
|
|
if (!lib_override_linked_group_tag_collections_keep_tagged_check_recursive(data, collection)) {
|
|
collection->id.tag &= ~data->tag;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* This will tag at least all 'boundary' linked IDs for a potential override group.
|
|
*
|
|
* Requires existing `Main.relations`.
|
|
*
|
|
* Note that you will then need to call #lib_override_hierarchy_dependencies_recursive_tag to
|
|
* complete tagging of all dependencies within the override group.
|
|
*
|
|
* We currently only consider Collections and Objects (that are not used as bone shapes) as valid
|
|
* boundary IDs to define an override group.
|
|
*/
|
|
static void lib_override_linked_group_tag(LibOverrideGroupTagData *data)
|
|
{
|
|
Main *bmain = data->bmain;
|
|
ID *id_root = data->id_root;
|
|
const bool is_resync = data->is_resync;
|
|
BLI_assert(!data->is_override);
|
|
|
|
if (id_root->tag & LIB_TAG_MISSING) {
|
|
id_root->tag |= data->missing_tag;
|
|
}
|
|
else {
|
|
id_root->tag |= data->tag;
|
|
}
|
|
|
|
/* Tag all collections and objects recursively. */
|
|
lib_override_linked_group_tag_recursive(data);
|
|
|
|
/* Do not override objects used as bone shapes, nor their collections if possible. */
|
|
lib_override_linked_group_tag_clear_boneshapes_objects(data);
|
|
|
|
/* For each object tagged for override, ensure we get at least one local or liboverride
|
|
* collection to host it. Avoids getting a bunch of random object in the scene's master
|
|
* collection when all objects' dependencies are not properly 'packed' into a single root
|
|
* collection.
|
|
*
|
|
* NOTE: In resync case, we do not handle this at all, since:
|
|
* - In normal, valid cases nothing would be needed anyway (resync process takes care
|
|
* of tagging needed 'owner' collection then).
|
|
* - Partial resync makes it extremely difficult to properly handle such extra
|
|
* collection 'tagging for override' (since one would need to know if the new object
|
|
* is actually going to replace an already existing override [most common case], or
|
|
* if it is actually a real new 'orphan' one).
|
|
* - While not ideal, having objects dangling around is less critical than both points
|
|
* above.
|
|
* So if users add new objects to their library override hierarchy in an invalid way, so
|
|
* be it. Trying to find a collection to override and host this new object would most
|
|
* likely make existing override very unclean anyway. */
|
|
if (is_resync) {
|
|
return;
|
|
}
|
|
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
|
|
if (ID_IS_LINKED(ob) && (ob->id.tag & data->tag) != 0) {
|
|
Collection *instantiating_collection = nullptr;
|
|
Collection *instantiating_collection_override_candidate = nullptr;
|
|
/* Loop over all collections instantiating the object, if we already have a 'locale' one we
|
|
* have nothing to do, otherwise try to find a 'linked' one that we can override too. */
|
|
LinkNodePair *instantiating_collection_linklist = static_cast<LinkNodePair *>(
|
|
BLI_ghash_lookup(data->linked_object_to_instantiating_collections, ob));
|
|
if (instantiating_collection_linklist != nullptr) {
|
|
for (LinkNode *instantiating_collection_linknode = instantiating_collection_linklist->list;
|
|
instantiating_collection_linknode != nullptr;
|
|
instantiating_collection_linknode = instantiating_collection_linknode->next)
|
|
{
|
|
instantiating_collection = static_cast<Collection *>(
|
|
instantiating_collection_linknode->link);
|
|
if (!ID_IS_LINKED(instantiating_collection)) {
|
|
/* There is a local collection instantiating the linked object to override, nothing
|
|
* else to be done here. */
|
|
break;
|
|
}
|
|
if (instantiating_collection->id.tag & data->tag) {
|
|
/* There is a linked collection instantiating the linked object to override,
|
|
* already tagged to be overridden, nothing else to be done here. */
|
|
break;
|
|
}
|
|
instantiating_collection_override_candidate = instantiating_collection;
|
|
instantiating_collection = nullptr;
|
|
}
|
|
}
|
|
|
|
if (instantiating_collection == nullptr &&
|
|
instantiating_collection_override_candidate != nullptr) {
|
|
if (instantiating_collection_override_candidate->id.tag & LIB_TAG_MISSING) {
|
|
instantiating_collection_override_candidate->id.tag |= data->missing_tag;
|
|
}
|
|
else {
|
|
instantiating_collection_override_candidate->id.tag |= data->tag;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void lib_override_overrides_group_tag_recursive(LibOverrideGroupTagData *data)
|
|
{
|
|
Main *bmain = data->bmain;
|
|
ID *id_owner = data->id_root;
|
|
BLI_assert(ID_IS_OVERRIDE_LIBRARY(id_owner));
|
|
BLI_assert(data->is_override);
|
|
|
|
ID *id_hierarchy_root = data->hierarchy_root_id;
|
|
|
|
if (ID_IS_OVERRIDE_LIBRARY_REAL(id_owner) &&
|
|
(id_owner->override_library->flag & LIBOVERRIDE_FLAG_NO_HIERARCHY) != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const uint tag = data->tag;
|
|
const uint missing_tag = data->missing_tag;
|
|
|
|
MainIDRelationsEntry *entry = static_cast<MainIDRelationsEntry *>(
|
|
BLI_ghash_lookup(bmain->relations->relations_from_pointers, id_owner));
|
|
BLI_assert(entry != nullptr);
|
|
|
|
if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) {
|
|
/* This ID has already been processed. */
|
|
return;
|
|
}
|
|
/* This way we won't process again that ID, should we encounter it again through another
|
|
* relationship hierarchy. */
|
|
entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED;
|
|
|
|
for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != nullptr;
|
|
to_id_entry = to_id_entry->next)
|
|
{
|
|
if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) {
|
|
/* Never consider non-overridable relationships as actual dependencies. */
|
|
continue;
|
|
}
|
|
|
|
ID *to_id = *to_id_entry->id_pointer.to;
|
|
if (ELEM(to_id, nullptr, id_owner)) {
|
|
continue;
|
|
}
|
|
/* Different libraries or different hierarchy roots are break points in override hierarchies.
|
|
*/
|
|
if (!ID_IS_OVERRIDE_LIBRARY(to_id) || (to_id->lib != id_owner->lib)) {
|
|
continue;
|
|
}
|
|
if (ID_IS_OVERRIDE_LIBRARY_REAL(to_id) &&
|
|
to_id->override_library->hierarchy_root != id_hierarchy_root)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const Library *reference_lib =
|
|
BKE_lib_override_library_get(bmain, id_owner, nullptr, nullptr)->reference->lib;
|
|
const ID *to_id_reference =
|
|
BKE_lib_override_library_get(bmain, to_id, nullptr, nullptr)->reference;
|
|
if (to_id_reference->lib != reference_lib) {
|
|
/* We do not override data-blocks from other libraries, nor do we process them. */
|
|
continue;
|
|
}
|
|
|
|
if (to_id_reference->tag & LIB_TAG_MISSING) {
|
|
to_id->tag |= missing_tag;
|
|
}
|
|
else {
|
|
to_id->tag |= tag;
|
|
}
|
|
|
|
/* Recursively process the dependencies. */
|
|
LibOverrideGroupTagData sub_data = *data;
|
|
sub_data.id_root = to_id;
|
|
lib_override_overrides_group_tag_recursive(&sub_data);
|
|
}
|
|
}
|
|
|
|
/* This will tag all override IDs of an override group defined by the given `id_root`. */
|
|
static void lib_override_overrides_group_tag(LibOverrideGroupTagData *data)
|
|
{
|
|
ID *id_root = data->id_root;
|
|
BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root));
|
|
BLI_assert(data->is_override);
|
|
|
|
ID *id_hierarchy_root = data->hierarchy_root_id;
|
|
BLI_assert(id_hierarchy_root != nullptr);
|
|
BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root));
|
|
UNUSED_VARS_NDEBUG(id_hierarchy_root);
|
|
|
|
if (id_root->override_library->reference->tag & LIB_TAG_MISSING) {
|
|
id_root->tag |= data->missing_tag;
|
|
}
|
|
else {
|
|
id_root->tag |= data->tag;
|
|
}
|
|
|
|
/* Tag all local overrides in id_root's group. */
|
|
lib_override_overrides_group_tag_recursive(data);
|
|
}
|
|
|
|
static bool lib_override_library_create_do(Main *bmain,
|
|
Scene *scene,
|
|
Library *owner_library,
|
|
ID *id_root_reference,
|
|
ID *id_hierarchy_root_reference,
|
|
const bool do_fully_editable)
|
|
{
|
|
BKE_main_relations_create(bmain, 0);
|
|
LibOverrideGroupTagData data{};
|
|
data.bmain = bmain;
|
|
data.scene = scene;
|
|
data.id_root = id_root_reference;
|
|
data.tag = LIB_TAG_DOIT;
|
|
data.missing_tag = LIB_TAG_MISSING;
|
|
data.is_override = false;
|
|
data.is_resync = false;
|
|
lib_override_group_tag_data_object_to_collection_init(&data);
|
|
lib_override_linked_group_tag(&data);
|
|
|
|
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
|
|
lib_override_hierarchy_dependencies_recursive_tag(&data);
|
|
|
|
/* In case the operation is on an already partially overridden hierarchy, all existing overrides
|
|
* in that hierarchy need to be tagged for remapping from linked reference ID usages to newly
|
|
* created overrides ones. */
|
|
if (id_hierarchy_root_reference->lib != id_root_reference->lib) {
|
|
BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference));
|
|
BLI_assert(id_hierarchy_root_reference->override_library->reference->lib ==
|
|
id_root_reference->lib);
|
|
|
|
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
|
|
data.hierarchy_root_id = id_hierarchy_root_reference;
|
|
data.id_root = id_hierarchy_root_reference;
|
|
data.is_override = true;
|
|
lib_override_overrides_group_tag(&data);
|
|
}
|
|
|
|
BKE_main_relations_free(bmain);
|
|
lib_override_group_tag_data_clear(&data);
|
|
|
|
bool success = false;
|
|
if (id_hierarchy_root_reference->lib != id_root_reference->lib) {
|
|
success = BKE_lib_override_library_create_from_tag(bmain,
|
|
owner_library,
|
|
id_root_reference,
|
|
id_hierarchy_root_reference,
|
|
nullptr,
|
|
false,
|
|
do_fully_editable);
|
|
}
|
|
else {
|
|
success = BKE_lib_override_library_create_from_tag(bmain,
|
|
owner_library,
|
|
id_root_reference,
|
|
nullptr,
|
|
id_hierarchy_root_reference,
|
|
false,
|
|
do_fully_editable);
|
|
}
|
|
|
|
/* Cleanup global namemap, to avoid extra processing with regular ID name management. Better to
|
|
* re-create the global namemap on demand. */
|
|
BKE_main_namemap_destroy(&bmain->name_map_global);
|
|
|
|
return success;
|
|
}
|
|
|
|
static void lib_override_library_create_post_process(Main *bmain,
|
|
Scene *scene,
|
|
ViewLayer *view_layer,
|
|
const Library *owner_library,
|
|
ID *id_root,
|
|
ID *id_instance_hint,
|
|
Collection *residual_storage,
|
|
const Object *old_active_object,
|
|
const bool is_resync)
|
|
{
|
|
/* If there is an old active object, there should also always be a given view layer. */
|
|
BLI_assert(old_active_object == nullptr || view_layer != nullptr);
|
|
|
|
/* NOTE: We only care about local IDs here, if a linked object is not instantiated in any way we
|
|
* do not do anything about it. */
|
|
|
|
/* We need to use the `_remap` version here as we prevented any LayerCollection resync during the
|
|
* whole liboverride resyncing, which involves a lot of ID remapping.
|
|
*
|
|
* Otherwise, cached Base GHash e.g. can contain invalid stale data. */
|
|
BKE_main_collection_sync_remap(bmain);
|
|
|
|
/* We create a set of all objects referenced into the scene by its hierarchy of collections.
|
|
* NOTE: This is different that the list of bases, since objects in excluded collections etc.
|
|
* won't have a base, but are still considered as instanced from our point of view. */
|
|
GSet *all_objects_in_scene = BKE_scene_objects_as_gset(scene, nullptr);
|
|
|
|
if (is_resync || id_root == nullptr || id_root->newid == nullptr) {
|
|
/* Instantiating the root collection or object should never be needed in resync case, since the
|
|
* old override would be remapped to the new one. */
|
|
}
|
|
else if (ID_IS_LINKED(id_root->newid) && id_root->newid->lib != owner_library) {
|
|
/* No instantiation in case the root override is linked data, unless it is part of the given
|
|
* owner library.
|
|
*
|
|
* NOTE: that last case should never happen actually in current code? Since non-null owner
|
|
* library should only happen in case of recursive resync, which is already excluded by the
|
|
* previous condition. */
|
|
}
|
|
else if ((id_root->newid->override_library->flag & LIBOVERRIDE_FLAG_NO_HIERARCHY) == 0 &&
|
|
id_root->newid->override_library->hierarchy_root != id_root->newid)
|
|
{
|
|
/* No instantiation in case this is not a hierarchy root, as it can be assumed already handled
|
|
* as part of hierarchy processing. */
|
|
}
|
|
else {
|
|
switch (GS(id_root->name)) {
|
|
case ID_GR: {
|
|
Object *ob_reference = id_instance_hint != nullptr && GS(id_instance_hint->name) == ID_OB ?
|
|
reinterpret_cast<Object *>(id_instance_hint) :
|
|
nullptr;
|
|
Collection *collection_new = (reinterpret_cast<Collection *>(id_root->newid));
|
|
if (is_resync && BKE_collection_is_in_scene(collection_new)) {
|
|
break;
|
|
}
|
|
if (ob_reference != nullptr) {
|
|
BKE_collection_add_from_object(bmain, scene, ob_reference, collection_new);
|
|
}
|
|
else if (id_instance_hint != nullptr) {
|
|
BLI_assert(GS(id_instance_hint->name) == ID_GR);
|
|
BKE_collection_add_from_collection(
|
|
bmain, scene, (reinterpret_cast<Collection *>(id_instance_hint)), collection_new);
|
|
}
|
|
else {
|
|
BKE_collection_add_from_collection(
|
|
bmain, scene, (reinterpret_cast<Collection *>(id_root)), collection_new);
|
|
}
|
|
|
|
BLI_assert(BKE_collection_is_in_scene(collection_new));
|
|
|
|
all_objects_in_scene = BKE_scene_objects_as_gset(scene, all_objects_in_scene);
|
|
break;
|
|
}
|
|
case ID_OB: {
|
|
Object *ob_new = reinterpret_cast<Object *>(id_root->newid);
|
|
if (BLI_gset_lookup(all_objects_in_scene, ob_new) == nullptr) {
|
|
BKE_collection_object_add_from(
|
|
bmain, scene, reinterpret_cast<Object *>(id_root), ob_new);
|
|
all_objects_in_scene = BKE_scene_objects_as_gset(scene, all_objects_in_scene);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (view_layer != nullptr) {
|
|
BKE_view_layer_synced_ensure(scene, view_layer);
|
|
}
|
|
else {
|
|
BKE_scene_view_layers_synced_ensure(scene);
|
|
}
|
|
|
|
/* We need to ensure all new overrides of objects are properly instantiated. */
|
|
Collection *default_instantiating_collection = residual_storage;
|
|
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
|
|
Object *ob_new = reinterpret_cast<Object *>(ob->id.newid);
|
|
if (ob_new == nullptr || (ID_IS_LINKED(ob_new) && ob_new->id.lib != owner_library)) {
|
|
continue;
|
|
}
|
|
|
|
BLI_assert(ob_new->id.override_library != nullptr &&
|
|
ob_new->id.override_library->reference == &ob->id);
|
|
|
|
if (old_active_object == ob) {
|
|
Base *basact = BKE_view_layer_base_find(view_layer, ob_new);
|
|
if (basact != nullptr) {
|
|
view_layer->basact = basact;
|
|
}
|
|
DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
|
|
}
|
|
|
|
if (BLI_gset_lookup(all_objects_in_scene, ob_new) == nullptr) {
|
|
if (id_root != nullptr && default_instantiating_collection == nullptr) {
|
|
ID *id_ref = id_root->newid != nullptr ? id_root->newid : id_root;
|
|
switch (GS(id_ref->name)) {
|
|
case ID_GR: {
|
|
/* Adding the object to a specific collection outside of the root overridden one is a
|
|
* fairly bad idea (it breaks the override hierarchy concept). But there is no other
|
|
* way to do this currently (we cannot add new collections to overridden root one,
|
|
* this is not currently supported).
|
|
* Since that will be fairly annoying and noisy, only do that in case the override
|
|
* object is not part of any existing collection (i.e. its user count is 0). In
|
|
* practice this should never happen I think. */
|
|
if (ID_REAL_USERS(ob_new) != 0) {
|
|
continue;
|
|
}
|
|
default_instantiating_collection = static_cast<Collection *>(
|
|
BKE_id_new(bmain, ID_GR, "OVERRIDE_HIDDEN"));
|
|
id_us_min(&default_instantiating_collection->id);
|
|
/* Hide the collection from viewport and render. */
|
|
default_instantiating_collection->flag |= COLLECTION_HIDE_VIEWPORT |
|
|
COLLECTION_HIDE_RENDER;
|
|
break;
|
|
}
|
|
case ID_OB: {
|
|
/* Add the other objects to one of the collections instantiating the
|
|
* root object, or scene's master collection if none found. */
|
|
Object *ob_ref = reinterpret_cast<Object *>(id_ref);
|
|
LISTBASE_FOREACH (Collection *, collection, &bmain->collections) {
|
|
if (BKE_collection_has_object(collection, ob_ref) &&
|
|
(view_layer != nullptr ?
|
|
BKE_view_layer_has_collection(view_layer, collection) :
|
|
BKE_collection_has_collection(scene->master_collection, collection)) &&
|
|
!ID_IS_LINKED(collection) && !ID_IS_OVERRIDE_LIBRARY(collection))
|
|
{
|
|
default_instantiating_collection = collection;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (default_instantiating_collection == nullptr) {
|
|
default_instantiating_collection = scene->master_collection;
|
|
}
|
|
|
|
BKE_collection_object_add(bmain, default_instantiating_collection, ob_new);
|
|
DEG_id_tag_update_ex(bmain, &ob_new->id, ID_RECALC_TRANSFORM | ID_RECALC_BASE_FLAGS);
|
|
}
|
|
}
|
|
|
|
if (id_root != nullptr &&
|
|
!ELEM(default_instantiating_collection, nullptr, scene->master_collection))
|
|
{
|
|
ID *id_ref = id_root->newid != nullptr ? id_root->newid : id_root;
|
|
switch (GS(id_ref->name)) {
|
|
case ID_GR:
|
|
BKE_collection_add_from_collection(bmain,
|
|
scene,
|
|
reinterpret_cast<Collection *>(id_ref),
|
|
default_instantiating_collection);
|
|
break;
|
|
default:
|
|
/* Add to master collection. */
|
|
BKE_collection_add_from_collection(
|
|
bmain, scene, nullptr, default_instantiating_collection);
|
|
break;
|
|
}
|
|
}
|
|
|
|
BLI_gset_free(all_objects_in_scene, nullptr);
|
|
}
|
|
|
|
bool BKE_lib_override_library_create(Main *bmain,
|
|
Scene *scene,
|
|
ViewLayer *view_layer,
|
|
Library *owner_library,
|
|
ID *id_root_reference,
|
|
ID *id_hierarchy_root_reference,
|
|
ID *id_instance_hint,
|
|
ID **r_id_root_override,
|
|
const bool do_fully_editable)
|
|
{
|
|
if (r_id_root_override != nullptr) {
|
|
*r_id_root_override = nullptr;
|
|
}
|
|
|
|
if (id_hierarchy_root_reference == nullptr) {
|
|
id_hierarchy_root_reference = id_root_reference;
|
|
}
|
|
|
|
/* While in theory it _should_ be enough to ensure sync of given view-layer (if any), or at least
|
|
* of given scene, think for now it's better to get a fully synced Main at this point, this code
|
|
* may do some very wide remapping/data access in some cases. */
|
|
BKE_main_view_layers_synced_ensure(bmain);
|
|
const Object *old_active_object = (view_layer != nullptr) ?
|
|
BKE_view_layer_active_object_get(view_layer) :
|
|
nullptr;
|
|
|
|
const bool success = lib_override_library_create_do(bmain,
|
|
scene,
|
|
owner_library,
|
|
id_root_reference,
|
|
id_hierarchy_root_reference,
|
|
do_fully_editable);
|
|
|
|
if (!success) {
|
|
return success;
|
|
}
|
|
|
|
if (r_id_root_override != nullptr) {
|
|
*r_id_root_override = id_root_reference->newid;
|
|
}
|
|
|
|
lib_override_library_create_post_process(bmain,
|
|
scene,
|
|
view_layer,
|
|
owner_library,
|
|
id_root_reference,
|
|
id_instance_hint,
|
|
nullptr,
|
|
old_active_object,
|
|
false);
|
|
|
|
/* Cleanup. */
|
|
BKE_main_id_newptr_and_tag_clear(bmain);
|
|
BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false);
|
|
|
|
/* We need to rebuild some of the deleted override rules (for UI feedback purpose). */
|
|
BKE_lib_override_library_main_operations_create(bmain, true, nullptr);
|
|
|
|
return success;
|
|
}
|
|
|
|
bool BKE_lib_override_library_template_create(ID *id)
|
|
{
|
|
if (ID_IS_LINKED(id)) {
|
|
return false;
|
|
}
|
|
if (ID_IS_OVERRIDE_LIBRARY(id)) {
|
|
return false;
|
|
}
|
|
|
|
BKE_lib_override_library_init(id, nullptr);
|
|
return true;
|
|
}
|
|
|
|
static ID *lib_override_root_find(Main *bmain, ID *id, const int curr_level, int *r_best_level)
|
|
{
|
|
if (curr_level > 1000) {
|
|
CLOG_ERROR(&LOG,
|
|
"Levels of dependency relationships between library overrides IDs is way too high, "
|
|
"skipping further processing loops (involves at least '%s')",
|
|
id->name);
|
|
return nullptr;
|
|
}
|
|
|
|
if (!ID_IS_OVERRIDE_LIBRARY(id)) {
|
|
BLI_assert_unreachable();
|
|
return nullptr;
|
|
}
|
|
|
|
MainIDRelationsEntry *entry = static_cast<MainIDRelationsEntry *>(
|
|
BLI_ghash_lookup(bmain->relations->relations_from_pointers, id));
|
|
BLI_assert(entry != nullptr);
|
|
|
|
if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) {
|
|
if (ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
|
|
/* This ID has already been processed. */
|
|
*r_best_level = curr_level;
|
|
return id->override_library->hierarchy_root;
|
|
}
|
|
|
|
BLI_assert(id->flag & LIB_EMBEDDED_DATA_LIB_OVERRIDE);
|
|
ID *id_owner;
|
|
int best_level_placeholder = 0;
|
|
BKE_lib_override_library_get(bmain, id, nullptr, &id_owner);
|
|
return lib_override_root_find(bmain, id_owner, curr_level + 1, &best_level_placeholder);
|
|
}
|
|
|
|
if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_INPROGRESS) {
|
|
/* Re-processing an entry already being processed higher in the call-graph (re-entry caused by
|
|
* a dependency loops). Just do nothing, there is no more useful info to provide here. */
|
|
return nullptr;
|
|
}
|
|
/* Flag this entry to avoid re-processing it in case some dependency loop leads to it again
|
|
* downwards in the call-stack. */
|
|
entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_INPROGRESS;
|
|
|
|
int best_level_candidate = curr_level;
|
|
ID *best_root_id_candidate = id;
|
|
|
|
for (MainIDRelationsEntryItem *from_id_entry = entry->from_ids; from_id_entry != nullptr;
|
|
from_id_entry = from_id_entry->next)
|
|
{
|
|
if ((from_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) {
|
|
/* Never consider non-overridable relationships as actual dependencies. */
|
|
continue;
|
|
}
|
|
|
|
ID *from_id = from_id_entry->id_pointer.from;
|
|
if (ELEM(from_id, nullptr, id)) {
|
|
continue;
|
|
}
|
|
if (!ID_IS_OVERRIDE_LIBRARY(from_id) || (from_id->lib != id->lib)) {
|
|
continue;
|
|
}
|
|
|
|
int level_candidate = curr_level + 1;
|
|
/* Recursively process the parent. */
|
|
ID *root_id_candidate = lib_override_root_find(
|
|
bmain, from_id, curr_level + 1, &level_candidate);
|
|
if (level_candidate > best_level_candidate && root_id_candidate != nullptr) {
|
|
best_root_id_candidate = root_id_candidate;
|
|
best_level_candidate = level_candidate;
|
|
}
|
|
}
|
|
|
|
if (!ID_IS_OVERRIDE_LIBRARY_REAL(best_root_id_candidate)) {
|
|
BLI_assert(id->flag & LIB_EMBEDDED_DATA_LIB_OVERRIDE);
|
|
ID *id_owner;
|
|
int best_level_placeholder = 0;
|
|
BKE_lib_override_library_get(bmain, best_root_id_candidate, nullptr, &id_owner);
|
|
best_root_id_candidate = lib_override_root_find(
|
|
bmain, id_owner, curr_level + 1, &best_level_placeholder);
|
|
}
|
|
|
|
BLI_assert(best_root_id_candidate != nullptr);
|
|
BLI_assert((best_root_id_candidate->flag & LIB_EMBEDDED_DATA_LIB_OVERRIDE) == 0);
|
|
|
|
/* This way this ID won't be processed again, should it be encountered again through another
|
|
* relationship hierarchy. */
|
|
entry->tags &= ~MAINIDRELATIONS_ENTRY_TAGS_INPROGRESS;
|
|
entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED;
|
|
|
|
*r_best_level = best_level_candidate;
|
|
return best_root_id_candidate;
|
|
}
|
|
|
|
static void lib_override_root_hierarchy_set(
|
|
Main *bmain, ID *id_root, ID *id, ID *id_from, blender::Set<ID *> &processed_ids)
|
|
{
|
|
if (processed_ids.contains(id)) {
|
|
/* This ID has already been checked as having a valid hierarchy root, do not attempt to replace
|
|
* it with another one just because it is also used by another liboverride hierarchy. */
|
|
return;
|
|
}
|
|
if (ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
|
|
if (id->override_library->hierarchy_root == id_root) {
|
|
/* Already set, nothing else to do here, sub-hierarchy is also assumed to be properly set
|
|
* then. */
|
|
return;
|
|
}
|
|
|
|
/* Hierarchy root already set, and not matching currently proposed one, try to find which is
|
|
* best. */
|
|
if (id->override_library->hierarchy_root != nullptr) {
|
|
/* Check if given `id_from` matches with the hierarchy of the linked reference ID, in which
|
|
* case we assume that the given hierarchy root is the 'real' one.
|
|
*
|
|
* NOTE: This can fail if user mixed dependencies between several overrides of a same
|
|
* reference linked hierarchy. Not much to be done in that case, it's virtually impossible to
|
|
* fix this automatically in a reliable way. */
|
|
if (id_from == nullptr || !ID_IS_OVERRIDE_LIBRARY_REAL(id_from)) {
|
|
/* Too complicated to deal with for now. */
|
|
CLOG_WARN(&LOG,
|
|
"Inconsistency in library override hierarchy of ID '%s'.\n"
|
|
"\tNot enough data to verify validity of current proposed root '%s', assuming "
|
|
"already set one '%s' is valid.",
|
|
id->name,
|
|
id_root->name,
|
|
id->override_library->hierarchy_root->name);
|
|
return;
|
|
}
|
|
|
|
ID *id_from_ref = id_from->override_library->reference;
|
|
MainIDRelationsEntry *entry = static_cast<MainIDRelationsEntry *>(BLI_ghash_lookup(
|
|
bmain->relations->relations_from_pointers, id->override_library->reference));
|
|
BLI_assert(entry != nullptr);
|
|
|
|
/* Enforce replacing hierarchy root if the current one is invalid. */
|
|
bool do_replace_root = (!id->override_library->hierarchy_root ||
|
|
!ID_IS_OVERRIDE_LIBRARY_REAL(id->override_library->hierarchy_root) ||
|
|
id->override_library->hierarchy_root->lib != id->lib);
|
|
for (MainIDRelationsEntryItem *from_id_entry = entry->from_ids; from_id_entry != nullptr;
|
|
from_id_entry = from_id_entry->next)
|
|
{
|
|
if ((from_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) {
|
|
/* Never consider non-overridable relationships as actual dependencies. */
|
|
continue;
|
|
}
|
|
|
|
if (id_from_ref == from_id_entry->id_pointer.from) {
|
|
/* A matching parent was found in reference linked data, assume given hierarchy root is
|
|
* the valid one. */
|
|
do_replace_root = true;
|
|
CLOG_WARN(
|
|
&LOG,
|
|
"Inconsistency in library override hierarchy of ID '%s'.\n"
|
|
"\tCurrent proposed root '%s' detected as valid, will replace already set one '%s'.",
|
|
id->name,
|
|
id_root->name,
|
|
id->override_library->hierarchy_root->name);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!do_replace_root) {
|
|
CLOG_WARN(
|
|
&LOG,
|
|
"Inconsistency in library override hierarchy of ID '%s'.\n"
|
|
"\tCurrent proposed root '%s' not detected as valid, keeping already set one '%s'.",
|
|
id->name,
|
|
id_root->name,
|
|
id->override_library->hierarchy_root->name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
CLOG_INFO(&LOG,
|
|
3,
|
|
"Modifying library override hierarchy of ID '%s'.\n"
|
|
"\tFrom old root '%s' to new root '%s'.",
|
|
id->name,
|
|
id->override_library->hierarchy_root ? id->override_library->hierarchy_root->name :
|
|
"<NONE>",
|
|
id_root->name);
|
|
|
|
id->override_library->hierarchy_root = id_root;
|
|
}
|
|
|
|
MainIDRelationsEntry *entry = static_cast<MainIDRelationsEntry *>(
|
|
BLI_ghash_lookup(bmain->relations->relations_from_pointers, id));
|
|
BLI_assert(entry != nullptr);
|
|
|
|
for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != nullptr;
|
|
to_id_entry = to_id_entry->next)
|
|
{
|
|
if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) {
|
|
/* Never consider non-overridable relationships as actual dependencies. */
|
|
continue;
|
|
}
|
|
|
|
ID *to_id = *to_id_entry->id_pointer.to;
|
|
if (ELEM(to_id, nullptr, id)) {
|
|
continue;
|
|
}
|
|
if (!ID_IS_OVERRIDE_LIBRARY(to_id) || (to_id->lib != id->lib)) {
|
|
continue;
|
|
}
|
|
|
|
/* Recursively process the sub-hierarchy. */
|
|
lib_override_root_hierarchy_set(bmain, id_root, to_id, id, processed_ids);
|
|
}
|
|
}
|
|
|
|
void BKE_lib_override_library_main_hierarchy_root_ensure(Main *bmain)
|
|
{
|
|
ID *id;
|
|
|
|
BKE_main_relations_create(bmain, 0);
|
|
blender::Set<ID *> processed_ids;
|
|
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
|
|
processed_ids.add(id);
|
|
continue;
|
|
}
|
|
if (id->override_library->hierarchy_root != nullptr) {
|
|
if (!ID_IS_OVERRIDE_LIBRARY_REAL(id->override_library->hierarchy_root) ||
|
|
id->override_library->hierarchy_root->lib != id->lib)
|
|
{
|
|
CLOG_ERROR(
|
|
&LOG,
|
|
"Existing override hierarchy root ('%s') for ID '%s' is invalid, will try to find a "
|
|
"new valid one",
|
|
id->override_library->hierarchy_root != nullptr ?
|
|
id->override_library->hierarchy_root->name :
|
|
"<NONE>",
|
|
id->name);
|
|
id->override_library->hierarchy_root = nullptr;
|
|
}
|
|
else {
|
|
/* This ID is considered as having a valid hierarchy root. */
|
|
processed_ids.add(id);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
|
|
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_INPROGRESS, false);
|
|
|
|
int best_level = 0;
|
|
ID *id_root = lib_override_root_find(bmain, id, best_level, &best_level);
|
|
|
|
if (!ELEM(id->override_library->hierarchy_root, id_root, nullptr)) {
|
|
/* In case the detected hierarchy root does not match with the currently defined one, this is
|
|
* likely an issue and is worth a warning. */
|
|
CLOG_WARN(&LOG,
|
|
"Potential inconsistency in library override hierarchy of ID '%s' (current root "
|
|
"%s), detected as part of the hierarchy of '%s' (current root '%s')",
|
|
id->name,
|
|
id->override_library->hierarchy_root != nullptr ?
|
|
id->override_library->hierarchy_root->name :
|
|
"<NONE>",
|
|
id_root->name,
|
|
id_root->override_library->hierarchy_root != nullptr ?
|
|
id_root->override_library->hierarchy_root->name :
|
|
"<NONE>");
|
|
processed_ids.add(id);
|
|
continue;
|
|
}
|
|
|
|
lib_override_root_hierarchy_set(bmain, id_root, id, nullptr, processed_ids);
|
|
|
|
BLI_assert(id->override_library->hierarchy_root != nullptr);
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
BKE_main_relations_free(bmain);
|
|
}
|
|
|
|
static void lib_override_library_remap(Main *bmain,
|
|
const ID *id_root_reference,
|
|
GHash *linkedref_to_old_override)
|
|
{
|
|
ID *id;
|
|
IDRemapper *remapper = BKE_id_remapper_create();
|
|
LinkNode *nomain_ids = nullptr;
|
|
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if (id->tag & LIB_TAG_DOIT && id->newid != nullptr && id->lib == id_root_reference->lib) {
|
|
ID *id_override_new = id->newid;
|
|
ID *id_override_old = static_cast<ID *>(BLI_ghash_lookup(linkedref_to_old_override, id));
|
|
if (id_override_old == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
BKE_id_remapper_add(remapper, id_override_old, id_override_new);
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
/* Remap no-main override IDs we just created too. */
|
|
GHashIterator linkedref_to_old_override_iter;
|
|
GHASH_ITER (linkedref_to_old_override_iter, linkedref_to_old_override) {
|
|
ID *id_override_old_iter = static_cast<ID *>(
|
|
BLI_ghashIterator_getValue(&linkedref_to_old_override_iter));
|
|
if ((id_override_old_iter->tag & LIB_TAG_NO_MAIN) == 0) {
|
|
continue;
|
|
}
|
|
|
|
BLI_linklist_prepend(&nomain_ids, id_override_old_iter);
|
|
}
|
|
|
|
/* Remap all IDs to use the new override. */
|
|
BKE_libblock_remap_multiple(bmain, remapper, 0);
|
|
BKE_libblock_relink_multiple(bmain,
|
|
nomain_ids,
|
|
ID_REMAP_TYPE_REMAP,
|
|
remapper,
|
|
ID_REMAP_FORCE_USER_REFCOUNT | ID_REMAP_FORCE_NEVER_NULL_USAGE);
|
|
BKE_id_remapper_free(remapper);
|
|
BLI_linklist_free(nomain_ids, nullptr);
|
|
}
|
|
|
|
/**
|
|
* Mapping to find suitable missing linked liboverrides to replace by the newly generated linked
|
|
* liboverrides during resync process.
|
|
*
|
|
* \note About Order:
|
|
* In most cases, if there are several virtual linked liboverrides generated with the same base
|
|
* name (like `OBCube.001`, `OBCube.002`, etc.), this mapping system will find the correct one, for
|
|
* the following reasons:
|
|
* - Order of creation of these virtual IDs in resync process is expected to be stable (i.e.
|
|
* several runs of resync code based on the same linked data would re-create the same virtual
|
|
* liboverride IDs in the same order);
|
|
* - Order of creation and usage of the mapping data (a FIFO queue) also ensures that the missing
|
|
* placeholder `OBCube.001` is always 're-used' before `OBCube.002`.
|
|
*
|
|
* In case linked data keep being modified, these conditions may fail and the mapping may start to
|
|
* return 'wrong' results. However, this is considered as an acceptable limitation here, since this
|
|
* is mainly a 'best effort' to recover from situations that should not be happening in the first
|
|
* place.
|
|
*/
|
|
|
|
using LibOverrideMissingIDsData_Key = const std::pair<std::string, Library *>;
|
|
using LibOverrideMissingIDsData_Value = std::deque<ID *>;
|
|
using LibOverrideMissingIDsData =
|
|
std::map<LibOverrideMissingIDsData_Key, LibOverrideMissingIDsData_Value>;
|
|
|
|
/* Return a key suitable for the missing IDs mapping, i.e. a pair of
|
|
* `<full ID name (including first two ID type chars) without a potential numeric extension,
|
|
* ID library>`.
|
|
*
|
|
* So e.g. returns `<"OBMyObject", lib>` for ID from `lib` with names like `"OBMyObject"`,
|
|
* `"OBMyObject.002"`, `"OBMyObject.12345"`, and so on, but _not_ for e.g. `"OBMyObject.12.002"`.
|
|
*/
|
|
static LibOverrideMissingIDsData_Key lib_override_library_resync_missing_id_key(ID *id)
|
|
{
|
|
std::string id_name_key(id->name);
|
|
const size_t last_key_index = id_name_key.find_last_not_of("0123456789");
|
|
|
|
BLI_assert(last_key_index != std::string::npos);
|
|
|
|
if (id_name_key[last_key_index] == '.') {
|
|
id_name_key.resize(last_key_index);
|
|
}
|
|
|
|
return LibOverrideMissingIDsData_Key(id_name_key, id->lib);
|
|
}
|
|
|
|
static LibOverrideMissingIDsData lib_override_library_resync_build_missing_ids_data(Main *bmain)
|
|
{
|
|
LibOverrideMissingIDsData missing_ids;
|
|
ID *id_iter;
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
|
|
if (!ID_IS_LINKED(id_iter)) {
|
|
continue;
|
|
}
|
|
const int required_tags = (LIB_TAG_MISSING | LIB_TAG_LIBOVERRIDE_NEED_RESYNC);
|
|
if ((id_iter->tag & required_tags) != required_tags) {
|
|
continue;
|
|
}
|
|
|
|
LibOverrideMissingIDsData_Key key = lib_override_library_resync_missing_id_key(id_iter);
|
|
std::pair<LibOverrideMissingIDsData::iterator, bool> value = missing_ids.try_emplace(
|
|
key, LibOverrideMissingIDsData_Value());
|
|
value.first->second.push_back(id_iter);
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
return missing_ids;
|
|
}
|
|
|
|
static ID *lib_override_library_resync_search_missing_ids_data(
|
|
LibOverrideMissingIDsData &missing_ids, ID *id_override)
|
|
{
|
|
LibOverrideMissingIDsData_Key key = lib_override_library_resync_missing_id_key(id_override);
|
|
const LibOverrideMissingIDsData::iterator value = missing_ids.find(key);
|
|
if (value == missing_ids.end()) {
|
|
return nullptr;
|
|
}
|
|
if (value->second.empty()) {
|
|
return nullptr;
|
|
}
|
|
ID *match_id = value->second.front();
|
|
value->second.pop_front();
|
|
return match_id;
|
|
}
|
|
|
|
static bool lib_override_library_resync(Main *bmain,
|
|
Scene *scene,
|
|
ViewLayer *view_layer,
|
|
ID *id_root,
|
|
LinkNode *id_resync_roots,
|
|
ListBase *no_main_ids_list,
|
|
Collection *override_resync_residual_storage,
|
|
const bool do_hierarchy_enforce,
|
|
const bool do_post_process,
|
|
BlendFileReadReport *reports)
|
|
{
|
|
BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root));
|
|
|
|
ID *id_root_reference = id_root->override_library->reference;
|
|
ID *id;
|
|
BKE_view_layer_synced_ensure(scene, view_layer);
|
|
const Object *old_active_object = BKE_view_layer_active_object_get(view_layer);
|
|
|
|
if (id_root_reference->tag & LIB_TAG_MISSING) {
|
|
BKE_reportf(reports != nullptr ? reports->reports : nullptr,
|
|
RPT_ERROR,
|
|
"Impossible to resync data-block %s and its dependencies, as its linked reference "
|
|
"is missing",
|
|
id_root->name + 2);
|
|
return false;
|
|
}
|
|
|
|
BKE_main_relations_create(bmain, 0);
|
|
LibOverrideGroupTagData data{};
|
|
data.bmain = bmain;
|
|
data.scene = scene;
|
|
data.id_root = id_root;
|
|
data.hierarchy_root_id = id_root->override_library->hierarchy_root;
|
|
data.tag = LIB_TAG_DOIT;
|
|
data.missing_tag = LIB_TAG_MISSING;
|
|
data.is_override = true;
|
|
data.is_resync = true;
|
|
lib_override_group_tag_data_object_to_collection_init(&data);
|
|
|
|
/* Mapping 'linked reference IDs' -> 'Local override IDs' of existing overrides, populated from
|
|
* each sub-tree that actually needs to be resynced. */
|
|
GHash *linkedref_to_old_override = BLI_ghash_new(
|
|
BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__);
|
|
|
|
/* Only tag linked IDs from related linked reference hierarchy that are actually part of
|
|
* the sub-trees of each detected sub-roots needing resync. */
|
|
for (LinkNode *resync_root_link = id_resync_roots; resync_root_link != nullptr;
|
|
resync_root_link = resync_root_link->next)
|
|
{
|
|
ID *id_resync_root = static_cast<ID *>(resync_root_link->link);
|
|
BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_resync_root));
|
|
|
|
if ((id_resync_root->tag & LIB_TAG_NO_MAIN) != 0) {
|
|
CLOG_ERROR(&LOG_RESYNC,
|
|
"While dealing with root '%s', resync root ID '%s' (%p) found to be alreaady "
|
|
"resynced.\n",
|
|
id_root->name,
|
|
id_resync_root->name,
|
|
id_resync_root);
|
|
}
|
|
// if (no_main_ids_list && BLI_findindex(no_main_ids_list, id_resync_root) != -1) {
|
|
// CLOG_ERROR(
|
|
// &LOG,
|
|
// "While dealing with root '%s', resync root ID '%s' found to be alreaady
|
|
// resynced.\n", id_root->name, id_resync_root->name);
|
|
// }
|
|
|
|
ID *id_resync_root_reference = id_resync_root->override_library->reference;
|
|
|
|
if (id_resync_root_reference->tag & LIB_TAG_MISSING) {
|
|
BKE_reportf(
|
|
reports != nullptr ? reports->reports : nullptr,
|
|
RPT_ERROR,
|
|
"Impossible to resync data-block %s and its dependencies, as its linked reference "
|
|
"is missing",
|
|
id_root->name + 2);
|
|
BLI_ghash_free(linkedref_to_old_override, nullptr, nullptr);
|
|
BKE_main_relations_free(bmain);
|
|
lib_override_group_tag_data_clear(&data);
|
|
return false;
|
|
}
|
|
|
|
/* Tag local overrides of the current resync sub-hierarchy. */
|
|
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
|
|
data.id_root = id_resync_root;
|
|
data.is_override = true;
|
|
lib_override_overrides_group_tag(&data);
|
|
|
|
/* Tag reference data matching the current resync sub-hierarchy. */
|
|
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
|
|
data.id_root = id_resync_root->override_library->reference;
|
|
data.is_override = false;
|
|
lib_override_linked_group_tag(&data);
|
|
|
|
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
|
|
lib_override_hierarchy_dependencies_recursive_tag(&data);
|
|
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if ((id->lib != id_root->lib) || !ID_IS_OVERRIDE_LIBRARY(id)) {
|
|
continue;
|
|
}
|
|
|
|
/* IDs that get fully removed from linked data remain as local overrides (using place-holder
|
|
* linked IDs as reference), but they are often not reachable from any current valid local
|
|
* override hierarchy anymore. This will ensure they get properly deleted at the end of this
|
|
* function. */
|
|
if (!ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY_REAL(id) &&
|
|
(id->override_library->reference->tag & LIB_TAG_MISSING) != 0 &&
|
|
/* Unfortunately deleting obdata means deleting their objects too. Since there is no
|
|
* guarantee that a valid override object using an obsolete override obdata gets properly
|
|
* updated, we ignore those here for now. In practice this should not be a big issue. */
|
|
!OB_DATA_SUPPORT_ID(GS(id->name)))
|
|
{
|
|
id->tag |= LIB_TAG_MISSING;
|
|
}
|
|
|
|
/* While this should not happen in typical cases (and won't be properly supported here),
|
|
* user is free to do all kind of very bad things, including having different local
|
|
* overrides of a same linked ID in a same hierarchy. */
|
|
IDOverrideLibrary *id_override_library = BKE_lib_override_library_get(
|
|
bmain, id, nullptr, nullptr);
|
|
|
|
if (id_override_library->hierarchy_root != id_root->override_library->hierarchy_root) {
|
|
continue;
|
|
}
|
|
|
|
ID *reference_id = id_override_library->reference;
|
|
if (GS(reference_id->name) != GS(id->name)) {
|
|
switch (GS(id->name)) {
|
|
case ID_KE:
|
|
reference_id = reinterpret_cast<ID *>(BKE_key_from_id(reference_id));
|
|
break;
|
|
case ID_GR:
|
|
BLI_assert(GS(reference_id->name) == ID_SCE);
|
|
reference_id = reinterpret_cast<ID *>(
|
|
reinterpret_cast<Scene *>(reference_id)->master_collection);
|
|
break;
|
|
case ID_NT:
|
|
reference_id = reinterpret_cast<ID *>(ntreeFromID(id));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (reference_id == nullptr) {
|
|
/* Can happen e.g. when there is a local override of a shape-key, but the matching linked
|
|
* obdata (mesh etc.) does not have any shape-key anymore. */
|
|
continue;
|
|
}
|
|
BLI_assert(GS(reference_id->name) == GS(id->name));
|
|
|
|
if (!BLI_ghash_haskey(linkedref_to_old_override, reference_id)) {
|
|
BLI_ghash_insert(linkedref_to_old_override, reference_id, id);
|
|
if (!ID_IS_OVERRIDE_LIBRARY_REAL(id) || (id->tag & LIB_TAG_DOIT) == 0) {
|
|
continue;
|
|
}
|
|
if ((id->override_library->reference->tag & LIB_TAG_DOIT) == 0) {
|
|
/* We have an override, but now it does not seem to be necessary to override that ID
|
|
* anymore. Check if there are some actual overrides from the user, otherwise assume
|
|
* that we can get rid of this local override. */
|
|
LISTBASE_FOREACH (IDOverrideLibraryProperty *, op, &id->override_library->properties) {
|
|
if (!ELEM(op->rna_prop_type, PROP_POINTER, PROP_COLLECTION)) {
|
|
id->override_library->reference->tag |= LIB_TAG_DOIT;
|
|
break;
|
|
}
|
|
|
|
bool do_break = false;
|
|
LISTBASE_FOREACH (IDOverrideLibraryPropertyOperation *, opop, &op->operations) {
|
|
if ((opop->flag & LIBOVERRIDE_OP_FLAG_IDPOINTER_MATCH_REFERENCE) == 0) {
|
|
id->override_library->reference->tag |= LIB_TAG_DOIT;
|
|
do_break = true;
|
|
break;
|
|
}
|
|
}
|
|
if (do_break) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
/* Code above may have added some tags, we need to update this too. */
|
|
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
|
|
lib_override_hierarchy_dependencies_recursive_tag(&data);
|
|
}
|
|
|
|
/* Tag all local overrides of the current hierarchy. */
|
|
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
|
|
data.id_root = id_root;
|
|
data.is_override = true;
|
|
lib_override_overrides_group_tag(&data);
|
|
|
|
BKE_main_relations_free(bmain);
|
|
lib_override_group_tag_data_clear(&data);
|
|
|
|
/* Make new override from linked data. */
|
|
/* Note that this call also remaps all pointers of tagged IDs from old override IDs to new
|
|
* override IDs (including within the old overrides themselves, since those are tagged too
|
|
* above). */
|
|
const bool success = BKE_lib_override_library_create_from_tag(
|
|
bmain,
|
|
nullptr,
|
|
id_root_reference,
|
|
id_root->override_library->hierarchy_root,
|
|
nullptr,
|
|
true,
|
|
false);
|
|
|
|
if (!success) {
|
|
BLI_ghash_free(linkedref_to_old_override, nullptr, nullptr);
|
|
return success;
|
|
}
|
|
|
|
/* Get a mapping of all missing linked IDs that were liboverrides, to search for 'old
|
|
* liboverrides' for newly created ones that do not already have one, in next step. */
|
|
LibOverrideMissingIDsData missing_ids_data = lib_override_library_resync_build_missing_ids_data(
|
|
bmain);
|
|
|
|
ListBase *lb;
|
|
FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) {
|
|
FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id) {
|
|
if ((id->tag & LIB_TAG_DOIT) == 0 || id->newid == nullptr ||
|
|
id->lib != id_root_reference->lib) {
|
|
continue;
|
|
}
|
|
ID *id_override_new = id->newid;
|
|
ID *id_override_old = static_cast<ID *>(BLI_ghash_lookup(linkedref_to_old_override, id));
|
|
|
|
BLI_assert((id_override_new->tag & LIB_TAG_LIBOVERRIDE_NEED_RESYNC) == 0);
|
|
|
|
/* We need to 'move back' newly created override into its proper library (since it was
|
|
* duplicated from the reference ID with 'no main' option, it should currently be the same
|
|
* as the reference ID one). */
|
|
BLI_assert(/*!ID_IS_LINKED(id_override_new) || */ id_override_new->lib == id->lib);
|
|
BLI_assert(id_override_old == nullptr || id_override_old->lib == id_root->lib);
|
|
id_override_new->lib = id_root->lib;
|
|
|
|
/* The old override may have been created as linked data and then referenced by local data
|
|
* during a previous Blender session, in which case it became directly linked and a reference
|
|
* to it was stored in the local .blend file. however, since that linked liboverride ID does
|
|
* not actually exist in the original library file, on next file read it is lost and marked
|
|
* as missing ID. */
|
|
if (id_override_old == nullptr && ID_IS_LINKED(id_override_new)) {
|
|
id_override_old = lib_override_library_resync_search_missing_ids_data(missing_ids_data,
|
|
id_override_new);
|
|
BLI_assert(id_override_old == nullptr || id_override_old->lib == id_override_new->lib);
|
|
if (id_override_old != nullptr) {
|
|
BLI_ghash_insert(linkedref_to_old_override, id, id_override_old);
|
|
CLOG_INFO(&LOG_RESYNC,
|
|
2,
|
|
"Found missing linked old override best-match %s for new linked override %s",
|
|
id_override_old->name,
|
|
id_override_new->name);
|
|
}
|
|
}
|
|
|
|
/* Remap step below will tag directly linked ones properly as needed. */
|
|
if (ID_IS_LINKED(id_override_new)) {
|
|
id_override_new->tag |= LIB_TAG_INDIRECT;
|
|
}
|
|
|
|
if (id_override_old != nullptr) {
|
|
/* Swap the names between old override ID and new one. */
|
|
char id_name_buf[MAX_ID_NAME];
|
|
memcpy(id_name_buf, id_override_old->name, sizeof(id_name_buf));
|
|
memcpy(id_override_old->name, id_override_new->name, sizeof(id_override_old->name));
|
|
memcpy(id_override_new->name, id_name_buf, sizeof(id_override_new->name));
|
|
|
|
BLI_insertlinkreplace(lb, id_override_old, id_override_new);
|
|
id_override_old->tag |= LIB_TAG_NO_MAIN;
|
|
id_override_new->tag &= ~LIB_TAG_NO_MAIN;
|
|
|
|
lib_override_object_posemode_transfer(id_override_new, id_override_old);
|
|
|
|
/* Missing old liboverrides cannot transfer their override rules to new liboverride.
|
|
* This is fine though, since these are expected to only be 'virtual' linked overrides
|
|
* generated by resync of linked overrides. So nothing is expected to be overridden here.
|
|
*/
|
|
if (ID_IS_OVERRIDE_LIBRARY_REAL(id_override_new) &&
|
|
(id_override_old->tag & LIB_TAG_MISSING) == 0) {
|
|
BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_override_old));
|
|
|
|
id_override_new->override_library->flag = id_override_old->override_library->flag;
|
|
|
|
/* NOTE: Since `runtime.tag` is not copied from old to new liboverride, the potential
|
|
* `LIBOVERRIDE_TAG_RESYNC_ISOLATED_FROM_ROOT` is kept on the old, to-be-freed
|
|
* liboverride, and the new one is assumed to be properly part of its hierarchy again. */
|
|
|
|
/* Copy over overrides rules from old override ID to new one. */
|
|
BLI_duplicatelist(&id_override_new->override_library->properties,
|
|
&id_override_old->override_library->properties);
|
|
IDOverrideLibraryProperty *op_new = static_cast<IDOverrideLibraryProperty *>(
|
|
id_override_new->override_library->properties.first);
|
|
IDOverrideLibraryProperty *op_old = static_cast<IDOverrideLibraryProperty *>(
|
|
id_override_old->override_library->properties.first);
|
|
for (; op_new; op_new = op_new->next, op_old = op_old->next) {
|
|
lib_override_library_property_copy(op_new, op_old);
|
|
}
|
|
}
|
|
|
|
BLI_addtail(no_main_ids_list, id_override_old);
|
|
}
|
|
else {
|
|
/* Add to proper main list, ensure unique name for local ID, sort, and clear relevant
|
|
* tags. */
|
|
BKE_libblock_management_main_add(bmain, id_override_new);
|
|
}
|
|
}
|
|
FOREACH_MAIN_LISTBASE_ID_END;
|
|
}
|
|
FOREACH_MAIN_LISTBASE_END;
|
|
|
|
/* We remap old to new override usages in a separate loop, after all new overrides have
|
|
* been added to Main. */
|
|
lib_override_library_remap(bmain, id_root_reference, linkedref_to_old_override);
|
|
|
|
BKE_main_collection_sync(bmain);
|
|
|
|
LinkNode *id_override_old_list = nullptr;
|
|
|
|
/* We need to apply override rules in a separate loop, after all ID pointers have been properly
|
|
* remapped, and all new local override IDs have gotten their proper original names, otherwise
|
|
* override operations based on those ID names would fail. */
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if ((id->tag & LIB_TAG_DOIT) == 0 || id->newid == nullptr || id->lib != id_root_reference->lib)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ID *id_override_new = id->newid;
|
|
if (!ID_IS_OVERRIDE_LIBRARY_REAL(id_override_new)) {
|
|
continue;
|
|
}
|
|
|
|
ID *id_override_old = static_cast<ID *>(BLI_ghash_lookup(linkedref_to_old_override, id));
|
|
if (id_override_old == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
if (ID_IS_OVERRIDE_LIBRARY_REAL(id_override_old)) {
|
|
/* The remapping from old to new liboverrides above has a sad side effect on ShapeKeys. Since
|
|
* old liboverrides are also remapped, it means that the old liboverride owner of the shape
|
|
* key is also now pointing to the new liboverride shape key, not the old one. Since shape
|
|
* keys do not own their liboverride data, the old liboverride shape key user has to be
|
|
* restored to use the old liboverride shape-key, otherwise applying shape key override
|
|
* operations will be useless (would apply using the new, from linked data, liboverride,
|
|
* being effectively a no-op). */
|
|
Key **key_override_old_p = BKE_key_from_id_p(id_override_old);
|
|
if (key_override_old_p != nullptr && *key_override_old_p != nullptr) {
|
|
Key *key_linked_reference = BKE_key_from_id(id_override_new->override_library->reference);
|
|
BLI_assert(key_linked_reference != nullptr);
|
|
BLI_assert(key_linked_reference->id.newid == &(*key_override_old_p)->id);
|
|
|
|
Key *key_override_old = static_cast<Key *>(
|
|
BLI_ghash_lookup(linkedref_to_old_override, &key_linked_reference->id));
|
|
BLI_assert(key_override_old != nullptr);
|
|
*key_override_old_p = key_override_old;
|
|
}
|
|
|
|
/* Apply rules on new override ID using old one as 'source' data. */
|
|
/* Note that since we already remapped ID pointers in old override IDs to new ones, we
|
|
* can also apply ID pointer override rules safely here. */
|
|
PointerRNA rnaptr_src = RNA_id_pointer_create(id_override_old);
|
|
PointerRNA rnaptr_dst = RNA_id_pointer_create(id_override_new);
|
|
|
|
/* In case the parent of the liboverride object matches hierarchy-wise the parent of its
|
|
* linked reference, also enforce clearing any override of the other related parenting
|
|
* settings.
|
|
*
|
|
* While this may break some rare use-cases, in almost all situations the best behavior here
|
|
* is to follow the values from the reference data (especially when it comes to the invert
|
|
* parent matrix). */
|
|
bool do_clear_parenting_override = false;
|
|
if (GS(id_override_new->name) == ID_OB) {
|
|
Object *ob_old = reinterpret_cast<Object *>(id_override_old);
|
|
Object *ob_new = reinterpret_cast<Object *>(id_override_new);
|
|
if (ob_new->parent && ob_new->parent != ob_old->parent &&
|
|
/* Parent is not a liboverride. */
|
|
(ob_new->parent ==
|
|
reinterpret_cast<Object *>(ob_new->id.override_library->reference)->parent ||
|
|
/* Parent is a hierarchy-matching liboverride. */
|
|
(ID_IS_OVERRIDE_LIBRARY_REAL(ob_new->parent) &&
|
|
reinterpret_cast<Object *>(ob_new->parent->id.override_library->reference) ==
|
|
reinterpret_cast<Object *>(ob_new->id.override_library->reference)->parent)))
|
|
{
|
|
do_clear_parenting_override = true;
|
|
}
|
|
}
|
|
|
|
/* We remove any operation tagged with `LIBOVERRIDE_OP_FLAG_IDPOINTER_MATCH_REFERENCE`,
|
|
* that way the potentially new pointer will be properly kept, when old one is still valid
|
|
* too (typical case: assigning new ID to some usage, while old one remains used elsewhere
|
|
* in the override hierarchy). */
|
|
LISTBASE_FOREACH_MUTABLE (
|
|
IDOverrideLibraryProperty *, op, &id_override_new->override_library->properties)
|
|
{
|
|
LISTBASE_FOREACH_MUTABLE (IDOverrideLibraryPropertyOperation *, opop, &op->operations) {
|
|
if (opop->flag & LIBOVERRIDE_OP_FLAG_IDPOINTER_MATCH_REFERENCE) {
|
|
lib_override_library_property_operation_clear(opop);
|
|
BLI_freelinkN(&op->operations, opop);
|
|
}
|
|
}
|
|
if (BLI_listbase_is_empty(&op->operations)) {
|
|
BKE_lib_override_library_property_delete(id_override_new->override_library, op);
|
|
}
|
|
else if (do_clear_parenting_override) {
|
|
if (strstr(op->rna_path, "matrix_parent_inverse") ||
|
|
strstr(op->rna_path, "parent_type") || strstr(op->rna_path, "parent_bone") ||
|
|
strstr(op->rna_path, "parent_vertices"))
|
|
{
|
|
CLOG_INFO(&LOG_RESYNC,
|
|
2,
|
|
"Deleting liboverride property '%s' from object %s, as its parent pointer "
|
|
"matches the reference data hierarchy wise",
|
|
id_override_new->name + 2,
|
|
op->rna_path);
|
|
BKE_lib_override_library_property_delete(id_override_new->override_library, op);
|
|
}
|
|
}
|
|
}
|
|
|
|
RNA_struct_override_apply(bmain,
|
|
&rnaptr_dst,
|
|
&rnaptr_src,
|
|
nullptr,
|
|
id_override_new->override_library,
|
|
do_hierarchy_enforce ? RNA_OVERRIDE_APPLY_FLAG_IGNORE_ID_POINTERS :
|
|
RNA_OVERRIDE_APPLY_FLAG_NOP);
|
|
|
|
/* Clear the old shape key pointer again, otherwise it won't make ID management code happy
|
|
* when freeing (at least from user count side of things). */
|
|
if (key_override_old_p != nullptr) {
|
|
*key_override_old_p = nullptr;
|
|
}
|
|
}
|
|
|
|
BLI_linklist_prepend(&id_override_old_list, id_override_old);
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
/* Once overrides have been properly 'transferred' from old to new ID, we can clear ID usages
|
|
* of the old one.
|
|
* This is necessary in case said old ID is not in Main anymore. */
|
|
IDRemapper *id_remapper = BKE_id_remapper_create();
|
|
BKE_libblock_relink_multiple(bmain,
|
|
id_override_old_list,
|
|
ID_REMAP_TYPE_CLEANUP,
|
|
id_remapper,
|
|
ID_REMAP_FORCE_USER_REFCOUNT | ID_REMAP_FORCE_NEVER_NULL_USAGE);
|
|
for (LinkNode *ln_iter = id_override_old_list; ln_iter != nullptr; ln_iter = ln_iter->next) {
|
|
ID *id_override_old = static_cast<ID *>(ln_iter->link);
|
|
id_override_old->tag |= LIB_TAG_NO_USER_REFCOUNT;
|
|
}
|
|
BKE_id_remapper_free(id_remapper);
|
|
BLI_linklist_free(id_override_old_list, nullptr);
|
|
id_override_old_list = nullptr;
|
|
|
|
/* Delete old override IDs.
|
|
* Note that we have to use tagged group deletion here, since ID deletion also uses
|
|
* LIB_TAG_DOIT. This improves performances anyway, so everything is fine. */
|
|
int user_edited_overrides_deletion_count = 0;
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if (id->tag & LIB_TAG_DOIT) {
|
|
/* Since this code can also be called on linked liboverride now (during recursive resync),
|
|
* order of processing cannot guarantee anymore that the old liboverride won't be tagged for
|
|
* deletion before being processed by this loop (which would then untag it again).
|
|
*
|
|
* So instead store old liboverrides in Main into a temp list again, and do the tagging
|
|
* separately once this loop over all IDs in main is done. */
|
|
if (id->newid != nullptr && id->lib == id_root_reference->lib) {
|
|
ID *id_override_old = static_cast<ID *>(BLI_ghash_lookup(linkedref_to_old_override, id));
|
|
|
|
if (id_override_old != nullptr) {
|
|
id->newid->tag &= ~LIB_TAG_DOIT;
|
|
if (id_override_old->tag & LIB_TAG_NO_MAIN) {
|
|
id_override_old->tag |= LIB_TAG_DOIT;
|
|
BLI_assert(BLI_findindex(no_main_ids_list, id_override_old) != -1);
|
|
}
|
|
else {
|
|
/* Defer tagging. */
|
|
BLI_linklist_prepend(&id_override_old_list, id_override_old);
|
|
}
|
|
}
|
|
}
|
|
id->tag &= ~LIB_TAG_DOIT;
|
|
}
|
|
/* Also deal with old overrides that went missing in new linked data - only for real local
|
|
* overrides for now, not those who are linked. */
|
|
else if (id->tag & LIB_TAG_MISSING && !ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
|
|
bool do_delete;
|
|
ID *hierarchy_root = id->override_library->hierarchy_root;
|
|
if (id->override_library->reference->lib->id.tag & LIB_TAG_MISSING) {
|
|
/* Do not delete overrides which reference is missing because the library itself is missing
|
|
* (ref. #100586). */
|
|
do_delete = false;
|
|
}
|
|
else if (hierarchy_root != nullptr &&
|
|
hierarchy_root->override_library->reference->tag & LIB_TAG_MISSING)
|
|
{
|
|
/* Do not delete overrides which root hierarchy reference is missing. This would typically
|
|
* cause more harm than good. */
|
|
do_delete = false;
|
|
}
|
|
else if (!BKE_lib_override_library_is_user_edited(id)) {
|
|
/* If user never edited them, we can delete them. */
|
|
do_delete = true;
|
|
CLOG_INFO(&LOG_RESYNC, 2, "Old override %s is being deleted", id->name);
|
|
}
|
|
#if 0
|
|
else {
|
|
/* Otherwise, keep them, user needs to decide whether what to do with them. */
|
|
BLI_assert((id->tag & LIB_TAG_DOIT) == 0);
|
|
do_delete = false;
|
|
id_fake_user_set(id);
|
|
id->flag |= LIB_LIB_OVERRIDE_RESYNC_LEFTOVER;
|
|
CLOG_INFO(&LOG_RESYNC,
|
|
2,
|
|
"Old override %s is being kept around as it was user-edited",
|
|
id->name);
|
|
}
|
|
#else
|
|
else {
|
|
/* Delete them nevertheless, with fat warning, user needs to decide whether they want to
|
|
* save that version of the file (and accept the loss), or not. */
|
|
do_delete = true;
|
|
CLOG_WARN(&LOG_RESYNC,
|
|
"Old override %s is being deleted even though it was user-edited",
|
|
id->name);
|
|
user_edited_overrides_deletion_count++;
|
|
}
|
|
#endif
|
|
if (do_delete) {
|
|
id->tag |= LIB_TAG_DOIT;
|
|
id->tag &= ~LIB_TAG_MISSING;
|
|
}
|
|
else if (id->override_library->runtime != nullptr) {
|
|
/* Cleanup of this temporary tag, since that somewhat broken liboverride is explicitly
|
|
* kept for now. */
|
|
id->override_library->runtime->tag &= ~LIBOVERRIDE_TAG_RESYNC_ISOLATED_FROM_ROOT;
|
|
}
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
/* Finalize tagging old liboverrides for deletion. */
|
|
for (LinkNode *ln_iter = id_override_old_list; ln_iter != nullptr; ln_iter = ln_iter->next) {
|
|
ID *id_override_old = static_cast<ID *>(ln_iter->link);
|
|
id_override_old->tag |= LIB_TAG_DOIT;
|
|
}
|
|
BLI_linklist_free(id_override_old_list, nullptr);
|
|
|
|
/* Cleanup, many pointers in this GHash are already invalid now. */
|
|
BLI_ghash_free(linkedref_to_old_override, nullptr, nullptr);
|
|
|
|
BKE_id_multi_tagged_delete(bmain);
|
|
|
|
/* At this point, `id_root` may have been resynced, therefore deleted. In that case we need to
|
|
* update it to its new version.
|
|
*/
|
|
if (id_root_reference->newid != nullptr) {
|
|
id_root = id_root_reference->newid;
|
|
}
|
|
|
|
if (user_edited_overrides_deletion_count > 0) {
|
|
BKE_reportf(reports != nullptr ? reports->reports : nullptr,
|
|
RPT_WARNING,
|
|
"During resync of data-block %s, %d obsolete overrides were deleted, that had "
|
|
"local changes defined by user",
|
|
id_root->name + 2,
|
|
user_edited_overrides_deletion_count);
|
|
}
|
|
|
|
if (do_post_process) {
|
|
/* Essentially ensures that potentially new overrides of new objects will be instantiated. */
|
|
/* NOTE: Here 'reference' collection and 'newly added' collection are the same, which is fine
|
|
* since we already relinked old root override collection to new resync'ed one above. So this
|
|
* call is not expected to instantiate this new resync'ed collection anywhere, just to ensure
|
|
* that we do not have any stray objects. */
|
|
lib_override_library_create_post_process(bmain,
|
|
scene,
|
|
view_layer,
|
|
nullptr,
|
|
id_root_reference,
|
|
id_root,
|
|
override_resync_residual_storage,
|
|
old_active_object,
|
|
true);
|
|
}
|
|
|
|
/* Cleanup. */
|
|
BKE_main_id_newptr_and_tag_clear(bmain);
|
|
/* That one should not be needed in fact, as #BKE_id_multi_tagged_delete call above should have
|
|
* deleted all tagged IDs. */
|
|
BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false);
|
|
|
|
return success;
|
|
}
|
|
|
|
bool BKE_lib_override_library_resync(Main *bmain,
|
|
Scene *scene,
|
|
ViewLayer *view_layer,
|
|
ID *id_root,
|
|
Collection *override_resync_residual_storage,
|
|
const bool do_hierarchy_enforce,
|
|
BlendFileReadReport *reports)
|
|
{
|
|
ListBase no_main_ids_list = {nullptr};
|
|
LinkNode id_resync_roots{};
|
|
id_resync_roots.link = id_root;
|
|
id_resync_roots.next = nullptr;
|
|
|
|
const bool success = lib_override_library_resync(bmain,
|
|
scene,
|
|
view_layer,
|
|
id_root,
|
|
&id_resync_roots,
|
|
&no_main_ids_list,
|
|
override_resync_residual_storage,
|
|
do_hierarchy_enforce,
|
|
true,
|
|
reports);
|
|
|
|
LISTBASE_FOREACH_MUTABLE (ID *, id_iter, &no_main_ids_list) {
|
|
BKE_id_free(bmain, id_iter);
|
|
}
|
|
|
|
/* Cleanup global namemap, to avoid extra processing with regular ID name management. Better to
|
|
* re-create the global namemap on demand. */
|
|
BKE_main_namemap_destroy(&bmain->name_map_global);
|
|
|
|
return success;
|
|
}
|
|
|
|
static bool lib_override_resync_id_lib_level_is_valid(ID *id,
|
|
const int library_indirect_level,
|
|
const bool do_strict_equal)
|
|
{
|
|
const int id_lib_level = (ID_IS_LINKED(id) ? id->lib->temp_index : 0);
|
|
return do_strict_equal ? id_lib_level == library_indirect_level :
|
|
id_lib_level <= library_indirect_level;
|
|
}
|
|
|
|
/* Check ancestors overrides for resync, to ensure all IDs in-between two tagged-for-resync ones
|
|
* are also properly tagged.
|
|
*
|
|
* WARNING: Expects `bmain` to have valid relation data.
|
|
*
|
|
* Returns `true` if it finds an ancestor within the current liboverride hierarchy also tagged as
|
|
* needing resync, `false` otherwise.
|
|
*
|
|
* NOTE: If `check_only` is true, it only does the check and returns, without any modification to
|
|
* the data.
|
|
*/
|
|
static void lib_override_resync_tagging_finalize_recurse(Main *bmain,
|
|
ID *id_root,
|
|
ID *id_from,
|
|
const int library_indirect_level,
|
|
bool is_in_partial_resync_hierarchy)
|
|
{
|
|
BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root));
|
|
BLI_assert(id_root->override_library->hierarchy_root != nullptr);
|
|
|
|
if (!lib_override_resync_id_lib_level_is_valid(id_root, library_indirect_level, false)) {
|
|
CLOG_ERROR(
|
|
&LOG,
|
|
"While processing indirect level %d, ID %s from lib %s of indirect level %d detected "
|
|
"as needing resync, skipping",
|
|
library_indirect_level,
|
|
id_root->name,
|
|
id_root->lib ? id_root->lib->filepath : "<LOCAL>",
|
|
id_root->lib ? id_root->lib->temp_index : 0);
|
|
id_root->tag &= ~LIB_TAG_LIBOVERRIDE_NEED_RESYNC;
|
|
return;
|
|
}
|
|
|
|
MainIDRelationsEntry *entry = static_cast<MainIDRelationsEntry *>(
|
|
BLI_ghash_lookup(bmain->relations->relations_from_pointers, id_root));
|
|
BLI_assert(entry != nullptr);
|
|
|
|
bool is_reprocessing_current_entry = false;
|
|
if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_INPROGRESS) {
|
|
/* This ID is already being processed, this indicates a dependency loop. */
|
|
BLI_assert((entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) == 0);
|
|
|
|
if (id_root->tag & LIB_TAG_LIBOVERRIDE_NEED_RESYNC) {
|
|
/* This ID is already tagged for resync, then the loop leading back to it is also fully
|
|
* tagged for resync, nothing else to do. */
|
|
BLI_assert(is_in_partial_resync_hierarchy);
|
|
return;
|
|
}
|
|
else if (!is_in_partial_resync_hierarchy) {
|
|
/* This ID is not tagged for resync, and is part of a loop where none of the other IDs are
|
|
* tagged for resync, nothing else to do. */
|
|
return;
|
|
}
|
|
/* This ID is not yet tagged for resync, but is part of a loop which is (partially) tagged
|
|
* for resync.
|
|
* The whole loop needs to be processed a second time to ensure all of its members are properly
|
|
* tagged for resync then. */
|
|
is_reprocessing_current_entry = true;
|
|
|
|
CLOG_INFO(&LOG,
|
|
4,
|
|
"ID %s (%p) is detected as part of a hierarchy dependency loop requiring resync, it "
|
|
"is now being re-processed to ensure proper tagging of the whole loop",
|
|
id_root->name,
|
|
id_root->lib);
|
|
}
|
|
else if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) {
|
|
/* This ID has already been processed. */
|
|
BLI_assert((entry->tags & MAINIDRELATIONS_ENTRY_TAGS_INPROGRESS) == 0);
|
|
|
|
/* If it was already detected as needing resync, then its whole sub-tree should also be fully
|
|
* processed. Only need to ensure that it is not tagged as potential partial resync root
|
|
* anymore, if now processed as part of another partial resync hierarchy. */
|
|
if (id_root->tag & LIB_TAG_LIBOVERRIDE_NEED_RESYNC) {
|
|
if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_DOIT && is_in_partial_resync_hierarchy) {
|
|
CLOG_INFO(
|
|
&LOG,
|
|
4,
|
|
"ID %s (%p) was marked as a potential root for partial resync, but it is used by "
|
|
"%s (%p), which is also tagged for resync, so it is not a root after all",
|
|
id_root->name,
|
|
id_root->lib,
|
|
id_from->name,
|
|
id_from->lib);
|
|
|
|
entry->tags &= ~MAINIDRELATIONS_ENTRY_TAGS_DOIT;
|
|
}
|
|
return;
|
|
}
|
|
/* Else, if it is not being processed as part of a resync hierarchy, nothing more to do either,
|
|
* its current status and the one of its whole dependency tree is also assumed valid. */
|
|
else if (!is_in_partial_resync_hierarchy) {
|
|
return;
|
|
}
|
|
|
|
/* Else, this ID was processed before and not detected as needing resync, but it now needs
|
|
* resync, so its whole sub-tree needs to be re-processed to be properly tagged as needing
|
|
* resync. */
|
|
entry->tags &= ~MAINIDRELATIONS_ENTRY_TAGS_PROCESSED;
|
|
}
|
|
|
|
if (is_in_partial_resync_hierarchy) {
|
|
BLI_assert(id_from != nullptr);
|
|
|
|
if ((id_root->tag & LIB_TAG_LIBOVERRIDE_NEED_RESYNC) == 0) {
|
|
CLOG_INFO(&LOG,
|
|
4,
|
|
"ID %s (%p) now tagged as needing resync because they are used by %s (%p) "
|
|
"that needs to be resynced",
|
|
id_root->name,
|
|
id_root->lib,
|
|
id_from->name,
|
|
id_from->lib);
|
|
id_root->tag |= LIB_TAG_LIBOVERRIDE_NEED_RESYNC;
|
|
}
|
|
}
|
|
else if (id_root->tag & LIB_TAG_LIBOVERRIDE_NEED_RESYNC) {
|
|
/* Not yet within a partial resync hierarchy, and this ID is tagged for resync, it is a
|
|
* potential partial resync root. */
|
|
is_in_partial_resync_hierarchy = true;
|
|
}
|
|
|
|
/* Temporary tag to help manage dependency loops. */
|
|
if (!is_reprocessing_current_entry) {
|
|
BLI_assert((entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) == 0);
|
|
entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_INPROGRESS;
|
|
|
|
/* Since this ID is reached from the hierarchy root, it is not isolated from it. */
|
|
if (id_root->override_library->hierarchy_root != id_root &&
|
|
id_root->override_library->runtime != nullptr)
|
|
{
|
|
id_root->override_library->runtime->tag &= ~LIBOVERRIDE_TAG_RESYNC_ISOLATED_FROM_ROOT;
|
|
}
|
|
}
|
|
|
|
/* Check the whole sub-tree hierarchy of this ID. */
|
|
for (MainIDRelationsEntryItem *entry_item = entry->to_ids; entry_item != nullptr;
|
|
entry_item = entry_item->next)
|
|
{
|
|
if (entry_item->usage_flag & (IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE | IDWALK_CB_LOOPBACK))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ID *id_to = *(entry_item->id_pointer.to);
|
|
/* Ensure the 'real' override is processed, in case `id_to` is e.g. an embedded ID, get its
|
|
* owner instead. */
|
|
BKE_lib_override_library_get(bmain, id_to, nullptr, &id_to);
|
|
|
|
if (id_to == id_root || !ID_IS_OVERRIDE_LIBRARY_REAL(id_to) || id_to->lib != id_root->lib ||
|
|
id_to->override_library->hierarchy_root != id_root->override_library->hierarchy_root)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
lib_override_resync_tagging_finalize_recurse(
|
|
bmain, id_to, id_root, library_indirect_level, is_in_partial_resync_hierarchy);
|
|
|
|
/* Call above may have changed that status in case of dependency loop, update it for the next
|
|
* dependency processing. */
|
|
is_in_partial_resync_hierarchy = (id_root->tag & LIB_TAG_LIBOVERRIDE_NEED_RESYNC) != 0;
|
|
}
|
|
|
|
if (!is_reprocessing_current_entry) {
|
|
BLI_assert((entry->tags & MAINIDRELATIONS_ENTRY_TAGS_INPROGRESS) != 0);
|
|
BLI_assert((entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) == 0);
|
|
|
|
entry->tags &= ~MAINIDRELATIONS_ENTRY_TAGS_INPROGRESS;
|
|
entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED;
|
|
|
|
if (is_in_partial_resync_hierarchy &&
|
|
(id_from == nullptr || (id_from->tag & LIB_TAG_LIBOVERRIDE_NEED_RESYNC) == 0))
|
|
{
|
|
/* This ID (and its whole sub-tree of dependencies) is now considered as processed. If it is
|
|
* tagged for resync, but its 'calling parent' is not, it is a potential partial resync root.
|
|
*/
|
|
CLOG_INFO(&LOG_RESYNC,
|
|
4,
|
|
"Potential root for partial resync: %s (%p)",
|
|
id_root->name,
|
|
id_root->lib);
|
|
entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_DOIT;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Return true if the ID should be skipped for resync given current context. */
|
|
static bool lib_override_library_main_resync_id_skip_check(ID *id,
|
|
const int library_indirect_level)
|
|
{
|
|
if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
|
|
return true;
|
|
}
|
|
|
|
if (!lib_override_resync_id_lib_level_is_valid(id, library_indirect_level, true)) {
|
|
return true;
|
|
}
|
|
|
|
/* Do not attempt to resync from missing data. */
|
|
if (((id->tag | id->override_library->reference->tag) & LIB_TAG_MISSING) != 0) {
|
|
return true;
|
|
}
|
|
|
|
if (id->override_library->flag & LIBOVERRIDE_FLAG_NO_HIERARCHY) {
|
|
/* This ID is not part of an override hierarchy. */
|
|
BLI_assert((id->tag & LIB_TAG_LIBOVERRIDE_NEED_RESYNC) == 0);
|
|
return true;
|
|
}
|
|
|
|
/* Do not attempt to resync when hierarchy root is missing, this would usually do more harm
|
|
* than good. */
|
|
ID *hierarchy_root = id->override_library->hierarchy_root;
|
|
if (hierarchy_root == nullptr ||
|
|
((hierarchy_root->tag | hierarchy_root->override_library->reference->tag) &
|
|
LIB_TAG_MISSING) != 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Clear 'unreachable' tag of existing liboverrides if they are using another reachable liboverride
|
|
* (typical case: Mesh object which only relationship to the rest of the liboverride hierarchy is
|
|
* through its 'parent' pointer (i.e. rest of the hierarchy has no actual relationship to this mesh
|
|
* object).
|
|
*
|
|
* Logic and rational of this function are very similar to these of
|
|
* #lib_override_hierarchy_dependencies_recursive_tag_from, but withing specific resync context.
|
|
*
|
|
* \returns True if it finds a non-isolated 'parent' ID, false otherwise.
|
|
*/
|
|
static bool lib_override_resync_tagging_finalize_recursive_check_from(
|
|
Main *bmain, ID *id, const int library_indirect_level)
|
|
{
|
|
BLI_assert(!lib_override_library_main_resync_id_skip_check(id, library_indirect_level));
|
|
|
|
if (id->override_library->hierarchy_root == id ||
|
|
(id->override_library->runtime->tag & LIBOVERRIDE_TAG_RESYNC_ISOLATED_FROM_ROOT) == 0)
|
|
{
|
|
BLI_assert(
|
|
id->override_library->hierarchy_root != id || id->override_library->runtime == nullptr ||
|
|
(id->override_library->runtime->tag & LIBOVERRIDE_TAG_RESYNC_ISOLATED_FROM_ROOT) == 0);
|
|
return true;
|
|
}
|
|
|
|
MainIDRelationsEntry *entry = static_cast<MainIDRelationsEntry *>(
|
|
BLI_ghash_lookup(bmain->relations->relations_from_pointers, id));
|
|
BLI_assert(entry != nullptr);
|
|
|
|
if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED_TO) {
|
|
/* This ID has already been processed, since 'true' conditions have already been checked above,
|
|
* it is validated as an isolated liboverride. */
|
|
return false;
|
|
}
|
|
/* This way we won't process again that ID, should we encounter it again through another
|
|
* relationship hierarchy. */
|
|
entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED_TO;
|
|
|
|
for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != nullptr;
|
|
to_id_entry = to_id_entry->next)
|
|
{
|
|
if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) {
|
|
/* Never consider non-overridable relationships ('from', 'parents', 'owner' etc. pointers)
|
|
* as actual dependencies. */
|
|
continue;
|
|
}
|
|
ID *to_id = *(to_id_entry->id_pointer.to);
|
|
if (lib_override_library_main_resync_id_skip_check(to_id, library_indirect_level)) {
|
|
continue;
|
|
}
|
|
|
|
if (lib_override_resync_tagging_finalize_recursive_check_from(
|
|
bmain, to_id, library_indirect_level))
|
|
{
|
|
id->override_library->runtime->tag &= ~LIBOVERRIDE_TAG_RESYNC_ISOLATED_FROM_ROOT;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Once all IDs needing resync have been tagged, partial ID roots can be found by processing each
|
|
* tagged-for-resync IDs' ancestors within their liboverride hierarchy. */
|
|
static void lib_override_resync_tagging_finalize(Main *bmain,
|
|
GHash *id_roots,
|
|
const int library_indirect_level)
|
|
{
|
|
ID *id_iter;
|
|
|
|
/* Tag all IDs to be processed, which are real liboverrides part of a hierarchy, and not the
|
|
* root of their hierarchy, as potentially isolated from their hierarchy root. */
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
|
|
if (lib_override_library_main_resync_id_skip_check(id_iter, library_indirect_level)) {
|
|
continue;
|
|
}
|
|
|
|
if (!ELEM(id_iter->override_library->hierarchy_root, id_iter, nullptr)) {
|
|
override_library_runtime_ensure(id_iter->override_library)->tag |=
|
|
LIBOVERRIDE_TAG_RESYNC_ISOLATED_FROM_ROOT;
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
/* Finalize all IDs needing tagging for resync, and tag partial resync roots. Will also clear the
|
|
* 'isolated' tag from all processed IDs. */
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
|
|
if (lib_override_library_main_resync_id_skip_check(id_iter, library_indirect_level)) {
|
|
continue;
|
|
}
|
|
|
|
/* Only process hierarchy root IDs here. */
|
|
if (id_iter->override_library->hierarchy_root != id_iter) {
|
|
continue;
|
|
}
|
|
|
|
lib_override_resync_tagging_finalize_recurse(
|
|
bmain, id_iter, nullptr, library_indirect_level, false);
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
#ifndef NDEBUG
|
|
/* Validation loop to ensure all entries have been processed as expected by
|
|
* `lib_override_resync_tagging_finalize_recurse`, in above loop. */
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
|
|
if (lib_override_library_main_resync_id_skip_check(id_iter, library_indirect_level)) {
|
|
continue;
|
|
}
|
|
if ((id_iter->tag & LIB_TAG_LIBOVERRIDE_NEED_RESYNC) == 0) {
|
|
continue;
|
|
}
|
|
|
|
MainIDRelationsEntry *entry = static_cast<MainIDRelationsEntry *>(
|
|
BLI_ghash_lookup(bmain->relations->relations_from_pointers, id_iter));
|
|
BLI_assert(entry != nullptr);
|
|
BLI_assert((entry->tags & MAINIDRELATIONS_ENTRY_TAGS_INPROGRESS) == 0);
|
|
|
|
if ((entry->tags & MAINIDRELATIONS_ENTRY_TAGS_DOIT) == 0) {
|
|
continue;
|
|
}
|
|
|
|
BLI_assert(entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED);
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
#endif
|
|
|
|
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
|
|
|
|
/* Process above cleared all IDs actually still in relation with their root from the tag.
|
|
*
|
|
* The only exception being IDs only in relation with their root through a 'reversed' from
|
|
* pointer (typical case: armature object is the hierarchy root, its child mesh object is only
|
|
* related to it through its own 'parent' pointer, the armature one has no 'to' relationships to
|
|
* its deformed mesh object.
|
|
*
|
|
* Remaining ones are in a limbo, typically they could have been removed or moved around in the
|
|
* hierarchy (e.g. an object moved into another sub-collection). Tag them as needing resync,
|
|
* actual resyncing code will handle them as needed. */
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
|
|
if (lib_override_library_main_resync_id_skip_check(id_iter, library_indirect_level)) {
|
|
continue;
|
|
}
|
|
|
|
if (!ELEM(id_iter->override_library->hierarchy_root, id_iter, nullptr) &&
|
|
(id_iter->override_library->runtime->tag & LIBOVERRIDE_TAG_RESYNC_ISOLATED_FROM_ROOT))
|
|
{
|
|
/* Check and clear 'isolated' tags from cases like child objects of a hierarchy root object.
|
|
* Sigh. */
|
|
if (lib_override_resync_tagging_finalize_recursive_check_from(
|
|
bmain, id_iter, library_indirect_level))
|
|
{
|
|
BLI_assert((id_iter->override_library->runtime->tag &
|
|
LIBOVERRIDE_TAG_RESYNC_ISOLATED_FROM_ROOT) == 0);
|
|
CLOG_INFO(&LOG_RESYNC,
|
|
4,
|
|
"ID %s (%p) detected as only related to its hierarchy root by 'reversed' "
|
|
"relationship(s) (e.g. object parenting), tagging it as needing "
|
|
"resync",
|
|
id_iter->name,
|
|
id_iter->lib);
|
|
}
|
|
else {
|
|
CLOG_INFO(
|
|
&LOG_RESYNC,
|
|
4,
|
|
"ID %s (%p) detected as 'isolated' from its hierarchy root, tagging it as needing "
|
|
"resync",
|
|
id_iter->name,
|
|
id_iter->lib);
|
|
}
|
|
id_iter->tag |= LIB_TAG_LIBOVERRIDE_NEED_RESYNC;
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
|
|
|
|
/* If no tagged-for-resync ancestor was found, but the iterated ID is tagged for resync, then it
|
|
* is a root of a resync sub-tree. Find the root of the whole override hierarchy and add the
|
|
* iterated ID as one of its resync sub-tree roots. */
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
|
|
if (lib_override_library_main_resync_id_skip_check(id_iter, library_indirect_level)) {
|
|
continue;
|
|
}
|
|
if ((id_iter->tag & LIB_TAG_LIBOVERRIDE_NEED_RESYNC) == 0) {
|
|
continue;
|
|
}
|
|
|
|
MainIDRelationsEntry *entry = static_cast<MainIDRelationsEntry *>(
|
|
BLI_ghash_lookup(bmain->relations->relations_from_pointers, id_iter));
|
|
BLI_assert(entry != nullptr);
|
|
|
|
if ((entry->tags & MAINIDRELATIONS_ENTRY_TAGS_DOIT) == 0) {
|
|
continue;
|
|
}
|
|
|
|
ID *hierarchy_root = id_iter->override_library->hierarchy_root;
|
|
BLI_assert(hierarchy_root->lib == id_iter->lib);
|
|
|
|
if (id_iter != hierarchy_root) {
|
|
CLOG_INFO(&LOG_RESYNC,
|
|
4,
|
|
"Found root ID '%s' for partial resync root ID '%s'",
|
|
hierarchy_root->name,
|
|
id_iter->name);
|
|
|
|
BLI_assert(hierarchy_root->override_library != nullptr);
|
|
|
|
BLI_assert((id_iter->override_library->runtime->tag &
|
|
LIBOVERRIDE_TAG_RESYNC_ISOLATED_FROM_ROOT) == 0);
|
|
}
|
|
|
|
LinkNodePair **id_resync_roots_p;
|
|
if (!BLI_ghash_ensure_p(
|
|
id_roots, hierarchy_root, reinterpret_cast<void ***>(&id_resync_roots_p))) {
|
|
*id_resync_roots_p = MEM_cnew<LinkNodePair>(__func__);
|
|
}
|
|
BLI_linklist_append(*id_resync_roots_p, id_iter);
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
BKE_main_relations_tag_set(
|
|
bmain,
|
|
static_cast<const eMainIDRelationsEntryTags>(MAINIDRELATIONS_ENTRY_TAGS_PROCESSED |
|
|
MAINIDRELATIONS_ENTRY_TAGS_DOIT |
|
|
MAINIDRELATIONS_ENTRY_TAGS_INPROGRESS),
|
|
false);
|
|
}
|
|
|
|
/* Ensure resync of all overrides at one level of indirect usage.
|
|
*
|
|
* We need to handle each level independently, since an override at level n may be affected by
|
|
* other overrides from level n + 1 etc. (i.e. from linked overrides it may use).
|
|
*/
|
|
static bool lib_override_library_main_resync_on_library_indirect_level(
|
|
Main *bmain,
|
|
Scene *scene,
|
|
ViewLayer *view_layer,
|
|
Collection *override_resync_residual_storage,
|
|
const int library_indirect_level,
|
|
BlendFileReadReport *reports)
|
|
{
|
|
const bool do_reports_recursive_resync_timing = (library_indirect_level != 0);
|
|
const double init_time = do_reports_recursive_resync_timing ? PIL_check_seconds_timer() : 0.0;
|
|
|
|
BKE_main_relations_create(bmain, 0);
|
|
BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false);
|
|
|
|
/* NOTE: in code below, the order in which `FOREACH_MAIN_ID_BEGIN` processes ID types ensures
|
|
* that we always process 'higher-level' overrides first (i.e. scenes, then collections, then
|
|
* objects, then other types). */
|
|
|
|
/* Detect all linked data that would need to be overridden if we had to create an override from
|
|
* those used by current existing overrides. */
|
|
LibOverrideGroupTagData data = {};
|
|
data.bmain = bmain;
|
|
data.scene = scene;
|
|
data.id_root = nullptr;
|
|
data.tag = LIB_TAG_DOIT;
|
|
data.missing_tag = LIB_TAG_MISSING;
|
|
data.is_override = false;
|
|
data.is_resync = true;
|
|
lib_override_group_tag_data_object_to_collection_init(&data);
|
|
ID *id;
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if (lib_override_library_main_resync_id_skip_check(id, library_indirect_level)) {
|
|
continue;
|
|
}
|
|
|
|
if (id->tag & (LIB_TAG_DOIT | LIB_TAG_MISSING)) {
|
|
/* We already processed that ID as part of another ID's hierarchy. */
|
|
continue;
|
|
}
|
|
|
|
data.id_root = id->override_library->reference;
|
|
lib_override_linked_group_tag(&data);
|
|
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
|
|
lib_override_hierarchy_dependencies_recursive_tag(&data);
|
|
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
lib_override_group_tag_data_clear(&data);
|
|
|
|
GHash *id_roots = BLI_ghash_ptr_new(__func__);
|
|
|
|
/* Now check existing overrides, those needing resync will be the one either already tagged as
|
|
* such, or the one using linked data that is now tagged as needing override. */
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if (lib_override_library_main_resync_id_skip_check(id, library_indirect_level)) {
|
|
continue;
|
|
}
|
|
|
|
if (id->tag & LIB_TAG_LIBOVERRIDE_NEED_RESYNC) {
|
|
CLOG_INFO(
|
|
&LOG_RESYNC, 4, "ID %s (%p) was already tagged as needing resync", id->name, id->lib);
|
|
if (ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
|
|
override_library_runtime_ensure(id->override_library)->tag |=
|
|
LIBOVERRIDE_TAG_NEED_RESYNC_ORIGINAL;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
MainIDRelationsEntry *entry = static_cast<MainIDRelationsEntry *>(
|
|
BLI_ghash_lookup(bmain->relations->relations_from_pointers, id));
|
|
BLI_assert(entry != nullptr);
|
|
|
|
for (MainIDRelationsEntryItem *entry_item = entry->to_ids; entry_item != nullptr;
|
|
entry_item = entry_item->next)
|
|
{
|
|
if (entry_item->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) {
|
|
continue;
|
|
}
|
|
ID *id_to = *entry_item->id_pointer.to;
|
|
|
|
/* Case where this ID pointer was to a linked ID, that now needs to be overridden. */
|
|
if (ID_IS_LINKED(id_to) && (id_to->lib != id->lib) && (id_to->tag & LIB_TAG_DOIT) != 0) {
|
|
CLOG_INFO(&LOG_RESYNC,
|
|
3,
|
|
"ID %s (%p) now tagged as needing resync because they use linked %s (%p) that "
|
|
"now needs to be overridden",
|
|
id->name,
|
|
id->lib,
|
|
id_to->name,
|
|
id_to->lib);
|
|
id->tag |= LIB_TAG_LIBOVERRIDE_NEED_RESYNC;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
/* Handling hierarchy relations for final tagging needs to happen after all IDs in a given
|
|
* hierarchy have been tagged for resync in previous loop above. Otherwise, some resync roots may
|
|
* be missing. */
|
|
lib_override_resync_tagging_finalize(bmain, id_roots, library_indirect_level);
|
|
|
|
#ifndef NDEBUG
|
|
/* Check for validity/integrity of the computed set of root IDs, and their sub-branches defined
|
|
* by their resync root IDs. */
|
|
{
|
|
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
|
|
GHashIterator *id_roots_iter = BLI_ghashIterator_new(id_roots);
|
|
while (!BLI_ghashIterator_done(id_roots_iter)) {
|
|
ID *id_root = static_cast<ID *>(BLI_ghashIterator_getKey(id_roots_iter));
|
|
LinkNodePair *id_resync_roots = static_cast<LinkNodePair *>(
|
|
BLI_ghashIterator_getValue(id_roots_iter));
|
|
CLOG_INFO(&LOG_RESYNC,
|
|
2,
|
|
"Checking validity of computed TODO data for root '%s'... \n",
|
|
id_root->name);
|
|
|
|
if (id_root->tag & LIB_TAG_LIBOVERRIDE_NEED_RESYNC) {
|
|
LinkNode *id_resync_root_iter = id_resync_roots->list;
|
|
ID *id_resync_root = static_cast<ID *>(id_resync_root_iter->link);
|
|
|
|
if (id_resync_roots->list != id_resync_roots->last_node || id_resync_root != id_root) {
|
|
CLOG_ERROR(&LOG_RESYNC,
|
|
"Hierarchy root ID is tagged for resync, yet it is not the only partial "
|
|
"resync roots, this should not happen."
|
|
"\n\tRoot ID: %s"
|
|
"\n\tFirst Resync root ID: %s"
|
|
"\n\tLast Resync root ID: %s",
|
|
id_root->name,
|
|
static_cast<ID *>(id_resync_roots->list->link)->name,
|
|
static_cast<ID *>(id_resync_roots->last_node->link)->name);
|
|
}
|
|
}
|
|
for (LinkNode *id_resync_root_iter = id_resync_roots->list; id_resync_root_iter != nullptr;
|
|
id_resync_root_iter = id_resync_root_iter->next)
|
|
{
|
|
ID *id_resync_root = static_cast<ID *>(id_resync_root_iter->link);
|
|
BLI_assert(id_resync_root == id_root || !BLI_ghash_haskey(id_roots, id_resync_root));
|
|
if (id_resync_root == id_root) {
|
|
if (id_resync_root_iter != id_resync_roots->list ||
|
|
id_resync_root_iter != id_resync_roots->last_node)
|
|
{
|
|
CLOG_ERROR(&LOG_RESYNC,
|
|
"Resync root ID is same as root ID of the override hierarchy, yet other "
|
|
"resync root IDs are also defined, this should not happen at this point."
|
|
"\n\tRoot ID: %s"
|
|
"\n\tFirst Resync root ID: %s"
|
|
"\n\tLast Resync root ID: %s",
|
|
id_root->name,
|
|
static_cast<ID *>(id_resync_roots->list->link)->name,
|
|
static_cast<ID *>(id_resync_roots->last_node->link)->name);
|
|
}
|
|
}
|
|
}
|
|
BLI_ghashIterator_step(id_roots_iter);
|
|
}
|
|
BLI_ghashIterator_free(id_roots_iter);
|
|
}
|
|
#endif
|
|
|
|
BKE_main_relations_free(bmain);
|
|
BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false);
|
|
|
|
ListBase no_main_ids_list = {nullptr};
|
|
|
|
GHashIterator *id_roots_iter = BLI_ghashIterator_new(id_roots);
|
|
while (!BLI_ghashIterator_done(id_roots_iter)) {
|
|
ID *id_root = static_cast<ID *>(BLI_ghashIterator_getKey(id_roots_iter));
|
|
Library *library = id_root->lib;
|
|
LinkNodePair *id_resync_roots = static_cast<LinkNodePair *>(
|
|
BLI_ghashIterator_getValue(id_roots_iter));
|
|
|
|
if (ID_IS_LINKED(id_root)) {
|
|
id_root->lib->tag |= LIBRARY_TAG_RESYNC_REQUIRED;
|
|
}
|
|
|
|
CLOG_INFO(&LOG_RESYNC,
|
|
2,
|
|
"Resyncing all dependencies under root %s (%p), first one being '%s'...",
|
|
id_root->name,
|
|
reinterpret_cast<void *>(library),
|
|
reinterpret_cast<ID *>(id_resync_roots->list->link)->name);
|
|
const bool success = lib_override_library_resync(bmain,
|
|
scene,
|
|
view_layer,
|
|
id_root,
|
|
id_resync_roots->list,
|
|
&no_main_ids_list,
|
|
override_resync_residual_storage,
|
|
false,
|
|
false,
|
|
reports);
|
|
CLOG_INFO(&LOG_RESYNC, 2, "\tSuccess: %d", success);
|
|
if (success) {
|
|
reports->count.resynced_lib_overrides++;
|
|
if (library_indirect_level > 0 && reports->do_resynced_lib_overrides_libraries_list &&
|
|
BLI_linklist_index(reports->resynced_lib_overrides_libraries, library) < 0)
|
|
{
|
|
BLI_linklist_prepend(&reports->resynced_lib_overrides_libraries, library);
|
|
reports->resynced_lib_overrides_libraries_count++;
|
|
}
|
|
}
|
|
|
|
BLI_linklist_free(id_resync_roots->list, nullptr);
|
|
BLI_ghashIterator_step(id_roots_iter);
|
|
}
|
|
BLI_ghashIterator_free(id_roots_iter);
|
|
|
|
LISTBASE_FOREACH_MUTABLE (ID *, id_iter, &no_main_ids_list) {
|
|
BKE_id_free(bmain, id_iter);
|
|
}
|
|
BLI_listbase_clear(&no_main_ids_list);
|
|
|
|
/* Just in case, should not be needed in theory, since #lib_override_library_resync should have
|
|
* already cleared them all. */
|
|
BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false);
|
|
|
|
/* Check there are no left-over IDs needing resync from the current (or higher) level of indirect
|
|
* library level. */
|
|
bool process_lib_level_again = false;
|
|
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if (lib_override_library_main_resync_id_skip_check(id, library_indirect_level)) {
|
|
continue;
|
|
}
|
|
|
|
const bool need_resync = (id->tag & LIB_TAG_LIBOVERRIDE_NEED_RESYNC) != 0;
|
|
const bool need_reseync_original = (id->override_library->runtime != nullptr &&
|
|
(id->override_library->runtime->tag &
|
|
LIBOVERRIDE_TAG_NEED_RESYNC_ORIGINAL) != 0);
|
|
const bool is_isolated_from_root = (id->override_library->runtime != nullptr &&
|
|
(id->override_library->runtime->tag &
|
|
LIBOVERRIDE_TAG_RESYNC_ISOLATED_FROM_ROOT) != 0);
|
|
|
|
if (need_resync && is_isolated_from_root) {
|
|
if (!BKE_lib_override_library_is_user_edited(id)) {
|
|
CLOG_WARN(
|
|
&LOG_RESYNC,
|
|
"Deleting unused ID override %s from library level %d, still found as needing "
|
|
"resync, and being isolated from its hierarchy root. This can happen when its "
|
|
"otherwise unchanged linked reference was moved around in the library file (e.g. if "
|
|
"an object was moved into another sub-collection of the same hierarchy).",
|
|
id->name,
|
|
ID_IS_LINKED(id) ? id->lib->temp_index : 0);
|
|
id->tag |= LIB_TAG_DOIT;
|
|
}
|
|
else {
|
|
CLOG_WARN(
|
|
&LOG_RESYNC,
|
|
"Keeping user-edited ID override %s from library level %d still found as "
|
|
"needing resync, and being isolated from its hierarchy root. This can happen when its "
|
|
"otherwise unchanged linked reference was moved around in the library file (e.g. if "
|
|
"an object was moved into another sub-collection of the same hierarchy).",
|
|
id->name,
|
|
ID_IS_LINKED(id) ? id->lib->temp_index : 0);
|
|
id->tag &= ~LIB_TAG_LIBOVERRIDE_NEED_RESYNC;
|
|
id->override_library->runtime->tag &= ~LIBOVERRIDE_TAG_RESYNC_ISOLATED_FROM_ROOT;
|
|
}
|
|
}
|
|
else if (need_resync) {
|
|
if (need_reseync_original) {
|
|
CLOG_INFO(&LOG_RESYNC,
|
|
2,
|
|
"ID override %s from library level %d still found as needing resync after "
|
|
"tackling library level %d. Since it was originally tagged as such by "
|
|
"RNA/liboverride apply code, this whole level of library needs to be processed "
|
|
"another time.",
|
|
id->name,
|
|
ID_IS_LINKED(id) ? id->lib->temp_index : 0,
|
|
library_indirect_level);
|
|
process_lib_level_again = true;
|
|
/* Cleanup tag for now, will be re-set by next iteration of this function. */
|
|
id->override_library->runtime->tag &= ~LIBOVERRIDE_TAG_NEED_RESYNC_ORIGINAL;
|
|
}
|
|
else {
|
|
/* If it was only tagged for resync as part of resync process itself, it means it was
|
|
* originally inside of a resync hierarchy, but not in the matching reference hierarchy
|
|
* anymore. So it did not actually need to be resynced, simply clear the tag. */
|
|
CLOG_INFO(&LOG_RESYNC,
|
|
4,
|
|
"ID override %s from library level %d still found as needing resync after "
|
|
"tackling library level %d. However, it was not tagged as such by "
|
|
"RNA/liboverride apply code, so ignoring it",
|
|
id->name,
|
|
ID_IS_LINKED(id) ? id->lib->temp_index : 0,
|
|
library_indirect_level);
|
|
id->tag &= ~LIB_TAG_LIBOVERRIDE_NEED_RESYNC;
|
|
}
|
|
}
|
|
else if (need_reseync_original) {
|
|
/* Just cleanup of temporary tag, the ID has been resynced successfully. */
|
|
id->override_library->runtime->tag &= ~LIBOVERRIDE_TAG_NEED_RESYNC_ORIGINAL;
|
|
}
|
|
else if (is_isolated_from_root) {
|
|
CLOG_ERROR(
|
|
&LOG_RESYNC,
|
|
"ID override %s from library level %d still tagged as isolated from its hierarchy root, "
|
|
"it should have been either properly resynced or removed at that point.",
|
|
id->name,
|
|
ID_IS_LINKED(id) ? id->lib->temp_index : 0);
|
|
id->override_library->runtime->tag &= ~LIBOVERRIDE_TAG_RESYNC_ISOLATED_FROM_ROOT;
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
/* Delete 'isolated from root' remaining IDs tagged in above check loop. */
|
|
BKE_id_multi_tagged_delete(bmain);
|
|
BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false);
|
|
|
|
BLI_ghash_free(id_roots, nullptr, MEM_freeN);
|
|
|
|
/* In some fairly rare (and degenerate) cases, some root ID from other liboverrides may have been
|
|
* freed, and therefore set to nullptr. Attempt to fix this as best as possible. */
|
|
BKE_lib_override_library_main_hierarchy_root_ensure(bmain);
|
|
|
|
if (do_reports_recursive_resync_timing) {
|
|
reports->duration.lib_overrides_recursive_resync += PIL_check_seconds_timer() - init_time;
|
|
}
|
|
|
|
return process_lib_level_again;
|
|
}
|
|
|
|
static int lib_override_sort_libraries_func(LibraryIDLinkCallbackData *cb_data)
|
|
{
|
|
if (cb_data->cb_flag & IDWALK_CB_LOOPBACK) {
|
|
return IDWALK_RET_NOP;
|
|
}
|
|
ID *id_owner = cb_data->owner_id;
|
|
ID *id = *cb_data->id_pointer;
|
|
if (id != nullptr && ID_IS_LINKED(id) && id->lib != id_owner->lib) {
|
|
const int owner_library_indirect_level = ID_IS_LINKED(id_owner) ? id_owner->lib->temp_index :
|
|
0;
|
|
if (owner_library_indirect_level > 100) {
|
|
CLOG_ERROR(&LOG_RESYNC,
|
|
"Levels of indirect usages of libraries is way too high, there are most likely "
|
|
"dependency loops, skipping further building loops (involves at least '%s' from "
|
|
"'%s' and '%s' from '%s')",
|
|
id_owner->name,
|
|
id_owner->lib->filepath,
|
|
id->name,
|
|
id->lib->filepath);
|
|
return IDWALK_RET_NOP;
|
|
}
|
|
if (owner_library_indirect_level > 90) {
|
|
CLOG_WARN(
|
|
&LOG_RESYNC,
|
|
"Levels of indirect usages of libraries is suspiciously too high, there are most likely "
|
|
"dependency loops (involves at least '%s' from '%s' and '%s' from '%s')",
|
|
id_owner->name,
|
|
id_owner->lib->filepath,
|
|
id->name,
|
|
id->lib->filepath);
|
|
}
|
|
|
|
if (owner_library_indirect_level >= id->lib->temp_index) {
|
|
id->lib->temp_index = owner_library_indirect_level + 1;
|
|
*reinterpret_cast<bool *>(cb_data->user_data) = true;
|
|
}
|
|
}
|
|
return IDWALK_RET_NOP;
|
|
}
|
|
|
|
/** Define the `temp_index` of libraries from their highest level of indirect usage.
|
|
*
|
|
* E.g. if lib_a uses lib_b, lib_c and lib_d, and lib_b also uses lib_d, then lib_a has an index of
|
|
* 1, lib_b and lib_c an index of 2, and lib_d an index of 3. */
|
|
static int lib_override_libraries_index_define(Main *bmain)
|
|
{
|
|
LISTBASE_FOREACH (Library *, library, &bmain->libraries) {
|
|
/* index 0 is reserved for local data. */
|
|
library->temp_index = 1;
|
|
}
|
|
bool do_continue = true;
|
|
while (do_continue) {
|
|
do_continue = false;
|
|
ID *id;
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
/* NOTE: In theory all non-liboverride IDs could be skipped here. This does not gives any
|
|
* performances boost though, so for now keep it as is (i.e. also consider non-liboverride
|
|
* relationships to establish libraries hierarchy). */
|
|
BKE_library_foreach_ID_link(
|
|
bmain, id, lib_override_sort_libraries_func, &do_continue, IDWALK_READONLY);
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
}
|
|
|
|
int library_indirect_level_max = 0;
|
|
LISTBASE_FOREACH (Library *, library, &bmain->libraries) {
|
|
if (library->temp_index > library_indirect_level_max) {
|
|
library_indirect_level_max = library->temp_index;
|
|
}
|
|
}
|
|
return library_indirect_level_max;
|
|
}
|
|
|
|
void BKE_lib_override_library_main_resync(Main *bmain,
|
|
Scene *scene,
|
|
ViewLayer *view_layer,
|
|
BlendFileReadReport *reports)
|
|
{
|
|
/* We use a specific collection to gather/store all 'orphaned' override collections and objects
|
|
* generated by re-sync-process. This avoids putting them in scene's master collection. */
|
|
#define OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME "OVERRIDE_RESYNC_LEFTOVERS"
|
|
Collection *override_resync_residual_storage = static_cast<Collection *>(BLI_findstring(
|
|
&bmain->collections, OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME, offsetof(ID, name) + 2));
|
|
if (override_resync_residual_storage != nullptr &&
|
|
ID_IS_LINKED(override_resync_residual_storage)) {
|
|
override_resync_residual_storage = nullptr;
|
|
}
|
|
if (override_resync_residual_storage == nullptr) {
|
|
override_resync_residual_storage = BKE_collection_add(
|
|
bmain, scene->master_collection, OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME);
|
|
/* Hide the collection from viewport and render. */
|
|
override_resync_residual_storage->flag |= COLLECTION_HIDE_VIEWPORT | COLLECTION_HIDE_RENDER;
|
|
}
|
|
/* BKE_collection_add above could have tagged the view_layer out of sync. */
|
|
BKE_view_layer_synced_ensure(scene, view_layer);
|
|
const Object *old_active_object = BKE_view_layer_active_object_get(view_layer);
|
|
|
|
/* Necessary to improve performances, and prevent layers matching override sub-collections to be
|
|
* lost when re-syncing the parent override collection.
|
|
* Ref. #73411. */
|
|
BKE_layer_collection_resync_forbid();
|
|
|
|
int library_indirect_level = lib_override_libraries_index_define(bmain);
|
|
while (library_indirect_level >= 0) {
|
|
int level_reprocess_count = 0;
|
|
/* Update overrides from each indirect level separately.
|
|
*
|
|
* About the looping here: It may happen that some sub-hierarchies of liboverride are moved
|
|
* around (the hierarchy in reference data does not match anymore the existing one in
|
|
* liboverride data). In some cases, these sub-hierarchies won't be resynced then. If some IDs
|
|
* in these sub-hierarchies actually do need resync, then the whole process needs to be applied
|
|
* again, until all cases are fully processed.
|
|
*
|
|
* In practice, even in very complex and 'dirty'/outdated production files, typically less than
|
|
* ten reprocesses are enough to cover all cases (in the vast majority of cases, no reprocess
|
|
* is needed at all). */
|
|
while (lib_override_library_main_resync_on_library_indirect_level(
|
|
bmain,
|
|
scene,
|
|
view_layer,
|
|
override_resync_residual_storage,
|
|
library_indirect_level,
|
|
reports))
|
|
{
|
|
level_reprocess_count++;
|
|
if (level_reprocess_count > 100) {
|
|
CLOG_WARN(
|
|
&LOG_RESYNC,
|
|
"Need to reprocess resync for library level %d more than %d times, aborting. This is "
|
|
"either caused by extremely complex liboverride hierarchies, or a bug",
|
|
library_indirect_level,
|
|
level_reprocess_count);
|
|
break;
|
|
}
|
|
else {
|
|
CLOG_INFO(&LOG_RESYNC,
|
|
4,
|
|
"Applying reprocess %d for resyncing at library level %d",
|
|
level_reprocess_count,
|
|
library_indirect_level);
|
|
}
|
|
}
|
|
library_indirect_level--;
|
|
}
|
|
|
|
BKE_layer_collection_resync_allow();
|
|
|
|
/* Essentially ensures that potentially new overrides of new objects will be instantiated. */
|
|
lib_override_library_create_post_process(bmain,
|
|
scene,
|
|
view_layer,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
override_resync_residual_storage,
|
|
old_active_object,
|
|
true);
|
|
|
|
if (BKE_collection_is_empty(override_resync_residual_storage)) {
|
|
BKE_collection_delete(bmain, override_resync_residual_storage, true);
|
|
}
|
|
|
|
LISTBASE_FOREACH (Library *, library, &bmain->libraries) {
|
|
if (library->tag & LIBRARY_TAG_RESYNC_REQUIRED) {
|
|
CLOG_INFO(&LOG_RESYNC,
|
|
2,
|
|
"library '%s' contains some linked overrides that required recursive resync, "
|
|
"consider updating it",
|
|
library->filepath);
|
|
}
|
|
}
|
|
|
|
/* Cleanup global namemap, to avoid extra processing with regular ID name management. Better to
|
|
* re-create the global namemap on demand. */
|
|
BKE_main_namemap_destroy(&bmain->name_map_global);
|
|
|
|
BLI_assert(BKE_main_namemap_validate(bmain));
|
|
}
|
|
|
|
void BKE_lib_override_library_delete(Main *bmain, ID *id_root)
|
|
{
|
|
BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root));
|
|
|
|
/* Tag all library overrides in the chains of dependencies from the given root one. */
|
|
BKE_main_relations_create(bmain, 0);
|
|
LibOverrideGroupTagData data{};
|
|
data.bmain = bmain;
|
|
data.scene = nullptr;
|
|
data.id_root = id_root;
|
|
data.hierarchy_root_id = id_root->override_library->hierarchy_root;
|
|
data.tag = LIB_TAG_DOIT;
|
|
data.missing_tag = LIB_TAG_MISSING;
|
|
data.is_override = true;
|
|
data.is_resync = false;
|
|
lib_override_group_tag_data_object_to_collection_init(&data);
|
|
lib_override_overrides_group_tag(&data);
|
|
|
|
BKE_main_relations_free(bmain);
|
|
lib_override_group_tag_data_clear(&data);
|
|
|
|
ID *id;
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if (id->tag & LIB_TAG_DOIT) {
|
|
if (ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
|
|
ID *id_override_reference = id->override_library->reference;
|
|
|
|
/* Remap the whole local IDs to use the linked data. */
|
|
BKE_libblock_remap(bmain, id, id_override_reference, ID_REMAP_SKIP_INDIRECT_USAGE);
|
|
}
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
/* Delete the override IDs. */
|
|
BKE_id_multi_tagged_delete(bmain);
|
|
|
|
/* Should not actually be needed here. */
|
|
BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false);
|
|
}
|
|
|
|
void BKE_lib_override_library_make_local(Main *bmain, ID *id)
|
|
{
|
|
if (!ID_IS_OVERRIDE_LIBRARY(id)) {
|
|
return;
|
|
}
|
|
if (ID_IS_OVERRIDE_LIBRARY_VIRTUAL(id)) {
|
|
/* We should never directly 'make local' virtual overrides (aka shape keys). */
|
|
BLI_assert_unreachable();
|
|
id->flag &= ~LIB_EMBEDDED_DATA_LIB_OVERRIDE;
|
|
return;
|
|
}
|
|
|
|
BKE_lib_override_library_free(&id->override_library, true);
|
|
|
|
Key *shape_key = BKE_key_from_id(id);
|
|
if (shape_key != nullptr) {
|
|
shape_key->id.flag &= ~LIB_EMBEDDED_DATA_LIB_OVERRIDE;
|
|
}
|
|
|
|
if (GS(id->name) == ID_SCE) {
|
|
Collection *master_collection = reinterpret_cast<Scene *>(id)->master_collection;
|
|
if (master_collection != nullptr) {
|
|
master_collection->id.flag &= ~LIB_EMBEDDED_DATA_LIB_OVERRIDE;
|
|
}
|
|
}
|
|
|
|
bNodeTree *node_tree = ntreeFromID(id);
|
|
if (node_tree != nullptr) {
|
|
node_tree->id.flag &= ~LIB_EMBEDDED_DATA_LIB_OVERRIDE;
|
|
}
|
|
|
|
/* In case a liboverride hierarchy root is 'made local', i.e. is not a liboverride anymore, all
|
|
* hierarchy roots of all liboverrides need to be validated/re-generated again.
|
|
* Only in case `bmain` is given, otherwise caller is responsible to do this. */
|
|
if (bmain) {
|
|
BKE_lib_override_library_main_hierarchy_root_ensure(bmain);
|
|
}
|
|
}
|
|
|
|
/* We only build override GHash on request. */
|
|
BLI_INLINE GHash *override_library_rna_path_mapping_ensure(IDOverrideLibrary *liboverride)
|
|
{
|
|
IDOverrideLibraryRuntime *liboverride_runtime = override_library_runtime_ensure(liboverride);
|
|
if (liboverride_runtime->rna_path_to_override_properties == nullptr) {
|
|
liboverride_runtime->rna_path_to_override_properties = BLI_ghash_new(
|
|
BLI_ghashutil_strhash_p_murmur, BLI_ghashutil_strcmp, __func__);
|
|
for (IDOverrideLibraryProperty *op =
|
|
static_cast<IDOverrideLibraryProperty *>(liboverride->properties.first);
|
|
op != nullptr;
|
|
op = op->next)
|
|
{
|
|
BLI_ghash_insert(liboverride_runtime->rna_path_to_override_properties, op->rna_path, op);
|
|
}
|
|
}
|
|
|
|
return liboverride_runtime->rna_path_to_override_properties;
|
|
}
|
|
|
|
IDOverrideLibraryProperty *BKE_lib_override_library_property_find(IDOverrideLibrary *liboverride,
|
|
const char *rna_path)
|
|
{
|
|
GHash *liboverride_runtime = override_library_rna_path_mapping_ensure(liboverride);
|
|
return static_cast<IDOverrideLibraryProperty *>(BLI_ghash_lookup(liboverride_runtime, rna_path));
|
|
}
|
|
|
|
IDOverrideLibraryProperty *BKE_lib_override_library_property_get(IDOverrideLibrary *liboverride,
|
|
const char *rna_path,
|
|
bool *r_created)
|
|
{
|
|
IDOverrideLibraryProperty *op = BKE_lib_override_library_property_find(liboverride, rna_path);
|
|
|
|
if (op == nullptr) {
|
|
op = MEM_cnew<IDOverrideLibraryProperty>(__func__);
|
|
op->rna_path = BLI_strdup(rna_path);
|
|
BLI_addtail(&liboverride->properties, op);
|
|
|
|
GHash *liboverride_runtime = override_library_rna_path_mapping_ensure(liboverride);
|
|
BLI_ghash_insert(liboverride_runtime, op->rna_path, op);
|
|
|
|
if (r_created) {
|
|
*r_created = true;
|
|
}
|
|
}
|
|
else if (r_created) {
|
|
*r_created = false;
|
|
}
|
|
|
|
return op;
|
|
}
|
|
|
|
bool BKE_lib_override_rna_property_find(PointerRNA *idpoin,
|
|
const IDOverrideLibraryProperty *library_prop,
|
|
PointerRNA *r_override_poin,
|
|
PropertyRNA **r_override_prop,
|
|
int *r_index)
|
|
{
|
|
BLI_assert(RNA_struct_is_ID(idpoin->type) && ID_IS_OVERRIDE_LIBRARY(idpoin->data));
|
|
return RNA_path_resolve_property_full(
|
|
idpoin, library_prop->rna_path, r_override_poin, r_override_prop, r_index);
|
|
}
|
|
|
|
void lib_override_library_property_copy(IDOverrideLibraryProperty *op_dst,
|
|
IDOverrideLibraryProperty *op_src)
|
|
{
|
|
op_dst->rna_path = BLI_strdup(op_src->rna_path);
|
|
BLI_duplicatelist(&op_dst->operations, &op_src->operations);
|
|
|
|
for (IDOverrideLibraryPropertyOperation *
|
|
opop_dst = static_cast<IDOverrideLibraryPropertyOperation *>(op_dst->operations.first),
|
|
*opop_src = static_cast<IDOverrideLibraryPropertyOperation *>(op_src->operations.first);
|
|
opop_dst;
|
|
opop_dst = opop_dst->next, opop_src = opop_src->next)
|
|
{
|
|
lib_override_library_property_operation_copy(opop_dst, opop_src);
|
|
}
|
|
}
|
|
|
|
void lib_override_library_property_clear(IDOverrideLibraryProperty *op)
|
|
{
|
|
BLI_assert(op->rna_path != nullptr);
|
|
|
|
MEM_freeN(op->rna_path);
|
|
|
|
LISTBASE_FOREACH (IDOverrideLibraryPropertyOperation *, opop, &op->operations) {
|
|
lib_override_library_property_operation_clear(opop);
|
|
}
|
|
BLI_freelistN(&op->operations);
|
|
}
|
|
|
|
bool BKE_lib_override_library_property_rna_path_change(IDOverrideLibrary *liboverride,
|
|
const char *old_rna_path,
|
|
const char *new_rna_path)
|
|
{
|
|
/* Find the override property by its old RNA path. */
|
|
GHash *liboverride_runtime = override_library_rna_path_mapping_ensure(liboverride);
|
|
IDOverrideLibraryProperty *liboverride_property = static_cast<IDOverrideLibraryProperty *>(
|
|
BLI_ghash_popkey(liboverride_runtime, old_rna_path, nullptr));
|
|
|
|
if (liboverride_property == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
/* Switch over the RNA path. */
|
|
MEM_SAFE_FREE(liboverride_property->rna_path);
|
|
liboverride_property->rna_path = BLI_strdup(new_rna_path);
|
|
|
|
/* Put property back into the lookup mapping, using the new RNA path. */
|
|
BLI_ghash_insert(liboverride_runtime, liboverride_property->rna_path, liboverride_property);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void lib_override_library_property_delete(IDOverrideLibrary *liboverride,
|
|
IDOverrideLibraryProperty *liboverride_property,
|
|
const bool do_runtime_updates)
|
|
{
|
|
if (do_runtime_updates &&
|
|
!ELEM(nullptr, liboverride->runtime, liboverride->runtime->rna_path_to_override_properties))
|
|
{
|
|
BLI_ghash_remove(liboverride->runtime->rna_path_to_override_properties,
|
|
liboverride_property->rna_path,
|
|
nullptr,
|
|
nullptr);
|
|
}
|
|
lib_override_library_property_clear(liboverride_property);
|
|
BLI_freelinkN(&liboverride->properties, liboverride_property);
|
|
}
|
|
|
|
bool BKE_lib_override_library_property_search_and_delete(IDOverrideLibrary *liboverride,
|
|
const char *rna_path)
|
|
{
|
|
/* Find the override property by its old RNA path. */
|
|
GHash *liboverride_runtime = override_library_rna_path_mapping_ensure(liboverride);
|
|
IDOverrideLibraryProperty *liboverride_property = static_cast<IDOverrideLibraryProperty *>(
|
|
BLI_ghash_popkey(liboverride_runtime, rna_path, nullptr));
|
|
|
|
if (liboverride_property == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
/* The key (rna_path) was already popped out of the runtime mapping above. */
|
|
lib_override_library_property_delete(liboverride, liboverride_property, false);
|
|
return true;
|
|
}
|
|
|
|
void BKE_lib_override_library_property_delete(IDOverrideLibrary *liboverride,
|
|
IDOverrideLibraryProperty *liboverride_property)
|
|
{
|
|
lib_override_library_property_delete(liboverride, liboverride_property, true);
|
|
}
|
|
|
|
static IDOverrideLibraryPropertyOperation *liboverride_opop_find_name_lib_iterative(
|
|
ListBase *liboverride_operations,
|
|
const char *subitem_main_name,
|
|
const char *subitem_other_name,
|
|
const std::optional<const ID *> &subitem_main_id,
|
|
const std::optional<const ID *> &subitem_other_id,
|
|
const size_t offesetof_opop_main_name,
|
|
const size_t offesetof_opop_other_name,
|
|
const size_t offesetof_opop_main_id,
|
|
const size_t offesetof_opop_other_id)
|
|
{
|
|
const bool do_ids(subitem_main_id);
|
|
IDOverrideLibraryPropertyOperation *opop;
|
|
for (opop = static_cast<IDOverrideLibraryPropertyOperation *>(BLI_findstring_ptr(
|
|
liboverride_operations, subitem_main_name, int(offesetof_opop_main_name)));
|
|
opop;
|
|
opop = static_cast<IDOverrideLibraryPropertyOperation *>(BLI_listbase_findafter_string_ptr(
|
|
reinterpret_cast<Link *>(opop), subitem_main_name, int(offesetof_opop_main_name))))
|
|
{
|
|
const char *opop_other_name = *reinterpret_cast<const char **>(reinterpret_cast<char *>(opop) +
|
|
offesetof_opop_other_name);
|
|
const bool opop_use_id = (opop->flag & LIBOVERRIDE_OP_FLAG_IDPOINTER_ITEM_USE_ID) != 0;
|
|
|
|
if (do_ids && opop_use_id) {
|
|
/* Skip if ID pointers are expected valid and they do not exactly match. */
|
|
const ID *opop_main_id = *reinterpret_cast<const ID **>(reinterpret_cast<char *>(opop) +
|
|
offesetof_opop_main_id);
|
|
if (*subitem_main_id != opop_main_id) {
|
|
continue;
|
|
}
|
|
const ID *opop_other_id = *reinterpret_cast<const ID **>(reinterpret_cast<char *>(opop) +
|
|
offesetof_opop_other_id);
|
|
if (*subitem_other_id != opop_other_id) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Only check other name if ID handling is matching between given search parameters and
|
|
* current liboverride operation (i.e. if both have valid ID pointers, or both have none). */
|
|
if ((do_ids && opop_use_id) || (!do_ids && !opop_use_id)) {
|
|
if (!subitem_other_name && !opop_other_name) {
|
|
return opop;
|
|
}
|
|
if (subitem_other_name && opop_other_name && STREQ(subitem_other_name, opop_other_name)) {
|
|
return opop;
|
|
}
|
|
}
|
|
|
|
/* No exact match found, keep checking the rest of the list of operations. */
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
IDOverrideLibraryPropertyOperation *BKE_lib_override_library_property_operation_find(
|
|
IDOverrideLibraryProperty *liboverride_property,
|
|
const char *subitem_refname,
|
|
const char *subitem_locname,
|
|
const std::optional<const ID *> &subitem_refid,
|
|
const std::optional<const ID *> &subitem_locid,
|
|
const int subitem_refindex,
|
|
const int subitem_locindex,
|
|
const bool strict,
|
|
bool *r_strict)
|
|
{
|
|
BLI_assert(!subitem_refid == !subitem_locid);
|
|
|
|
IDOverrideLibraryPropertyOperation *opop;
|
|
const int subitem_defindex = -1;
|
|
|
|
if (r_strict) {
|
|
*r_strict = true;
|
|
}
|
|
|
|
if (subitem_locname != nullptr) {
|
|
opop = liboverride_opop_find_name_lib_iterative(
|
|
&liboverride_property->operations,
|
|
subitem_locname,
|
|
subitem_refname,
|
|
subitem_locid,
|
|
subitem_refid,
|
|
offsetof(IDOverrideLibraryPropertyOperation, subitem_local_name),
|
|
offsetof(IDOverrideLibraryPropertyOperation, subitem_reference_name),
|
|
offsetof(IDOverrideLibraryPropertyOperation, subitem_local_id),
|
|
offsetof(IDOverrideLibraryPropertyOperation, subitem_reference_id));
|
|
|
|
if (opop != nullptr) {
|
|
return opop;
|
|
}
|
|
}
|
|
|
|
if (subitem_refname != nullptr) {
|
|
opop = liboverride_opop_find_name_lib_iterative(
|
|
&liboverride_property->operations,
|
|
subitem_refname,
|
|
subitem_locname,
|
|
subitem_refid,
|
|
subitem_locid,
|
|
offsetof(IDOverrideLibraryPropertyOperation, subitem_reference_name),
|
|
offsetof(IDOverrideLibraryPropertyOperation, subitem_local_name),
|
|
offsetof(IDOverrideLibraryPropertyOperation, subitem_reference_id),
|
|
offsetof(IDOverrideLibraryPropertyOperation, subitem_local_id));
|
|
|
|
if (opop != nullptr) {
|
|
return opop;
|
|
}
|
|
}
|
|
|
|
if ((opop = static_cast<IDOverrideLibraryPropertyOperation *>(BLI_listbase_bytes_find(
|
|
&liboverride_property->operations,
|
|
&subitem_locindex,
|
|
sizeof(subitem_locindex),
|
|
offsetof(IDOverrideLibraryPropertyOperation, subitem_local_index)))))
|
|
{
|
|
return ELEM(subitem_refindex, -1, opop->subitem_reference_index) ? opop : nullptr;
|
|
}
|
|
|
|
if ((opop = static_cast<IDOverrideLibraryPropertyOperation *>(BLI_listbase_bytes_find(
|
|
&liboverride_property->operations,
|
|
&subitem_refindex,
|
|
sizeof(subitem_refindex),
|
|
offsetof(IDOverrideLibraryPropertyOperation, subitem_reference_index)))))
|
|
{
|
|
return ELEM(subitem_locindex, -1, opop->subitem_local_index) ? opop : nullptr;
|
|
}
|
|
|
|
/* `index == -1` means all indices, that is a valid fallback in case we requested specific index.
|
|
*/
|
|
if (!strict && (subitem_locindex != subitem_defindex) &&
|
|
(opop = static_cast<IDOverrideLibraryPropertyOperation *>(BLI_listbase_bytes_find(
|
|
&liboverride_property->operations,
|
|
&subitem_defindex,
|
|
sizeof(subitem_defindex),
|
|
offsetof(IDOverrideLibraryPropertyOperation, subitem_local_index)))))
|
|
{
|
|
if (r_strict) {
|
|
*r_strict = false;
|
|
}
|
|
return opop;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
IDOverrideLibraryPropertyOperation *BKE_lib_override_library_property_operation_get(
|
|
IDOverrideLibraryProperty *liboverride_property,
|
|
const short operation,
|
|
const char *subitem_refname,
|
|
const char *subitem_locname,
|
|
const std::optional<ID *> &subitem_refid,
|
|
const std::optional<ID *> &subitem_locid,
|
|
const int subitem_refindex,
|
|
const int subitem_locindex,
|
|
const bool strict,
|
|
bool *r_strict,
|
|
bool *r_created)
|
|
{
|
|
BLI_assert(!subitem_refid == !subitem_locid);
|
|
|
|
IDOverrideLibraryPropertyOperation *opop = BKE_lib_override_library_property_operation_find(
|
|
liboverride_property,
|
|
subitem_refname,
|
|
subitem_locname,
|
|
subitem_refid,
|
|
subitem_locid,
|
|
subitem_refindex,
|
|
subitem_locindex,
|
|
strict,
|
|
r_strict);
|
|
|
|
if (opop == nullptr) {
|
|
opop = MEM_cnew<IDOverrideLibraryPropertyOperation>(__func__);
|
|
opop->operation = operation;
|
|
if (subitem_locname) {
|
|
opop->subitem_local_name = BLI_strdup(subitem_locname);
|
|
}
|
|
if (subitem_refname) {
|
|
opop->subitem_reference_name = BLI_strdup(subitem_refname);
|
|
}
|
|
opop->subitem_local_index = subitem_locindex;
|
|
opop->subitem_reference_index = subitem_refindex;
|
|
|
|
if (subitem_refid) {
|
|
opop->subitem_reference_id = *subitem_refid;
|
|
opop->subitem_local_id = *subitem_locid;
|
|
opop->flag |= LIBOVERRIDE_OP_FLAG_IDPOINTER_ITEM_USE_ID;
|
|
}
|
|
|
|
BLI_addtail(&liboverride_property->operations, opop);
|
|
|
|
if (r_created) {
|
|
*r_created = true;
|
|
}
|
|
}
|
|
else if (r_created) {
|
|
*r_created = false;
|
|
}
|
|
|
|
return opop;
|
|
}
|
|
|
|
void lib_override_library_property_operation_copy(IDOverrideLibraryPropertyOperation *opop_dst,
|
|
IDOverrideLibraryPropertyOperation *opop_src)
|
|
{
|
|
if (opop_src->subitem_reference_name) {
|
|
opop_dst->subitem_reference_name = BLI_strdup(opop_src->subitem_reference_name);
|
|
}
|
|
if (opop_src->subitem_local_name) {
|
|
opop_dst->subitem_local_name = BLI_strdup(opop_src->subitem_local_name);
|
|
}
|
|
}
|
|
|
|
void lib_override_library_property_operation_clear(IDOverrideLibraryPropertyOperation *opop)
|
|
{
|
|
if (opop->subitem_reference_name) {
|
|
MEM_freeN(opop->subitem_reference_name);
|
|
}
|
|
if (opop->subitem_local_name) {
|
|
MEM_freeN(opop->subitem_local_name);
|
|
}
|
|
}
|
|
|
|
void BKE_lib_override_library_property_operation_delete(
|
|
IDOverrideLibraryProperty *liboverride_property,
|
|
IDOverrideLibraryPropertyOperation *liboverride_property_operation)
|
|
{
|
|
lib_override_library_property_operation_clear(liboverride_property_operation);
|
|
BLI_freelinkN(&liboverride_property->operations, liboverride_property_operation);
|
|
}
|
|
|
|
bool BKE_lib_override_library_property_operation_operands_validate(
|
|
IDOverrideLibraryPropertyOperation *liboverride_property_operation,
|
|
PointerRNA *ptr_dst,
|
|
PointerRNA *ptr_src,
|
|
PointerRNA *ptr_storage,
|
|
PropertyRNA *prop_dst,
|
|
PropertyRNA *prop_src,
|
|
PropertyRNA *prop_storage)
|
|
{
|
|
switch (liboverride_property_operation->operation) {
|
|
case LIBOVERRIDE_OP_NOOP:
|
|
return true;
|
|
case LIBOVERRIDE_OP_ADD:
|
|
ATTR_FALLTHROUGH;
|
|
case LIBOVERRIDE_OP_SUBTRACT:
|
|
ATTR_FALLTHROUGH;
|
|
case LIBOVERRIDE_OP_MULTIPLY:
|
|
if (ptr_storage == nullptr || ptr_storage->data == nullptr || prop_storage == nullptr) {
|
|
BLI_assert_msg(0, "Missing data to apply differential override operation.");
|
|
return false;
|
|
}
|
|
ATTR_FALLTHROUGH;
|
|
case LIBOVERRIDE_OP_INSERT_AFTER:
|
|
ATTR_FALLTHROUGH;
|
|
case LIBOVERRIDE_OP_INSERT_BEFORE:
|
|
ATTR_FALLTHROUGH;
|
|
case LIBOVERRIDE_OP_REPLACE:
|
|
if ((ptr_dst == nullptr || ptr_dst->data == nullptr || prop_dst == nullptr) ||
|
|
(ptr_src == nullptr || ptr_src->data == nullptr || prop_src == nullptr))
|
|
{
|
|
BLI_assert_msg(0, "Missing data to apply override operation.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void BKE_lib_override_library_validate(Main * /*bmain*/, ID *id, ReportList *reports)
|
|
{
|
|
if (id->override_library == nullptr) {
|
|
return;
|
|
}
|
|
if (id->override_library->reference == nullptr) {
|
|
/* This is a template ID, could be linked or local, not an override. */
|
|
return;
|
|
}
|
|
if (id->override_library->reference == id) {
|
|
/* Very serious data corruption, cannot do much about it besides removing the reference
|
|
* (therefore making the id a local override template one only). */
|
|
BKE_reportf(reports,
|
|
RPT_ERROR,
|
|
"Data corruption: data-block '%s' is using itself as library override reference",
|
|
id->name);
|
|
id->override_library->reference = nullptr;
|
|
return;
|
|
}
|
|
if (!ID_IS_LINKED(id->override_library->reference)) {
|
|
/* Very serious data corruption, cannot do much about it besides removing the reference
|
|
* (therefore making the id a local override template one only). */
|
|
BKE_reportf(reports,
|
|
RPT_ERROR,
|
|
"Data corruption: data-block '%s' is using another local data-block ('%s') as "
|
|
"library override reference",
|
|
id->name,
|
|
id->override_library->reference->name);
|
|
id->override_library->reference = nullptr;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void BKE_lib_override_library_main_validate(Main *bmain, ReportList *reports)
|
|
{
|
|
ID *id;
|
|
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if (id->override_library != nullptr) {
|
|
BKE_lib_override_library_validate(bmain, id, reports);
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
}
|
|
|
|
bool BKE_lib_override_library_status_check_local(Main *bmain, ID *local)
|
|
{
|
|
BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(local));
|
|
|
|
ID *reference = local->override_library->reference;
|
|
|
|
if (reference == nullptr) {
|
|
/* This is an override template, local status is always OK! */
|
|
return true;
|
|
}
|
|
|
|
BLI_assert(GS(local->name) == GS(reference->name));
|
|
|
|
if (GS(local->name) == ID_OB) {
|
|
/* Our beloved pose's bone cross-data pointers. Usually, depsgraph evaluation would
|
|
* ensure this is valid, but in some situations (like hidden collections etc.) this won't
|
|
* be the case, so we need to take care of this ourselves. */
|
|
Object *ob_local = reinterpret_cast<Object *>(local);
|
|
if (ob_local->type == OB_ARMATURE) {
|
|
Object *ob_reference = reinterpret_cast<Object *>(local->override_library->reference);
|
|
BLI_assert(ob_local->data != nullptr);
|
|
BLI_assert(ob_reference->data != nullptr);
|
|
BKE_pose_ensure(bmain, ob_local, static_cast<bArmature *>(ob_local->data), true);
|
|
BKE_pose_ensure(bmain, ob_reference, static_cast<bArmature *>(ob_reference->data), true);
|
|
}
|
|
}
|
|
|
|
/* Note that reference is assumed always valid, caller has to ensure that itself. */
|
|
|
|
PointerRNA rnaptr_local = RNA_id_pointer_create(local);
|
|
PointerRNA rnaptr_reference = RNA_id_pointer_create(reference);
|
|
|
|
if (!RNA_struct_override_matches(
|
|
bmain,
|
|
&rnaptr_local,
|
|
&rnaptr_reference,
|
|
nullptr,
|
|
0,
|
|
local->override_library,
|
|
(eRNAOverrideMatch)(RNA_OVERRIDE_COMPARE_IGNORE_NON_OVERRIDABLE |
|
|
RNA_OVERRIDE_COMPARE_IGNORE_OVERRIDDEN),
|
|
nullptr))
|
|
{
|
|
local->tag &= ~LIB_TAG_LIBOVERRIDE_REFOK;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BKE_lib_override_library_status_check_reference(Main *bmain, ID *local)
|
|
{
|
|
BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(local));
|
|
|
|
ID *reference = local->override_library->reference;
|
|
|
|
if (reference == nullptr) {
|
|
/* This is an override template, reference is virtual, so its status is always OK! */
|
|
return true;
|
|
}
|
|
|
|
BLI_assert(GS(local->name) == GS(reference->name));
|
|
|
|
if (reference->override_library && (reference->tag & LIB_TAG_LIBOVERRIDE_REFOK) == 0) {
|
|
if (!BKE_lib_override_library_status_check_reference(bmain, reference)) {
|
|
/* If reference is also an override of another data-block, and its status is not OK,
|
|
* then this override is not OK either.
|
|
* Note that this should only happen when reloading libraries. */
|
|
local->tag &= ~LIB_TAG_LIBOVERRIDE_REFOK;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (GS(local->name) == ID_OB) {
|
|
/* Our beloved pose's bone cross-data pointers. Usually, depsgraph evaluation would
|
|
* ensure this is valid, but in some situations (like hidden collections etc.) this won't
|
|
* be the case, so we need to take care of this ourselves. */
|
|
Object *ob_local = reinterpret_cast<Object *>(local);
|
|
if (ob_local->type == OB_ARMATURE) {
|
|
Object *ob_reference = reinterpret_cast<Object *>(local->override_library->reference);
|
|
BLI_assert(ob_local->data != nullptr);
|
|
BLI_assert(ob_reference->data != nullptr);
|
|
BKE_pose_ensure(bmain, ob_local, static_cast<bArmature *>(ob_local->data), true);
|
|
BKE_pose_ensure(bmain, ob_reference, static_cast<bArmature *>(ob_reference->data), true);
|
|
}
|
|
}
|
|
|
|
PointerRNA rnaptr_local = RNA_id_pointer_create(local);
|
|
PointerRNA rnaptr_reference = RNA_id_pointer_create(reference);
|
|
|
|
if (!RNA_struct_override_matches(bmain,
|
|
&rnaptr_local,
|
|
&rnaptr_reference,
|
|
nullptr,
|
|
0,
|
|
local->override_library,
|
|
RNA_OVERRIDE_COMPARE_IGNORE_OVERRIDDEN,
|
|
nullptr))
|
|
{
|
|
local->tag &= ~LIB_TAG_LIBOVERRIDE_REFOK;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void lib_override_library_operations_create(Main *bmain,
|
|
ID *local,
|
|
const eRNAOverrideMatch liboverride_match_flags,
|
|
eRNAOverrideMatchResult *r_report_flags)
|
|
{
|
|
BLI_assert(!ID_IS_LINKED(local));
|
|
BLI_assert(local->override_library != nullptr);
|
|
const bool is_template = (local->override_library->reference == nullptr);
|
|
|
|
if (!is_template) {
|
|
/* Do not attempt to generate overriding rules from an empty place-holder generated by link
|
|
* code when it cannot find the actual library/ID. Much better to keep the local data-block as
|
|
* is in the file in that case, until broken lib is fixed. */
|
|
if (ID_MISSING(local->override_library->reference)) {
|
|
return;
|
|
}
|
|
|
|
if (GS(local->name) == ID_OB) {
|
|
/* Our beloved pose's bone cross-data pointers. Usually, depsgraph evaluation would
|
|
* ensure this is valid, but in some situations (like hidden collections etc.) this won't
|
|
* be the case, so we need to take care of this ourselves. */
|
|
Object *ob_local = reinterpret_cast<Object *>(local);
|
|
if (ob_local->type == OB_ARMATURE) {
|
|
Object *ob_reference = reinterpret_cast<Object *>(local->override_library->reference);
|
|
BLI_assert(ob_local->data != nullptr);
|
|
BLI_assert(ob_reference->data != nullptr);
|
|
BKE_pose_ensure(bmain, ob_local, static_cast<bArmature *>(ob_local->data), true);
|
|
BKE_pose_ensure(bmain, ob_reference, static_cast<bArmature *>(ob_reference->data), true);
|
|
}
|
|
}
|
|
|
|
PointerRNA rnaptr_local = RNA_id_pointer_create(local);
|
|
PointerRNA rnaptr_reference = RNA_id_pointer_create(local->override_library->reference);
|
|
|
|
eRNAOverrideMatchResult local_report_flags = RNA_OVERRIDE_MATCH_RESULT_INIT;
|
|
RNA_struct_override_matches(bmain,
|
|
&rnaptr_local,
|
|
&rnaptr_reference,
|
|
nullptr,
|
|
0,
|
|
local->override_library,
|
|
liboverride_match_flags,
|
|
&local_report_flags);
|
|
|
|
if (local_report_flags & RNA_OVERRIDE_MATCH_RESULT_RESTORED) {
|
|
CLOG_INFO(&LOG, 2, "We did restore some properties of %s from its reference", local->name);
|
|
}
|
|
if (local_report_flags & RNA_OVERRIDE_MATCH_RESULT_RESTORE_TAGGED) {
|
|
CLOG_INFO(&LOG,
|
|
2,
|
|
"We did tag some properties of %s for restoration from its reference",
|
|
local->name);
|
|
}
|
|
if (local_report_flags & RNA_OVERRIDE_MATCH_RESULT_CREATED) {
|
|
CLOG_INFO(&LOG, 2, "We did generate library override rules for %s", local->name);
|
|
}
|
|
else {
|
|
CLOG_INFO(&LOG, 2, "No new library override rules for %s", local->name);
|
|
}
|
|
|
|
if (r_report_flags != nullptr) {
|
|
*r_report_flags = static_cast<eRNAOverrideMatchResult>(*r_report_flags | local_report_flags);
|
|
}
|
|
}
|
|
}
|
|
void BKE_lib_override_library_operations_create(Main *bmain, ID *local, int *r_report_flags)
|
|
{
|
|
lib_override_library_operations_create(
|
|
bmain,
|
|
local,
|
|
static_cast<eRNAOverrideMatch>(RNA_OVERRIDE_COMPARE_CREATE | RNA_OVERRIDE_COMPARE_RESTORE),
|
|
reinterpret_cast<eRNAOverrideMatchResult *>(r_report_flags));
|
|
}
|
|
|
|
void BKE_lib_override_library_operations_restore(Main *bmain, ID *local, int *r_report_flags)
|
|
{
|
|
if (!ID_IS_OVERRIDE_LIBRARY_REAL(local) ||
|
|
(local->override_library->runtime->tag & LIBOVERRIDE_TAG_NEEDS_RESTORE) == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
PointerRNA rnaptr_src = RNA_id_pointer_create(local);
|
|
PointerRNA rnaptr_dst = RNA_id_pointer_create(local->override_library->reference);
|
|
RNA_struct_override_apply(
|
|
bmain,
|
|
&rnaptr_dst,
|
|
&rnaptr_src,
|
|
nullptr,
|
|
local->override_library,
|
|
static_cast<eRNAOverrideApplyFlag>(RNA_OVERRIDE_APPLY_FLAG_SKIP_RESYNC_CHECK |
|
|
RNA_OVERRIDE_APPLY_FLAG_RESTORE_ONLY));
|
|
|
|
LISTBASE_FOREACH_MUTABLE (IDOverrideLibraryProperty *, op, &local->override_library->properties)
|
|
{
|
|
if (op->tag & LIBOVERRIDE_PROP_TAG_NEEDS_RETORE) {
|
|
LISTBASE_FOREACH_MUTABLE (IDOverrideLibraryPropertyOperation *, opop, &op->operations) {
|
|
if (opop->tag & LIBOVERRIDE_PROP_TAG_NEEDS_RETORE) {
|
|
BKE_lib_override_library_property_operation_delete(op, opop);
|
|
}
|
|
}
|
|
if (BLI_listbase_is_empty(&local->override_library->properties)) {
|
|
BKE_lib_override_library_property_delete(local->override_library, op);
|
|
}
|
|
else {
|
|
BKE_lib_override_library_operations_tag(op, LIBOVERRIDE_PROP_TAG_NEEDS_RETORE, false);
|
|
}
|
|
}
|
|
}
|
|
local->override_library->runtime->tag &= ~LIBOVERRIDE_TAG_NEEDS_RESTORE;
|
|
|
|
if (r_report_flags != nullptr) {
|
|
*r_report_flags |= RNA_OVERRIDE_MATCH_RESULT_RESTORED;
|
|
}
|
|
}
|
|
|
|
struct LibOverrideOpCreateData {
|
|
Main *bmain;
|
|
eRNAOverrideMatchResult report_flags;
|
|
};
|
|
|
|
static void lib_override_library_operations_create_cb(TaskPool *__restrict pool, void *taskdata)
|
|
{
|
|
LibOverrideOpCreateData *create_data = static_cast<LibOverrideOpCreateData *>(
|
|
BLI_task_pool_user_data(pool));
|
|
ID *id = static_cast<ID *>(taskdata);
|
|
|
|
eRNAOverrideMatchResult report_flags = RNA_OVERRIDE_MATCH_RESULT_INIT;
|
|
lib_override_library_operations_create(
|
|
create_data->bmain,
|
|
id,
|
|
static_cast<eRNAOverrideMatch>(RNA_OVERRIDE_COMPARE_CREATE |
|
|
RNA_OVERRIDE_COMPARE_TAG_FOR_RESTORE),
|
|
&report_flags);
|
|
atomic_fetch_and_or_uint32(reinterpret_cast<uint32_t *>(&create_data->report_flags),
|
|
report_flags);
|
|
}
|
|
|
|
void BKE_lib_override_library_main_operations_create(Main *bmain,
|
|
const bool force_auto,
|
|
int *r_report_flags)
|
|
{
|
|
ID *id;
|
|
|
|
#ifdef DEBUG_OVERRIDE_TIMEIT
|
|
TIMEIT_START_AVERAGED(BKE_lib_override_library_main_operations_create);
|
|
#endif
|
|
|
|
/* When force-auto is set, we also remove all unused existing override properties & operations.
|
|
*/
|
|
if (force_auto) {
|
|
BKE_lib_override_library_main_tag(bmain, LIBOVERRIDE_PROP_OP_TAG_UNUSED, true);
|
|
}
|
|
|
|
/* Usual pose bones issue, need to be done outside of the threaded process or we may run into
|
|
* concurrency issues here.
|
|
* Note that calling #BKE_pose_ensure again in thread in
|
|
* #BKE_lib_override_library_operations_create is not a problem then. */
|
|
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
|
|
if (ob->type == OB_ARMATURE) {
|
|
BLI_assert(ob->data != nullptr);
|
|
BKE_pose_ensure(bmain, ob, static_cast<bArmature *>(ob->data), true);
|
|
}
|
|
}
|
|
|
|
LibOverrideOpCreateData create_pool_data{};
|
|
create_pool_data.bmain = bmain;
|
|
create_pool_data.report_flags = RNA_OVERRIDE_MATCH_RESULT_INIT;
|
|
TaskPool *task_pool = BLI_task_pool_create(&create_pool_data, TASK_PRIORITY_HIGH);
|
|
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if (ID_IS_LINKED(id) || !ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
|
|
continue;
|
|
}
|
|
/* Propagate potential embedded data tagg to the owner ID (see also
|
|
* #BKE_lib_override_id_tag_on_deg_tag_from_user). */
|
|
if (Key *key = BKE_key_from_id(id)) {
|
|
if (key->id.tag & LIB_TAG_LIBOVERRIDE_AUTOREFRESH) {
|
|
key->id.tag &= ~LIB_TAG_LIBOVERRIDE_AUTOREFRESH;
|
|
id->tag |= LIB_TAG_LIBOVERRIDE_AUTOREFRESH;
|
|
}
|
|
}
|
|
if (bNodeTree *ntree = ntreeFromID(id)) {
|
|
if (ntree->id.tag & LIB_TAG_LIBOVERRIDE_AUTOREFRESH) {
|
|
ntree->id.tag &= ~LIB_TAG_LIBOVERRIDE_AUTOREFRESH;
|
|
id->tag |= LIB_TAG_LIBOVERRIDE_AUTOREFRESH;
|
|
}
|
|
}
|
|
if (GS(id->name) == ID_SCE) {
|
|
if (Collection *scene_collection = reinterpret_cast<Scene *>(id)->master_collection) {
|
|
if (scene_collection->id.tag & LIB_TAG_LIBOVERRIDE_AUTOREFRESH) {
|
|
scene_collection->id.tag &= ~LIB_TAG_LIBOVERRIDE_AUTOREFRESH;
|
|
id->tag |= LIB_TAG_LIBOVERRIDE_AUTOREFRESH;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (force_auto || (id->tag & LIB_TAG_LIBOVERRIDE_AUTOREFRESH)) {
|
|
/* Usual issue with pose, it's quiet rare but sometimes they may not be up to date when this
|
|
* function is called. */
|
|
if (GS(id->name) == ID_OB) {
|
|
Object *ob = reinterpret_cast<Object *>(id);
|
|
if (ob->type == OB_ARMATURE) {
|
|
BLI_assert(ob->data != nullptr);
|
|
BKE_pose_ensure(bmain, ob, static_cast<bArmature *>(ob->data), true);
|
|
}
|
|
}
|
|
/* Only check overrides if we do have the real reference data available, and not some empty
|
|
* 'placeholder' for missing data (broken links). */
|
|
if ((id->override_library->reference->tag & LIB_TAG_MISSING) == 0) {
|
|
BLI_task_pool_push(
|
|
task_pool, lib_override_library_operations_create_cb, id, false, nullptr);
|
|
}
|
|
else {
|
|
BKE_lib_override_library_properties_tag(
|
|
id->override_library, LIBOVERRIDE_PROP_OP_TAG_UNUSED, false);
|
|
}
|
|
}
|
|
else {
|
|
/* Clear 'unused' tag for un-processed IDs, otherwise e.g. linked overrides will loose their
|
|
* list of overridden properties. */
|
|
BKE_lib_override_library_properties_tag(
|
|
id->override_library, LIBOVERRIDE_PROP_OP_TAG_UNUSED, false);
|
|
}
|
|
id->tag &= ~LIB_TAG_LIBOVERRIDE_AUTOREFRESH;
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
BLI_task_pool_work_and_wait(task_pool);
|
|
|
|
BLI_task_pool_free(task_pool);
|
|
|
|
if (create_pool_data.report_flags & RNA_OVERRIDE_MATCH_RESULT_RESTORE_TAGGED) {
|
|
BKE_lib_override_library_main_operations_restore(
|
|
bmain, reinterpret_cast<int *>(&create_pool_data.report_flags));
|
|
create_pool_data.report_flags = static_cast<eRNAOverrideMatchResult>(
|
|
(create_pool_data.report_flags & ~RNA_OVERRIDE_MATCH_RESULT_RESTORE_TAGGED));
|
|
}
|
|
|
|
if (r_report_flags != nullptr) {
|
|
*r_report_flags |= create_pool_data.report_flags;
|
|
}
|
|
|
|
if (force_auto) {
|
|
BKE_lib_override_library_main_unused_cleanup(bmain);
|
|
}
|
|
|
|
#ifdef DEBUG_OVERRIDE_TIMEIT
|
|
TIMEIT_END_AVERAGED(BKE_lib_override_library_main_operations_create);
|
|
#endif
|
|
}
|
|
|
|
void BKE_lib_override_library_main_operations_restore(Main *bmain, int *r_report_flags)
|
|
{
|
|
ID *id;
|
|
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if (!(!ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY_REAL(id) && id->override_library->runtime &&
|
|
(id->override_library->runtime->tag & LIBOVERRIDE_TAG_NEEDS_RESTORE) != 0))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* Only restore overrides if we do have the real reference data available, and not some empty
|
|
* 'placeholder' for missing data (broken links). */
|
|
if (id->override_library->reference->tag & LIB_TAG_MISSING) {
|
|
continue;
|
|
}
|
|
|
|
BKE_lib_override_library_operations_restore(bmain, id, r_report_flags);
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
}
|
|
|
|
static bool lib_override_library_id_reset_do(Main *bmain,
|
|
ID *id_root,
|
|
const bool do_reset_system_override)
|
|
{
|
|
bool was_op_deleted = false;
|
|
|
|
if (do_reset_system_override) {
|
|
id_root->override_library->flag |= LIBOVERRIDE_FLAG_SYSTEM_DEFINED;
|
|
}
|
|
|
|
LISTBASE_FOREACH_MUTABLE (
|
|
IDOverrideLibraryProperty *, op, &id_root->override_library->properties) {
|
|
bool do_op_delete = true;
|
|
const bool is_collection = op->rna_prop_type == PROP_COLLECTION;
|
|
if (is_collection || op->rna_prop_type == PROP_POINTER) {
|
|
PointerRNA ptr, ptr_lib;
|
|
PropertyRNA *prop, *prop_lib;
|
|
|
|
PointerRNA ptr_root = RNA_pointer_create(id_root, &RNA_ID, id_root);
|
|
PointerRNA ptr_root_lib = RNA_pointer_create(
|
|
id_root->override_library->reference, &RNA_ID, id_root->override_library->reference);
|
|
|
|
bool prop_exists = RNA_path_resolve_property(&ptr_root, op->rna_path, &ptr, &prop);
|
|
if (prop_exists) {
|
|
prop_exists = RNA_path_resolve_property(&ptr_root_lib, op->rna_path, &ptr_lib, &prop_lib);
|
|
|
|
if (prop_exists) {
|
|
BLI_assert(ELEM(RNA_property_type(prop), PROP_POINTER, PROP_COLLECTION));
|
|
BLI_assert(RNA_property_type(prop) == RNA_property_type(prop_lib));
|
|
if (is_collection) {
|
|
ptr.type = RNA_property_pointer_type(&ptr, prop);
|
|
ptr_lib.type = RNA_property_pointer_type(&ptr_lib, prop_lib);
|
|
}
|
|
else {
|
|
ptr = RNA_property_pointer_get(&ptr, prop);
|
|
ptr_lib = RNA_property_pointer_get(&ptr_lib, prop_lib);
|
|
}
|
|
if (ptr.owner_id != nullptr && ptr_lib.owner_id != nullptr) {
|
|
BLI_assert(ptr.type == ptr_lib.type);
|
|
do_op_delete = !(RNA_struct_is_ID(ptr.type) &&
|
|
ptr.owner_id->override_library != nullptr &&
|
|
ptr.owner_id->override_library->reference == ptr_lib.owner_id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (do_op_delete) {
|
|
BKE_lib_override_library_property_delete(id_root->override_library, op);
|
|
was_op_deleted = true;
|
|
}
|
|
}
|
|
|
|
if (was_op_deleted) {
|
|
DEG_id_tag_update_ex(bmain, id_root, ID_RECALC_COPY_ON_WRITE);
|
|
IDOverrideLibraryRuntime *liboverride_runtime = override_library_runtime_ensure(
|
|
id_root->override_library);
|
|
liboverride_runtime->tag |= LIBOVERRIDE_TAG_NEEDS_RELOAD;
|
|
}
|
|
|
|
return was_op_deleted;
|
|
}
|
|
|
|
void BKE_lib_override_library_id_reset(Main *bmain,
|
|
ID *id_root,
|
|
const bool do_reset_system_override)
|
|
{
|
|
if (!ID_IS_OVERRIDE_LIBRARY_REAL(id_root)) {
|
|
return;
|
|
}
|
|
|
|
if (lib_override_library_id_reset_do(bmain, id_root, do_reset_system_override)) {
|
|
if (id_root->override_library->runtime != nullptr &&
|
|
(id_root->override_library->runtime->tag & LIBOVERRIDE_TAG_NEEDS_RELOAD) != 0)
|
|
{
|
|
BKE_lib_override_library_update(bmain, id_root);
|
|
id_root->override_library->runtime->tag &= ~LIBOVERRIDE_TAG_NEEDS_RELOAD;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void lib_override_library_id_hierarchy_recursive_reset(Main *bmain,
|
|
ID *id_root,
|
|
const bool do_reset_system_override)
|
|
{
|
|
if (!ID_IS_OVERRIDE_LIBRARY_REAL(id_root)) {
|
|
return;
|
|
}
|
|
|
|
void **entry_vp = BLI_ghash_lookup_p(bmain->relations->relations_from_pointers, id_root);
|
|
if (entry_vp == nullptr) {
|
|
/* This ID is not used by nor using any other ID. */
|
|
lib_override_library_id_reset_do(bmain, id_root, do_reset_system_override);
|
|
return;
|
|
}
|
|
|
|
MainIDRelationsEntry *entry = static_cast<MainIDRelationsEntry *>(*entry_vp);
|
|
if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) {
|
|
/* This ID has already been processed. */
|
|
return;
|
|
}
|
|
|
|
lib_override_library_id_reset_do(bmain, id_root, do_reset_system_override);
|
|
|
|
/* This way we won't process again that ID, should we encounter it again through another
|
|
* relationship hierarchy. */
|
|
entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED;
|
|
|
|
for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != nullptr;
|
|
to_id_entry = to_id_entry->next)
|
|
{
|
|
if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) {
|
|
/* Never consider non-overridable relationships ('from', 'parents', 'owner' etc. pointers) as
|
|
* actual dependencies. */
|
|
continue;
|
|
}
|
|
/* We only consider IDs from the same library. */
|
|
if (*to_id_entry->id_pointer.to != nullptr) {
|
|
ID *to_id = *to_id_entry->id_pointer.to;
|
|
if (to_id->override_library != nullptr) {
|
|
lib_override_library_id_hierarchy_recursive_reset(bmain, to_id, do_reset_system_override);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_lib_override_library_id_hierarchy_reset(Main *bmain,
|
|
ID *id_root,
|
|
const bool do_reset_system_override)
|
|
{
|
|
BKE_main_relations_create(bmain, 0);
|
|
|
|
lib_override_library_id_hierarchy_recursive_reset(bmain, id_root, do_reset_system_override);
|
|
|
|
BKE_main_relations_free(bmain);
|
|
|
|
ID *id;
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if (!ID_IS_OVERRIDE_LIBRARY_REAL(id) || id->override_library->runtime == nullptr ||
|
|
(id->override_library->runtime->tag & LIBOVERRIDE_TAG_NEEDS_RELOAD) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
BKE_lib_override_library_update(bmain, id);
|
|
id->override_library->runtime->tag &= ~LIBOVERRIDE_TAG_NEEDS_RELOAD;
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
}
|
|
|
|
void BKE_lib_override_library_operations_tag(IDOverrideLibraryProperty *liboverride_property,
|
|
const short tag,
|
|
const bool do_set)
|
|
{
|
|
if (liboverride_property != nullptr) {
|
|
if (do_set) {
|
|
liboverride_property->tag |= tag;
|
|
}
|
|
else {
|
|
liboverride_property->tag &= ~tag;
|
|
}
|
|
|
|
LISTBASE_FOREACH (
|
|
IDOverrideLibraryPropertyOperation *, opop, &liboverride_property->operations) {
|
|
if (do_set) {
|
|
opop->tag |= tag;
|
|
}
|
|
else {
|
|
opop->tag &= ~tag;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_lib_override_library_properties_tag(IDOverrideLibrary *liboverride,
|
|
const short tag,
|
|
const bool do_set)
|
|
{
|
|
if (liboverride != nullptr) {
|
|
LISTBASE_FOREACH (IDOverrideLibraryProperty *, op, &liboverride->properties) {
|
|
BKE_lib_override_library_operations_tag(op, tag, do_set);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_lib_override_library_main_tag(Main *bmain, const short tag, const bool do_set)
|
|
{
|
|
ID *id;
|
|
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if (ID_IS_OVERRIDE_LIBRARY(id)) {
|
|
BKE_lib_override_library_properties_tag(id->override_library, tag, do_set);
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
}
|
|
|
|
void BKE_lib_override_library_id_unused_cleanup(ID *local)
|
|
{
|
|
if (ID_IS_OVERRIDE_LIBRARY_REAL(local)) {
|
|
LISTBASE_FOREACH_MUTABLE (
|
|
IDOverrideLibraryProperty *, op, &local->override_library->properties) {
|
|
if (op->tag & LIBOVERRIDE_PROP_OP_TAG_UNUSED) {
|
|
BKE_lib_override_library_property_delete(local->override_library, op);
|
|
}
|
|
else {
|
|
LISTBASE_FOREACH_MUTABLE (IDOverrideLibraryPropertyOperation *, opop, &op->operations) {
|
|
if (opop->tag & LIBOVERRIDE_PROP_OP_TAG_UNUSED) {
|
|
BKE_lib_override_library_property_operation_delete(op, opop);
|
|
}
|
|
}
|
|
if (BLI_listbase_is_empty(&op->operations)) {
|
|
BKE_lib_override_library_property_delete(local->override_library, op);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_lib_override_library_main_unused_cleanup(Main *bmain)
|
|
{
|
|
ID *id;
|
|
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if (ID_IS_OVERRIDE_LIBRARY(id)) {
|
|
BKE_lib_override_library_id_unused_cleanup(id);
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
}
|
|
|
|
static void lib_override_id_swap(Main *bmain, ID *id_local, ID *id_temp)
|
|
{
|
|
/* Ensure ViewLayers are in sync in case a Scene is being swapped, and prevent any further resync
|
|
* during the swapping itself. */
|
|
if (GS(id_local->name) == ID_SCE) {
|
|
BKE_scene_view_layers_synced_ensure(reinterpret_cast<Scene *>(id_local));
|
|
BKE_scene_view_layers_synced_ensure(reinterpret_cast<Scene *>(id_temp));
|
|
}
|
|
BKE_layer_collection_resync_forbid();
|
|
|
|
BKE_lib_id_swap(bmain, id_local, id_temp, true, 0);
|
|
/* We need to keep these tags from temp ID into orig one.
|
|
* ID swap does not swap most of ID data itself. */
|
|
id_local->tag |= (id_temp->tag & LIB_TAG_LIBOVERRIDE_NEED_RESYNC);
|
|
|
|
BKE_layer_collection_resync_allow();
|
|
}
|
|
|
|
void BKE_lib_override_library_update(Main *bmain, ID *local)
|
|
{
|
|
if (!ID_IS_OVERRIDE_LIBRARY_REAL(local)) {
|
|
return;
|
|
}
|
|
|
|
/* Do not attempt to apply overriding rules over an empty place-holder generated by link code
|
|
* when it cannot find the actual library/ID. Much better to keep the local data-block as loaded
|
|
* from the file in that case, until broken lib is fixed. */
|
|
if (ID_MISSING(local->override_library->reference)) {
|
|
return;
|
|
}
|
|
|
|
/* Recursively do 'ancestor' overrides first, if any. */
|
|
if (local->override_library->reference->override_library &&
|
|
(local->override_library->reference->tag & LIB_TAG_LIBOVERRIDE_REFOK) == 0)
|
|
{
|
|
BKE_lib_override_library_update(bmain, local->override_library->reference);
|
|
}
|
|
|
|
/* We want to avoid having to remap here, however creating up-to-date override is much simpler
|
|
* if based on reference than on current override.
|
|
* So we work on temp copy of reference, and 'swap' its content with local. */
|
|
|
|
/* XXX We need a way to get off-Main copies of IDs (similar to localized mats/texts/ etc.)!
|
|
* However, this is whole bunch of code work in itself, so for now plain stupid ID copy
|
|
* will do, as inefficient as it is. :/
|
|
* Actually, maybe not! Since we are swapping with original ID's local content, we want to
|
|
* keep user-count in correct state when freeing tmp_id
|
|
* (and that user-counts of IDs used by 'new' local data also remain correct). */
|
|
/* This would imply change in handling of user-count all over RNA
|
|
* (and possibly all over Blender code).
|
|
* Not impossible to do, but would rather see first if extra useless usual user handling
|
|
* is actually a (performances) issue here. */
|
|
|
|
ID *tmp_id = BKE_id_copy_ex(bmain,
|
|
local->override_library->reference,
|
|
nullptr,
|
|
LIB_ID_COPY_DEFAULT | LIB_ID_COPY_NO_LIB_OVERRIDE_LOCAL_DATA_FLAG);
|
|
|
|
if (tmp_id == nullptr) {
|
|
return;
|
|
}
|
|
|
|
/* Remove the pair (idname, lib) of this temp id from the name map. */
|
|
BKE_main_namemap_remove_name(bmain, tmp_id, tmp_id->name + 2);
|
|
|
|
tmp_id->lib = local->lib;
|
|
|
|
/* This ID name is problematic, since it is an 'rna name property' it should not be editable or
|
|
* different from reference linked ID. But local ID names need to be unique in a given type
|
|
* list of Main, so we cannot always keep it identical, which is why we need this special
|
|
* manual handling here. */
|
|
STRNCPY(tmp_id->name, local->name);
|
|
|
|
/* Those ugly loop-back pointers again. Luckily we only need to deal with the shape keys here,
|
|
* collections' parents are fully runtime and reconstructed later. */
|
|
Key *local_key = BKE_key_from_id(local);
|
|
Key *tmp_key = BKE_key_from_id(tmp_id);
|
|
if (local_key != nullptr && tmp_key != nullptr) {
|
|
tmp_key->id.flag |= (local_key->id.flag & LIB_EMBEDDED_DATA_LIB_OVERRIDE);
|
|
BKE_main_namemap_remove_name(bmain, &tmp_key->id, tmp_key->id.name + 2);
|
|
tmp_key->id.lib = local_key->id.lib;
|
|
STRNCPY(tmp_key->id.name, local_key->id.name);
|
|
}
|
|
|
|
PointerRNA rnaptr_src = RNA_id_pointer_create(local);
|
|
PointerRNA rnaptr_dst = RNA_id_pointer_create(tmp_id);
|
|
PointerRNA rnaptr_storage_stack, *rnaptr_storage = nullptr;
|
|
if (local->override_library->storage) {
|
|
rnaptr_storage_stack = RNA_id_pointer_create(local->override_library->storage);
|
|
rnaptr_storage = &rnaptr_storage_stack;
|
|
}
|
|
|
|
RNA_struct_override_apply(bmain,
|
|
&rnaptr_dst,
|
|
&rnaptr_src,
|
|
rnaptr_storage,
|
|
local->override_library,
|
|
RNA_OVERRIDE_APPLY_FLAG_NOP);
|
|
|
|
lib_override_object_posemode_transfer(tmp_id, local);
|
|
|
|
/* This also transfers all pointers (memory) owned by local to tmp_id, and vice-versa.
|
|
* So when we'll free tmp_id, we'll actually free old, outdated data from local. */
|
|
lib_override_id_swap(bmain, local, tmp_id);
|
|
|
|
if (local_key != nullptr && tmp_key != nullptr) {
|
|
/* This is some kind of hard-coded 'always enforced override'. */
|
|
lib_override_id_swap(bmain, &local_key->id, &tmp_key->id);
|
|
tmp_key->id.flag |= (local_key->id.flag & LIB_EMBEDDED_DATA_LIB_OVERRIDE);
|
|
/* The swap of local and tmp_id inverted those pointers, we need to redefine proper
|
|
* relationships. */
|
|
*BKE_key_from_id_p(local) = local_key;
|
|
*BKE_key_from_id_p(tmp_id) = tmp_key;
|
|
local_key->from = local;
|
|
tmp_key->from = tmp_id;
|
|
}
|
|
|
|
/* Again, horribly inefficient in our case, we need something off-Main
|
|
* (aka more generic nolib copy/free stuff).
|
|
* NOTE: Do not remove this tmp_id's name from the namemap here, since this name actually still
|
|
* exists in `bmain`. */
|
|
BKE_id_free_ex(bmain, tmp_id, LIB_ID_FREE_NO_UI_USER | LIB_ID_FREE_NO_NAMEMAP_REMOVE, true);
|
|
|
|
if (GS(local->name) == ID_AR) {
|
|
/* Fun times again, thanks to bone pointers in pose data of objects. We keep same ID addresses,
|
|
* but internal data has changed for sure, so we need to invalidate pose-bones caches. */
|
|
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
|
|
if (ob->pose != nullptr && ob->data == local) {
|
|
BLI_assert(ob->type == OB_ARMATURE);
|
|
ob->pose->flag |= POSE_RECALC;
|
|
/* We need to clear pose bone pointers immediately, some code may access those before pose
|
|
* is actually recomputed, which can lead to segfault. */
|
|
BKE_pose_clear_pointers(ob->pose);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (local->override_library->storage) {
|
|
/* We know this data-block is not used anywhere besides local->override->storage. */
|
|
/* XXX For until we get fully shadow copies, we still need to ensure storage releases
|
|
* its usage of any ID pointers it may have. */
|
|
BKE_id_free_ex(bmain, local->override_library->storage, LIB_ID_FREE_NO_UI_USER, true);
|
|
local->override_library->storage = nullptr;
|
|
}
|
|
|
|
local->tag |= LIB_TAG_LIBOVERRIDE_REFOK;
|
|
|
|
/* NOTE: Since we reload full content from linked ID here, potentially from edited local
|
|
* override, we do not really have a way to know *what* is changed, so we need to rely on the
|
|
* massive destruction weapon of `ID_RECALC_ALL` here. */
|
|
DEG_id_tag_update_ex(bmain, local, ID_RECALC_ALL);
|
|
/* For same reason as above, also assume that the relationships between IDs changed. */
|
|
DEG_relations_tag_update(bmain);
|
|
}
|
|
|
|
void BKE_lib_override_library_main_update(Main *bmain)
|
|
{
|
|
ID *id;
|
|
|
|
/* This temporary swap of G_MAIN is rather ugly,
|
|
* but necessary to avoid asserts checks in some RNA assignment functions,
|
|
* since those always use G_MAIN when they need access to a Main database. */
|
|
Main *orig_gmain = BKE_blender_globals_main_swap(bmain);
|
|
|
|
BLI_assert(BKE_main_namemap_validate(bmain));
|
|
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if (id->override_library != nullptr) {
|
|
BKE_lib_override_library_update(bmain, id);
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
BLI_assert(BKE_main_namemap_validate(bmain));
|
|
|
|
Main *tmp_gmain = BKE_blender_globals_main_swap(orig_gmain);
|
|
BLI_assert(tmp_gmain == bmain);
|
|
UNUSED_VARS_NDEBUG(tmp_gmain);
|
|
}
|
|
|
|
bool BKE_lib_override_library_id_is_user_deletable(Main *bmain, ID *id)
|
|
{
|
|
/* The only strong known case currently are objects used by override collections. */
|
|
/* TODO: There are most likely other cases... This may need to be addressed in a better way at
|
|
* some point. */
|
|
if (GS(id->name) != ID_OB) {
|
|
return true;
|
|
}
|
|
Object *ob = reinterpret_cast<Object *>(id);
|
|
LISTBASE_FOREACH (Collection *, collection, &bmain->collections) {
|
|
if (!ID_IS_OVERRIDE_LIBRARY(collection)) {
|
|
continue;
|
|
}
|
|
if (BKE_collection_has_object(collection, ob)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void BKE_lib_override_debug_print(IDOverrideLibrary *liboverride, const char *intro_txt)
|
|
{
|
|
const char *line_prefix = "";
|
|
if (intro_txt != nullptr) {
|
|
std::cout << intro_txt << "\n";
|
|
line_prefix = "\t";
|
|
}
|
|
|
|
LISTBASE_FOREACH (IDOverrideLibraryProperty *, op, &liboverride->properties) {
|
|
std::cout << line_prefix << op->rna_path << " [";
|
|
if (op->tag & LIBOVERRIDE_PROP_OP_TAG_UNUSED) {
|
|
std::cout << " UNUSED ";
|
|
}
|
|
std::cout << "]\n";
|
|
|
|
LISTBASE_FOREACH (IDOverrideLibraryPropertyOperation *, opop, &op->operations) {
|
|
std::cout << line_prefix << line_prefix << opop->operation << " [";
|
|
if (opop->tag & LIBOVERRIDE_PROP_OP_TAG_UNUSED) {
|
|
std::cout << " UNUSED ";
|
|
}
|
|
if (opop->flag & LIBOVERRIDE_OP_FLAG_IDPOINTER_MATCH_REFERENCE) {
|
|
std::cout << " MATCH_REF ";
|
|
}
|
|
std::cout << "] ";
|
|
if (opop->subitem_reference_name || opop->subitem_local_name) {
|
|
std::cout << "(" << opop->subitem_reference_name << " <" << opop->subitem_reference_id
|
|
<< "> -> " << opop->subitem_local_name << " <" << opop->subitem_local_id << ">)";
|
|
}
|
|
else if (opop->subitem_reference_index >= 0 || opop->subitem_local_index >= 0) {
|
|
std::cout << "(" << opop->subitem_reference_index << " -> " << opop->subitem_local_index
|
|
<< ")";
|
|
}
|
|
std::cout << "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Storage (how to store overriding data into `.blend` files).
|
|
*
|
|
* Basically:
|
|
* 1) Only 'differential' overrides needs special handling here. All others (replacing values or
|
|
* inserting/removing items from a collection) can be handled with simply storing current
|
|
* content of local data-block.
|
|
* 2) We store the differential value into a second 'ghost' data-block, which is an empty ID of
|
|
* same type as the local one, where we only define values that need differential data.
|
|
*
|
|
* This avoids us having to modify 'real' data-block at write time (and restoring it afterwards),
|
|
* which is inefficient, and potentially dangerous (in case of concurrent access...), while not
|
|
* using much extra memory in typical cases. It also ensures stored data-block always contains
|
|
* exact same data as "desired" ones (kind of "baked" data-blocks).
|
|
*/
|
|
|
|
OverrideLibraryStorage *BKE_lib_override_library_operations_store_init()
|
|
{
|
|
return BKE_main_new();
|
|
}
|
|
|
|
ID *BKE_lib_override_library_operations_store_start(Main *bmain,
|
|
OverrideLibraryStorage *liboverride_storage,
|
|
ID *local)
|
|
{
|
|
if (ID_IS_OVERRIDE_LIBRARY_TEMPLATE(local) || ID_IS_OVERRIDE_LIBRARY_VIRTUAL(local)) {
|
|
/* This is actually purely local data with an override template, or one of those embedded IDs
|
|
* (root node trees, master collections or shape-keys) that cannot have their own override.
|
|
* Nothing to do here! */
|
|
return nullptr;
|
|
}
|
|
|
|
BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(local));
|
|
BLI_assert(liboverride_storage != nullptr);
|
|
UNUSED_VARS_NDEBUG(liboverride_storage);
|
|
|
|
/* Forcefully ensure we know about all needed override operations. */
|
|
BKE_lib_override_library_operations_create(bmain, local, nullptr);
|
|
|
|
ID *storage_id;
|
|
#ifdef DEBUG_OVERRIDE_TIMEIT
|
|
TIMEIT_START_AVERAGED(BKE_lib_override_library_operations_store_start);
|
|
#endif
|
|
|
|
/* This is fully disabled for now, as it generated very hard to solve issues with Collections and
|
|
* how they reference each-other in their parents/children relations.
|
|
* Core of the issue is creating and storing those copies in a separate Main, while collection
|
|
* copy code re-assign blindly parents/children, even if they do not belong to the same Main.
|
|
* One solution could be to implement special flag as discussed below, and prevent any
|
|
* other-ID-reference creation/update in that case (since no differential operation is expected
|
|
* to involve those anyway). */
|
|
#if 0
|
|
/* XXX TODO: We may also want a specialized handling of things here too, to avoid copying heavy
|
|
* never-overridable data (like Mesh geometry etc.)? And also maybe avoid lib
|
|
* reference-counting completely (shallow copy). */
|
|
/* This would imply change in handling of user-count all over RNA
|
|
* (and possibly all over Blender code).
|
|
* Not impossible to do, but would rather see first is extra useless usual user handling is
|
|
* actually a (performances) issue here, before doing it. */
|
|
storage_id = BKE_id_copy(reinterpret_cast<Main *>(liboverride_storage), local);
|
|
|
|
if (storage_id != nullptr) {
|
|
PointerRNA rnaptr_reference = RNA_id_pointer_create(local->override_library->reference);
|
|
PointerRNA rnaptr_final = RNA_id_pointer_create(local);
|
|
PointerRNA rnaptr_storage = RNA_id_pointer_create(storage_id);
|
|
|
|
if (!RNA_struct_override_store(
|
|
bmain, &rnaptr_final, &rnaptr_reference, &rnaptr_storage, local->override_library))
|
|
{
|
|
BKE_id_free_ex(override_storage, storage_id, LIB_ID_FREE_NO_UI_USER, true);
|
|
storage_id = nullptr;
|
|
}
|
|
}
|
|
#else
|
|
storage_id = nullptr;
|
|
#endif
|
|
|
|
local->override_library->storage = storage_id;
|
|
|
|
#ifdef DEBUG_OVERRIDE_TIMEIT
|
|
TIMEIT_END_AVERAGED(BKE_lib_override_library_operations_store_start);
|
|
#endif
|
|
return storage_id;
|
|
}
|
|
|
|
void BKE_lib_override_library_operations_store_end(
|
|
OverrideLibraryStorage * /*liboverride_storage*/, ID *local)
|
|
{
|
|
BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(local));
|
|
|
|
/* Nothing else to do here really, we need to keep all temp override storage data-blocks in
|
|
* memory until whole file is written anyway (otherwise we'd get mem pointers overlap). */
|
|
local->override_library->storage = nullptr;
|
|
}
|
|
|
|
void BKE_lib_override_library_operations_store_finalize(
|
|
OverrideLibraryStorage *liboverride_storage)
|
|
{
|
|
/* We cannot just call BKE_main_free(override_storage), not until we have option to make
|
|
* 'ghost' copies of IDs without increasing user-count of used data-blocks. */
|
|
ID *id;
|
|
|
|
FOREACH_MAIN_ID_BEGIN (liboverride_storage, id) {
|
|
BKE_id_free_ex(liboverride_storage, id, LIB_ID_FREE_NO_UI_USER, true);
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
BKE_main_free(liboverride_storage);
|
|
}
|