LibOverride: Add a dedicated view in the Outliner.

This is a minimal, information-only view currently, listing by default
all the override data-blocks, with their user-edited override
properties.

System-generated overrides (like the overrides of pointers to other
override data-blocks) can be shown through a filter option.

Finally, potential info or warning messages from (auto-)resync propcess
are also shown, as an icon + tooltip next to the affected items.

Part of D10855.
This commit is contained in:
Bastien Montagne 2021-04-07 15:58:34 +02:00
parent 8b68a75872
commit e1ae5bd45f
15 changed files with 414 additions and 26 deletions

View File

@ -51,13 +51,13 @@ class OUTLINER_HT_header(Header):
row.prop(space, "use_sync_select", icon='UV_SYNC_SELECT', text="")
row = layout.row(align=True)
if display_mode in {'SCENES', 'VIEW_LAYER'}:
if display_mode in {'SCENES', 'VIEW_LAYER', 'LIBRARY_OVERRIDES'}:
row.popover(
panel="OUTLINER_PT_filter",
text="",
icon='FILTER',
)
elif display_mode in {'LIBRARIES', 'ORPHAN_DATA'}:
if 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
@ -341,19 +341,26 @@ class OUTLINER_PT_filter(Panel):
col = layout.column(align=True)
col.prop(space, "use_sort_alpha")
row = layout.row(align=True)
row.prop(space, "use_sync_select", text="Sync Selection")
if display_mode not in {'LIBRARY_OVERRIDES'}:
row = layout.row(align=True)
row.prop(space, "use_sync_select", text="Sync Selection")
row = layout.row(align=True)
row.prop(space, "show_mode_column", text="Show Mode Column")
layout.separator()
row = layout.row(align=True)
row.prop(space, "show_mode_column", text="Show Mode Column")
layout.separator()
col = layout.column(align=True)
col.label(text="Search")
col.prop(space, "use_filter_complete", text="Exact Match")
col.prop(space, "use_filter_case_sensitive", text="Case Sensitive")
if display_mode != 'VIEW_LAYER':
if display_mode in {'LIBRARY_OVERRIDES'} and bpy.data.libraries:
col.separator()
row = col.row()
row.label(icon='LIBRARY_DATA_OVERRIDE')
row.prop(space, "use_filter_lib_override_system", text="System Overrides")
if display_mode not in {'VIEW_LAYER'}:
return
layout.separator()
@ -405,10 +412,6 @@ class OUTLINER_PT_filter(Panel):
row = sub.row()
row.label(icon='EMPTY_DATA')
row.prop(space, "use_filter_object_empty", text="Empties")
row = sub.row()
if bpy.data.libraries:
row.label(icon='LIBRARY_DATA_OVERRIDE')
row.prop(space, "use_filter_lib_override", text="Library Overrides")
if (
bpy.data.curves or
@ -425,6 +428,16 @@ class OUTLINER_PT_filter(Panel):
row.label(icon='BLANK1')
row.prop(space, "use_filter_object_others", text="Others")
if bpy.data.libraries:
col.separator()
row = col.row()
row.label(icon='LIBRARY_DATA_OVERRIDE')
row.prop(space, "use_filter_lib_override", text="Library Overrides")
row = col.row()
row.label(icon='LIBRARY_DATA_OVERRIDE')
row.prop(space, "use_filter_lib_override_system", text="System Overrides")
classes = (
OUTLINER_HT_header,

View File

@ -51,6 +51,7 @@ 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_scenes.cc
tree/tree_display_sequencer.cc
tree/tree_display_view_layer.cc

View File

@ -1775,6 +1775,88 @@ static void outliner_draw_userbuts(uiBlock *block,
}
}
static bool outliner_draw_overrides_buts(uiBlock *block,
ARegion *region,
SpaceOutliner *space_outliner,
ListBase *lb,
const bool is_open)
{
bool any_item_has_warnings = false;
LISTBASE_FOREACH (TreeElement *, te, lb) {
bool item_has_warnings = false;
const bool do_draw = (te->ys + 2 * UI_UNIT_Y >= region->v2d.cur.ymin &&
te->ys <= region->v2d.cur.ymax);
int but_flag = UI_BUT_DRAG_LOCK;
const char *tip = NULL;
TreeStoreElem *tselem = TREESTORE(te);
switch (tselem->type) {
case TSE_LIBRARY_OVERRIDE_BASE: {
ID *id = tselem->id;
if (ID_IS_OVERRIDE_LIBRARY_REAL(id) && ID_REAL_USERS(id) == 0) {
item_has_warnings = true;
if (do_draw) {
tip = TIP_("This override data-block is unused");
}
}
else if (id->flag & LIB_LIB_OVERRIDE_RESYNC_LEFTOVER) {
item_has_warnings = true;
if (do_draw) {
tip = TIP_(
"This override data-block is not needed anymore, but was detected as user-edited");
}
}
break;
}
case TSE_LIBRARY_OVERRIDE: {
const bool is_rna_path_valid = (bool)(POINTER_AS_UINT(te->directdata));
if (!is_rna_path_valid) {
item_has_warnings = true;
if (do_draw) {
tip = TIP_(
"This override property does not exist in current data, it will be removed on "
"next .blend file save");
}
}
break;
}
default:
break;
}
const bool any_child_has_warnings = outliner_draw_overrides_buts(
block,
region,
space_outliner,
&te->subtree,
is_open && TSELEM_OPEN(tselem, space_outliner));
if (do_draw &&
(item_has_warnings || (any_child_has_warnings && !TSELEM_OPEN(tselem, space_outliner)))) {
if (tip == NULL) {
tip = TIP_("Some sub-items require attention");
}
uiBut *bt = uiDefIconBlockBut(block,
NULL,
NULL,
1,
ICON_ERROR,
(int)(region->v2d.cur.xmax - OL_TOG_USER_BUTS_STATUS),
te->ys,
UI_UNIT_X,
UI_UNIT_Y,
tip);
UI_but_flag_enable(bt, but_flag);
}
any_item_has_warnings = any_item_has_warnings || item_has_warnings || any_child_has_warnings;
}
return any_item_has_warnings;
}
static void outliner_draw_rnacols(ARegion *region, int sizex)
{
View2D *v2d = &region->v2d;
@ -2896,7 +2978,13 @@ static void outliner_draw_iconrow(bContext *C,
active = tree_element_type_active_state_get(C, tvc, te, tselem);
}
if (!ELEM(tselem->type, TSE_SOME_ID, TSE_LAYER_COLLECTION, TSE_R_LAYER, TSE_GP_LAYER)) {
if (!ELEM(tselem->type,
TSE_SOME_ID,
TSE_LAYER_COLLECTION,
TSE_R_LAYER,
TSE_GP_LAYER,
TSE_LIBRARY_OVERRIDE_BASE,
TSE_LIBRARY_OVERRIDE)) {
outliner_draw_iconrow_doit(block, te, fstyle, xmax, offsx, ys, alpha_fac, active, 1);
}
else {
@ -3656,7 +3744,11 @@ void draw_outliner(const bContext *C)
}
/* Sync selection state from view layer. */
if (!ELEM(space_outliner->outlinevis, SO_LIBRARIES, SO_DATA_API, SO_ID_ORPHANS) &&
if (!ELEM(space_outliner->outlinevis,
SO_LIBRARIES,
SO_OVERRIDES_LIBRARY,
SO_DATA_API,
SO_ID_ORPHANS) &&
space_outliner->flag & SO_SYNC_SELECT) {
outliner_sync_selection(C, space_outliner);
}
@ -3703,6 +3795,10 @@ 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_buts(block, region, space_outliner, &space_outliner->tree, true);
}
else if (restrict_column_width > 0.0f) {
/* draw restriction columns */
RestrictPropertiesActive props_active;

View File

@ -193,7 +193,7 @@ typedef enum {
/* The outliner display modes that support the filter system.
* Note: keep it synced with space_outliner.py */
#define SUPPORT_FILTER_OUTLINER(space_outliner_) \
(ELEM((space_outliner_)->outlinevis, SO_VIEW_LAYER))
(ELEM((space_outliner_)->outlinevis, SO_VIEW_LAYER, SO_OVERRIDES_LIBRARY))
/* Outliner Searching --
*

View File

@ -356,8 +356,11 @@ static void outliner_sync_selection_from_outliner(Scene *scene,
void ED_outliner_select_sync_from_outliner(bContext *C, SpaceOutliner *space_outliner)
{
/* Don't sync if not checked or in certain outliner display modes */
if (!(space_outliner->flag & SO_SYNC_SELECT) ||
ELEM(space_outliner->outlinevis, SO_LIBRARIES, SO_DATA_API, SO_ID_ORPHANS)) {
if (!(space_outliner->flag & SO_SYNC_SELECT) || ELEM(space_outliner->outlinevis,
SO_LIBRARIES,
SO_OVERRIDES_LIBRARY,
SO_DATA_API,
SO_ID_ORPHANS)) {
return;
}

View File

@ -908,6 +908,11 @@ TreeElement *outliner_add_element(SpaceOutliner *space_outliner,
BLI_assert(!"Expected this ID type to be ported to new Outliner tree-element design");
}
}
else if (ELEM(type, TSE_LIBRARY_OVERRIDE_BASE, TSE_LIBRARY_OVERRIDE)) {
if (!te->type) {
BLI_assert(!"Expected override types to be ported to new Outliner tree-element design");
}
}
else {
/* Other cases must be caught above. */
BLI_assert(TSE_IS_REAL_ID(tselem));

View File

@ -361,6 +361,7 @@ float outliner_restrict_columns_width(const SpaceOutliner *space_outliner)
case SO_DATA_API:
case SO_SEQUENCE:
case SO_LIBRARIES:
case SO_OVERRIDES_LIBRARY:
return 0.0f;
case SO_ID_ORPHANS:
num_columns = 3;

View File

@ -273,7 +273,7 @@ static void outliner_main_region_message_subscribe(const wmRegionMessageSubscrib
.notify = ED_region_do_msg_notify_tag_redraw,
};
if (ELEM(space_outliner->outlinevis, SO_VIEW_LAYER, SO_SCENES)) {
if (ELEM(space_outliner->outlinevis, SO_VIEW_LAYER, SO_SCENES, SO_OVERRIDES_LIBRARY)) {
WM_msg_subscribe_rna_anon_prop(mbus, Window, view_layer, &msg_sub_value_region_tag_redraw);
}
}

View File

@ -44,6 +44,9 @@ TreeDisplay *outliner_tree_display_create(eSpaceOutliner_Mode mode, SpaceOutline
case SO_ID_ORPHANS:
tree_display = new TreeDisplayIDOrphans(*space_outliner);
break;
case SO_OVERRIDES_LIBRARY:
tree_display = new TreeDisplayOverrideLibrary(*space_outliner);
break;
case SO_VIEW_LAYER:
default:
tree_display = new TreeDisplayViewLayer(*space_outliner);

View File

@ -110,6 +110,24 @@ class TreeDisplayLibraries final : public AbstractTreeDisplay {
short id_filter_get() const;
};
/* -------------------------------------------------------------------- */
/* Library Overrides Tree-Display. */
/**
* \brief Tree-Display for the Library Overrides display mode.
*/
class TreeDisplayOverrideLibrary final : public AbstractTreeDisplay {
public:
TreeDisplayOverrideLibrary(SpaceOutliner &space_outliner);
ListBase buildTree(const TreeSourceData &source_data) override;
private:
TreeElement *add_library_contents(Main &, ListBase &, Library *) const;
bool override_library_id_filter_poll(Library *lib, ID *id) const;
short id_filter_get() const;
};
/* -------------------------------------------------------------------- */
/* Video Sequencer Tree-Display */

View File

@ -0,0 +1,204 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup spoutliner
*/
#include "BLI_listbase.h"
#include "BLI_listbase_wrapper.hh"
#include "BKE_collection.h"
#include "BKE_main.h"
#include "DNA_collection_types.h"
#include "BLT_translation.h"
#include "../outliner_intern.h"
#include "tree_display.hh"
namespace blender::ed::outliner {
/* Convenience/readability. */
template<typename T> using List = ListBaseWrapper<T>;
TreeDisplayOverrideLibrary::TreeDisplayOverrideLibrary(SpaceOutliner &space_outliner)
: AbstractTreeDisplay(space_outliner)
{
}
ListBase TreeDisplayOverrideLibrary::buildTree(const TreeSourceData &source_data)
{
ListBase tree = {nullptr};
{
/* current file first - mainvar provides tselem with unique pointer - not used */
TreeElement *ten = add_library_contents(*source_data.bmain, tree, nullptr);
TreeStoreElem *tselem;
if (ten) {
tselem = TREESTORE(ten);
if (!tselem->used) {
tselem->flag &= ~TSE_CLOSED;
}
}
}
for (ID *id : List<ID>(source_data.bmain->libraries)) {
Library *lib = reinterpret_cast<Library *>(id);
TreeElement *ten = add_library_contents(*source_data.bmain, tree, lib);
/* NULL-check matters, due to filtering there may not be a new element. */
if (ten) {
lib->id.newid = (ID *)ten;
}
}
/* make hierarchy */
for (TreeElement *ten : List<TreeElement>(tree)) {
if (ten == tree.first) {
/* First item is main, skip. */
continue;
}
TreeStoreElem *tselem = TREESTORE(ten);
Library *lib = (Library *)tselem->id;
BLI_assert(!lib || (GS(lib->id.name) == ID_LI));
if (!lib || !lib->parent) {
continue;
}
TreeElement *parent = (TreeElement *)lib->parent->id.newid;
if (tselem->id->tag & LIB_TAG_INDIRECT) {
/* Only remove from 'first level' if lib is not also directly used. */
BLI_remlink(&tree, ten);
BLI_addtail(&parent->subtree, ten);
ten->parent = parent;
}
else {
/* Else, make a new copy of the libtree for our parent. */
TreeElement *dupten = add_library_contents(*source_data.bmain, parent->subtree, lib);
if (dupten) {
dupten->parent = parent;
}
}
}
/* restore newid pointers */
for (ID *library_id : List<ID>(source_data.bmain->libraries)) {
library_id->newid = nullptr;
}
return tree;
}
TreeElement *TreeDisplayOverrideLibrary::add_library_contents(Main &mainvar,
ListBase &lb,
Library *lib) const
{
const short filter_id_type = id_filter_get();
ListBase *lbarray[INDEX_ID_MAX];
int tot;
if (filter_id_type) {
lbarray[0] = which_libbase(&mainvar, space_outliner_.filter_id_type);
tot = 1;
}
else {
tot = set_listbasepointers(&mainvar, lbarray);
}
TreeElement *tenlib = nullptr;
for (int a = 0; a < tot; a++) {
if (!lbarray[a] || !lbarray[a]->first) {
continue;
}
ID *id = nullptr;
/* check if there's data in current lib */
for (ID *id_iter : List<ID>(lbarray[a])) {
if (id_iter->lib == lib && ID_IS_OVERRIDE_LIBRARY_REAL(id_iter)) {
id = id_iter;
break;
}
}
if (id != nullptr) {
if (!tenlib) {
/* Create library tree element on demand, depending if there are any data-blocks. */
if (lib) {
tenlib = outliner_add_element(&space_outliner_, &lb, lib, nullptr, TSE_SOME_ID, 0);
}
else {
tenlib = outliner_add_element(&space_outliner_, &lb, &mainvar, nullptr, TSE_ID_BASE, 0);
tenlib->name = IFACE_("Current File");
}
}
/* Create data-block list parent element on demand. */
if (id != nullptr) {
TreeElement *ten;
if (filter_id_type) {
ten = tenlib;
}
else {
ten = outliner_add_element(
&space_outliner_, &tenlib->subtree, lbarray[a], nullptr, TSE_ID_BASE, 0);
ten->directdata = lbarray[a];
ten->name = outliner_idcode_to_plural(GS(id->name));
}
for (ID *id : List<ID>(lbarray[a])) {
if (override_library_id_filter_poll(lib, id)) {
TreeElement *override_tree_element = outliner_add_element(
&space_outliner_, &ten->subtree, id, ten, TSE_LIBRARY_OVERRIDE_BASE, 0);
if (BLI_listbase_is_empty(&override_tree_element->subtree)) {
outliner_free_tree_element(override_tree_element, &ten->subtree);
}
}
}
}
}
}
return tenlib;
}
short TreeDisplayOverrideLibrary::id_filter_get() const
{
if (space_outliner_.filter & SO_FILTER_ID_TYPE) {
return space_outliner_.filter_id_type;
}
return 0;
}
bool TreeDisplayOverrideLibrary::override_library_id_filter_poll(Library *lib, ID *id) const
{
if (id->lib != lib) {
return false;
}
if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
return false;
}
return true;
}
} // namespace blender::ed::outliner

View File

@ -40,13 +40,24 @@ TreeElementOverridesBase::TreeElementOverridesBase(TreeElement &legacy_te, ID &i
: AbstractTreeElement(legacy_te), id_(id)
{
BLI_assert(legacy_te.store_elem->type == TSE_LIBRARY_OVERRIDE_BASE);
legacy_te.name = IFACE_("Library Overrides");
if (legacy_te.parent != nullptr &&
ELEM(legacy_te.parent->store_elem->type, TSE_SOME_ID, TSE_LAYER_COLLECTION))
{
legacy_te.name = IFACE_("Library Overrides");
}
else {
legacy_te.name = id.name + 2;
}
}
void TreeElementOverridesBase::expand(SpaceOutliner &space_outliner) const
{
BLI_assert(id_.override_library != nullptr);
const bool show_system_overrides = (SUPPORT_FILTER_OUTLINER(&space_outliner) &&
(space_outliner.filter & SO_FILTER_SHOW_SYSTEM_OVERRIDES) !=
0);
PointerRNA idpoin;
RNA_id_pointer_create(&id_, &idpoin);
@ -56,15 +67,26 @@ void TreeElementOverridesBase::expand(SpaceOutliner &space_outliner) const
for (auto *override_prop :
ListBaseWrapper<IDOverrideLibraryProperty>(id_.override_library->properties)) {
if (!BKE_lib_override_rna_property_find(
&idpoin, override_prop, &override_rna_ptr, &override_rna_prop)) {
/* This is fine, override properties list is not always fully up-to-date with current
* RNA/IDProps etc., this gets cleaned up when re-generating the overrides rules,
* no error here. */
continue;
const bool is_rna_path_valid = BKE_lib_override_rna_property_find(
&idpoin, override_prop, &override_rna_ptr, &override_rna_prop);
if (is_rna_path_valid && !show_system_overrides &&
ELEM(override_prop->rna_prop_type, PROP_POINTER, PROP_COLLECTION) &&
RNA_struct_is_ID(RNA_property_pointer_type(&override_rna_ptr, override_rna_prop))) {
bool do_continue = true;
for (auto *override_prop_op :
ListBaseWrapper<IDOverrideLibraryPropertyOperation>(override_prop->operations)) {
if ((override_prop_op->flag & IDOVERRIDE_LIBRARY_FLAG_IDPOINTER_MATCH_REFERENCE) == 0) {
do_continue = false;
break;
}
}
if (do_continue) {
continue;
}
}
TreeElementOverridesData data = {id_, *override_prop};
TreeElementOverridesData data = {id_, *override_prop, is_rna_path_valid};
outliner_add_element(
&space_outliner, &legacy_te_.subtree, &data, &legacy_te_, TSE_LIBRARY_OVERRIDE, index++);
}
@ -79,6 +101,9 @@ TreeElementOverridesProperty::TreeElementOverridesProperty(TreeElement &legacy_t
BLI_assert(legacy_te.store_elem->type == TSE_LIBRARY_OVERRIDE);
legacy_te.name = override_prop_.rna_path;
/* Abusing this for now, better way to do it is also pending current refacor of the whole tree
* code to use CPP... */
legacy_te.directdata = POINTER_FROM_UINT(override_data.is_rna_path_valid);
}
} // namespace blender::ed::outliner

View File

@ -27,6 +27,7 @@ namespace blender::ed::outliner {
struct TreeElementOverridesData {
ID &id;
IDOverrideLibraryProperty &override_property;
bool is_rna_path_valid;
};
class TreeElementOverridesBase final : public AbstractTreeElement {

View File

@ -323,6 +323,8 @@ typedef enum eSpaceOutliner_Filter {
SO_FILTER_NO_CHILDREN = (1 << 4),
SO_FILTER_UNUSED_5 = (1 << 5), /* cleared */
/** Show overrides that are defined/controlled by Blender. */
SO_FILTER_SHOW_SYSTEM_OVERRIDES = SO_FILTER_UNUSED_5, /* re-use */
SO_FILTER_NO_OB_MESH = (1 << 6),
SO_FILTER_NO_OB_ARMATURE = (1 << 7),
SO_FILTER_NO_OB_EMPTY = (1 << 8),
@ -391,6 +393,7 @@ typedef enum eSpaceOutliner_Mode {
/* SO_KEYMAP = 13, */ /* deprecated! */
SO_ID_ORPHANS = 14,
SO_VIEW_LAYER = 15,
SO_OVERRIDES_LIBRARY = 16,
} eSpaceOutliner_Mode;
/* SpaceOutliner.storeflag */

View File

@ -3399,6 +3399,11 @@ static void rna_def_space_outliner(BlenderRNA *brna)
ICON_RNA,
"Data API",
"Display low level Blender data and its properties"},
{SO_OVERRIDES_LIBRARY,
"LIBRARY_OVERRIDES",
ICON_LIBRARY_DATA_OVERRIDE,
"Library Overrides",
"Display data-blocks with library overrides and list their overridden properties"},
{SO_ID_ORPHANS,
"ORPHAN_DATA",
ICON_ORPHAN_DATA,
@ -3588,6 +3593,16 @@ static void rna_def_space_outliner(BlenderRNA *brna)
"Show Library Overrides",
"For libraries with overrides created, show the overridden values");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_OUTLINER, NULL);
prop = RNA_def_property(srna, "use_filter_lib_override_system", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "filter", SO_FILTER_SHOW_SYSTEM_OVERRIDES);
RNA_def_property_ui_text(
prop,
"Show System Overrides",
"For libraries with overrides created, show the overridden values that are "
"defined/controlled automatically (e.g. to make users of an overridden data-block point to "
"the override data, not the original linked data)");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_OUTLINER, NULL);
}
static void rna_def_space_view3d_shading(BlenderRNA *brna)