diff options
13 files changed, 337 insertions, 17 deletions
diff --git a/release/scripts/startup/bl_ui/space_outliner.py b/release/scripts/startup/bl_ui/space_outliner.py index 0f3dc1f8794..eff8ad8e8b3 100644 --- a/release/scripts/startup/bl_ui/space_outliner.py +++ b/release/scripts/startup/bl_ui/space_outliner.py @@ -22,6 +22,8 @@ class OUTLINER_HT_header(Header): if display_mode == 'DATA_API': OUTLINER_MT_editor_menus.draw_collapsible(context, layout) + if display_mode == 'LIBRARY_OVERRIDES': + layout.prop(space, "lib_override_view_mode", text="") layout.separator_spacer() @@ -41,7 +43,11 @@ class OUTLINER_HT_header(Header): text="", icon='FILTER', ) - if display_mode in {'LIBRARIES', 'LIBRARY_OVERRIDES', 'ORPHAN_DATA'}: + if display_mode == 'LIBRARY_OVERRIDES' and space.lib_override_view_mode == 'HIERARCHIES': + # Don't add ID type filter for library overrides hierarchies mode. Point of it is to see a hierarchy that is + # usually constructed out of different ID types. + pass + elif display_mode in {'LIBRARIES', 'LIBRARY_OVERRIDES', 'ORPHAN_DATA'}: row.prop(space, "use_filter_id_type", text="", icon='FILTER') sub = row.row(align=True) sub.active = space.use_filter_id_type @@ -364,7 +370,7 @@ class OUTLINER_PT_filter(Panel): col.prop(space, "use_filter_complete", text="Exact Match") col.prop(space, "use_filter_case_sensitive", text="Case Sensitive") - if display_mode == 'LIBRARY_OVERRIDES' and bpy.data.libraries: + if display_mode == 'LIBRARY_OVERRIDES' and space.lib_override_view_mode == 'PROPERTIES' and bpy.data.libraries: col.separator() row = col.row() row.label(icon='LIBRARY_DATA_OVERRIDE') diff --git a/source/blender/editors/space_outliner/CMakeLists.txt b/source/blender/editors/space_outliner/CMakeLists.txt index b57525854d6..fae0e4be2a8 100644 --- a/source/blender/editors/space_outliner/CMakeLists.txt +++ b/source/blender/editors/space_outliner/CMakeLists.txt @@ -38,7 +38,8 @@ set(SRC tree/tree_display_data.cc tree/tree_display_libraries.cc tree/tree_display_orphaned.cc - tree/tree_display_override_library.cc + tree/tree_display_override_library_properties.cc + tree/tree_display_override_library_hierarchies.cc tree/tree_display_scenes.cc tree/tree_display_sequencer.cc tree/tree_display_view_layer.cc diff --git a/source/blender/editors/space_outliner/outliner_draw.cc b/source/blender/editors/space_outliner/outliner_draw.cc index a6f09a86eb4..d204e12b41d 100644 --- a/source/blender/editors/space_outliner/outliner_draw.cc +++ b/source/blender/editors/space_outliner/outliner_draw.cc @@ -3899,6 +3899,12 @@ void draw_outliner(const bContext *C) /* Default to no emboss for outliner UI. */ UI_block_emboss_set(block, UI_EMBOSS_NONE_OR_STATUS); + if (space_outliner->outlinevis == SO_OVERRIDES_LIBRARY) { + /* Draw overrides status columns. */ + outliner_draw_overrides_warning_buts( + block, region, space_outliner, &space_outliner->tree, true); + } + if (space_outliner->outlinevis == SO_DATA_API) { int buttons_start_x = outliner_data_api_buttons_start_x(tree_width); /* draw rna buttons */ @@ -3913,11 +3919,8 @@ void draw_outliner(const bContext *C) /* draw user toggle columns */ outliner_draw_userbuts(block, region, space_outliner, &space_outliner->tree); } - else if (space_outliner->outlinevis == SO_OVERRIDES_LIBRARY) { - /* Draw overrides status columns. */ - outliner_draw_overrides_warning_buts( - block, region, space_outliner, &space_outliner->tree, true); - + else if ((space_outliner->outlinevis == SO_OVERRIDES_LIBRARY) && + (space_outliner->lib_override_view_mode == SO_LIB_OVERRIDE_VIEW_PROPERTIES)) { UI_block_emboss_set(block, UI_EMBOSS); UI_block_flag_enable(block, UI_BLOCK_NO_DRAW_OVERRIDDEN_STATE); const int x = region->v2d.cur.xmax - right_column_width; diff --git a/source/blender/editors/space_outliner/outliner_utils.cc b/source/blender/editors/space_outliner/outliner_utils.cc index ed5a2108d3c..7526cc7ef5c 100644 --- a/source/blender/editors/space_outliner/outliner_utils.cc +++ b/source/blender/editors/space_outliner/outliner_utils.cc @@ -324,6 +324,9 @@ float outliner_right_columns_width(const SpaceOutliner *space_outliner) case SO_LIBRARIES: return 0.0f; case SO_OVERRIDES_LIBRARY: + if (space_outliner->lib_override_view_mode != SO_LIB_OVERRIDE_VIEW_PROPERTIES) { + return 0.0f; + } num_columns = OL_RNA_COL_SIZEX / UI_UNIT_X; break; case SO_ID_ORPHANS: diff --git a/source/blender/editors/space_outliner/tree/tree_display.cc b/source/blender/editors/space_outliner/tree/tree_display.cc index f9141dffd6a..141c68594e8 100644 --- a/source/blender/editors/space_outliner/tree/tree_display.cc +++ b/source/blender/editors/space_outliner/tree/tree_display.cc @@ -30,7 +30,13 @@ std::unique_ptr<AbstractTreeDisplay> AbstractTreeDisplay::createFromDisplayMode( case SO_ID_ORPHANS: return std::make_unique<TreeDisplayIDOrphans>(space_outliner); case SO_OVERRIDES_LIBRARY: - return std::make_unique<TreeDisplayOverrideLibrary>(space_outliner); + switch ((eSpaceOutliner_LibOverrideViewMode)space_outliner.lib_override_view_mode) { + case SO_LIB_OVERRIDE_VIEW_PROPERTIES: + return std::make_unique<TreeDisplayOverrideLibraryProperties>(space_outliner); + case SO_LIB_OVERRIDE_VIEW_HIERARCHIES: + return std::make_unique<TreeDisplayOverrideLibraryHierarchies>(space_outliner); + } + break; case SO_VIEW_LAYER: return std::make_unique<TreeDisplayViewLayer>(space_outliner); } diff --git a/source/blender/editors/space_outliner/tree/tree_display.hh b/source/blender/editors/space_outliner/tree/tree_display.hh index a60d3339042..327f29aa15e 100644 --- a/source/blender/editors/space_outliner/tree/tree_display.hh +++ b/source/blender/editors/space_outliner/tree/tree_display.hh @@ -35,6 +35,8 @@ struct ViewLayer; namespace blender::ed::outliner { +class TreeElementID; + /** * \brief The data to build the tree from. */ @@ -127,11 +129,11 @@ class TreeDisplayLibraries final : public AbstractTreeDisplay { /* Library Overrides Tree-Display. */ /** - * \brief Tree-Display for the Library Overrides display mode. + * \brief Tree-Display for the Library Overrides display mode, Properties view mode. */ -class TreeDisplayOverrideLibrary final : public AbstractTreeDisplay { +class TreeDisplayOverrideLibraryProperties final : public AbstractTreeDisplay { public: - TreeDisplayOverrideLibrary(SpaceOutliner &space_outliner); + TreeDisplayOverrideLibraryProperties(SpaceOutliner &space_outliner); ListBase buildTree(const TreeSourceData &source_data) override; @@ -140,6 +142,22 @@ class TreeDisplayOverrideLibrary final : public AbstractTreeDisplay { short id_filter_get() const; }; +/** + * \brief Tree-Display for the Library Overrides display mode, Hierarchies view mode. + */ +class TreeDisplayOverrideLibraryHierarchies final : public AbstractTreeDisplay { + public: + TreeDisplayOverrideLibraryHierarchies(SpaceOutliner &space_outliner); + + ListBase buildTree(const TreeSourceData &source_data) override; + + private: + ListBase build_hierarchy_for_lib_or_main(Main *bmain, + TreeElement &parent_te, + Library *lib = nullptr); + void build_hierarchy_for_ID(Main *bmain, ID &override_root_id, TreeElementID &te_id) const; +}; + /* -------------------------------------------------------------------- */ /* Video Sequencer Tree-Display */ diff --git a/source/blender/editors/space_outliner/tree/tree_display_override_library_hierarchies.cc b/source/blender/editors/space_outliner/tree/tree_display_override_library_hierarchies.cc new file mode 100644 index 00000000000..4b568a6004d --- /dev/null +++ b/source/blender/editors/space_outliner/tree/tree_display_override_library_hierarchies.cc @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup spoutliner + */ + +#include "DNA_ID.h" +#include "DNA_collection_types.h" +#include "DNA_key_types.h" +#include "DNA_space_types.h" + +#include "BLI_listbase.h" +#include "BLI_map.hh" +#include "BLI_set.hh" + +#include "BLT_translation.h" + +#include "BKE_collection.h" +#include "BKE_lib_query.h" +#include "BKE_main.h" + +#include "../outliner_intern.hh" +#include "common.hh" +#include "tree_display.hh" +#include "tree_element_id.hh" + +namespace blender::ed::outliner { + +class AbstractTreeElement; + +TreeDisplayOverrideLibraryHierarchies::TreeDisplayOverrideLibraryHierarchies( + SpaceOutliner &space_outliner) + : AbstractTreeDisplay(space_outliner) +{ +} + +/* XXX Remove expanded subtree, we add our own items here. Expanding should probably be + * optional. */ +static void remove_expanded_children(TreeElement &te) +{ + outliner_free_tree(&te.subtree); +} + +ListBase TreeDisplayOverrideLibraryHierarchies::buildTree(const TreeSourceData &source_data) +{ + ListBase tree = {nullptr}; + + /* First step: Build "Current File" hierarchy. */ + TreeElement *current_file_te = outliner_add_element( + &space_outliner_, &tree, source_data.bmain, nullptr, TSE_ID_BASE, -1); + current_file_te->name = IFACE_("Current File"); + { + AbstractTreeElement::uncollapse_by_default(current_file_te); + build_hierarchy_for_lib_or_main(source_data.bmain, *current_file_te); + + /* Add dummy child if there's nothing to display. */ + if (BLI_listbase_is_empty(¤t_file_te->subtree)) { + TreeElement *dummy_te = outliner_add_element( + &space_outliner_, ¤t_file_te->subtree, nullptr, current_file_te, TSE_ID_BASE, 0); + dummy_te->name = IFACE_("No Library Overrides"); + } + } + + /* Second step: Build hierarchies for external libraries. */ + for (Library *lib = (Library *)source_data.bmain->libraries.first; lib; + lib = (Library *)lib->id.next) { + TreeElement *tenlib = outliner_add_element( + &space_outliner_, &tree, lib, nullptr, TSE_SOME_ID, 0); + build_hierarchy_for_lib_or_main(source_data.bmain, *tenlib, lib); + } + + /* Remove top level library elements again that don't contain any overrides. */ + LISTBASE_FOREACH_MUTABLE (TreeElement *, top_level_te, &tree) { + if (top_level_te == current_file_te) { + continue; + } + + if (BLI_listbase_is_empty(&top_level_te->subtree)) { + outliner_free_tree_element(top_level_te, &tree); + } + } + + return tree; +} + +ListBase TreeDisplayOverrideLibraryHierarchies::build_hierarchy_for_lib_or_main( + Main *bmain, TreeElement &parent_te, Library *lib) +{ + ListBase tree = {nullptr}; + + /* Keep track over which ID base elements were already added, and expand them once added. */ + Map<ID_Type, TreeElement *> id_base_te_map; + /* Index for the ID base elements ("Objects", "Materials", etc). */ + int base_index = 0; + + ID *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)) { + continue; + } + if (iter_id->lib != lib) { + continue; + } + + TreeElement *new_base_te = id_base_te_map.lookup_or_add_cb(GS(iter_id->name), [&]() { + TreeElement *new_te = outliner_add_element(&space_outliner_, + &parent_te.subtree, + lib ? (void *)lib : bmain, + &parent_te, + TSE_ID_BASE, + base_index++); + new_te->name = outliner_idcode_to_plural(GS(iter_id->name)); + return new_te; + }); + + TreeElement *new_id_te = outliner_add_element( + &space_outliner_, &new_base_te->subtree, iter_id, new_base_te, TSE_SOME_ID, 0); + remove_expanded_children(*new_id_te); + + build_hierarchy_for_ID(bmain, *iter_id, *tree_element_cast<TreeElementID>(new_id_te)); + } + FOREACH_MAIN_ID_END; + + return tree; +} + +struct BuildHierarchyForeachIDCbData { + /* Don't allow copies, the sets below would need deep copying. */ + 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) { + return IDWALK_RET_NOP; + } + + BuildHierarchyForeachIDCbData &build_data = *reinterpret_cast<BuildHierarchyForeachIDCbData *>( + cb_data->user_data); + /* Note that this may be an embedded ID (see #real_override_id). */ + ID &id = **cb_data->id_pointer; + /* If #id is an embedded ID, this will be set to the owner, which is a real ID and contains the + * 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)) { + if (GS(id.name) == ID_KE) { + Key *key = (Key *)&id; + real_override_id = key->from; + } + 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)) { + return IDWALK_RET_NOP; + } + /* 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. + */ + if (build_data.parent_ids.lookup_key_default(&id, nullptr)) { + return IDWALK_RET_NOP; + } + + /* Avoid duplicates: If there is a sibling for this ID already, the same ID is just used multiple + * times by the same parent. */ + if (build_data.sibling_ids.lookup_key_default(&id, nullptr)) { + return IDWALK_RET_NOP; + } + + TreeElement *new_te = outliner_add_element(&build_data.space_outliner, + &build_data.parent_te->getLegacyElement().subtree, + &id, + &build_data.parent_te->getLegacyElement(), + TSE_SOME_ID, + 0); + remove_expanded_children(*new_te); + build_data.sibling_ids.add(&id); + + BuildHierarchyForeachIDCbData child_build_data{build_data.bmain, + build_data.space_outliner, + 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.add(&id); + child_build_data.sibling_ids.reserve(10); + BKE_library_foreach_ID_link( + &build_data.bmain, &id, build_hierarchy_foreach_ID_cb, &child_build_data, IDWALK_READONLY); + + return IDWALK_RET_NOP; +} + +void TreeDisplayOverrideLibraryHierarchies::build_hierarchy_for_ID(Main *bmain, + ID &override_root_id, + TreeElementID &te_id) const +{ + BuildHierarchyForeachIDCbData build_data{*bmain, space_outliner_, override_root_id, &te_id}; + build_data.parent_ids.add(&override_root_id); + + BKE_library_foreach_ID_link( + bmain, &te_id.get_ID(), build_hierarchy_foreach_ID_cb, &build_data, IDWALK_READONLY); +} + +} // namespace blender::ed::outliner diff --git a/source/blender/editors/space_outliner/tree/tree_display_override_library.cc b/source/blender/editors/space_outliner/tree/tree_display_override_library_properties.cc index 2162c303da1..4c57f91a0a7 100644 --- a/source/blender/editors/space_outliner/tree/tree_display_override_library.cc +++ b/source/blender/editors/space_outliner/tree/tree_display_override_library_properties.cc @@ -25,12 +25,12 @@ namespace blender::ed::outliner { /* Convenience/readability. */ template<typename T> using List = ListBaseWrapper<T>; -TreeDisplayOverrideLibrary::TreeDisplayOverrideLibrary(SpaceOutliner &space_outliner) +TreeDisplayOverrideLibraryProperties::TreeDisplayOverrideLibraryProperties(SpaceOutliner &space_outliner) : AbstractTreeDisplay(space_outliner) { } -ListBase TreeDisplayOverrideLibrary::buildTree(const TreeSourceData &source_data) +ListBase TreeDisplayOverrideLibraryProperties::buildTree(const TreeSourceData &source_data) { ListBase tree = add_library_contents(*source_data.bmain); @@ -44,7 +44,7 @@ ListBase TreeDisplayOverrideLibrary::buildTree(const TreeSourceData &source_data return tree; } -ListBase TreeDisplayOverrideLibrary::add_library_contents(Main &mainvar) +ListBase TreeDisplayOverrideLibraryProperties::add_library_contents(Main &mainvar) { ListBase tree = {nullptr}; @@ -114,7 +114,7 @@ ListBase TreeDisplayOverrideLibrary::add_library_contents(Main &mainvar) return tree; } -short TreeDisplayOverrideLibrary::id_filter_get() const +short TreeDisplayOverrideLibraryProperties::id_filter_get() const { if (space_outliner_.filter & SO_FILTER_ID_TYPE) { return space_outliner_.filter_id_type; diff --git a/source/blender/editors/space_outliner/tree/tree_element.cc b/source/blender/editors/space_outliner/tree/tree_element.cc index ca67aad00db..7fe3f08b3be 100644 --- a/source/blender/editors/space_outliner/tree/tree_element.cc +++ b/source/blender/editors/space_outliner/tree/tree_element.cc @@ -98,6 +98,13 @@ std::unique_ptr<AbstractTreeElement> AbstractTreeElement::createFromType(const i return nullptr; } +void AbstractTreeElement::uncollapse_by_default(TreeElement *legacy_te) +{ + if (!TREESTORE(legacy_te)->used) { + TREESTORE(legacy_te)->flag &= ~TSE_CLOSED; + } +} + void tree_element_expand(const AbstractTreeElement &tree_element, SpaceOutliner &space_outliner) { /* Most types can just expand. IDs optionally expand (hence the poll) and do additional, common diff --git a/source/blender/editors/space_outliner/tree/tree_element.hh b/source/blender/editors/space_outliner/tree/tree_element.hh index 6f2d803ae96..c6593a517dd 100644 --- a/source/blender/editors/space_outliner/tree/tree_element.hh +++ b/source/blender/editors/space_outliner/tree/tree_element.hh @@ -50,6 +50,19 @@ class AbstractTreeElement { return true; } + TreeElement &getLegacyElement() + { + return legacy_te_; + } + + /** + * Expand this tree element if it is displayed for the first time (as identified by its + * tree-store element). + * + * Static for now to allow doing this from the legacy tree element. + */ + static void uncollapse_by_default(TreeElement *legacy_te); + friend void tree_element_expand(const AbstractTreeElement &tree_element, SpaceOutliner &space_outliner); diff --git a/source/blender/editors/space_outliner/tree/tree_element_id.hh b/source/blender/editors/space_outliner/tree/tree_element_id.hh index b7519fe06f9..c2bbfd9f107 100644 --- a/source/blender/editors/space_outliner/tree/tree_element_id.hh +++ b/source/blender/editors/space_outliner/tree/tree_element_id.hh @@ -35,6 +35,11 @@ class TreeElementID : public AbstractTreeElement { return false; } + ID &get_ID() + { + return id_; + } + protected: /* ID types with animation data can use this. */ void expand_animation_data(SpaceOutliner &, const AnimData *) const; diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 6d7952cb799..e9bf3126c97 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -280,8 +280,12 @@ typedef struct SpaceOutliner { char search_string[64]; struct TreeStoreElem search_tse; - short flag, outlinevis, storeflag; + short flag; + short outlinevis; + short lib_override_view_mode; + short storeflag; char search_flags; + char _pad[6]; /** Selection syncing flag (#WM_OUTLINER_SYNC_SELECT_FROM_OBJECT and similar flags). */ char sync_select_dirty; @@ -388,6 +392,14 @@ typedef enum eSpaceOutliner_Mode { SO_OVERRIDES_LIBRARY = 16, } eSpaceOutliner_Mode; +/** #SpaceOutliner.outlinevis */ +typedef enum eSpaceOutliner_LibOverrideViewMode { + /* View all overrides with RNA buttons to edit the overridden values. */ + SO_LIB_OVERRIDE_VIEW_PROPERTIES = 0, + /* View entire override hierarchies (relationships between overriden data-blocks). */ + SO_LIB_OVERRIDE_VIEW_HIERARCHIES = 1, +} eSpaceOutliner_LibOverrideViewMode; + /** #SpaceOutliner.storeflag */ typedef enum eSpaceOutliner_StoreFlag { /* cleanup tree */ diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index 3839b3ba6a4..e21c10166ab 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -3593,6 +3593,21 @@ static void rna_def_space_outliner(BlenderRNA *brna) {0, NULL, 0, NULL, NULL}, }; + static const EnumPropertyItem lib_override_view_mode[] = { + {SO_LIB_OVERRIDE_VIEW_PROPERTIES, + "PROPERTIES", + ICON_NONE, + "Properties", + "Display all local override data-blocks with their overridden properties and buttons to " + "edit them"}, + {SO_LIB_OVERRIDE_VIEW_HIERARCHIES, + "HIERARCHIES", + ICON_NONE, + "Hierarchies", + "Display library override relationships"}, + {0, NULL, 0, NULL, NULL}, + }; + static const EnumPropertyItem filter_state_items[] = { {SO_FILTER_OB_ALL, "ALL", 0, "All", "Show all objects in the view layer"}, {SO_FILTER_OB_VISIBLE, "VISIBLE", 0, "Visible", "Show visible objects"}, @@ -3612,6 +3627,13 @@ static void rna_def_space_outliner(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Display Mode", "Type of information to display"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_OUTLINER, NULL); + prop = RNA_def_property(srna, "lib_override_view_mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, lib_override_view_mode); + RNA_def_property_ui_text(prop, + "Library Override View Mode", + "Choose different visualizations of library override data"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_OUTLINER, NULL); + prop = RNA_def_property(srna, "filter_text", PROP_STRING, PROP_NONE); RNA_def_property_string_sdna(prop, NULL, "search_string"); RNA_def_property_ui_text(prop, "Display Filter", "Live search filtering string"); |