Fix T93799: Outliner: Remaping objects could result in duplicates in a collection.

Fix is similar to how CollectionObject with NULL object pointers are handled.

Using one of the 'free' pad bytes in Object_Runtime struct instead of a
gset (or other external way to detect object duplicates), as this is
several times faster.

NOTE: This makes remapping slightly slower again (adds 10 extra seconds
to file case in T94059).

General improvements of remapping time complexity, especially when
remapping a lot of IDs at once, is a separate topic currently
investigated in D13615.
This commit is contained in:
Bastien Montagne 2021-12-22 16:57:07 +01:00
parent 902318f0fd
commit 8d3e57f338
Notes: blender-bot 2023-09-08 04:55:43 +02:00
Referenced by issue #93799, Outliner: Remap Users crash (for `ID Type` `Object`)
4 changed files with 70 additions and 1 deletions

View File

@ -163,7 +163,21 @@ bool BKE_scene_collections_object_remove(struct Main *bmain,
struct Scene *scene,
struct Object *object,
const bool free_us);
/**
* Check all collections in \a bmain (including embedded ones in scenes) for CollectionObject with
* NULL object pointer, and remove them.
*/
void BKE_collections_object_remove_nulls(struct Main *bmain);
/**
* Check all collections in \a bmain (including embedded ones in scenes) for duplicate
* CollectionObject with a same object pointer within a same object, and remove them.
*
* NOTE: Always keeps the first of the detected duplicates.
*/
void BKE_collections_object_remove_duplicates(struct Main *bmain);
/**
* Remove all NULL children from parent collections of changed \a collection.
* This is used for library remapping, where these pointers have been set to NULL.

View File

@ -1231,6 +1231,50 @@ void BKE_collections_object_remove_nulls(Main *bmain)
}
}
/*
* Remove all duplicate objects from collections.
* This is used for library remapping, happens when remapping an object to another one already
* present in the collection. Otherwise this should never happen.
*/
static void collection_object_remove_duplicates(Collection *collection)
{
bool changed = false;
LISTBASE_FOREACH_MUTABLE (CollectionObject *, cob, &collection->gobject) {
if (cob->ob->runtime.collection_management) {
BLI_freelinkN(&collection->gobject, cob);
changed = true;
continue;
}
cob->ob->runtime.collection_management = true;
}
/* Cleanup. */
LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
cob->ob->runtime.collection_management = false;
}
if (changed) {
BKE_collection_object_cache_free(collection);
}
}
void BKE_collections_object_remove_duplicates(struct Main *bmain)
{
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
ob->runtime.collection_management = false;
}
for (Scene *scene = bmain->scenes.first; scene; scene = scene->id.next) {
collection_object_remove_duplicates(scene->master_collection);
}
for (Collection *collection = bmain->collections.first; collection;
collection = collection->id.next) {
collection_object_remove_duplicates(collection);
}
}
static void collection_null_children_remove(Collection *collection)
{
for (CollectionChild *child = collection->children.first, *child_next = NULL; child;

View File

@ -282,6 +282,11 @@ static void libblock_remap_data_postprocess_object_update(Main *bmain,
* to remove the NULL children from collections not used in any scene. */
BKE_collections_object_remove_nulls(bmain);
}
else {
/* Remapping may have created duplicates of CollectionObject pointing to the same object within
* the same collection. */
BKE_collections_object_remove_duplicates(bmain);
}
BKE_main_collection_sync_remap(bmain);
@ -319,6 +324,7 @@ static void libblock_remap_data_postprocess_collection_update(Main *bmain,
else {
/* Temp safe fix, but a "tad" brute force... We should probably be able to use parents from
* old_collection instead? */
/* NOTE: Also takes care of duplicated child collections that remapping may have created. */
BKE_main_collections_parent_relations_rebuild(bmain);
}

View File

@ -126,7 +126,12 @@ typedef struct Object_Runtime {
/** Did last modifier stack generation need mapping support? */
char last_need_mapping;
char _pad0[3];
/** Opaque data reserved for management of objects in collection context.
* E.g. used currently to check for potential duplicates of objects in a collection, after
* remapping process. */
char collection_management;
char _pad0[2];
/** Only used for drawing the parent/child help-line. */
float parent_display_origin[3];