Changeset View
Changeset View
Standalone View
Standalone View
source/blender/blenkernel/intern/collection.c
| Show All 25 Lines | |||||
| #include <string.h> | #include <string.h> | ||||
| #include "BLI_blenlib.h" | #include "BLI_blenlib.h" | ||||
| #include "BLI_ghash.h" | #include "BLI_ghash.h" | ||||
| #include "BLI_iterator.h" | #include "BLI_iterator.h" | ||||
| #include "BLI_listbase.h" | #include "BLI_listbase.h" | ||||
| #include "BLI_math_base.h" | #include "BLI_math_base.h" | ||||
| #include "BLI_threads.h" | |||||
| #include "BLT_translation.h" | #include "BLT_translation.h" | ||||
| #include "BLI_string_utils.h" | #include "BLI_string_utils.h" | ||||
| #include "BKE_collection.h" | #include "BKE_collection.h" | ||||
| #include "BKE_group.h" | #include "BKE_icons.h" | ||||
| #include "BKE_idprop.h" | #include "BKE_idprop.h" | ||||
| #include "BKE_layer.h" | #include "BKE_layer.h" | ||||
| #include "BKE_library.h" | #include "BKE_library.h" | ||||
| #include "BKE_main.h" | #include "BKE_main.h" | ||||
| #include "BKE_object.h" | |||||
| #include "BKE_scene.h" | #include "BKE_scene.h" | ||||
| #include "DNA_group_types.h" | #include "DNA_group_types.h" | ||||
| #include "DNA_ID.h" | #include "DNA_ID.h" | ||||
| #include "DNA_layer_types.h" | #include "DNA_layer_types.h" | ||||
| #include "DNA_object_types.h" | #include "DNA_object_types.h" | ||||
| #include "DNA_scene_types.h" | #include "DNA_scene_types.h" | ||||
| #include "DEG_depsgraph.h" | |||||
| #include "DEG_depsgraph_query.h" | |||||
| #include "MEM_guardedalloc.h" | #include "MEM_guardedalloc.h" | ||||
| /* Prototypes. */ | /******************************** Prototypes ********************************/ | ||||
| static SceneCollection *find_collection_parent(const struct SceneCollection *sc_child, struct SceneCollection *sc_parent); | |||||
| static bool is_collection_in_tree(const struct SceneCollection *sc_reference, struct SceneCollection *sc_parent); | static bool collection_child_add(Collection *parent, Collection *collection, int flag, const bool add_us); | ||||
| static bool collection_child_remove(Collection *parent, Collection *collection); | |||||
| static SceneCollection *collection_master_from_id(const ID *owner_id) | static bool collection_object_add(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); | ||||
| switch (GS(owner_id->name)) { | |||||
| case ID_SCE: | static CollectionChild *collection_find_child(Collection *parent, Collection *collection); | ||||
| return ((Scene *)owner_id)->collection; | static CollectionParent *collection_find_parent(Collection *child, Collection *collection); | ||||
| case ID_GR: | |||||
| return ((Group *)owner_id)->collection; | static bool collection_find_child_recursive(Collection *parent, Collection *collection); | ||||
| default: | |||||
| BLI_assert(!"ID doesn't support collections"); | /***************************** Add Collection *******************************/ | ||||
| return NULL; | |||||
| /* 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; | |||||
| } | } | ||||
| /** | /** | ||||
| * The automatic/fallback name of a new collection. | * Add a collection to a collection ListBase and syncronize all render layers | ||||
| * The ListBase is NULL when the collection is to be added to the master collection | |||||
| */ | */ | ||||
| void BKE_collection_new_name_get(ID *owner_id, SceneCollection *sc_parent, char *rname) | Collection *BKE_collection_add(Main *bmain, Collection *collection_parent, const char *name_custom) | ||||
| { | { | ||||
| SceneCollection *sc_master = collection_master_from_id(owner_id); | Collection *collection = collection_add(bmain, collection_parent, name_custom); | ||||
| char *name; | BKE_main_collection_sync(bmain); | ||||
| return collection; | |||||
| if (sc_parent == sc_master) { | |||||
| name = BLI_sprintfN("Collection %d", BLI_listbase_count(&sc_master->scene_collections) + 1); | |||||
| } | |||||
| else { | |||||
| const int number = BLI_listbase_count(&sc_parent->scene_collections) + 1; | |||||
| const int digits = integer_digits_i(number); | |||||
| const int max_len = sizeof(sc_parent->name) - 1 /* NULL terminator */ - (1 + digits) /* " %d" */; | |||||
| name = BLI_sprintfN("%.*s %d", max_len, sc_parent->name, number); | |||||
| } | } | ||||
| BLI_strncpy(rname, name, MAX_NAME); | /*********************** Free and Delete Collection ****************************/ | ||||
| MEM_freeN(name); | |||||
| /** 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); | |||||
| } | } | ||||
| /** | /** | ||||
| * Add a new collection, but don't handle syncing with layer collections | * Remove a collection, optionally removing its child objects or moving | ||||
| * them to parent collections. | |||||
| */ | */ | ||||
| static SceneCollection *collection_add(ID *owner_id, SceneCollection *sc_parent, const int type, const char *name_custom) | bool BKE_collection_delete(Main *bmain, Collection *collection, bool hierarchy) | ||||
| { | { | ||||
| SceneCollection *sc_master = collection_master_from_id(owner_id); | /* Master collection is not real datablock, can't be removed. */ | ||||
| SceneCollection *sc = MEM_callocN(sizeof(SceneCollection), "New Collection"); | if (collection->flag & COLLECTION_IS_MASTER) { | ||||
| sc->type = type; | BLI_assert("!Scene master collection can't be deleted"); | ||||
| char name[MAX_NAME]; | return false; | ||||
| } | |||||
| if (!sc_parent) { | if (hierarchy) { | ||||
| sc_parent = sc_master; | /* Remove child objects. */ | ||||
| CollectionObject *cob = collection->gobject.first; | |||||
| while (cob != NULL) { | |||||
| collection_object_remove(bmain, collection, cob->ob, true); | |||||
| cob = collection->gobject.first; | |||||
| } | } | ||||
| if (name_custom != NULL) { | /* Delete all child collections recursively. */ | ||||
| BLI_strncpy(name, name_custom, MAX_NAME); | CollectionChild *child = collection->children.first; | ||||
| while (child != NULL) { | |||||
| BKE_collection_delete(bmain, child->collection, hierarchy); | |||||
| child = collection->children.first; | |||||
| } | |||||
| } | } | ||||
| else { | else { | ||||
| BKE_collection_new_name_get(owner_id, sc_parent, name); | /* 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); | |||||
| } | |||||
| } | } | ||||
| BLI_addtail(&sc_parent->scene_collections, sc); | CollectionObject *cob = collection->gobject.first; | ||||
| BKE_collection_rename(owner_id, sc, name); | 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(parent, cob->ob, 0, true); | |||||
| } | |||||
| return sc; | /* Remove child object. */ | ||||
| collection_object_remove(bmain, collection, cob->ob, true); | |||||
| cob = collection->gobject.first; | |||||
| } | |||||
| } | } | ||||
| BKE_libblock_delete(bmain, collection); | |||||
| BKE_main_collection_sync(bmain); | |||||
| return true; | |||||
| } | |||||
| /***************************** Collection Copy *******************************/ | |||||
| /** | /** | ||||
| * Add a collection to a collection ListBase and syncronize all render layers | * Only copy internal data of Collection ID from source to already allocated/initialized destination. | ||||
| * The ListBase is NULL when the collection is to be added to the master collection | * You probably nerver want to use that directly, use 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). | |||||
| */ | */ | ||||
| SceneCollection *BKE_collection_add(ID *owner_id, SceneCollection *sc_parent, const int type, const char *name_custom) | void BKE_collection_copy_data(Main *UNUSED(bmain), Collection *collection_dst, const Collection *collection_src, const int flag) | ||||
| { | { | ||||
| if (sc_parent == NULL) { | /* Do not copy collection's preview (same behavior as for objects). */ | ||||
| sc_parent = BKE_collection_master(owner_id); | 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; | |||||
| } | } | ||||
| SceneCollection *scene_collection = collection_add(owner_id, sc_parent, type, name_custom); | collection_dst->flag &= ~COLLECTION_HAS_OBJECT_CACHE; | ||||
| BKE_layer_sync_new_scene_collection(owner_id, sc_parent, scene_collection); | BLI_listbase_clear(&collection_dst->object_cache); | ||||
| return scene_collection; | |||||
| 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(collection_dst, cob->ob, flag, false); | |||||
| } | |||||
| } | } | ||||
| /** | /** | ||||
| * Free the collection items recursively | * Makes a shallow copy of a Collection | ||||
| * | |||||
| * Add a new collection in the same level as the old one, copy any nested collections | |||||
| * but link the objects to the new collection (as oppose to copy them). | |||||
| */ | */ | ||||
| static void collection_free(SceneCollection *sc, const bool do_id_user) | Collection *BKE_collection_copy(Main *bmain, Collection *parent, Collection *collection) | ||||
| { | { | ||||
| if (do_id_user) { | /* It's not allowed to copy the master collection. */ | ||||
| for (LinkData *link = sc->objects.first; link; link = link->next) { | if (collection->flag & COLLECTION_IS_MASTER) { | ||||
| id_us_min(link->data); | BLI_assert("!Master collection can't be copied"); | ||||
| return NULL; | |||||
| } | |||||
| Collection *collection_new; | |||||
| BKE_id_copy_ex(bmain, &collection->id, (ID **)&collection_new, 0, false); | |||||
| /* Optionally add to parent. */ | |||||
| if (parent) { | |||||
| if (collection_child_add(parent, collection_new, 0, true)) { | |||||
| /* Put collection right after existing one. */ | |||||
| CollectionChild *child = collection_find_child(parent, collection); | |||||
| 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); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| BLI_freelistN(&sc->objects); | BKE_main_collection_sync(bmain); | ||||
| for (SceneCollection *nsc = sc->scene_collections.first; nsc; nsc = nsc->next) { | return collection_new; | ||||
| collection_free(nsc, do_id_user); | |||||
| } | } | ||||
| BLI_freelistN(&sc->scene_collections); | |||||
| Collection *BKE_collection_copy_master(Main *bmain, Collection *collection, const int flag) | |||||
| { | |||||
| BLI_assert(collection->flag & COLLECTION_IS_MASTER); | |||||
| Collection *collection_dst = MEM_dupallocN(collection); | |||||
| BKE_collection_copy_data(bmain, collection_dst, collection, flag); | |||||
| return collection_dst; | |||||
| } | |||||
| void BKE_collection_copy_full(Main *UNUSED(bmain), Collection *UNUSED(collection)) | |||||
| { | |||||
| // TODO: implement full scene copy | |||||
| } | } | ||||
| 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 *******************************/ | |||||
| /** | /** | ||||
| * Unlink the collection recursively | * The automatic/fallback name of a new collection. | ||||
| * \return true if unlinked. | |||||
| */ | */ | ||||
| static bool collection_remlink(SceneCollection *sc_parent, SceneCollection *sc_gone) | void BKE_collection_new_name_get(Collection *collection_parent, char *rname) | ||||
| { | { | ||||
| for (SceneCollection *sc = sc_parent->scene_collections.first; sc; sc = sc->next) { | char *name; | ||||
| if (sc == sc_gone) { | |||||
| BLI_remlink(&sc_parent->scene_collections, sc_gone); | if (!collection_parent) { | ||||
| return true; | name = BLI_sprintfN("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); | |||||
| } | } | ||||
| if (collection_remlink(sc, sc_gone)) { | BLI_strncpy(rname, name, MAX_NAME); | ||||
| MEM_freeN(name); | |||||
| } | |||||
| /************************* Dependencies ****************************/ | |||||
| bool BKE_collection_is_animated(Collection *collection, Object *UNUSED(parent)) | |||||
| { | |||||
| FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN(collection, object) | |||||
| { | |||||
| if (object->proxy) { | |||||
| return true; | return true; | ||||
| } | } | ||||
| } | } | ||||
| FOREACH_COLLECTION_OBJECT_RECURSIVE_END; | |||||
| return false; | return false; | ||||
| } | } | ||||
| /** | /* puts all collection members in local timing system, after this call | ||||
| * Recursively remove any instance of this SceneCollection | * you can draw everything, leaves tags in objects to signal it needs further updating */ | ||||
| */ | |||||
| static void layer_collection_remove(ViewLayer *view_layer, ListBase *lb, const SceneCollection *sc) | /* note: does not work for derivedmesh and render... it recreates all again in convertblender.c */ | ||||
| void BKE_collection_handle_recalc_and_update(struct Depsgraph *depsgraph, Scene *scene, Object *UNUSED(parent), Collection *collection) | |||||
| { | |||||
| /* only do existing tags, as set by regular depsgraph */ | |||||
| FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN(collection, object) | |||||
| { | { | ||||
| LayerCollection *lc = lb->first; | if (object->id.recalc & ID_RECALC_ALL) { | ||||
| while (lc) { | BKE_object_handle_update(depsgraph, scene, object); | ||||
| if (lc->scene_collection == sc) { | } | ||||
| BKE_layer_collection_free(view_layer, lc); | } | ||||
| BLI_remlink(lb, lc); | FOREACH_COLLECTION_OBJECT_RECURSIVE_END; | ||||
| } | |||||
| LayerCollection *lc_next = lc->next; | /* **************** Object List Cache *******************/ | ||||
| MEM_freeN(lc); | |||||
| lc = lc_next; | |||||
| /* only the "top-level" layer collections may have the | static void collection_object_cache_fill(ListBase *lb, Collection *collection, int parent_restrict) | ||||
| * same SceneCollection in a sibling tree. | { | ||||
| */ | int child_restrict = collection->flag | parent_restrict; | ||||
| if (lb != &view_layer->layer_collections) { | |||||
| return; | 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; | |||||
| if ((child_restrict & COLLECTION_RESTRICT_VIEW) == 0) { | |||||
| base->flag |= BASE_VISIBLED | BASE_VISIBLE_VIEWPORT; | |||||
| if ((child_restrict & COLLECTION_RESTRICT_SELECT) == 0) { | |||||
| base->flag |= BASE_SELECTABLED; | |||||
| } | } | ||||
| } | } | ||||
| else { | if ((child_restrict & COLLECTION_RESTRICT_RENDER) == 0) { | ||||
| layer_collection_remove(view_layer, &lc->layer_collections, sc); | base->flag |= BASE_VISIBLE_RENDER; | ||||
| lc = lc->next; | } | ||||
| BLI_addtail(lb, base); | |||||
| } | |||||
| } | } | ||||
| 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) | ||||
| * Remove a collection from the scene, and syncronize all render layers | |||||
| * | |||||
| * If an object is in any other collection, link the object to the master collection. | |||||
| */ | |||||
| bool BKE_collection_remove(ID *owner_id, SceneCollection *sc) | |||||
| { | { | ||||
| SceneCollection *sc_master = collection_master_from_id(owner_id); | if (!(collection->flag & COLLECTION_HAS_OBJECT_CACHE)) { | ||||
| static ThreadMutex cache_lock = BLI_MUTEX_INITIALIZER; | |||||
| /* The master collection cannot be removed. */ | if (!(collection->flag & COLLECTION_HAS_OBJECT_CACHE)) { | ||||
| if (sc == sc_master) { | BLI_mutex_lock(&cache_lock); | ||||
| return false; | collection_object_cache_fill(&collection->object_cache, collection, 0); | ||||
| collection->flag |= COLLECTION_HAS_OBJECT_CACHE; | |||||
| BLI_mutex_unlock(&cache_lock); | |||||
| } | } | ||||
| /* We need to do bottom up removal, otherwise we get a crash when we remove a collection that | |||||
| * has one of its nested collections linked to a view layer. */ | |||||
| SceneCollection *scene_collection_nested = sc->scene_collections.first; | |||||
| while (scene_collection_nested != NULL) { | |||||
| SceneCollection *scene_collection_next = scene_collection_nested->next; | |||||
| BKE_collection_remove(owner_id, scene_collection_nested); | |||||
| scene_collection_nested = scene_collection_next; | |||||
| } | } | ||||
| /* Unlink from the respective collection tree. */ | return collection->object_cache; | ||||
| if (!collection_remlink(sc_master, sc)) { | |||||
| BLI_assert(false); | |||||
| } | } | ||||
| /* If an object is no longer in any collection, we add it to the master collection. */ | static void collection_object_cache_free(Collection *collection) | ||||
| ListBase collection_objects; | { | ||||
| BLI_duplicatelist(&collection_objects, &sc->objects); | /* 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); | |||||
| } | |||||
| } | |||||
| FOREACH_SCENE_COLLECTION_BEGIN(owner_id, scene_collection_iter) | void BKE_collection_object_cache_free(Collection *collection) | ||||
| { | { | ||||
| if (scene_collection_iter == sc) { | collection_object_cache_free(collection); | ||||
| continue; | |||||
| } | } | ||||
| LinkData *link_next, *link = collection_objects.first; | Base *BKE_collection_or_layer_objects(Depsgraph *depsgraph, | ||||
| while (link) { | const Scene *scene, | ||||
| link_next = link->next; | const ViewLayer *view_layer, | ||||
| Collection *collection) | |||||
| { | |||||
| // TODO: this is used by physics to get objects from a collection, but the | |||||
| // the physics systems are not all using the depsgraph correctly which means | |||||
| // we try different things. Instead we should explicitly get evaluated or | |||||
| // non-evaluated data and always have the depsgraph available when needed | |||||
| if (BLI_findptr(&scene_collection_iter->objects, link->data, offsetof(LinkData, data))) { | if (collection) { | ||||
| BLI_remlink(&collection_objects, link); | return BKE_collection_object_cache_get(collection).first; | ||||
| MEM_freeN(link); | |||||
| } | } | ||||
| else if (depsgraph) { | |||||
| view_layer = DEG_get_evaluated_view_layer(depsgraph); | |||||
| link = link_next; | if (view_layer) { | ||||
| return FIRSTBASE(view_layer); | |||||
| } | } | ||||
| else { | |||||
| view_layer = DEG_get_input_view_layer(depsgraph); | |||||
| return FIRSTBASE(view_layer); | |||||
| } | |||||
| } | |||||
| else if (view_layer) { | |||||
| return FIRSTBASE(view_layer); | |||||
| } | |||||
| else { | |||||
| /* depsgraph is NULL during deg build */ | |||||
| return FIRSTBASE(BKE_view_layer_context_active_PLACEHOLDER(scene)); | |||||
| } | } | ||||
| FOREACH_SCENE_COLLECTION_END; | |||||
| for (LinkData *link = collection_objects.first; link; link = link->next) { | |||||
| BKE_collection_object_add(owner_id, sc_master, link->data); | |||||
| } | } | ||||
| BLI_freelistN(&collection_objects); | /*********************** Scene Master Collection ***************/ | ||||
| /* Clear the collection items. */ | Collection *BKE_collection_master_add() | ||||
| collection_free(sc, true); | { | ||||
| /* 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->flag |= COLLECTION_IS_MASTER; | |||||
| return master_collection; | |||||
| } | |||||
| /* check all layers that use this collection and clear them */ | Collection *BKE_collection_master(const Scene *scene) | ||||
| for (ViewLayer *view_layer = BKE_view_layer_first_from_id(owner_id); view_layer; view_layer = view_layer->next) { | { | ||||
| layer_collection_remove(view_layer, &view_layer->layer_collections, sc); | return scene->master_collection; | ||||
| view_layer->active_collection = 0; | |||||
| } | } | ||||
| MEM_freeN(sc); | /*********************** Cyclic Checks ************************/ | ||||
| static bool collection_object_cyclic_check_internal(Object *object, Collection *collection) | |||||
| { | |||||
| if (object->dup_group) { | |||||
| Collection *dup_collection = object->dup_group; | |||||
| if ((dup_collection->id.tag & LIB_TAG_DOIT) == 0) { | |||||
| /* Cycle already exists in collections, let's prevent further crappyness */ | |||||
| return true; | return true; | ||||
| } | } | ||||
| /* flag the object to identify cyclic dependencies in further dupli collections */ | |||||
| dup_collection->id.tag &= ~LIB_TAG_DOIT; | |||||
| /** | if (dup_collection == collection) { | ||||
| * Copy SceneCollection tree but keep pointing to the same objects | return true; | ||||
| * | } | ||||
| * \param flag Copying options (see BKE_library.h's LIB_ID_COPY_... flags for more). | else { | ||||
| */ | FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN(dup_collection, collection_object) | ||||
| void BKE_collection_copy_data(SceneCollection *sc_dst, SceneCollection *sc_src, const int flag) | |||||
| { | { | ||||
| BLI_duplicatelist(&sc_dst->objects, &sc_src->objects); | if (collection_object_cyclic_check_internal(collection_object, dup_collection)) { | ||||
| if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) { | return true; | ||||
| for (LinkData *link = sc_dst->objects.first; link; link = link->next) { | |||||
| id_us_plus(link->data); | |||||
| } | } | ||||
| } | } | ||||
| FOREACH_COLLECTION_OBJECT_RECURSIVE_END; | |||||
| } | |||||
| BLI_duplicatelist(&sc_dst->scene_collections, &sc_src->scene_collections); | /* un-flag the object, it's allowed to have the same collection multiple times in parallel */ | ||||
| for (SceneCollection *nsc_src = sc_src->scene_collections.first, *nsc_dst = sc_dst->scene_collections.first; | dup_collection->id.tag |= LIB_TAG_DOIT; | ||||
| nsc_src; | |||||
| nsc_src = nsc_src->next, nsc_dst = nsc_dst->next) | |||||
| { | |||||
| BKE_collection_copy_data(nsc_dst, nsc_src, flag); | |||||
| } | } | ||||
| return false; | |||||
| } | } | ||||
| /** | bool BKE_collection_object_cyclic_check(Main *bmain, Object *object, Collection *collection) | ||||
| * Makes a shallow copy of a SceneCollection | |||||
| * | |||||
| * Add a new collection in the same level as the old one, copy any nested collections | |||||
| * but link the objects to the new collection (as oppose to copy them). | |||||
| */ | |||||
| SceneCollection *BKE_collection_duplicate(ID *owner_id, SceneCollection *scene_collection) | |||||
| { | { | ||||
| SceneCollection *scene_collection_master = BKE_collection_master(owner_id); | /* first flag all collections */ | ||||
| SceneCollection *scene_collection_parent = find_collection_parent(scene_collection, scene_collection_master); | BKE_main_id_tag_listbase(&bmain->collection, LIB_TAG_DOIT, true); | ||||
| /* It's not allowed to copy the master collection. */ | return collection_object_cyclic_check_internal(object, collection); | ||||
| if (scene_collection_master == scene_collection) { | |||||
| return NULL; | |||||
| } | } | ||||
| SceneCollection *scene_collection_new = collection_add( | /******************* Collection Object Membership *******************/ | ||||
| owner_id, | |||||
| scene_collection_parent, | |||||
| scene_collection->type, | |||||
| scene_collection->name); | |||||
| if (scene_collection_new != scene_collection->next) { | bool BKE_collection_has_object(Collection *collection, Object *ob) | ||||
| BLI_remlink(&scene_collection_parent->scene_collections, scene_collection_new); | { | ||||
| BLI_insertlinkafter(&scene_collection_parent->scene_collections, scene_collection, scene_collection_new); | if (ELEM(NULL, collection, ob)) { | ||||
| return false; | |||||
| } | } | ||||
| BKE_collection_copy_data(scene_collection_new, scene_collection, 0); | return (BLI_findptr(&collection->gobject, ob, offsetof(CollectionObject, ob))); | ||||
| BKE_layer_sync_new_scene_collection(owner_id, scene_collection_parent, scene_collection_new); | } | ||||
| /* Make sure every linked instance of the new collection has the same values (flags, overrides, ...) as the | bool BKE_collection_has_object_recursive(Collection *collection, Object *ob) | ||||
| * corresponding original collection. */ | { | ||||
| BKE_layer_collection_sync_flags(owner_id, scene_collection_new, scene_collection); | if (ELEM(NULL, collection, ob)) { | ||||
| return false; | |||||
| } | |||||
| return scene_collection_new; | const ListBase objects = BKE_collection_object_cache_get(collection); | ||||
| return (BLI_findptr(&objects, ob, offsetof(Base, object))); | |||||
| } | } | ||||
| static SceneCollection *master_collection_from_id(const ID *owner_id) | Collection *BKE_collection_object_find(Main *bmain, Collection *collection, Object *ob) | ||||
| { | { | ||||
| switch (GS(owner_id->name)) { | if (collection) | ||||
| case ID_SCE: | collection = collection->id.next; | ||||
| return ((const Scene *)owner_id)->collection; | else | ||||
| case ID_GR: | collection = bmain->collection.first; | ||||
| return ((const Group *)owner_id)->collection; | |||||
| default: | while (collection) { | ||||
| BLI_assert(!"ID doesn't support scene collection"); | if (BKE_collection_has_object(collection, ob)) | ||||
| return NULL; | return collection; | ||||
| collection = collection->id.next; | |||||
| } | } | ||||
| return NULL; | |||||
| } | } | ||||
| /** | /********************** Collection Objects *********************/ | ||||
| * Returns the master collection of the scene or group | |||||
| */ | static bool collection_object_add(Collection *collection, Object *ob, int flag, const bool add_us) | ||||
| SceneCollection *BKE_collection_master(const ID *owner_id) | |||||
| { | { | ||||
| return master_collection_from_id(owner_id); | if (ob->dup_group) { | ||||
| /* Cyclic dependency check. */ | |||||
| if (collection_find_child_recursive(collection, ob->dup_group)) { | |||||
| return false; | |||||
| } | |||||
| } | } | ||||
| static void collection_rename(const ID *owner_id, SceneCollection *sc, const char *name) | CollectionObject *cob = BLI_findptr(&collection->gobject, ob, offsetof(CollectionObject, ob)); | ||||
| { | if (cob) { | ||||
| SceneCollection *sc_parent = find_collection_parent(sc, collection_master_from_id(owner_id)); | return false; | ||||
| BLI_strncpy(sc->name, name, sizeof(sc->name)); | |||||
| BLI_uniquename(&sc_parent->scene_collections, sc, DATA_("Collection"), '.', offsetof(SceneCollection, name), sizeof(sc->name)); | |||||
| } | } | ||||
| void BKE_collection_rename(const ID *owner_id, SceneCollection *sc, const char *name) | cob = MEM_callocN(sizeof(CollectionObject), __func__); | ||||
| { | cob->ob = ob; | ||||
| collection_rename(owner_id, sc, name); | 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); | |||||
| } | } | ||||
| /** | return true; | ||||
| * Make sure the collection name is still unique within its siblings. | |||||
| */ | |||||
| static void collection_name_check(const ID *owner_id, SceneCollection *sc) | |||||
| { | |||||
| /* It's a bit of a hack, we simply try to make sure the collection name is valid. */ | |||||
| collection_rename(owner_id, sc, sc->name); | |||||
| } | } | ||||
| /** | static bool collection_object_remove(Main *bmain, Collection *collection, Object *ob, const bool free_us) | ||||
| * Free (or release) any data used by the master collection (does not free the master collection itself). | |||||
| * Used only to clear the entire scene or group data since it's not doing re-syncing of the LayerCollection tree | |||||
| */ | |||||
| void BKE_collection_master_free(ID *owner_id, const bool do_id_user) | |||||
| { | { | ||||
| collection_free(BKE_collection_master(owner_id), do_id_user); | CollectionObject *cob = BLI_findptr(&collection->gobject, ob, offsetof(CollectionObject, ob)); | ||||
| if (cob == NULL) { | |||||
| return false; | |||||
| } | } | ||||
| static void collection_object_add(const ID *owner_id, SceneCollection *sc, Object *ob) | BLI_freelinkN(&collection->gobject, cob); | ||||
| { | BKE_collection_object_cache_free(collection); | ||||
| BLI_addtail(&sc->objects, BLI_genericNodeN(ob)); | |||||
| if (GS(owner_id->name) == ID_SCE) { | if (free_us) { | ||||
| id_us_plus((ID *)ob); | BKE_libblock_free_us(bmain, ob); | ||||
| } | } | ||||
| else { | else { | ||||
| BLI_assert(GS(owner_id->name) == ID_GR); | id_us_min(&ob->id); | ||||
| if ((ob->flag & OB_FROMGROUP) == 0) { | |||||
| ob->flag |= OB_FROMGROUP; | |||||
| } | |||||
| } | } | ||||
| BKE_layer_sync_object_link(owner_id, sc, ob); | return true; | ||||
| } | } | ||||
| /** | /** | ||||
| * Add object to collection | * Add object to collection | ||||
| */ | */ | ||||
| bool BKE_collection_object_add(const ID *owner_id, SceneCollection *sc, Object *ob) | bool BKE_collection_object_add(Main *bmain, Collection *collection, Object *ob) | ||||
| { | { | ||||
| if (BKE_collection_object_exists(sc, ob)) { | if (ELEM(NULL, collection, ob)) { | ||||
| /* don't add the same object twice */ | |||||
| return false; | return false; | ||||
| } | } | ||||
| collection_object_add(owner_id, sc, ob); | if (!collection_object_add(collection, ob, 0, true)) { | ||||
| return false; | |||||
| } | |||||
| if (BKE_collection_is_in_scene(collection)) { | |||||
| BKE_main_collection_sync(bmain); | |||||
| } | |||||
| return true; | return true; | ||||
| } | } | ||||
| /** | /** | ||||
| * Add object to all collections that reference objects is in | * Add object to all scene collections that reference objects is in | ||||
| * (used to copy objects) | * (used to copy objects) | ||||
| */ | */ | ||||
| void BKE_collection_object_add_from(Scene *scene, Object *ob_src, Object *ob_dst) | void BKE_collection_object_add_from(Main *bmain, Scene *scene, Object *ob_src, Object *ob_dst) | ||||
| { | { | ||||
| FOREACH_SCENE_COLLECTION_BEGIN(scene, sc) | FOREACH_SCENE_COLLECTION_BEGIN(scene, collection) | ||||
| { | { | ||||
| if (BLI_findptr(&sc->objects, ob_src, offsetof(LinkData, data))) { | if (BKE_collection_has_object(collection, ob_src)) { | ||||
| collection_object_add(&scene->id, sc, ob_dst); | collection_object_add(collection, ob_dst, 0, true); | ||||
| } | } | ||||
| } | } | ||||
| FOREACH_SCENE_COLLECTION_END; | FOREACH_SCENE_COLLECTION_END; | ||||
| BKE_main_collection_sync(bmain); | |||||
| } | } | ||||
| /** | /** | ||||
| * Remove object from collection. | * Remove object from collection. | ||||
| * \param bmain: Can be NULL if free_us is false. | |||||
| */ | */ | ||||
| bool BKE_collection_object_remove(Main *bmain, ID *owner_id, SceneCollection *sc, Object *ob, const bool free_us) | bool BKE_collection_object_remove(Main *bmain, Collection *collection, Object *ob, const bool free_us) | ||||
| { | { | ||||
| LinkData *link = BLI_findptr(&sc->objects, ob, offsetof(LinkData, data)); | if (ELEM(NULL, collection, ob)) { | ||||
| if (link == NULL) { | |||||
| return false; | return false; | ||||
| } | } | ||||
| BLI_remlink(&sc->objects, link); | if (collection_object_remove(bmain, collection, ob, free_us)) { | ||||
| MEM_freeN(link); | return false; | ||||
| BKE_layer_sync_object_unlink(owner_id, sc, ob); | |||||
| if (GS(owner_id->name) == ID_SCE) { | |||||
| if (free_us) { | |||||
| BKE_libblock_free_us(bmain, ob); | |||||
| } | |||||
| else { | |||||
| id_us_min(&ob->id); | |||||
| } | |||||
| } | } | ||||
| else { | |||||
| BLI_assert(GS(owner_id->name) == ID_GR); | if (BKE_collection_is_in_scene(collection)) { | ||||
| BKE_main_collection_sync(bmain); | |||||
| } | } | ||||
| return true; | return true; | ||||
| } | } | ||||
| /** | /** | ||||
| * Remove object from all collections of scene | * Remove object from all collections of scene | ||||
| * \param scene_collection_skip: Don't remove base from this collection. | * \param scene_collection_skip: Don't remove base from this collection. | ||||
| */ | */ | ||||
| static bool collections_object_remove_ex(Main *bmain, ID *owner_id, Object *ob, const bool free_us, | static bool scene_collections_object_remove(Main *bmain, Scene *scene, Object *ob, const bool free_us, | ||||
| SceneCollection *scene_collection_skip) | Collection *collection_skip) | ||||
| { | { | ||||
| bool removed = false; | bool removed = false; | ||||
| if (GS(owner_id->name) == ID_SCE) { | |||||
| BKE_scene_remove_rigidbody_object((Scene *)owner_id, ob); | |||||
| } | |||||
| else { | |||||
| BLI_assert(GS(owner_id->name) == ID_GR); | |||||
| } | |||||
| FOREACH_SCENE_COLLECTION_BEGIN(owner_id, sc) | BKE_scene_remove_rigidbody_object(scene, ob); | ||||
| FOREACH_SCENE_COLLECTION_BEGIN(scene, collection) | |||||
| { | { | ||||
| if (sc != scene_collection_skip) { | if (collection != collection_skip) { | ||||
| removed |= BKE_collection_object_remove(bmain, owner_id, sc, ob, free_us); | removed |= collection_object_remove(bmain, collection, ob, free_us); | ||||
| } | } | ||||
| } | } | ||||
| FOREACH_SCENE_COLLECTION_END; | FOREACH_SCENE_COLLECTION_END; | ||||
| BKE_main_collection_sync(bmain); | |||||
| return removed; | return removed; | ||||
| } | } | ||||
| /** | /** | ||||
| * Remove object from all collections of scene | * Remove object from all collections of scene | ||||
| */ | */ | ||||
| bool BKE_collections_object_remove(Main *bmain, ID *owner_id, Object *ob, const bool free_us) | bool BKE_scene_collections_object_remove(Main *bmain, Scene *scene, Object *ob, const bool free_us) | ||||
| { | { | ||||
| return collections_object_remove_ex(bmain, owner_id, ob, free_us, NULL); | return scene_collections_object_remove(bmain, scene, ob, free_us, NULL); | ||||
| } | |||||
| /* | |||||
| * Remove all NULL objects from non-scene collections. | |||||
| * This is used for library remapping, where these pointers have been set to NULL. | |||||
| * Otherwise this should never happen. | |||||
| */ | |||||
| void BKE_collections_object_remove_nulls(Main *bmain) | |||||
| { | |||||
| for (Collection *collection = bmain->collection.first; collection; collection = collection->id.next) { | |||||
| if (!BKE_collection_is_in_scene(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); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| /* | |||||
| * Remove all NULL children from parent objects of changed old_collection. | |||||
| * This is used for library remapping, where these pointers have been set to NULL. | |||||
| * Otherwise this should never happen. | |||||
| */ | |||||
| void BKE_collections_child_remove_nulls(Main *bmain, Collection *old_collection) | |||||
| { | |||||
| bool changed = false; | |||||
| for (CollectionChild *child = old_collection->children.first; child; child = child->next) { | |||||
| CollectionParent *cparent = collection_find_parent(child->collection, old_collection); | |||||
| if (cparent) { | |||||
| BLI_freelinkN(&child->collection->parents, cparent); | |||||
| } | |||||
| } | |||||
| for (CollectionParent *cparent = old_collection->parents.first; cparent; cparent = cparent->next) { | |||||
| Collection *parent = cparent->collection; | |||||
| for (CollectionChild *child = parent->children.first, *child_next = NULL; child; child = child_next) { | |||||
| child_next = child->next; | |||||
| if (child->collection == NULL) { | |||||
| BLI_freelinkN(&parent->children, child); | |||||
| changed = true; | |||||
| } | |||||
| } | |||||
| } | |||||
| BLI_freelistN(&old_collection->parents); | |||||
| if (changed) { | |||||
| BKE_main_collection_sync(bmain); | |||||
| } | |||||
| } | } | ||||
| /** | /** | ||||
| * Move object from a collection into another | * Move object from a collection into another | ||||
| * | * | ||||
| * If source collection is NULL move it from all the existing collections. | * If source collection is NULL move it from all the existing collections. | ||||
| */ | */ | ||||
| void BKE_collection_object_move(ID *owner_id, SceneCollection *sc_dst, SceneCollection *sc_src, Object *ob) | 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. | /* 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. */ | * Otherwise we lose the original base and whether it was active and selected. */ | ||||
| if (sc_src != NULL) { | if (collection_src != NULL) { | ||||
| if (BKE_collection_object_add(owner_id, sc_dst, ob)) { | if (BKE_collection_object_add(bmain, collection_dst, ob)) { | ||||
| BKE_collection_object_remove(NULL, owner_id, sc_src, ob, false); | BKE_collection_object_remove(bmain, collection_src, ob, false); | ||||
| } | } | ||||
| } | } | ||||
| else { | else { | ||||
| /* Adding will fail if object is already in collection. | /* Adding will fail if object is already in collection. | ||||
| * However we still need to remove it from the other collections. */ | * However we still need to remove it from the other collections. */ | ||||
| BKE_collection_object_add(owner_id, sc_dst, ob); | BKE_collection_object_add(bmain, collection_dst, ob); | ||||
| collections_object_remove_ex(NULL, owner_id, ob, false, sc_dst); | scene_collections_object_remove(bmain, scene, ob, false, collection_dst); | ||||
| } | } | ||||
| } | } | ||||
| /** | /***************** Collection Scene Membership ****************/ | ||||
| * Whether the object is directly inside the collection. | |||||
| */ | bool BKE_collection_is_in_scene(Collection *collection) | ||||
| bool BKE_collection_object_exists(struct SceneCollection *scene_collection, struct Object *ob) | |||||
| { | { | ||||
| if (BLI_findptr(&scene_collection->objects, ob, offsetof(LinkData, data))) { | 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 true; | ||||
| } | } | ||||
| } | |||||
| return false; | return false; | ||||
| } | } | ||||
| static SceneCollection *scene_collection_from_index_recursive(SceneCollection *scene_collection, const int index, int *index_current) | void BKE_collections_after_lib_link(Main *bmain) | ||||
| { | { | ||||
| if (index == (*index_current)) { | /* Update view layer collections to match any changes in linked | ||||
| return scene_collection; | * collections after file load. */ | ||||
| BKE_main_collection_sync(bmain); | |||||
| } | } | ||||
| (*index_current)++; | /********************** Collection Children *******************/ | ||||
| for (SceneCollection *scene_collection_iter = scene_collection->scene_collections.first; | bool BKE_collection_find_cycle(Collection *new_ancestor, Collection *collection) | ||||
| scene_collection_iter != NULL; | |||||
| scene_collection_iter = scene_collection_iter->next) | |||||
| { | { | ||||
| SceneCollection *nested = scene_collection_from_index_recursive(scene_collection_iter, index, index_current); | if (collection == new_ancestor) { | ||||
| if (nested != NULL) { | return true; | ||||
| return nested; | |||||
| } | } | ||||
| for (CollectionParent *parent = new_ancestor->parents.first; parent; parent = parent->next) { | |||||
| if (BKE_collection_find_cycle(parent->collection, collection)) { | |||||
| return true; | |||||
| } | } | ||||
| return NULL; | |||||
| } | } | ||||
| /** | return false; | ||||
| * Return Scene Collection for a given index. | |||||
| * | |||||
| * The index is calculated from top to bottom counting the children before the siblings. | |||||
| */ | |||||
| SceneCollection *BKE_collection_from_index(Scene *scene, const int index) | |||||
| { | |||||
| int index_current = 0; | |||||
| SceneCollection *master_collection = BKE_collection_master(&scene->id); | |||||
| return scene_collection_from_index_recursive(master_collection, index, &index_current); | |||||
| } | } | ||||
| static void layer_collection_sync(LayerCollection *lc_dst, LayerCollection *lc_src) | static CollectionChild *collection_find_child(Collection *parent, Collection *collection) | ||||
| { | { | ||||
| lc_dst->flag = lc_src->flag; | return BLI_findptr(&parent->children, collection, offsetof(CollectionChild, collection)); | ||||
| } | |||||
| /* Continue recursively. */ | static bool collection_find_child_recursive(Collection *parent, Collection *collection) | ||||
| LayerCollection *lc_dst_nested, *lc_src_nested; | |||||
| lc_src_nested = lc_src->layer_collections.first; | |||||
| for (lc_dst_nested = lc_dst->layer_collections.first; | |||||
| lc_dst_nested && lc_src_nested; | |||||
| lc_dst_nested = lc_dst_nested->next, lc_src_nested = lc_src_nested->next) | |||||
| { | { | ||||
| layer_collection_sync(lc_dst_nested, lc_src_nested); | for (CollectionChild *child = parent->children.first; child; child = child->next) { | ||||
| } | if (child->collection == collection) { | ||||
| return true; | |||||
| } | } | ||||
| /** | if (collection_find_child_recursive(child->collection, collection)) { | ||||
| * Select all the objects in this SceneCollection (and its nested collections) for this ViewLayer. | |||||
| * Return true if any object was selected. | |||||
| */ | |||||
| bool BKE_collection_objects_select(ViewLayer *view_layer, SceneCollection *scene_collection) | |||||
| { | |||||
| LayerCollection *layer_collection = BKE_layer_collection_first_from_scene_collection(view_layer, scene_collection); | |||||
| if (layer_collection != NULL) { | |||||
| BKE_layer_collection_objects_select(layer_collection); | |||||
| return true; | return true; | ||||
| } | } | ||||
| else { | |||||
| /* Slower approach, we need to iterate over all the objects and for each one we see if there is a base. */ | |||||
| bool changed = false; | |||||
| for (LinkData *link = scene_collection->objects.first; link; link = link->next) { | |||||
| Base *base = BKE_view_layer_base_find(view_layer, link->data); | |||||
| if (base != NULL) { | |||||
| if (((base->flag & BASE_SELECTED) == 0) && ((base->flag & BASE_SELECTABLED) != 0)) { | |||||
| base->flag |= BASE_SELECTED; | |||||
| changed = true; | |||||
| } | } | ||||
| return false; | |||||
| } | } | ||||
| static CollectionParent *collection_find_parent(Collection *child, Collection *collection) | |||||
| { | |||||
| return BLI_findptr(&child->parents, collection, offsetof(CollectionParent, collection)); | |||||
| } | } | ||||
| return changed; | |||||
| 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"); | ||||
| * Leave only the master collection in, remove everything else. | child->collection = collection; | ||||
| * @param group | BLI_addtail(&parent->children, child); | ||||
| */ | |||||
| static void collection_group_cleanup(Group *group) | /* Don't add parent links for depsgraph datablocks, these are not kept in sync. */ | ||||
| { | if ((flag & LIB_ID_CREATE_NO_MAIN) == 0) { | ||||
| /* Unlink all the LayerCollections. */ | CollectionParent *cparent = MEM_callocN(sizeof(CollectionParent), "CollectionParent"); | ||||
| while (group->view_layer->layer_collections.last != NULL) { | cparent->collection = parent; | ||||
| BKE_collection_unlink(group->view_layer, group->view_layer->layer_collections.last); | BLI_addtail(&collection->parents, cparent); | ||||
| } | } | ||||
| /* Remove all the SceneCollections but the master. */ | if (add_us) { | ||||
| collection_free(group->collection, false); | id_us_plus(&collection->id); | ||||
| } | } | ||||
| /** | BKE_collection_object_cache_free(parent); | ||||
| * Create a group from a collection | |||||
| * | |||||
| * Any ViewLayer that may have this the related SceneCollection linked is converted | |||||
| * to a Group Collection. | |||||
| */ | |||||
| Group *BKE_collection_group_create(Main *bmain, Scene *scene, LayerCollection *lc_src) | |||||
| { | |||||
| SceneCollection *sc_dst, *sc_src = lc_src->scene_collection; | |||||
| LayerCollection *lc_dst; | |||||
| /* The master collection can't be converted. */ | return true; | ||||
| if (sc_src == BKE_collection_master(&scene->id)) { | |||||
| return NULL; | |||||
| } | } | ||||
| /* If a sub-collection of sc_dst is directly linked into a ViewLayer we can't convert. */ | static bool collection_child_remove(Collection *parent, Collection *collection) | ||||
| for (ViewLayer *view_layer = scene->view_layers.first; view_layer; view_layer = view_layer->next) { | { | ||||
| for (LayerCollection *lc_child = view_layer->layer_collections.first; lc_child; lc_child = lc_child->next) { | CollectionChild *child = collection_find_child(parent, collection); | ||||
| if (is_collection_in_tree(lc_child->scene_collection, sc_src)) { | if (child == NULL) { | ||||
| return 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; | |||||
| } | } | ||||
| /* Create new group with the same data as the original collection. */ | bool BKE_collection_child_add(Main *bmain, Collection *parent, Collection *child) | ||||
| Group *group = BKE_group_add(bmain, sc_src->name); | |||||
| collection_group_cleanup(group); | |||||
| sc_dst = BKE_collection_add(&group->id, NULL, COLLECTION_TYPE_GROUP_INTERNAL, sc_src->name); | |||||
| BKE_collection_copy_data(sc_dst, sc_src, 0); | |||||
| FOREACH_SCENE_COLLECTION_BEGIN(&group->id, sc_group) | |||||
| { | { | ||||
| sc_group->type = COLLECTION_TYPE_GROUP_INTERNAL; | if (!collection_child_add(parent, child, 0, true)) { | ||||
| return false; | |||||
| } | } | ||||
| FOREACH_SCENE_COLLECTION_END; | |||||
| lc_dst = BKE_collection_link(group->view_layer, sc_dst); | BKE_main_collection_sync(bmain); | ||||
| layer_collection_sync(lc_dst, lc_src); | return true; | ||||
| } | |||||
| bool BKE_collection_child_remove(Main *bmain, Collection *parent, Collection *child) | |||||
| { | |||||
| if (!collection_child_remove(parent, child)) { | |||||
| return false; | |||||
| } | |||||
| return group; | BKE_main_collection_sync(bmain); | ||||
| return true; | |||||
| } | } | ||||
| /* ---------------------------------------------------------------------- */ | /********************** Collection index *********************/ | ||||
| /* Outliner drag and drop */ | |||||
| /** | static Collection *collection_from_index_recursive(Collection *collection, const int index, int *index_current) | ||||
| * Find and return the SceneCollection that has \a sc_child as one of its directly | |||||
| * nested SceneCollection. | |||||
| * | |||||
| * \param sc_parent Initial SceneCollection to look into recursively, usually the master collection | |||||
| */ | |||||
| static SceneCollection *find_collection_parent(const SceneCollection *sc_child, SceneCollection *sc_parent) | |||||
| { | { | ||||
| for (SceneCollection *sc_nested = sc_parent->scene_collections.first; sc_nested; sc_nested = sc_nested->next) { | if (index == (*index_current)) { | ||||
| if (sc_nested == sc_child) { | return collection; | ||||
| return sc_parent; | |||||
| } | } | ||||
| SceneCollection *found = find_collection_parent(sc_child, sc_nested); | (*index_current)++; | ||||
| if (found) { | |||||
| return found; | 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 NULL; | ||||
| } | } | ||||
| /** | /** | ||||
| * Check if \a sc_reference is nested to \a sc_parent SceneCollection | * Return Scene Collection for a given index. | ||||
| * | |||||
| * The index is calculated from top to bottom counting the children before the siblings. | |||||
| */ | */ | ||||
| static bool is_collection_in_tree(const SceneCollection *sc_reference, SceneCollection *sc_parent) | Collection *BKE_collection_from_index(Scene *scene, const int index) | ||||
| { | { | ||||
| return find_collection_parent(sc_reference, sc_parent) != NULL; | int index_current = 0; | ||||
| Collection *master_collection = BKE_collection_master(scene); | |||||
| return collection_from_index_recursive(master_collection, index, &index_current); | |||||
| } | } | ||||
| bool BKE_collection_move_above(const ID *owner_id, SceneCollection *sc_dst, SceneCollection *sc_src) | static bool collection_objects_select(ViewLayer *view_layer, Collection *collection, bool deselect) | ||||
| { | { | ||||
| /* Find the SceneCollection the sc_src belongs to */ | bool changed = false; | ||||
| SceneCollection *sc_master = master_collection_from_id(owner_id); | |||||
| /* Master Layer can't be moved around*/ | if (collection->flag & COLLECTION_RESTRICT_SELECT) { | ||||
| if (ELEM(sc_master, sc_src, sc_dst)) { | |||||
| return false; | return false; | ||||
| } | } | ||||
| /* collection is already where we wanted it to be */ | for (CollectionObject *cob = collection->gobject.first; cob; cob = cob->next) { | ||||
| if (sc_dst->prev == sc_src) { | Base *base = BKE_view_layer_base_find(view_layer, cob->ob); | ||||
| return false; | |||||
| } | |||||
| /* We can't move a collection fs the destiny collection | if (base) { | ||||
| * is nested to the source collection */ | if (deselect) { | ||||
| if (is_collection_in_tree(sc_dst, sc_src)) { | if (base->flag & BASE_SELECTED) { | ||||
| return false; | base->flag &= ~BASE_SELECTED; | ||||
| changed = true; | |||||
| } | |||||
| } | |||||
| else { | |||||
| if ((base->flag & BASE_SELECTABLED) && !(base->flag & BASE_SELECTED)) { | |||||
| base->flag |= BASE_SELECTED; | |||||
| changed = true; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | } | ||||
| SceneCollection *sc_src_parent = find_collection_parent(sc_src, sc_master); | for (CollectionChild *child = collection->children.first; child; child = child->next) { | ||||
| SceneCollection *sc_dst_parent = find_collection_parent(sc_dst, sc_master); | if (collection_objects_select(view_layer, collection, deselect)) { | ||||
| BLI_assert(sc_src_parent); | changed = true; | ||||
| BLI_assert(sc_dst_parent); | } | ||||
| } | |||||
| /* Remove sc_src from its parent */ | |||||
| BLI_remlink(&sc_src_parent->scene_collections, sc_src); | |||||
| /* Re-insert it where it belongs */ | |||||
| BLI_insertlinkbefore(&sc_dst_parent->scene_collections, sc_dst, sc_src); | |||||
| /* Update the tree */ | |||||
| BKE_layer_collection_resync(owner_id, sc_src_parent); | |||||
| BKE_layer_collection_resync(owner_id, sc_dst_parent); | |||||
| /* Keep names unique. */ | |||||
| collection_name_check(owner_id, sc_src); | |||||
| return true; | return changed; | ||||
| } | } | ||||
| bool BKE_collection_move_below(const ID *owner_id, SceneCollection *sc_dst, SceneCollection *sc_src) | /** | ||||
| * 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) | |||||
| { | { | ||||
| /* Find the SceneCollection the sc_src belongs to */ | LayerCollection *layer_collection = BKE_layer_collection_first_from_scene_collection(view_layer, collection); | ||||
| SceneCollection *sc_master = master_collection_from_id(owner_id); | |||||
| /* Master Layer can't be moved around*/ | if (layer_collection != NULL) { | ||||
| if (ELEM(sc_master, sc_src, sc_dst)) { | return BKE_layer_collection_objects_select(view_layer, layer_collection, deselect); | ||||
| return false; | } | ||||
| else { | |||||
| return collection_objects_select(view_layer, collection, deselect); | |||||
| } | } | ||||
| } | |||||
| /***************** Collection move (outliner drag & drop) *********************/ | |||||
| /* Collection is already where we wanted it to be */ | bool BKE_collection_move(Main *bmain, | ||||
| if (sc_dst->next == sc_src) { | Collection *to_parent, | ||||
| Collection *from_parent, | |||||
| Collection *relative, | |||||
| bool relative_after, | |||||
| Collection *collection) | |||||
| { | |||||
| if (collection->flag & COLLECTION_IS_MASTER) { | |||||
| return false; | return false; | ||||
| } | } | ||||
| if (BKE_collection_find_cycle(to_parent, collection)) { | |||||
| /* We can't move a collection if the destiny collection | |||||
| * is nested to the source collection */ | |||||
| if (is_collection_in_tree(sc_dst, sc_src)) { | |||||
| return false; | return false; | ||||
| } | } | ||||
| SceneCollection *sc_src_parent = find_collection_parent(sc_src, sc_master); | /* Move to new parent collection */ | ||||
| SceneCollection *sc_dst_parent = find_collection_parent(sc_dst, sc_master); | if (from_parent) { | ||||
| BLI_assert(sc_src_parent); | collection_child_remove(from_parent, collection); | ||||
| BLI_assert(sc_dst_parent); | } | ||||
| /* Remove sc_src from its parent */ | |||||
| BLI_remlink(&sc_src_parent->scene_collections, sc_src); | |||||
| /* Re-insert it where it belongs */ | collection_child_add(to_parent, collection, 0, true); | ||||
| BLI_insertlinkafter(&sc_dst_parent->scene_collections, sc_dst, sc_src); | |||||
| /* Update the tree */ | /* Move to specified location under parent. */ | ||||
| BKE_layer_collection_resync(owner_id, sc_src_parent); | if (relative) { | ||||
| BKE_layer_collection_resync(owner_id, sc_dst_parent); | CollectionChild *child = collection_find_child(to_parent, collection); | ||||
| CollectionChild *relative_child = collection_find_child(to_parent, relative); | |||||
| /* Keep names unique. */ | if (relative_child) { | ||||
| collection_name_check(owner_id, sc_src); | BLI_remlink(&to_parent->children, child); | ||||
| return true; | if (relative_after) { | ||||
| BLI_insertlinkafter(&to_parent->children, relative_child, child); | |||||
| } | } | ||||
| else { | |||||
| bool BKE_collection_move_into(const ID *owner_id, SceneCollection *sc_dst, SceneCollection *sc_src) | BLI_insertlinkbefore(&to_parent->children, relative_child, child); | ||||
| { | |||||
| /* Find the SceneCollection the sc_src belongs to */ | |||||
| SceneCollection *sc_master = master_collection_from_id(owner_id); | |||||
| if (sc_src == sc_master) { | |||||
| return false; | |||||
| } | } | ||||
| /* We can't move a collection if the destiny collection | BKE_collection_object_cache_free(to_parent); | ||||
| * is nested to the source collection */ | |||||
| if (is_collection_in_tree(sc_dst, sc_src)) { | |||||
| return false; | |||||
| } | } | ||||
| SceneCollection *sc_src_parent = find_collection_parent(sc_src, sc_master); | |||||
| BLI_assert(sc_src_parent); | |||||
| /* collection is already where we wanted it to be */ | |||||
| if (sc_dst->scene_collections.last == sc_src) { | |||||
| return false; | |||||
| } | } | ||||
| /* Remove sc_src from it */ | BKE_main_collection_sync(bmain); | ||||
| BLI_remlink(&sc_src_parent->scene_collections, sc_src); | |||||
| /* Insert sc_src into sc_dst */ | |||||
| BLI_addtail(&sc_dst->scene_collections, sc_src); | |||||
| /* Update the tree */ | |||||
| BKE_layer_collection_resync(owner_id, sc_src_parent); | |||||
| BKE_layer_collection_resync(owner_id, sc_dst); | |||||
| /* Keep names unique. */ | |||||
| collection_name_check(owner_id, sc_src); | |||||
| return true; | return true; | ||||
| } | } | ||||
| /* ---------------------------------------------------------------------- */ | /**************************** Iterators ******************************/ | ||||
| /* Iteractors */ | |||||
| /* scene collection iteractor */ | /* scene collection iteractor */ | ||||
| typedef struct SceneCollectionsIteratorData { | typedef struct CollectionsIteratorData { | ||||
| ID *owner_id; | Scene *scene; | ||||
| void **array; | void **array; | ||||
| int tot, cur; | int tot, cur; | ||||
| } SceneCollectionsIteratorData; | } CollectionsIteratorData; | ||||
| static void scene_collection_callback(SceneCollection *sc, BKE_scene_collections_Cb callback, void *data) | static void scene_collection_callback(Collection *collection, BKE_scene_collections_Cb callback, void *data) | ||||
| { | { | ||||
| callback(sc, data); | callback(collection, data); | ||||
| for (SceneCollection *nsc = sc->scene_collections.first; nsc; nsc = nsc->next) { | for (CollectionChild *child = collection->children.first; child; child = child->next) { | ||||
| scene_collection_callback(nsc, callback, data); | scene_collection_callback(child->collection, callback, data); | ||||
| } | } | ||||
| } | } | ||||
| static void scene_collections_count(SceneCollection *UNUSED(sc), void *data) | static void scene_collections_count(Collection *UNUSED(collection), void *data) | ||||
| { | { | ||||
| int *tot = data; | int *tot = data; | ||||
| (*tot)++; | (*tot)++; | ||||
| } | } | ||||
| static void scene_collections_build_array(SceneCollection *sc, void *data) | static void scene_collections_build_array(Collection *collection, void *data) | ||||
| { | { | ||||
| SceneCollection ***array = data; | Collection ***array = data; | ||||
| **array = sc; | **array = collection; | ||||
| (*array)++; | (*array)++; | ||||
| } | } | ||||
| static void scene_collections_array(ID *owner_id, SceneCollection ***collections_array, int *tot) | static void scene_collections_array(Scene *scene, Collection ***collections_array, int *tot) | ||||
| { | { | ||||
| SceneCollection *sc; | Collection *collection; | ||||
| SceneCollection **array; | Collection **array; | ||||
| *collections_array = NULL; | *collections_array = NULL; | ||||
| *tot = 0; | *tot = 0; | ||||
| if (owner_id == NULL) { | if (scene == NULL) { | ||||
| return; | return; | ||||
| } | } | ||||
| sc = master_collection_from_id(owner_id); | collection = BKE_collection_master(scene); | ||||
| BLI_assert(sc != NULL); | BLI_assert(collection != NULL); | ||||
| scene_collection_callback(sc, scene_collections_count, tot); | scene_collection_callback(collection, scene_collections_count, tot); | ||||
| if (*tot == 0) | if (*tot == 0) | ||||
| return; | return; | ||||
| *collections_array = array = MEM_mallocN(sizeof(SceneCollection *) * (*tot), "SceneCollectionArray"); | *collections_array = array = MEM_mallocN(sizeof(Collection *) * (*tot), "CollectionArray"); | ||||
| scene_collection_callback(sc, scene_collections_build_array, &array); | scene_collection_callback(collection, scene_collections_build_array, &array); | ||||
| } | } | ||||
| /** | /** | ||||
| * Only use this in non-performance critical situations | * Only use this in non-performance critical situations | ||||
| * (it iterates over all scene collections twice) | * (it iterates over all scene collections twice) | ||||
| */ | */ | ||||
| void BKE_scene_collections_iterator_begin(BLI_Iterator *iter, void *data_in) | void BKE_scene_collections_iterator_begin(BLI_Iterator *iter, void *data_in) | ||||
| { | { | ||||
| ID *owner_id = data_in; | Scene *scene = data_in; | ||||
| SceneCollectionsIteratorData *data = MEM_callocN(sizeof(SceneCollectionsIteratorData), __func__); | CollectionsIteratorData *data = MEM_callocN(sizeof(CollectionsIteratorData), __func__); | ||||
| data->owner_id = owner_id; | data->scene = scene; | ||||
| iter->data = data; | iter->data = data; | ||||
| iter->valid = true; | iter->valid = true; | ||||
| scene_collections_array(owner_id, (SceneCollection ***)&data->array, &data->tot); | scene_collections_array(scene, (Collection ***)&data->array, &data->tot); | ||||
| BLI_assert(data->tot != 0); | BLI_assert(data->tot != 0); | ||||
| data->cur = 0; | data->cur = 0; | ||||
| iter->current = data->array[data->cur]; | iter->current = data->array[data->cur]; | ||||
| } | } | ||||
| void BKE_scene_collections_iterator_next(struct BLI_Iterator *iter) | void BKE_scene_collections_iterator_next(struct BLI_Iterator *iter) | ||||
| { | { | ||||
| SceneCollectionsIteratorData *data = iter->data; | CollectionsIteratorData *data = iter->data; | ||||
| if (++data->cur < data->tot) { | if (++data->cur < data->tot) { | ||||
| iter->current = data->array[data->cur]; | iter->current = data->array[data->cur]; | ||||
| } | } | ||||
| else { | else { | ||||
| iter->valid = false; | iter->valid = false; | ||||
| } | } | ||||
| } | } | ||||
| void BKE_scene_collections_iterator_end(struct BLI_Iterator *iter) | void BKE_scene_collections_iterator_end(struct BLI_Iterator *iter) | ||||
| { | { | ||||
| SceneCollectionsIteratorData *data = iter->data; | CollectionsIteratorData *data = iter->data; | ||||
| if (data) { | if (data) { | ||||
| if (data->array) { | if (data->array) { | ||||
| MEM_freeN(data->array); | MEM_freeN(data->array); | ||||
| } | } | ||||
| MEM_freeN(data); | MEM_freeN(data); | ||||
| } | } | ||||
| iter->valid = false; | iter->valid = false; | ||||
| } | } | ||||
| /* scene objects iteractor */ | /* scene objects iteractor */ | ||||
| typedef struct SceneObjectsIteratorData { | typedef struct SceneObjectsIteratorData { | ||||
| GSet *visited; | GSet *visited; | ||||
| LinkData *link_next; | CollectionObject *cob_next; | ||||
| BLI_Iterator scene_collection_iter; | BLI_Iterator scene_collection_iter; | ||||
| } SceneObjectsIteratorData; | } SceneObjectsIteratorData; | ||||
| void BKE_scene_objects_iterator_begin(BLI_Iterator *iter, void *data_in) | void BKE_scene_objects_iterator_begin(BLI_Iterator *iter, void *data_in) | ||||
| { | { | ||||
| Scene *scene = data_in; | Scene *scene = data_in; | ||||
| SceneObjectsIteratorData *data = MEM_callocN(sizeof(SceneObjectsIteratorData), __func__); | SceneObjectsIteratorData *data = MEM_callocN(sizeof(SceneObjectsIteratorData), __func__); | ||||
| iter->data = data; | iter->data = data; | ||||
| /* lookup list ot make sure each object is object called once */ | /* lookup list ot make sure each object is object called once */ | ||||
| data->visited = BLI_gset_ptr_new(__func__); | data->visited = BLI_gset_ptr_new(__func__); | ||||
| /* we wrap the scenecollection iterator here to go over the scene collections */ | /* we wrap the scenecollection iterator here to go over the scene collections */ | ||||
| BKE_scene_collections_iterator_begin(&data->scene_collection_iter, scene); | BKE_scene_collections_iterator_begin(&data->scene_collection_iter, scene); | ||||
| SceneCollection *sc = data->scene_collection_iter.current; | Collection *collection = data->scene_collection_iter.current; | ||||
| if (sc->objects.first != NULL) { | if (collection->gobject.first != NULL) { | ||||
| iter->current = ((LinkData *)sc->objects.first)->data; | iter->current = ((CollectionObject *)collection->gobject.first)->ob; | ||||
| } | } | ||||
| else { | else { | ||||
| BKE_scene_objects_iterator_next(iter); | BKE_scene_objects_iterator_next(iter); | ||||
| } | } | ||||
| } | } | ||||
| /** | /** | ||||
| * Gets the first unique object in the sequence | * Gets the first unique object in the sequence | ||||
| */ | */ | ||||
| static LinkData *object_base_unique(GSet *gs, LinkData *link) | static CollectionObject *object_base_unique(GSet *gs, CollectionObject *cob) | ||||
| { | { | ||||
| for (; link != NULL; link = link->next) { | for (; cob != NULL; cob = cob->next) { | ||||
| Object *ob = link->data; | Object *ob = cob->ob; | ||||
| void **ob_key_p; | void **ob_key_p; | ||||
| if (!BLI_gset_ensure_p_ex(gs, ob, &ob_key_p)) { | if (!BLI_gset_ensure_p_ex(gs, ob, &ob_key_p)) { | ||||
| *ob_key_p = ob; | *ob_key_p = ob; | ||||
| return link; | return cob; | ||||
| } | } | ||||
| } | } | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| void BKE_scene_objects_iterator_next(BLI_Iterator *iter) | void BKE_scene_objects_iterator_next(BLI_Iterator *iter) | ||||
| { | { | ||||
| SceneObjectsIteratorData *data = iter->data; | SceneObjectsIteratorData *data = iter->data; | ||||
| LinkData *link = data->link_next ? object_base_unique(data->visited, data->link_next) : NULL; | CollectionObject *cob = data->cob_next ? object_base_unique(data->visited, data->cob_next) : NULL; | ||||
| if (link) { | if (cob) { | ||||
| data->link_next = link->next; | data->cob_next = cob->next; | ||||
| iter->current = link->data; | iter->current = cob->ob; | ||||
| } | } | ||||
| else { | else { | ||||
| /* if this is the last object of this ListBase look at the next SceneCollection */ | /* if this is the last object of this ListBase look at the next Collection */ | ||||
| SceneCollection *sc; | Collection *collection; | ||||
| BKE_scene_collections_iterator_next(&data->scene_collection_iter); | BKE_scene_collections_iterator_next(&data->scene_collection_iter); | ||||
| do { | do { | ||||
| sc = data->scene_collection_iter.current; | collection = data->scene_collection_iter.current; | ||||
| /* get the first unique object of this collection */ | /* get the first unique object of this collection */ | ||||
| LinkData *new_link = object_base_unique(data->visited, sc->objects.first); | CollectionObject *new_cob = object_base_unique(data->visited, collection->gobject.first); | ||||
| if (new_link) { | if (new_cob) { | ||||
| data->link_next = new_link->next; | data->cob_next = new_cob->next; | ||||
| iter->current = new_link->data; | iter->current = new_cob->ob; | ||||
| return; | return; | ||||
| } | } | ||||
| BKE_scene_collections_iterator_next(&data->scene_collection_iter); | BKE_scene_collections_iterator_next(&data->scene_collection_iter); | ||||
| } while (data->scene_collection_iter.valid); | } while (data->scene_collection_iter.valid); | ||||
| if (!data->scene_collection_iter.valid) { | if (!data->scene_collection_iter.valid) { | ||||
| iter->valid = false; | iter->valid = false; | ||||
| } | } | ||||
| Show All 12 Lines | |||||