Refactor duplicate code for collections.
* Fix incorrect handling of children collections being linked more than once in the hierarchy (previous code would make a new copy for each link, instead of just re-linking the first copy for each extra link). * Simplify some aspects of it (we do not need a GHash for new objects, we can use ID->newid pointer instead, and some iterations can be done directly on existing linked lists of old collection, instead of making temp local copies of them). * Move all copy logic into a single private recursive function (it was a bit odd/disturbing to see calling function being indirectly called again by the recursive helper one - not wrong, but that kind of code path can quickly become problematic in recursive patterns). * Added some comments about expected behavior of `BKE_collection_duplicate()` depending on its booleans options.
This commit is contained in:
parent
dcbc09e429
commit
52f318b914
Notes:
blender-bot
2023-02-14 11:25:11 +01:00
Referenced by issue #58702, Collections Duplication Issue
|
@ -209,84 +209,31 @@ void BKE_collection_copy_data(
|
|||
}
|
||||
}
|
||||
|
||||
static void collection_duplicate_recursive(Main *bmain, GHash *visited, Collection *collection, const int dupflag)
|
||||
static Collection *collection_duplicate_recursive(
|
||||
Main *bmain, Collection *parent, Collection *collection_old, const bool do_hierarchy, const bool do_deep_copy)
|
||||
{
|
||||
const bool is_first_run = (visited == NULL);
|
||||
if (is_first_run) {
|
||||
visited = BLI_ghash_ptr_new(__func__);
|
||||
BKE_main_id_tag_idcode(bmain, ID_GR, LIB_TAG_DOIT, false);
|
||||
}
|
||||
|
||||
if (collection->id.tag & LIB_TAG_DOIT) {
|
||||
return;
|
||||
}
|
||||
collection->id.tag |= LIB_TAG_DOIT;
|
||||
|
||||
ListBase collection_object_list = {NULL, NULL};
|
||||
BLI_duplicatelist(&collection_object_list, &collection->gobject);
|
||||
for (CollectionObject *cob = collection_object_list.first; cob; cob = cob->next) {
|
||||
Object *ob_old = cob->ob;
|
||||
Object *ob_new = NULL;
|
||||
void **ob_key_p, **ob_value_p;
|
||||
|
||||
if (!BLI_ghash_ensure_p_ex(visited, ob_old, &ob_key_p, &ob_value_p)) {
|
||||
ob_new = BKE_object_duplicate(bmain, ob_old, dupflag);
|
||||
*ob_key_p = ob_old;
|
||||
*ob_value_p = ob_new;
|
||||
}
|
||||
else {
|
||||
ob_new = *ob_value_p;
|
||||
}
|
||||
|
||||
collection_object_add(bmain, collection, ob_new, 0, true);
|
||||
collection_object_remove(bmain, collection, ob_old, false);
|
||||
}
|
||||
BLI_freelistN(&collection_object_list);
|
||||
|
||||
ListBase collection_child_list = {NULL, NULL};
|
||||
BLI_duplicatelist(&collection_child_list, &collection->children);
|
||||
for (CollectionChild *child = collection_child_list.first; child; child = child->next) {
|
||||
Collection *child_collection_old = child->collection;
|
||||
Collection *child_collection_new = BKE_collection_copy(bmain, collection, child_collection_old);
|
||||
|
||||
collection_duplicate_recursive(bmain, visited, child_collection_new, dupflag);
|
||||
collection_child_remove(collection, child_collection_old);
|
||||
}
|
||||
BLI_freelistN(&collection_child_list);
|
||||
|
||||
if (is_first_run) {
|
||||
BLI_ghash_free(visited, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a shallow copy of a Collection
|
||||
*
|
||||
* Add a new collection in the same level as the old one, link any nested collections
|
||||
* and finally link the objects to the new collection (as oppose to copy them).
|
||||
*/
|
||||
Collection *BKE_collection_copy(Main *bmain, Collection *parent, Collection *collection)
|
||||
{
|
||||
return BKE_collection_duplicate(bmain, parent, collection, false, false);
|
||||
}
|
||||
|
||||
Collection *BKE_collection_duplicate(Main *bmain, Collection *parent, Collection *collection, const bool do_hierarchy, const bool do_deep_copy)
|
||||
{
|
||||
/* It's not allowed to copy the master collection. */
|
||||
if (collection->flag & COLLECTION_IS_MASTER) {
|
||||
BLI_assert("!Master collection can't be duplicated");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Collection *collection_new;
|
||||
BKE_id_copy(bmain, &collection->id, (ID **)&collection_new);
|
||||
id_us_min(&collection_new->id); /* Copying add one user by default, need to get rid of that one. */
|
||||
bool do_full_process = false;
|
||||
const int object_dupflag = (do_deep_copy) ? U.dupflag : 0;
|
||||
|
||||
/* Optionally add to parent. */
|
||||
if (parent) {
|
||||
if (!do_hierarchy || collection_old->id.newid == NULL) {
|
||||
BKE_id_copy(bmain, &collection_old->id, (ID **)&collection_new);
|
||||
id_us_min(&collection_new->id); /* Copying add one user by default, need to get rid of that one. */
|
||||
|
||||
if (do_hierarchy) {
|
||||
ID_NEW_SET(collection_old, collection_new);
|
||||
}
|
||||
do_full_process = true;
|
||||
}
|
||||
else {
|
||||
collection_new = (Collection *)collection_old->id.newid;
|
||||
}
|
||||
|
||||
/* Optionally add to parent (we always want to do that, even if collection_old had already been duplicated). */
|
||||
if (parent != NULL) {
|
||||
if (collection_child_add(parent, collection_new, 0, true)) {
|
||||
/* Put collection right after existing one. */
|
||||
CollectionChild *child = collection_find_child(parent, collection);
|
||||
CollectionChild *child = collection_find_child(parent, collection_old);
|
||||
CollectionChild *child_new = collection_find_child(parent, collection_new);
|
||||
|
||||
if (child && child_new) {
|
||||
|
@ -296,8 +243,80 @@ Collection *BKE_collection_duplicate(Main *bmain, Collection *parent, Collection
|
|||
}
|
||||
}
|
||||
|
||||
/* If we are not doing any kind of deep-copy, we can return immediately.
|
||||
* False do_full_process means collection_old had already been duplicated, no need to redo some deep-copy on it. */
|
||||
if (!do_hierarchy || !do_full_process) {
|
||||
return collection_new;
|
||||
}
|
||||
|
||||
/* We can loop on collection_old's objects, that list is currently identical the collection_new' objects,
|
||||
* and won't be changed here. */
|
||||
for (CollectionObject *cob = collection_old->gobject.first; cob; cob = cob->next) {
|
||||
Object *ob_old = cob->ob;
|
||||
Object *ob_new = (Object *)ob_old->id.newid;
|
||||
|
||||
if (ob_new == NULL) {
|
||||
ob_new = BKE_object_duplicate(bmain, ob_old, object_dupflag);
|
||||
ID_NEW_SET(ob_old, ob_new);
|
||||
}
|
||||
|
||||
collection_object_add(bmain, collection_new, ob_new, 0, true);
|
||||
collection_object_remove(bmain, collection_new, ob_old, false);
|
||||
}
|
||||
|
||||
/* We can loop on collection_old's children, that list is currently identical the collection_new' children,
|
||||
* and won't be changed here. */
|
||||
for (CollectionChild *child = collection_old->children.first; child; child = child->next) {
|
||||
Collection *child_collection_old = child->collection;
|
||||
|
||||
collection_duplicate_recursive(bmain, collection_new, child_collection_old, do_hierarchy, do_deep_copy);
|
||||
collection_child_remove(collection_new, child_collection_old);
|
||||
}
|
||||
|
||||
return collection_new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a standard (aka shallow) ID copy of a Collection.
|
||||
*
|
||||
* Add a new collection in the same level as the old one, link any nested collections
|
||||
* and finally link the objects to the new collection (as opposed to copying them).
|
||||
*/
|
||||
Collection *BKE_collection_copy(Main *bmain, Collection *parent, Collection *collection)
|
||||
{
|
||||
return BKE_collection_duplicate(bmain, parent, collection, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make either a shallow copy, or deeper duplicate of given collection.
|
||||
*
|
||||
* If \a do_hierarchy and \a do_deep_copy are false, this is a regular (shallow) ID copy.
|
||||
*
|
||||
* \warning If any 'deep copy' behavior is enabled, this functions will clear all \a bmain id.idnew pointers.
|
||||
*
|
||||
* \param do_hierarchy If true, it will recursively make shallow copies of children collections and objects.
|
||||
* \param do_deep_copy If true, it will also make deep duplicates of objects, using behavior defined in user settings
|
||||
* (U.dupflag). This one does nothing if \a do_hierarchy is not set.
|
||||
*/
|
||||
Collection *BKE_collection_duplicate(
|
||||
Main *bmain, Collection *parent, Collection *collection, const bool do_hierarchy, const bool do_deep_copy)
|
||||
{
|
||||
/* It's not allowed to copy the master collection. */
|
||||
if (collection->flag & COLLECTION_IS_MASTER) {
|
||||
BLI_assert("!Master collection can't be duplicated");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (do_hierarchy) {
|
||||
collection_duplicate_recursive(bmain, NULL, collection_new, (do_deep_copy) ? U.dupflag : 0);
|
||||
BKE_main_id_clear_newpoins(bmain);
|
||||
}
|
||||
|
||||
Collection *collection_new = collection_duplicate_recursive(
|
||||
bmain, parent, collection, do_hierarchy, do_deep_copy);
|
||||
|
||||
if (do_hierarchy) {
|
||||
/* Cleanup. */
|
||||
BKE_main_id_clear_newpoins(bmain);
|
||||
}
|
||||
|
||||
BKE_main_collection_sync(bmain);
|
||||
|
|
Loading…
Reference in New Issue