Differential D15339 Diff 53358 source/blender/editors/space_outliner/tree/tree_display_override_library_hierarchies.cc
Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/space_outliner/tree/tree_display_override_library_hierarchies.cc
| /* SPDX-License-Identifier: GPL-2.0-or-later */ | /* SPDX-License-Identifier: GPL-2.0-or-later */ | ||||
| /** \file | /** \file | ||||
| * \ingroup spoutliner | * \ingroup spoutliner | ||||
| */ | */ | ||||
| #include "DNA_ID.h" | |||||
| #include "DNA_collection_types.h" | |||||
| #include "DNA_key_types.h" | #include "DNA_key_types.h" | ||||
| #include "DNA_space_types.h" | #include "DNA_space_types.h" | ||||
| #include "BLI_listbase.h" | #include "BLI_function_ref.hh" | ||||
| #include "BLI_ghash.h" | |||||
| #include "BLI_map.hh" | #include "BLI_map.hh" | ||||
| #include "BLI_set.hh" | #include "BLI_set.hh" | ||||
| #include "BLT_translation.h" | #include "BLT_translation.h" | ||||
| #include "BKE_collection.h" | |||||
| #include "BKE_lib_query.h" | #include "BKE_lib_query.h" | ||||
| #include "BKE_main.h" | #include "BKE_main.h" | ||||
| #include "../outliner_intern.hh" | #include "../outliner_intern.hh" | ||||
| #include "common.hh" | #include "common.hh" | ||||
| #include "tree_display.hh" | #include "tree_display.hh" | ||||
| #include "tree_element_id.hh" | |||||
| namespace blender::ed::outliner { | namespace blender::ed::outliner { | ||||
| class AbstractTreeElement; | class AbstractTreeElement; | ||||
| TreeDisplayOverrideLibraryHierarchies::TreeDisplayOverrideLibraryHierarchies( | TreeDisplayOverrideLibraryHierarchies::TreeDisplayOverrideLibraryHierarchies( | ||||
| SpaceOutliner &space_outliner) | SpaceOutliner &space_outliner) | ||||
| : AbstractTreeDisplay(space_outliner) | : AbstractTreeDisplay(space_outliner) | ||||
| { | { | ||||
| } | } | ||||
| ListBase TreeDisplayOverrideLibraryHierarchies::buildTree(const TreeSourceData &source_data) | ListBase TreeDisplayOverrideLibraryHierarchies::buildTree(const TreeSourceData &source_data) | ||||
| { | { | ||||
| ListBase tree = {nullptr}; | ListBase tree = {nullptr}; | ||||
| /* First step: Build "Current File" hierarchy. */ | /* First step: Build "Current File" hierarchy. */ | ||||
| TreeElement *current_file_te = outliner_add_element( | TreeElement *current_file_te = outliner_add_element( | ||||
| &space_outliner_, &tree, source_data.bmain, nullptr, TSE_ID_BASE, -1); | &space_outliner_, &tree, source_data.bmain, nullptr, TSE_ID_BASE, -1); | ||||
| current_file_te->name = IFACE_("Current File"); | current_file_te->name = IFACE_("Current File"); | ||||
| { | |||||
| AbstractTreeElement::uncollapse_by_default(current_file_te); | AbstractTreeElement::uncollapse_by_default(current_file_te); | ||||
| { | |||||
| build_hierarchy_for_lib_or_main(source_data.bmain, *current_file_te); | build_hierarchy_for_lib_or_main(source_data.bmain, *current_file_te); | ||||
| /* Add dummy child if there's nothing to display. */ | /* Add dummy child if there's nothing to display. */ | ||||
| if (BLI_listbase_is_empty(¤t_file_te->subtree)) { | if (BLI_listbase_is_empty(¤t_file_te->subtree)) { | ||||
| TreeElement *dummy_te = outliner_add_element( | TreeElement *dummy_te = outliner_add_element( | ||||
| &space_outliner_, ¤t_file_te->subtree, nullptr, current_file_te, TSE_ID_BASE, 0); | &space_outliner_, ¤t_file_te->subtree, nullptr, current_file_te, TSE_ID_BASE, 0); | ||||
| dummy_te->name = IFACE_("No Library Overrides"); | dummy_te->name = IFACE_("No Library Overrides"); | ||||
| } | } | ||||
| Show All 16 Lines | LISTBASE_FOREACH_MUTABLE (TreeElement *, top_level_te, &tree) { | ||||
| if (BLI_listbase_is_empty(&top_level_te->subtree)) { | if (BLI_listbase_is_empty(&top_level_te->subtree)) { | ||||
| outliner_free_tree_element(top_level_te, &tree); | outliner_free_tree_element(top_level_te, &tree); | ||||
| } | } | ||||
| } | } | ||||
| return tree; | return tree; | ||||
| } | } | ||||
| /* -------------------------------------------------------------------- */ | |||||
| /** \name Library override hierarchy building | |||||
| * \{ */ | |||||
| class OverrideIDHierarchyBuilder { | |||||
| SpaceOutliner &space_outliner_; | |||||
| MainIDRelations &id_relations_; | |||||
| struct HierarchyBuildData { | |||||
| const ID &override_root_id_; | |||||
| /* The ancestor IDs leading to the current ID, to avoid IDs recursing into themselves. Changes | |||||
| * with every level of recursion. */ | |||||
| Set<const ID *> parent_ids{}; | |||||
| /* The IDs that were already added to #parent_te, to avoid duplicates. Entirely new set with | |||||
| * every level of recursion. */ | |||||
| Set<const ID *> sibling_ids{}; | |||||
| }; | |||||
| public: | |||||
| OverrideIDHierarchyBuilder(SpaceOutliner &space_outliner, MainIDRelations &id_relations) | |||||
| : space_outliner_(space_outliner), id_relations_(id_relations) | |||||
| { | |||||
| } | |||||
| void build_hierarchy_for_ID(ID &root_id, TreeElement &te_to_expand); | |||||
| private: | |||||
| void build_hierarchy_for_ID_recursive(const ID &parent_id, | |||||
| HierarchyBuildData &build_data, | |||||
| TreeElement &te_to_expand); | |||||
| }; | |||||
| ListBase TreeDisplayOverrideLibraryHierarchies::build_hierarchy_for_lib_or_main( | ListBase TreeDisplayOverrideLibraryHierarchies::build_hierarchy_for_lib_or_main( | ||||
| Main *bmain, TreeElement &parent_te, Library *lib) | Main *bmain, TreeElement &parent_te, Library *lib) | ||||
| { | { | ||||
| ListBase tree = {nullptr}; | ListBase tree = {nullptr}; | ||||
| /* Ensure #Main.relations contains the latest mapping of relations. Must be freed before | |||||
| * returning. */ | |||||
| BKE_main_relations_create(bmain, 0); | |||||
| OverrideIDHierarchyBuilder builder(space_outliner_, *bmain->relations); | |||||
| /* Keep track over which ID base elements were already added, and expand them once added. */ | /* Keep track over which ID base elements were already added, and expand them once added. */ | ||||
| Map<ID_Type, TreeElement *> id_base_te_map; | Map<ID_Type, TreeElement *> id_base_te_map; | ||||
| /* Index for the ID base elements ("Objects", "Materials", etc). */ | /* Index for the ID base elements ("Objects", "Materials", etc). */ | ||||
| int base_index = 0; | int base_index = 0; | ||||
| ID *iter_id; | ID *iter_id; | ||||
| FOREACH_MAIN_ID_BEGIN (bmain, iter_id) { | FOREACH_MAIN_ID_BEGIN (bmain, iter_id) { | ||||
| if (!ID_IS_OVERRIDE_LIBRARY_REAL(iter_id) || !ID_IS_OVERRIDE_LIBRARY_HIERARCHY_ROOT(iter_id)) { | if (!ID_IS_OVERRIDE_LIBRARY_REAL(iter_id) || !ID_IS_OVERRIDE_LIBRARY_HIERARCHY_ROOT(iter_id)) { | ||||
| Show All 12 Lines | TreeElement *new_base_te = id_base_te_map.lookup_or_add_cb(GS(iter_id->name), [&]() { | ||||
| base_index++); | base_index++); | ||||
| new_te->name = outliner_idcode_to_plural(GS(iter_id->name)); | new_te->name = outliner_idcode_to_plural(GS(iter_id->name)); | ||||
| return new_te; | return new_te; | ||||
| }); | }); | ||||
| TreeElement *new_id_te = outliner_add_element( | TreeElement *new_id_te = outliner_add_element( | ||||
| &space_outliner_, &new_base_te->subtree, iter_id, new_base_te, TSE_SOME_ID, 0, false); | &space_outliner_, &new_base_te->subtree, iter_id, new_base_te, TSE_SOME_ID, 0, false); | ||||
| build_hierarchy_for_ID(bmain, *iter_id, *tree_element_cast<TreeElementID>(new_id_te)); | builder.build_hierarchy_for_ID(*iter_id, *new_id_te); | ||||
| } | } | ||||
| FOREACH_MAIN_ID_END; | FOREACH_MAIN_ID_END; | ||||
| BKE_main_relations_free(bmain); | |||||
| return tree; | return tree; | ||||
| } | } | ||||
| struct BuildHierarchyForeachIDCbData { | void OverrideIDHierarchyBuilder::build_hierarchy_for_ID(ID &override_root_id, | ||||
| /* Don't allow copies, the sets below would need deep copying. */ | TreeElement &te_to_expand) | ||||
| BuildHierarchyForeachIDCbData(const BuildHierarchyForeachIDCbData &) = delete; | |||||
| Main &bmain; | |||||
| SpaceOutliner &space_outliner; | |||||
| ID &override_root_id; | |||||
| /* The tree element to expand. Changes with every level of recursion. */ | |||||
| TreeElementID *parent_te; | |||||
| /* The ancestor IDs leading to the current ID, to avoid IDs recursing into themselves. Changes | |||||
| * with every level of recursion. */ | |||||
| Set<ID *> parent_ids{}; | |||||
| /* The IDs that were already added to #parent_te, to avoid duplicates. Entirely new set with | |||||
| * every level of recursion. */ | |||||
| Set<ID *> sibling_ids{}; | |||||
| }; | |||||
| static int build_hierarchy_foreach_ID_cb(LibraryIDLinkCallbackData *cb_data) | |||||
| { | { | ||||
| if (!*cb_data->id_pointer) { | HierarchyBuildData build_data{override_root_id}; | ||||
| return IDWALK_RET_NOP; | build_hierarchy_for_ID_recursive(override_root_id, build_data, te_to_expand); | ||||
| } | } | ||||
| BuildHierarchyForeachIDCbData &build_data = *reinterpret_cast<BuildHierarchyForeachIDCbData *>( | /* Helpers (defined below). */ | ||||
| cb_data->user_data); | static void foreach_natural_hierarchy_child(const MainIDRelations &id_relations, | ||||
| /* Note that this may be an embedded ID (see #real_override_id). */ | const ID &parent_id, | ||||
| ID &id = **cb_data->id_pointer; | FunctionRef<void(ID &)> fn); | ||||
| /* If #id is an embedded ID, this will be set to the owner, which is a real ID and contains the | static bool id_is_in_override_hierarchy(const ID &id, | ||||
| * override data. So queries of override data should be done via this, but the actual tree | const ID &relationship_parent_id, | ||||
| * element we add is the embedded ID. */ | const ID &override_root_id); | ||||
| const ID *real_override_id = &id; | |||||
| void OverrideIDHierarchyBuilder::build_hierarchy_for_ID_recursive(const ID &parent_id, | |||||
| if (ID_IS_OVERRIDE_LIBRARY_VIRTUAL(&id)) { | HierarchyBuildData &build_data, | ||||
| if (GS(id.name) == ID_KE) { | TreeElement &te_to_expand) | ||||
| Key *key = (Key *)&id; | { | ||||
| real_override_id = key->from; | /* In case this isn't added to the parents yet (does nothing if already there). */ | ||||
| } | build_data.parent_ids.add(&parent_id); | ||||
| else if (id.flag & LIB_EMBEDDED_DATA) { | |||||
| /* TODO Needs double-checking if this handles all embedded IDs correctly. */ | |||||
| real_override_id = cb_data->id_owner; | |||||
| } | |||||
| } | |||||
| if (!ID_IS_OVERRIDE_LIBRARY(real_override_id)) { | foreach_natural_hierarchy_child(id_relations_, parent_id, [&](ID &id) { | ||||
| return IDWALK_RET_NOP; | if (!id_is_in_override_hierarchy(id, parent_id, build_data.override_root_id_)) { | ||||
| } | return; | ||||
| /* Is this ID part of the same override hierarchy? */ | |||||
| if (real_override_id->override_library->hierarchy_root != &build_data.override_root_id) { | |||||
| return IDWALK_RET_NOP; | |||||
| } | } | ||||
| /* Avoid endless recursion: If there is an ancestor for this ID already, it recurses into itself. | /* Avoid endless recursion: If there is an ancestor for this ID already, it recurses into | ||||
| */ | * itself. */ | ||||
| if (build_data.parent_ids.lookup_key_default(&id, nullptr)) { | if (build_data.parent_ids.lookup_key_default(&id, nullptr)) { | ||||
| return IDWALK_RET_NOP; | return; | ||||
| } | } | ||||
| /* Avoid duplicates: If there is a sibling for this ID already, the same ID is just used multiple | /* Avoid duplicates: If there is a sibling for this ID already, the same ID is just used | ||||
| * times by the same parent. */ | * multiple times by the same parent. */ | ||||
| if (build_data.sibling_ids.lookup_key_default(&id, nullptr)) { | if (build_data.sibling_ids.lookup_key_default(&id, nullptr)) { | ||||
| return IDWALK_RET_NOP; | return; | ||||
| } | } | ||||
| TreeElement *new_te = outliner_add_element(&build_data.space_outliner, | TreeElement *new_te = outliner_add_element( | ||||
| &build_data.parent_te->getLegacyElement().subtree, | &space_outliner_, &te_to_expand.subtree, &id, &te_to_expand, TSE_SOME_ID, 0, false); | ||||
| &id, | |||||
| &build_data.parent_te->getLegacyElement(), | |||||
| TSE_SOME_ID, | |||||
| 0, | |||||
| false); | |||||
| build_data.sibling_ids.add(&id); | build_data.sibling_ids.add(&id); | ||||
| BuildHierarchyForeachIDCbData child_build_data{build_data.bmain, | /* Recurse into this ID. */ | ||||
| build_data.space_outliner, | HierarchyBuildData child_build_data{build_data.override_root_id_}; | ||||
| build_data.override_root_id, | |||||
| tree_element_cast<TreeElementID>(new_te)}; | |||||
| child_build_data.parent_ids = build_data.parent_ids; | child_build_data.parent_ids = build_data.parent_ids; | ||||
| child_build_data.parent_ids.add(&id); | child_build_data.parent_ids.add(&id); | ||||
| child_build_data.sibling_ids.reserve(10); | child_build_data.sibling_ids.reserve(10); | ||||
| BKE_library_foreach_ID_link( | build_hierarchy_for_ID_recursive(id, child_build_data, *new_te); | ||||
| &build_data.bmain, &id, build_hierarchy_foreach_ID_cb, &child_build_data, IDWALK_READONLY); | }); | ||||
| } | |||||
| return IDWALK_RET_NOP; | /** \} */ | ||||
| /* -------------------------------------------------------------------- */ | |||||
| /** \name Helpers for library override hierarchy building | |||||
| * \{ */ | |||||
| /** | |||||
| * Iterate over the IDs \a parent_id uses. E.g. the child collections and contained objects of a | |||||
| * parent collection. Also does special handling for object parenting, so that: | |||||
| * - When iterating over a child object, \a fn is executed for the parent instead. | |||||
| * - When iterating over a parent object, \a fn is _additionally_ executed for all children. Given | |||||
| * that the parent object isn't skipped, the caller has to ensure it's not added in the hierarchy | |||||
| * twice. | |||||
| * This allows us to build the hierarchy in the expected ("natural") order, where parent objects | |||||
| * are actual parent elements in the hierarchy, even though in data, the relation goes the other | |||||
| * way around (children point to or "use" the parent). | |||||
| * | |||||
| * Only handles regular object parenting, not cases like the "Child of" constraint. Other Outliner | |||||
| * display modes don't show this as parent in the hierarchy either. | |||||
| */ | |||||
| static void foreach_natural_hierarchy_child(const MainIDRelations &id_relations, | |||||
| const ID &parent_id, | |||||
| FunctionRef<void(ID &)> fn) | |||||
| { | |||||
| const MainIDRelationsEntry *relations_of_id = static_cast<MainIDRelationsEntry *>( | |||||
| BLI_ghash_lookup(id_relations.relations_from_pointers, &parent_id)); | |||||
| /* Iterate over all IDs used by the parent ID (e.g. the child-collections of a collection). */ | |||||
| for (MainIDRelationsEntryItem *to_id_entry = relations_of_id->to_ids; to_id_entry; | |||||
| to_id_entry = to_id_entry->next) { | |||||
| /* An ID pointed to (used) by the ID to recurse into. */ | |||||
| ID &target_id = **to_id_entry->id_pointer.to; | |||||
| /* Don't walk up the hierarchy, e.g. ignore pointers to parent collections. */ | |||||
| if (to_id_entry->usage_flag & IDWALK_CB_LOOPBACK) { | |||||
| continue; | |||||
| } | } | ||||
| void TreeDisplayOverrideLibraryHierarchies::build_hierarchy_for_ID(Main *bmain, | /* Special case for objects: Process the parent object instead of the child object. Below the | ||||
| ID &override_root_id, | * parent will add the child objects then. */ | ||||
| TreeElementID &te_id) const | if (GS(target_id.name) == ID_OB) { | ||||
| const Object &potential_child_ob = reinterpret_cast<const Object &>(target_id); | |||||
| if (potential_child_ob.parent) { | |||||
| fn(potential_child_ob.parent->id); | |||||
| continue; | |||||
| } | |||||
| } | |||||
| fn(target_id); | |||||
| } | |||||
| /* If the ID is an object, find and iterate over any child objects. */ | |||||
| if (GS(parent_id.name) == ID_OB) { | |||||
| for (MainIDRelationsEntryItem *from_id_entry = relations_of_id->from_ids; from_id_entry; | |||||
| from_id_entry = from_id_entry->next) { | |||||
| ID &potential_child_id = *from_id_entry->id_pointer.from; | |||||
| if (GS(potential_child_id.name) != ID_OB) { | |||||
| continue; | |||||
| } | |||||
| Object &potential_child_ob = reinterpret_cast<Object &>(potential_child_id); | |||||
| if (potential_child_ob.parent && &potential_child_ob.parent->id == &parent_id) { | |||||
| fn(potential_child_id); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| static bool id_is_in_override_hierarchy(const ID &id, | |||||
| const ID &relationship_parent_id, | |||||
| const ID &override_root_id) | |||||
| { | { | ||||
| BuildHierarchyForeachIDCbData build_data{*bmain, space_outliner_, override_root_id, &te_id}; | /* If #id is an embedded ID, this will be set to the owner, which is a real ID and contains the | ||||
| build_data.parent_ids.add(&override_root_id); | * override data. So queries of override data should be done via this, but the actual tree | ||||
| * element we add is the embedded ID. */ | |||||
| const ID *real_override_id = &id; | |||||
| if (ID_IS_OVERRIDE_LIBRARY_VIRTUAL(&id)) { | |||||
| real_override_id = &relationship_parent_id; | |||||
mont29: Would add a comment here explaining a bit, something like:
```
/* This assumes that the parent… | |||||
| #ifndef NDEBUG | |||||
| if (GS(id.name) == ID_KE) { | |||||
| const Key *key = (Key *)&id; | |||||
| BLI_assert(real_override_id == key->from); | |||||
| } | |||||
| else { | |||||
| BLI_assert((id.flag & LIB_EMBEDDED_DATA) != 0); | |||||
| } | |||||
| #endif | |||||
| } | |||||
Not Done Inline ActionsI would rather systematically assume that real_override_id = &relationship_parent_id; is valid (it should be!), and ideally add an assert like that: const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(real_override_id); BLI_assert( (id_type->owner_get != nullptr); /* The #IDTypeInfo::owner_get callback should not modify the arguments, so casting away const * is okay. */ BLI_assert(real_override_id == id_type->owner_get(const_cast<Main *>(bmain), const_cast<ID *>(id))); But since you do not have a bmain here, and call to owner_get is expensive currently in some cases, you could just do something like that for the time being: #ifndef NDEBUG
if (GS(id.name) == ID_KE) {
const Key *key = (Key *)&id;
BLI_assert(real_override_id == key->from);
}
else {
BLI_assert((id.flag & LIB_EMBEDDED_DATA) != 0);
}
#endifNOTE: Ideally one would make lib_override_get public and use it here, but again, usage of owner_get would be way too expensive here currently. :( mont29: I would rather systematically assume that `real_override_id = &relationship_parent_id;` is… | |||||
| if (!ID_IS_OVERRIDE_LIBRARY(real_override_id)) { | |||||
| return false; | |||||
| } | |||||
| /* Is this ID part of the same override hierarchy? */ | |||||
| if (real_override_id->override_library->hierarchy_root != &override_root_id) { | |||||
| return false; | |||||
| } | |||||
| BKE_library_foreach_ID_link( | return true; | ||||
| bmain, &te_id.get_ID(), build_hierarchy_foreach_ID_cb, &build_data, IDWALK_READONLY); | |||||
| } | } | ||||
| /** \} */ | |||||
| } // namespace blender::ed::outliner | } // namespace blender::ed::outliner | ||||
Would add a comment here explaining a bit, something like: