This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/blenkernel/intern/collection.c
Bastien Montagne 5472ae6fdf Fix memory leak when full-copying a scene after recent changes.
Once again, am not exactly sure why that was working before, and not
anymore - but in any case, doing that kind of update here is not only
useless (since we have to do it at the end of the whole
collections/objects duplication and remapping anyway), it is also rather
dangerous, as collections are currently in rather invalid states at that
point of the code...

Note that in ideal world, `BKE_main_collection_sync()` & co would be
lazy (setting only a flag, then code actually needing this to be valid
again should call some sort of `BKE_main_collection_sync_ensure()`).
Then we would not have to worry about such things (and we'd get nice
performance improvements in some cases, also in main remapping code,
etc.).

Food for some refactoring, some day...
2020-01-17 19:59:00 +01:00

1505 lines
46 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup bke
*/
#include <string.h>
#include "BLI_blenlib.h"
#include "BLI_ghash.h"
#include "BLI_iterator.h"
#include "BLI_listbase.h"
#include "BLI_math_base.h"
#include "BLI_threads.h"
#include "BLT_translation.h"
#include "BKE_collection.h"
#include "BKE_icons.h"
#include "BKE_idprop.h"
#include "BKE_layer.h"
#include "BKE_library.h"
#include "BKE_library_remap.h"
#include "BKE_main.h"
#include "BKE_object.h"
#include "BKE_rigidbody.h"
#include "BKE_scene.h"
#include "DNA_ID.h"
#include "DNA_collection_types.h"
#include "DNA_layer_types.h"
#include "DNA_object_types.h"
#include "DNA_rigidbody_types.h"
#include "DNA_scene_types.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "MEM_guardedalloc.h"
/******************************** Prototypes ********************************/
static bool collection_child_add(Collection *parent,
Collection *collection,
const int flag,
const bool add_us);
static bool collection_child_remove(Collection *parent, Collection *collection);
static bool collection_object_add(
Main *bmain, Collection *collection, Object *ob, int flag, const bool add_us);
static bool collection_object_remove(Main *bmain,
Collection *collection,
Object *ob,
const bool free_us);
static CollectionChild *collection_find_child(Collection *parent, Collection *collection);
static CollectionParent *collection_find_parent(Collection *child, Collection *collection);
static bool collection_find_child_recursive(Collection *parent, Collection *collection);
/***************************** Add Collection *******************************/
/* Add new collection, without view layer syncing. */
static Collection *collection_add(Main *bmain,
Collection *collection_parent,
const char *name_custom)
{
/* Determine new collection name. */
char name[MAX_NAME];
if (name_custom) {
STRNCPY(name, name_custom);
}
else {
BKE_collection_new_name_get(collection_parent, name);
}
/* Create new collection. */
Collection *collection = BKE_libblock_alloc(bmain, ID_GR, name, 0);
/* We increase collection user count when linking to Collections. */
id_us_min(&collection->id);
/* Optionally add to parent collection. */
if (collection_parent) {
collection_child_add(collection_parent, collection, 0, true);
}
return collection;
}
/**
* Add a collection to a collection ListBase and synchronize all render layers
* The ListBase is NULL when the collection is to be added to the master collection
*/
Collection *BKE_collection_add(Main *bmain, Collection *collection_parent, const char *name_custom)
{
Collection *collection = collection_add(bmain, collection_parent, name_custom);
BKE_main_collection_sync(bmain);
return collection;
}
/*********************** Free and Delete Collection ****************************/
/** Free (or release) any data used by this collection (does not free the collection itself). */
void BKE_collection_free(Collection *collection)
{
/* No animdata here. */
BKE_previewimg_free(&collection->preview);
BLI_freelistN(&collection->gobject);
BLI_freelistN(&collection->children);
BLI_freelistN(&collection->parents);
BKE_collection_object_cache_free(collection);
}
/**
* Remove a collection, optionally removing its child objects or moving
* them to parent collections.
*/
bool BKE_collection_delete(Main *bmain, Collection *collection, bool hierarchy)
{
/* Master collection is not real datablock, can't be removed. */
if (collection->flag & COLLECTION_IS_MASTER) {
BLI_assert(!"Scene master collection can't be deleted");
return false;
}
if (hierarchy) {
/* Remove child objects. */
CollectionObject *cob = collection->gobject.first;
while (cob != NULL) {
collection_object_remove(bmain, collection, cob->ob, true);
cob = collection->gobject.first;
}
/* Delete all child collections recursively. */
CollectionChild *child = collection->children.first;
while (child != NULL) {
BKE_collection_delete(bmain, child->collection, hierarchy);
child = collection->children.first;
}
}
else {
/* Link child collections into parent collection. */
for (CollectionChild *child = collection->children.first; child; child = child->next) {
for (CollectionParent *cparent = collection->parents.first; cparent;
cparent = cparent->next) {
Collection *parent = cparent->collection;
collection_child_add(parent, child->collection, 0, true);
}
}
CollectionObject *cob = collection->gobject.first;
while (cob != NULL) {
/* Link child object into parent collections. */
for (CollectionParent *cparent = collection->parents.first; cparent;
cparent = cparent->next) {
Collection *parent = cparent->collection;
collection_object_add(bmain, parent, cob->ob, 0, true);
}
/* Remove child object. */
collection_object_remove(bmain, collection, cob->ob, true);
cob = collection->gobject.first;
}
}
BKE_id_delete(bmain, collection);
BKE_main_collection_sync(bmain);
return true;
}
/***************************** Collection Copy *******************************/
/**
* Only copy internal data of Collection ID from source
* to already allocated/initialized destination.
* You probably never want to use that directly,
* use #BKE_id_copy or #BKE_id_copy_ex for typical needs.
*
* WARNING! This function will not handle ID user count!
*
* \param flag: Copying options (see BKE_library.h's LIB_ID_COPY_... flags for more).
*/
void BKE_collection_copy_data(Main *bmain,
Collection *collection_dst,
const Collection *collection_src,
const int flag)
{
BLI_assert(((collection_src->flag & COLLECTION_IS_MASTER) != 0) ==
((collection_src->id.flag & LIB_PRIVATE_DATA) != 0));
/* Do not copy collection's preview (same behavior as for objects). */
if ((flag & LIB_ID_COPY_NO_PREVIEW) == 0 && false) { /* XXX TODO temp hack */
BKE_previewimg_id_copy(&collection_dst->id, &collection_src->id);
}
else {
collection_dst->preview = NULL;
}
collection_dst->flag &= ~COLLECTION_HAS_OBJECT_CACHE;
BLI_listbase_clear(&collection_dst->object_cache);
BLI_listbase_clear(&collection_dst->gobject);
BLI_listbase_clear(&collection_dst->children);
BLI_listbase_clear(&collection_dst->parents);
for (CollectionChild *child = collection_src->children.first; child; child = child->next) {
collection_child_add(collection_dst, child->collection, flag, false);
}
for (CollectionObject *cob = collection_src->gobject.first; cob; cob = cob->next) {
collection_object_add(bmain, collection_dst, cob->ob, flag, false);
}
}
static Collection *collection_duplicate_recursive(Main *bmain,
Collection *parent,
Collection *collection_old,
const bool do_hierarchy,
const bool do_objects,
const bool do_obdata)
{
Collection *collection_new;
bool do_full_process = false;
const int object_dupflag = (do_obdata) ? U.dupflag : 0;
if (!do_hierarchy || collection_old->id.newid == NULL) {
BKE_id_copy(bmain, &collection_old->id, (ID **)&collection_new);
/* Copying add one user by default, need to get rid of that one. */
id_us_min(&collection_new->id);
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_old);
CollectionChild *child_new = collection_find_child(parent, collection_new);
if (child && child_new) {
BLI_remlink(&parent->children, child_new);
BLI_insertlinkafter(&parent->children, child, child_new);
}
}
}
/* 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;
}
if (do_objects) {
/* 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_objects, do_obdata);
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, 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.
* \param do_objects: If true, it will also make duplicates of objects.
* This one does nothing if \a do_hierarchy is not set.
* \param do_obdata: 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 and \a do_objects are not set.
*/
Collection *BKE_collection_duplicate(Main *bmain,
Collection *parent,
Collection *collection,
const bool do_hierarchy,
const bool do_objects,
const bool do_obdata)
{
/* It's not allowed to copy the master collection. */
BLI_assert((collection->id.flag & LIB_PRIVATE_DATA) == 0);
BLI_assert((collection->flag & COLLECTION_IS_MASTER) == 0);
if (collection->flag & COLLECTION_IS_MASTER) {
return NULL;
}
if (do_hierarchy) {
BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false);
BKE_main_id_clear_newpoins(bmain);
}
Collection *collection_new = collection_duplicate_recursive(
bmain, parent, collection, do_hierarchy, do_objects, do_obdata);
/* This code will follows into all ID links using an ID tagged with LIB_TAG_NEW.*/
BKE_libblock_relink_to_newid(&collection_new->id);
if (do_hierarchy) {
/* Cleanup. */
BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false);
BKE_main_id_clear_newpoins(bmain);
}
BKE_main_collection_sync(bmain);
return collection_new;
}
void BKE_collection_make_local(Main *bmain, Collection *collection, const bool lib_local)
{
BKE_id_make_local_generic(bmain, &collection->id, true, lib_local);
}
/********************************* Naming *******************************/
/**
* The automatic/fallback name of a new collection.
*/
void BKE_collection_new_name_get(Collection *collection_parent, char *rname)
{
char *name;
if (!collection_parent) {
name = BLI_strdup("Collection");
}
else if (collection_parent->flag & COLLECTION_IS_MASTER) {
name = BLI_sprintfN("Collection %d", BLI_listbase_count(&collection_parent->children) + 1);
}
else {
const int number = BLI_listbase_count(&collection_parent->children) + 1;
const int digits = integer_digits_i(number);
const int max_len = sizeof(collection_parent->id.name) - 1 /* NULL terminator */ -
(1 + digits) /* " %d" */ - 2 /* ID */;
name = BLI_sprintfN("%.*s %d", max_len, collection_parent->id.name + 2, number);
}
BLI_strncpy(rname, name, MAX_NAME);
MEM_freeN(name);
}
/**
* The name to show in the interface.
*/
const char *BKE_collection_ui_name_get(struct Collection *collection)
{
if (collection->flag & COLLECTION_IS_MASTER) {
return IFACE_("Scene Collection");
}
else {
return collection->id.name + 2;
}
}
/* **************** Object List Cache *******************/
static void collection_object_cache_fill(ListBase *lb, Collection *collection, int parent_restrict)
{
int child_restrict = collection->flag | parent_restrict;
for (CollectionObject *cob = collection->gobject.first; cob; cob = cob->next) {
Base *base = BLI_findptr(lb, cob->ob, offsetof(Base, object));
if (base == NULL) {
base = MEM_callocN(sizeof(Base), "Object Base");
base->object = cob->ob;
BLI_addtail(lb, base);
}
/* Only collection flags are checked here currently, object restrict flag is checked
* in FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN since it can be animated
* without updating the cache. */
if (((child_restrict & COLLECTION_RESTRICT_VIEWPORT) == 0)) {
base->flag |= BASE_ENABLED_VIEWPORT;
}
if (((child_restrict & COLLECTION_RESTRICT_RENDER) == 0)) {
base->flag |= BASE_ENABLED_RENDER;
}
}
for (CollectionChild *child = collection->children.first; child; child = child->next) {
collection_object_cache_fill(lb, child->collection, child_restrict);
}
}
ListBase BKE_collection_object_cache_get(Collection *collection)
{
if (!(collection->flag & COLLECTION_HAS_OBJECT_CACHE)) {
static ThreadMutex cache_lock = BLI_MUTEX_INITIALIZER;
BLI_mutex_lock(&cache_lock);
if (!(collection->flag & COLLECTION_HAS_OBJECT_CACHE)) {
collection_object_cache_fill(&collection->object_cache, collection, 0);
collection->flag |= COLLECTION_HAS_OBJECT_CACHE;
}
BLI_mutex_unlock(&cache_lock);
}
return collection->object_cache;
}
static void collection_object_cache_free(Collection *collection)
{
/* Clear own cache an for all parents, since those are affected by changes as well. */
collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE;
BLI_freelistN(&collection->object_cache);
for (CollectionParent *parent = collection->parents.first; parent; parent = parent->next) {
collection_object_cache_free(parent->collection);
}
}
void BKE_collection_object_cache_free(Collection *collection)
{
collection_object_cache_free(collection);
}
Base *BKE_collection_or_layer_objects(const ViewLayer *view_layer, Collection *collection)
{
if (collection) {
return BKE_collection_object_cache_get(collection).first;
}
else {
return FIRSTBASE(view_layer);
}
}
/*********************** Scene Master Collection ***************/
Collection *BKE_collection_master_add()
{
/* Not an actual datablock, but owned by scene. */
Collection *master_collection = MEM_callocN(sizeof(Collection), "Master Collection");
STRNCPY(master_collection->id.name, "GRMaster Collection");
master_collection->id.flag |= LIB_PRIVATE_DATA;
master_collection->flag |= COLLECTION_IS_MASTER;
return master_collection;
}
Scene *BKE_collection_master_scene_search(const Main *bmain, const Collection *master_collection)
{
BLI_assert((master_collection->flag & COLLECTION_IS_MASTER) != 0);
for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) {
if (scene->master_collection == master_collection) {
return scene;
}
}
return NULL;
}
/*********************** Cyclic Checks ************************/
static bool collection_object_cyclic_check_internal(Object *object, Collection *collection)
{
if (object->instance_collection) {
Collection *dup_collection = object->instance_collection;
if ((dup_collection->id.tag & LIB_TAG_DOIT) == 0) {
/* Cycle already exists in collections, let's prevent further crappyness */
return true;
}
/* flag the object to identify cyclic dependencies in further dupli collections */
dup_collection->id.tag &= ~LIB_TAG_DOIT;
if (dup_collection == collection) {
return true;
}
else {
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (dup_collection, collection_object) {
if (collection_object_cyclic_check_internal(collection_object, dup_collection)) {
return true;
}
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
/* un-flag the object, it's allowed to have the same collection multiple times in parallel */
dup_collection->id.tag |= LIB_TAG_DOIT;
}
return false;
}
bool BKE_collection_object_cyclic_check(Main *bmain, Object *object, Collection *collection)
{
/* first flag all collections */
BKE_main_id_tag_listbase(&bmain->collections, LIB_TAG_DOIT, true);
return collection_object_cyclic_check_internal(object, collection);
}
/******************* Collection Object Membership *******************/
bool BKE_collection_has_object(Collection *collection, const Object *ob)
{
if (ELEM(NULL, collection, ob)) {
return false;
}
return (BLI_findptr(&collection->gobject, ob, offsetof(CollectionObject, ob)));
}
bool BKE_collection_has_object_recursive(Collection *collection, Object *ob)
{
if (ELEM(NULL, collection, ob)) {
return false;
}
const ListBase objects = BKE_collection_object_cache_get(collection);
return (BLI_findptr(&objects, ob, offsetof(Base, object)));
}
static Collection *collection_next_find(Main *bmain, Scene *scene, Collection *collection)
{
if (scene && collection == scene->master_collection) {
return bmain->collections.first;
}
else {
return collection->id.next;
}
}
Collection *BKE_collection_object_find(Main *bmain,
Scene *scene,
Collection *collection,
Object *ob)
{
if (collection) {
collection = collection_next_find(bmain, scene, collection);
}
else if (scene) {
collection = scene->master_collection;
}
else {
collection = bmain->collections.first;
}
while (collection) {
if (BKE_collection_has_object(collection, ob)) {
return collection;
}
collection = collection_next_find(bmain, scene, collection);
}
return NULL;
}
bool BKE_collection_is_empty(Collection *collection)
{
return BLI_listbase_is_empty(&collection->gobject) &&
BLI_listbase_is_empty(&collection->children);
}
/********************** Collection Objects *********************/
static void collection_tag_update_parent_recursive(Main *bmain,
Collection *collection,
const int flag)
{
if (collection->flag & COLLECTION_IS_MASTER) {
return;
}
DEG_id_tag_update_ex(bmain, &collection->id, flag);
for (CollectionParent *collection_parent = collection->parents.first; collection_parent;
collection_parent = collection_parent->next) {
if (collection_parent->collection->flag & COLLECTION_IS_MASTER) {
/* We don't care about scene/master collection here. */
continue;
}
collection_tag_update_parent_recursive(bmain, collection_parent->collection, flag);
}
}
static bool collection_object_add(
Main *bmain, Collection *collection, Object *ob, int flag, const bool add_us)
{
if (ob->instance_collection) {
/* Cyclic dependency check. */
if (collection_find_child_recursive(ob->instance_collection, collection)) {
return false;
}
}
CollectionObject *cob = BLI_findptr(&collection->gobject, ob, offsetof(CollectionObject, ob));
if (cob) {
return false;
}
cob = MEM_callocN(sizeof(CollectionObject), __func__);
cob->ob = ob;
BLI_addtail(&collection->gobject, cob);
BKE_collection_object_cache_free(collection);
if (add_us && (flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) {
id_us_plus(&ob->id);
}
if ((flag & LIB_ID_CREATE_NO_MAIN) == 0) {
collection_tag_update_parent_recursive(bmain, collection, ID_RECALC_COPY_ON_WRITE);
}
if ((flag & LIB_ID_CREATE_NO_MAIN) == 0) {
BKE_rigidbody_main_collection_object_add(bmain, collection, ob);
}
return true;
}
static bool collection_object_remove(Main *bmain,
Collection *collection,
Object *ob,
const bool free_us)
{
CollectionObject *cob = BLI_findptr(&collection->gobject, ob, offsetof(CollectionObject, ob));
if (cob == NULL) {
return false;
}
BLI_freelinkN(&collection->gobject, cob);
BKE_collection_object_cache_free(collection);
if (free_us) {
BKE_id_free_us(bmain, ob);
}
else {
id_us_min(&ob->id);
}
collection_tag_update_parent_recursive(bmain, collection, ID_RECALC_COPY_ON_WRITE);
return true;
}
/**
* Add object to collection
*/
bool BKE_collection_object_add(Main *bmain, Collection *collection, Object *ob)
{
if (ELEM(NULL, collection, ob)) {
return false;
}
if (!collection_object_add(bmain, collection, ob, 0, true)) {
return false;
}
if (BKE_collection_is_in_scene(collection)) {
BKE_main_collection_sync(bmain);
}
return true;
}
/**
* Add object to all scene collections that reference object is in
* (used to copy objects).
*/
void BKE_collection_object_add_from(Main *bmain, Scene *scene, Object *ob_src, Object *ob_dst)
{
bool is_instantiated = false;
FOREACH_SCENE_COLLECTION_BEGIN (scene, collection) {
if (!ID_IS_LINKED(collection) && BKE_collection_has_object(collection, ob_src)) {
collection_object_add(bmain, collection, ob_dst, 0, true);
is_instantiated = true;
}
}
FOREACH_SCENE_COLLECTION_END;
if (!is_instantiated) {
/* In case we could not find any non-linked collections in which instantiate our ob_dst,
* fallback to scene's master collection... */
collection_object_add(bmain, scene->master_collection, ob_dst, 0, true);
}
BKE_main_collection_sync(bmain);
}
/**
* Remove object from collection.
*/
bool BKE_collection_object_remove(Main *bmain,
Collection *collection,
Object *ob,
const bool free_us)
{
if (ELEM(NULL, collection, ob)) {
return false;
}
if (!collection_object_remove(bmain, collection, ob, free_us)) {
return false;
}
if (BKE_collection_is_in_scene(collection)) {
BKE_main_collection_sync(bmain);
}
return true;
}
/**
* Remove object from all collections of scene
* \param scene_collection_skip: Don't remove base from this collection.
*/
static bool scene_collections_object_remove(
Main *bmain, Scene *scene, Object *ob, const bool free_us, Collection *collection_skip)
{
bool removed = false;
if (collection_skip == NULL) {
BKE_scene_remove_rigidbody_object(bmain, scene, ob, free_us);
}
FOREACH_SCENE_COLLECTION_BEGIN (scene, collection) {
if (collection != collection_skip) {
removed |= collection_object_remove(bmain, collection, ob, free_us);
}
}
FOREACH_SCENE_COLLECTION_END;
BKE_main_collection_sync(bmain);
return removed;
}
/**
* Remove object from all collections of scene
*/
bool BKE_scene_collections_object_remove(Main *bmain, Scene *scene, Object *ob, const bool free_us)
{
return scene_collections_object_remove(bmain, scene, ob, free_us, NULL);
}
/*
* Remove all NULL objects from collections.
* This is used for library remapping, where these pointers have been set to NULL.
* Otherwise this should never happen.
*/
static void collection_object_remove_nulls(Collection *collection)
{
bool changed = false;
for (CollectionObject *cob = collection->gobject.first, *cob_next = NULL; cob; cob = cob_next) {
cob_next = cob->next;
if (cob->ob == NULL) {
BLI_freelinkN(&collection->gobject, cob);
changed = true;
}
}
if (changed) {
BKE_collection_object_cache_free(collection);
}
}
void BKE_collections_object_remove_nulls(Main *bmain)
{
for (Scene *scene = bmain->scenes.first; scene; scene = scene->id.next) {
collection_object_remove_nulls(scene->master_collection);
}
for (Collection *collection = bmain->collections.first; collection;
collection = collection->id.next) {
collection_object_remove_nulls(collection);
}
}
static void collection_null_children_remove(Collection *collection)
{
for (CollectionChild *child = collection->children.first, *child_next = NULL; child;
child = child_next) {
child_next = child->next;
if (child->collection == NULL) {
BLI_freelinkN(&collection->children, child);
}
}
}
static void collection_missing_parents_remove(Collection *collection)
{
for (CollectionParent *parent = collection->parents.first, *parent_next; parent != NULL;
parent = parent_next) {
parent_next = parent->next;
if ((parent->collection == NULL) || !collection_find_child(parent->collection, collection)) {
BLI_freelinkN(&collection->parents, parent);
}
}
}
/**
* 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.
* Otherwise this should never happen.
*
* \note caller must ensure #BKE_main_collection_sync_remap() is called afterwards!
*
* \param collection: may be \a NULL,
* in which case whole \a bmain database of collections is checked.
*/
void BKE_collections_child_remove_nulls(Main *bmain, Collection *collection)
{
if (collection == NULL) {
/* We need to do the checks in two steps when more than one collection may be involved,
* otherwise we can miss some cases...
* Also, master collections are not in bmain, so we also need to loop over scenes.
*/
for (collection = bmain->collections.first; collection != NULL;
collection = collection->id.next) {
collection_null_children_remove(collection);
}
for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) {
collection_null_children_remove(scene->master_collection);
}
for (collection = bmain->collections.first; collection != NULL;
collection = collection->id.next) {
collection_missing_parents_remove(collection);
}
for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) {
collection_missing_parents_remove(scene->master_collection);
}
}
else {
for (CollectionParent *parent = collection->parents.first, *parent_next; parent;
parent = parent_next) {
parent_next = parent->next;
collection_null_children_remove(parent->collection);
if (!collection_find_child(parent->collection, collection)) {
BLI_freelinkN(&collection->parents, parent);
}
}
}
}
/**
* Move object from a collection into another
*
* If source collection is NULL move it from all the existing collections.
*/
void BKE_collection_object_move(
Main *bmain, Scene *scene, Collection *collection_dst, Collection *collection_src, Object *ob)
{
/* In both cases we first add the object, then remove it from the other collections.
* Otherwise we lose the original base and whether it was active and selected. */
if (collection_src != NULL) {
if (BKE_collection_object_add(bmain, collection_dst, ob)) {
BKE_collection_object_remove(bmain, collection_src, ob, false);
}
}
else {
/* Adding will fail if object is already in collection.
* However we still need to remove it from the other collections. */
BKE_collection_object_add(bmain, collection_dst, ob);
scene_collections_object_remove(bmain, scene, ob, false, collection_dst);
}
}
/***************** Collection Scene Membership ****************/
bool BKE_collection_is_in_scene(Collection *collection)
{
if (collection->flag & COLLECTION_IS_MASTER) {
return true;
}
for (CollectionParent *cparent = collection->parents.first; cparent; cparent = cparent->next) {
if (BKE_collection_is_in_scene(cparent->collection)) {
return true;
}
}
return false;
}
void BKE_collections_after_lib_link(Main *bmain)
{
/* Need to update layer collections because objects might have changed
* in linked files, and because undo push does not include updated base
* flags since those are refreshed after the operator completes. */
BKE_main_collection_sync(bmain);
}
/********************** Collection Children *******************/
bool BKE_collection_find_cycle(Collection *new_ancestor, Collection *collection)
{
if (collection == new_ancestor) {
return true;
}
for (CollectionParent *parent = new_ancestor->parents.first; parent; parent = parent->next) {
if (BKE_collection_find_cycle(parent->collection, collection)) {
return true;
}
}
return false;
}
static CollectionChild *collection_find_child(Collection *parent, Collection *collection)
{
return BLI_findptr(&parent->children, collection, offsetof(CollectionChild, collection));
}
static bool collection_find_child_recursive(Collection *parent, Collection *collection)
{
for (CollectionChild *child = parent->children.first; child; child = child->next) {
if (child->collection == collection) {
return true;
}
if (collection_find_child_recursive(child->collection, collection)) {
return true;
}
}
return false;
}
bool BKE_collection_has_collection(Collection *parent, Collection *collection)
{
return collection_find_child_recursive(parent, collection);
}
static CollectionParent *collection_find_parent(Collection *child, Collection *collection)
{
return BLI_findptr(&child->parents, collection, offsetof(CollectionParent, collection));
}
static bool collection_child_add(Collection *parent,
Collection *collection,
const int flag,
const bool add_us)
{
CollectionChild *child = collection_find_child(parent, collection);
if (child) {
return false;
}
if (BKE_collection_find_cycle(parent, collection)) {
return false;
}
child = MEM_callocN(sizeof(CollectionChild), "CollectionChild");
child->collection = collection;
BLI_addtail(&parent->children, child);
/* Don't add parent links for depsgraph datablocks, these are not kept in sync. */
if ((flag & LIB_ID_CREATE_NO_MAIN) == 0) {
CollectionParent *cparent = MEM_callocN(sizeof(CollectionParent), "CollectionParent");
cparent->collection = parent;
BLI_addtail(&collection->parents, cparent);
}
if (add_us) {
id_us_plus(&collection->id);
}
BKE_collection_object_cache_free(parent);
return true;
}
static bool collection_child_remove(Collection *parent, Collection *collection)
{
CollectionChild *child = collection_find_child(parent, collection);
if (child == NULL) {
return false;
}
CollectionParent *cparent = collection_find_parent(collection, parent);
BLI_freelinkN(&collection->parents, cparent);
BLI_freelinkN(&parent->children, child);
id_us_min(&collection->id);
BKE_collection_object_cache_free(parent);
return true;
}
bool BKE_collection_child_add(Main *bmain, Collection *parent, Collection *child)
{
if (!collection_child_add(parent, child, 0, true)) {
return false;
}
BKE_main_collection_sync(bmain);
return true;
}
bool BKE_collection_child_add_no_sync(Collection *parent, Collection *child)
{
return collection_child_add(parent, child, 0, true);
}
bool BKE_collection_child_remove(Main *bmain, Collection *parent, Collection *child)
{
if (!collection_child_remove(parent, child)) {
return false;
}
BKE_main_collection_sync(bmain);
return true;
}
/**
* Rebuild parent relationships from child ones, for all children of given \a collection.
*
* \note Given collection is assumed to already have valid parents.
*/
void BKE_collection_parent_relations_rebuild(Collection *collection)
{
for (CollectionChild *child = collection->children.first, *child_next = NULL; child;
child = child_next) {
child_next = child->next;
if (child->collection == NULL || BKE_collection_find_cycle(collection, child->collection)) {
BLI_freelinkN(&collection->children, child);
}
else {
CollectionParent *cparent = MEM_callocN(sizeof(CollectionParent), __func__);
cparent->collection = collection;
BLI_addtail(&child->collection->parents, cparent);
}
}
}
static void collection_parents_rebuild_recursive(Collection *collection)
{
BKE_collection_parent_relations_rebuild(collection);
collection->tag &= ~COLLECTION_TAG_RELATION_REBUILD;
for (CollectionChild *child = collection->children.first; child != NULL; child = child->next) {
collection_parents_rebuild_recursive(child->collection);
}
}
/**
* Rebuild parent relationships from child ones, for all collections in given \a bmain.
*/
void BKE_main_collections_parent_relations_rebuild(Main *bmain)
{
/* Only collections not in bmain (master ones in scenes) have no parent... */
for (Collection *collection = bmain->collections.first; collection != NULL;
collection = collection->id.next) {
BLI_freelistN(&collection->parents);
collection->tag |= COLLECTION_TAG_RELATION_REBUILD;
}
/* Scene's master collections will be 'root' parent of most of our collections, so start with
* them. */
for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) {
collection_parents_rebuild_recursive(scene->master_collection);
}
/* We may have parent chains outside of scene's master_collection context? At least, readfile's
* lib_link_collection_data() seems to assume that, so do the same here. */
for (Collection *collection = bmain->collections.first; collection != NULL;
collection = collection->id.next) {
if (collection->tag & COLLECTION_TAG_RELATION_REBUILD) {
/* Note: we do not have easy access to 'which collections is root' info in that case, which
* means test for cycles in collection relationships may fail here. I don't think that is an
* issue in practice here, but worth keeping in mind... */
collection_parents_rebuild_recursive(collection);
}
}
}
/********************** Collection index *********************/
static Collection *collection_from_index_recursive(Collection *collection,
const int index,
int *index_current)
{
if (index == (*index_current)) {
return collection;
}
(*index_current)++;
for (CollectionChild *child = collection->children.first; child; child = child->next) {
Collection *nested = collection_from_index_recursive(child->collection, index, index_current);
if (nested != NULL) {
return nested;
}
}
return NULL;
}
/**
* Return Scene Collection for a given index.
*
* The index is calculated from top to bottom counting the children before the siblings.
*/
Collection *BKE_collection_from_index(Scene *scene, const int index)
{
int index_current = 0;
Collection *master_collection = scene->master_collection;
return collection_from_index_recursive(master_collection, index, &index_current);
}
static bool collection_objects_select(ViewLayer *view_layer, Collection *collection, bool deselect)
{
bool changed = false;
if (collection->flag & COLLECTION_RESTRICT_SELECT) {
return false;
}
for (CollectionObject *cob = collection->gobject.first; cob; cob = cob->next) {
Base *base = BKE_view_layer_base_find(view_layer, cob->ob);
if (base) {
if (deselect) {
if (base->flag & BASE_SELECTED) {
base->flag &= ~BASE_SELECTED;
changed = true;
}
}
else {
if ((base->flag & BASE_SELECTABLE) && !(base->flag & BASE_SELECTED)) {
base->flag |= BASE_SELECTED;
changed = true;
}
}
}
}
for (CollectionChild *child = collection->children.first; child; child = child->next) {
if (collection_objects_select(view_layer, collection, deselect)) {
changed = true;
}
}
return changed;
}
/**
* Select all the objects in this Collection (and its nested collections) for this ViewLayer.
* Return true if any object was selected.
*/
bool BKE_collection_objects_select(ViewLayer *view_layer, Collection *collection, bool deselect)
{
LayerCollection *layer_collection = BKE_layer_collection_first_from_scene_collection(view_layer,
collection);
if (layer_collection != NULL) {
return BKE_layer_collection_objects_select(view_layer, layer_collection, deselect);
}
else {
return collection_objects_select(view_layer, collection, deselect);
}
}
/***************** Collection move (outliner drag & drop) *********************/
bool BKE_collection_move(Main *bmain,
Collection *to_parent,
Collection *from_parent,
Collection *relative,
bool relative_after,
Collection *collection)
{
if (collection->flag & COLLECTION_IS_MASTER) {
return false;
}
if (BKE_collection_find_cycle(to_parent, collection)) {
return false;
}
/* Move to new parent collection */
if (from_parent) {
collection_child_remove(from_parent, collection);
}
collection_child_add(to_parent, collection, 0, true);
/* Move to specified location under parent. */
if (relative) {
CollectionChild *child = collection_find_child(to_parent, collection);
CollectionChild *relative_child = collection_find_child(to_parent, relative);
if (relative_child) {
BLI_remlink(&to_parent->children, child);
if (relative_after) {
BLI_insertlinkafter(&to_parent->children, relative_child, child);
}
else {
BLI_insertlinkbefore(&to_parent->children, relative_child, child);
}
BKE_collection_object_cache_free(to_parent);
}
}
/* Make sure we store the flag of the layer collections before we remove and re-create them.
* Otherwise they will get lost and everything will be copied from the new parent collection. */
GHash *view_layer_hash = BLI_ghash_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__);
for (Scene *scene = bmain->scenes.first; scene; scene = scene->id.next) {
for (ViewLayer *view_layer = scene->view_layers.first; view_layer;
view_layer = view_layer->next) {
LayerCollection *layer_collection = BKE_layer_collection_first_from_scene_collection(
view_layer, collection);
if (layer_collection == NULL) {
continue;
}
BLI_ghash_insert(view_layer_hash, view_layer, POINTER_FROM_INT(layer_collection->flag));
}
}
/* Create and remove layer collections. */
BKE_main_collection_sync(bmain);
/* Restore back the original layer collection flags. */
GHashIterator gh_iter;
GHASH_ITER (gh_iter, view_layer_hash) {
ViewLayer *view_layer = BLI_ghashIterator_getKey(&gh_iter);
LayerCollection *layer_collection = BKE_layer_collection_first_from_scene_collection(
view_layer, collection);
if (layer_collection) {
/* We treat exclude as a special case.
*
* If in a different view layer the parent collection was disabled (e.g., background)
* and now we moved a new collection to be part of the background this collection should
* probably be disabled.
*
* Note: If we were to also keep the exclude flag we would need to re-sync the collections.
*/
layer_collection->flag = POINTER_AS_INT(BLI_ghashIterator_getValue(&gh_iter)) |
(layer_collection->flag & LAYER_COLLECTION_EXCLUDE);
}
}
BLI_ghash_free(view_layer_hash, NULL, NULL);
/* We need to sync it again to pass the correct flags to the collections objects. */
BKE_main_collection_sync(bmain);
return true;
}
/**************************** Iterators ******************************/
/* scene collection iteractor */
typedef struct CollectionsIteratorData {
Scene *scene;
void **array;
int tot, cur;
} CollectionsIteratorData;
static void scene_collection_callback(Collection *collection,
BKE_scene_collections_Cb callback,
void *data)
{
callback(collection, data);
for (CollectionChild *child = collection->children.first; child; child = child->next) {
scene_collection_callback(child->collection, callback, data);
}
}
static void scene_collections_count(Collection *UNUSED(collection), void *data)
{
int *tot = data;
(*tot)++;
}
static void scene_collections_build_array(Collection *collection, void *data)
{
Collection ***array = data;
**array = collection;
(*array)++;
}
static void scene_collections_array(Scene *scene, Collection ***collections_array, int *tot)
{
Collection *collection;
Collection **array;
*collections_array = NULL;
*tot = 0;
if (scene == NULL) {
return;
}
collection = scene->master_collection;
BLI_assert(collection != NULL);
scene_collection_callback(collection, scene_collections_count, tot);
if (*tot == 0) {
return;
}
*collections_array = array = MEM_mallocN(sizeof(Collection *) * (*tot), "CollectionArray");
scene_collection_callback(collection, scene_collections_build_array, &array);
}
/**
* Only use this in non-performance critical situations
* (it iterates over all scene collections twice)
*/
void BKE_scene_collections_iterator_begin(BLI_Iterator *iter, void *data_in)
{
Scene *scene = data_in;
CollectionsIteratorData *data = MEM_callocN(sizeof(CollectionsIteratorData), __func__);
data->scene = scene;
iter->data = data;
iter->valid = true;
scene_collections_array(scene, (Collection ***)&data->array, &data->tot);
BLI_assert(data->tot != 0);
data->cur = 0;
iter->current = data->array[data->cur];
}
void BKE_scene_collections_iterator_next(struct BLI_Iterator *iter)
{
CollectionsIteratorData *data = iter->data;
if (++data->cur < data->tot) {
iter->current = data->array[data->cur];
}
else {
iter->valid = false;
}
}
void BKE_scene_collections_iterator_end(struct BLI_Iterator *iter)
{
CollectionsIteratorData *data = iter->data;
if (data) {
if (data->array) {
MEM_freeN(data->array);
}
MEM_freeN(data);
}
iter->valid = false;
}
/* scene objects iterator */
typedef struct SceneObjectsIteratorData {
GSet *visited;
CollectionObject *cob_next;
BLI_Iterator scene_collection_iter;
} SceneObjectsIteratorData;
void BKE_scene_objects_iterator_begin(BLI_Iterator *iter, void *data_in)
{
Scene *scene = data_in;
SceneObjectsIteratorData *data = MEM_callocN(sizeof(SceneObjectsIteratorData), __func__);
iter->data = data;
/* lookup list ot make sure each object is object called once */
data->visited = BLI_gset_ptr_new(__func__);
/* we wrap the scenecollection iterator here to go over the scene collections */
BKE_scene_collections_iterator_begin(&data->scene_collection_iter, scene);
Collection *collection = data->scene_collection_iter.current;
data->cob_next = collection->gobject.first;
BKE_scene_objects_iterator_next(iter);
}
/**
* Ensures we only get each object once, even when included in several collections.
*/
static CollectionObject *object_base_unique(GSet *gs, CollectionObject *cob)
{
for (; cob != NULL; cob = cob->next) {
Object *ob = cob->ob;
void **ob_key_p;
if (!BLI_gset_ensure_p_ex(gs, ob, &ob_key_p)) {
*ob_key_p = ob;
return cob;
}
}
return NULL;
}
void BKE_scene_objects_iterator_next(BLI_Iterator *iter)
{
SceneObjectsIteratorData *data = iter->data;
CollectionObject *cob = data->cob_next ? object_base_unique(data->visited, data->cob_next) :
NULL;
if (cob) {
data->cob_next = cob->next;
iter->current = cob->ob;
}
else {
/* if this is the last object of this ListBase look at the next Collection */
Collection *collection;
BKE_scene_collections_iterator_next(&data->scene_collection_iter);
do {
collection = data->scene_collection_iter.current;
/* get the first unique object of this collection */
CollectionObject *new_cob = object_base_unique(data->visited, collection->gobject.first);
if (new_cob) {
data->cob_next = new_cob->next;
iter->current = new_cob->ob;
return;
}
BKE_scene_collections_iterator_next(&data->scene_collection_iter);
} while (data->scene_collection_iter.valid);
if (!data->scene_collection_iter.valid) {
iter->valid = false;
}
}
}
void BKE_scene_objects_iterator_end(BLI_Iterator *iter)
{
SceneObjectsIteratorData *data = iter->data;
if (data) {
BKE_scene_collections_iterator_end(&data->scene_collection_iter);
BLI_gset_free(data->visited, NULL);
MEM_freeN(data);
}
}