UI: List library overrides in the Outliner

Having a centeral place to find a list of all library overrides should be
useful for managing production scenes where library overrides are used a lot.
This change adds the individually overridden properties of a data-block under
the data-block itself. Just how we show modifiers, constraints or pose channels
there. This way we can also expose library override operations/options better
in future.

There's also a filter option for the library overrides now, so they can be
hidden. It is only available in the View Layer display mode though, like the
other filter options.

One internal change this has to do is adding more informative return values to
undo pushes and the library override functions called by it. That way we can
send a notifier when library overrides change for the Outliner to know when to
rebuild the tree.

Differential Revision: https://developer.blender.org/D7631

Reviewed by: Andy Goralczyk, Bastien Montagne, William Reynish
This commit is contained in:
Julian Eisel 2020-12-27 22:15:20 +01:00
parent 960a0b892c
commit aa64fd69e7
Notes: blender-bot 2023-02-14 11:20:29 +01:00
Referenced by issue #73318, Library overrides
16 changed files with 149 additions and 35 deletions

View File

@ -410,6 +410,10 @@ 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

View File

@ -95,6 +95,10 @@ struct IDOverrideLibraryProperty *BKE_lib_override_library_property_get(
struct IDOverrideLibrary *override, const char *rna_path, bool *r_created);
void BKE_lib_override_library_property_delete(struct IDOverrideLibrary *override,
struct IDOverrideLibraryProperty *override_property);
bool BKE_lib_override_rna_property_find(struct PointerRNA *idpoin,
const struct IDOverrideLibraryProperty *library_prop,
struct PointerRNA *r_override_poin,
struct PropertyRNA **r_override_prop);
struct IDOverrideLibraryPropertyOperation *BKE_lib_override_library_property_operation_find(
struct IDOverrideLibraryProperty *override_property,
@ -131,7 +135,7 @@ bool BKE_lib_override_library_status_check_local(struct Main *bmain, struct ID *
bool BKE_lib_override_library_status_check_reference(struct Main *bmain, struct ID *local);
bool BKE_lib_override_library_operations_create(struct Main *bmain, struct ID *local);
void BKE_lib_override_library_main_operations_create(struct Main *bmain, const bool force_auto);
bool BKE_lib_override_library_main_operations_create(struct Main *bmain, const bool force_auto);
void BKE_lib_override_library_id_reset(struct Main *bmain, struct ID *id_root);
void BKE_lib_override_library_id_hierarchy_reset(struct Main *bmain, struct ID *id_root);

View File

@ -96,6 +96,12 @@ typedef struct UndoStep {
/* Over alloc 'type->struct_size'. */
} UndoStep;
typedef enum UndoPushReturn {
UNDO_PUSH_RET_FAILURE = 0,
UNDO_PUSH_RET_SUCCESS = (1 << 0),
UNDO_PUSH_RET_OVERRIDE_CHANGED = (1 << 1),
} UndoPushReturn;
typedef void (*UndoTypeForEachIDRefFn)(void *user_data, struct UndoRefID *id_ref);
typedef struct UndoType {
@ -172,11 +178,11 @@ UndoStep *BKE_undosys_step_push_init_with_type(UndoStack *ustack,
const UndoType *ut);
UndoStep *BKE_undosys_step_push_init(UndoStack *ustack, struct bContext *C, const char *name);
bool BKE_undosys_step_push_with_type(UndoStack *ustack,
struct bContext *C,
const char *name,
const UndoType *ut);
bool BKE_undosys_step_push(UndoStack *ustack, struct bContext *C, const char *name);
UndoPushReturn BKE_undosys_step_push_with_type(UndoStack *ustack,
struct bContext *C,
const char *name,
const UndoType *ut);
UndoPushReturn BKE_undosys_step_push(UndoStack *ustack, struct bContext *C, const char *name);
UndoStep *BKE_undosys_step_find_by_name_with_type(UndoStack *ustack,
const char *name,

View File

@ -57,6 +57,8 @@
#include "RNA_access.h"
#include "RNA_types.h"
#include "atomic_ops.h"
#define OVERRIDE_AUTO_CHECK_DELAY 0.2 /* 200ms between auto-override checks. */
//#define DEBUG_OVERRIDE_TIMEIT
@ -982,6 +984,23 @@ IDOverrideLibraryProperty *BKE_lib_override_library_property_get(IDOverrideLibra
return op;
}
/**
* Get the RNA-property matching the \a library_prop override property. Used for UI to query
* additional data about the overriden property (e.g. UI name).
*
* \param idpoin: Pointer to the override ID.
* \param library_prop: The library override property to find the matching RNA property for.
*/
bool BKE_lib_override_rna_property_find(PointerRNA *idpoin,
const IDOverrideLibraryProperty *library_prop,
PointerRNA *r_override_poin,
PropertyRNA **r_override_prop)
{
BLI_assert(RNA_struct_is_ID(idpoin->type) && ID_IS_OVERRIDE_LIBRARY(idpoin->data));
return RNA_path_resolve_property(
idpoin, library_prop->rna_path, r_override_poin, r_override_prop);
}
void lib_override_library_property_copy(IDOverrideLibraryProperty *op_dst,
IDOverrideLibraryProperty *op_src)
{
@ -1370,19 +1389,20 @@ bool BKE_lib_override_library_status_check_reference(Main *bmain, ID *local)
* since it has to go over all properties in depth (all overridable ones at least).
* Generating differential values and applying overrides are much cheaper.
*
* \return true if a new overriding op was created, or some local data was reset. */
* \return true if any library operation was created.
*/
bool BKE_lib_override_library_operations_create(Main *bmain, ID *local)
{
BLI_assert(local->override_library != NULL);
const bool is_template = (local->override_library->reference == NULL);
bool ret = false;
bool created = false;
if (!is_template) {
/* Do not attempt to generate overriding rules from an empty place-holder generated by link
* code when it cannot find the actual library/ID. Much better to keep the local data-block as
* is in the file in that case, until broken lib is fixed. */
if (ID_MISSING(local->override_library->reference)) {
return ret;
return created;
}
if (GS(local->name) == ID_OB) {
@ -1412,14 +1432,16 @@ bool BKE_lib_override_library_operations_create(Main *bmain, ID *local)
local->override_library,
RNA_OVERRIDE_COMPARE_CREATE | RNA_OVERRIDE_COMPARE_RESTORE,
&report_flags);
if (report_flags & RNA_OVERRIDE_MATCH_RESULT_CREATED) {
ret = true;
created = true;
}
#ifndef NDEBUG
if (report_flags & RNA_OVERRIDE_MATCH_RESULT_RESTORED) {
printf("We did restore some properties of %s from its reference.\n", local->name);
}
if (ret) {
if (report_flags & RNA_OVERRIDE_MATCH_RESULT_CREATED) {
printf("We did generate library override rules for %s\n", local->name);
}
else {
@ -1427,19 +1449,28 @@ bool BKE_lib_override_library_operations_create(Main *bmain, ID *local)
}
#endif
}
return ret;
return created;
}
struct LibOverrideOpCreateData {
Main *bmain;
bool changed;
};
static void lib_override_library_operations_create_cb(TaskPool *__restrict pool, void *taskdata)
{
Main *bmain = BLI_task_pool_user_data(pool);
struct LibOverrideOpCreateData *create_data = BLI_task_pool_user_data(pool);
ID *id = taskdata;
BKE_lib_override_library_operations_create(bmain, id);
if (BKE_lib_override_library_operations_create(create_data->bmain, id)) {
/* Technically no need for atomic, all jobs write the same value and we only care if one did
* it. But play safe and avoid implicit assumptions. */
atomic_fetch_and_or_uint8((uint8_t *)&create_data->changed, true);
}
}
/** Check all overrides from given \a bmain and create/update overriding operations as needed. */
void BKE_lib_override_library_main_operations_create(Main *bmain, const bool force_auto)
bool BKE_lib_override_library_main_operations_create(Main *bmain, const bool force_auto)
{
ID *id;
@ -1464,7 +1495,8 @@ void BKE_lib_override_library_main_operations_create(Main *bmain, const bool for
}
}
TaskPool *task_pool = BLI_task_pool_create(bmain, TASK_PRIORITY_HIGH);
struct LibOverrideOpCreateData create_pool_data = {.bmain = bmain, .changed = false};
TaskPool *task_pool = BLI_task_pool_create(&create_pool_data, TASK_PRIORITY_HIGH);
FOREACH_MAIN_ID_BEGIN (bmain, id) {
if (ID_IS_OVERRIDE_LIBRARY_REAL(id) &&
@ -1503,6 +1535,8 @@ void BKE_lib_override_library_main_operations_create(Main *bmain, const bool for
#ifdef DEBUG_OVERRIDE_TIMEIT
TIMEIT_END_AVERAGED(BKE_lib_override_library_main_operations_create);
#endif
return create_pool_data.changed;
}
static bool lib_override_library_id_reset_do(Main *bmain, ID *id_root)

View File

@ -342,9 +342,10 @@ static bool undosys_stack_push_main(UndoStack *ustack, const char *name, struct
CLOG_INFO(&LOG, 1, "'%s'", name);
bContext *C_temp = CTX_create();
CTX_data_main_set(C_temp, bmain);
bool ok = BKE_undosys_step_push_with_type(ustack, C_temp, name, BKE_UNDOSYS_TYPE_MEMFILE);
UndoPushReturn ret = BKE_undosys_step_push_with_type(
ustack, C_temp, name, BKE_UNDOSYS_TYPE_MEMFILE);
CTX_free(C_temp);
return ok;
return (ret & UNDO_PUSH_RET_SUCCESS);
}
void BKE_undosys_stack_init_from_main(UndoStack *ustack, struct Main *bmain)
@ -495,18 +496,21 @@ UndoStep *BKE_undosys_step_push_init(UndoStack *ustack, bContext *C, const char
/**
* \param C: Can be NULL from some callers if their encoding function doesn't need it
*/
bool BKE_undosys_step_push_with_type(UndoStack *ustack,
bContext *C,
const char *name,
const UndoType *ut)
UndoPushReturn BKE_undosys_step_push_with_type(UndoStack *ustack,
bContext *C,
const char *name,
const UndoType *ut)
{
UNDO_NESTED_ASSERT(false);
undosys_stack_validate(ustack, false);
bool is_not_empty = ustack->step_active != NULL;
UndoPushReturn retval = UNDO_PUSH_RET_FAILURE;
/* Might not be final place for this to be called - probably only want to call it from some
* undo handlers, not all of them? */
BKE_lib_override_library_main_operations_create(G_MAIN, false);
if (BKE_lib_override_library_main_operations_create(G_MAIN, false)) {
retval |= UNDO_PUSH_RET_OVERRIDE_CHANGED;
}
/* Remove all undos after (also when 'ustack->step_active == NULL'). */
while (ustack->steps.last != ustack->step_active) {
@ -558,7 +562,7 @@ bool BKE_undosys_step_push_with_type(UndoStack *ustack,
if (!undosys_step_encode(C, G_MAIN, ustack, us)) {
MEM_freeN(us);
undosys_stack_validate(ustack, true);
return false;
return retval;
}
ustack->step_active = us;
BLI_addtail(&ustack->steps, us);
@ -589,10 +593,10 @@ bool BKE_undosys_step_push_with_type(UndoStack *ustack,
}
undosys_stack_validate(ustack, true);
return true;
return (retval | UNDO_PUSH_RET_SUCCESS);
}
bool BKE_undosys_step_push(UndoStack *ustack, bContext *C, const char *name)
UndoPushReturn BKE_undosys_step_push(UndoStack *ustack, bContext *C, const char *name)
{
UNDO_NESTED_ASSERT(false);
const UndoType *ut = ustack->step_init ? ustack->step_init->type :

View File

@ -3421,7 +3421,7 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
case SPACE_OUTLINER: {
SpaceOutliner *space_outliner = (SpaceOutliner *)sl;
space_outliner->filter &= ~(SO_FILTER_UNUSED_1 | SO_FILTER_UNUSED_5 |
space_outliner->filter &= ~(SO_FILTER_CLEARED_1 | SO_FILTER_UNUSED_5 |
SO_FILTER_OB_STATE_SELECTABLE);
space_outliner->storeflag &= ~(SO_TREESTORE_UNUSED_1);
break;

View File

@ -493,7 +493,7 @@ static bool parent_clear_poll(bContext *C,
case ID_OB:
return ELEM(tselem->type, TSE_MODIFIER_BASE, TSE_CONSTRAINT_BASE);
case ID_GR:
return event->shift;
return event->shift || ELEM(tselem->type, TSE_LIBRARY_OVERRIDE_BASE);
default:
return true;
}

View File

@ -2180,6 +2180,10 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te)
data.icon = ICON_MODIFIER_DATA;
data.drag_id = tselem->id;
break;
case TSE_LIBRARY_OVERRIDE_BASE:
case TSE_LIBRARY_OVERRIDE:
data.icon = ICON_LIBRARY_DATA_OVERRIDE;
break;
case TSE_LINKED_OB:
data.icon = ICON_OBJECT_DATA;
break;

View File

@ -331,7 +331,8 @@ static void do_item_rename(ARegion *region,
TSE_POSEGRP_BASE,
TSE_R_LAYER_BASE,
TSE_SCENE_COLLECTION_BASE,
TSE_VIEW_COLLECTION_BASE)) {
TSE_VIEW_COLLECTION_BASE,
TSE_LIBRARY_OVERRIDE_BASE)) {
BKE_report(reports, RPT_WARNING, "Cannot edit builtin name");
}
else if (ELEM(tselem->type, TSE_SEQUENCE, TSE_SEQ_STRIP, TSE_SEQUENCE_DUP)) {

View File

@ -66,6 +66,7 @@
#include "BKE_idtype.h"
#include "BKE_layer.h"
#include "BKE_lib_id.h"
#include "BKE_lib_override.h"
#include "BKE_main.h"
#include "BKE_modifier.h"
#include "BKE_outliner_treehash.h"
@ -640,6 +641,30 @@ static void outliner_add_object_contents(SpaceOutliner *space_outliner,
}
}
static void outliner_add_library_override_contents(SpaceOutliner *soops, TreeElement *te, ID *id)
{
if (!id->override_library) {
return;
}
PointerRNA idpoin;
RNA_id_pointer_create(id, &idpoin);
PointerRNA override_ptr;
PropertyRNA *override_prop;
int index = 0;
LISTBASE_FOREACH (IDOverrideLibraryProperty *, op, &id->override_library->properties) {
if (!BKE_lib_override_rna_property_find(&idpoin, op, &override_ptr, &override_prop)) {
BLI_assert(false);
continue;
}
TreeElement *ten = outliner_add_element(
soops, &te->subtree, id, te, TSE_LIBRARY_OVERRIDE, index++);
ten->name = RNA_property_ui_name(override_prop);
}
}
/* Can be inlined if necessary. */
static void outliner_add_id_contents(SpaceOutliner *space_outliner,
TreeElement *te,
@ -903,6 +928,17 @@ static void outliner_add_id_contents(SpaceOutliner *space_outliner,
default:
break;
}
const bool lib_overrides_visible = !SUPPORT_FILTER_OUTLINER(space_outliner) ||
((space_outliner->filter & SO_FILTER_NO_LIB_OVERRIDE) == 0);
if (lib_overrides_visible && ID_IS_OVERRIDE_LIBRARY(id)) {
TreeElement *ten = outliner_add_element(
space_outliner, &te->subtree, id, te, TSE_LIBRARY_OVERRIDE_BASE, 0);
ten->name = IFACE_("Library Overrides");
outliner_add_library_override_contents(space_outliner, ten, id);
}
}
/**

View File

@ -112,6 +112,12 @@ static void outliner_main_region_listener(wmWindow *UNUSED(win),
/* context changes */
switch (wmn->category) {
case NC_WM:
switch (wmn->data) {
case ND_LIB_OVERRIDE_CHANGED:
ED_region_tag_redraw(region);
break;
}
case NC_SCENE:
switch (wmn->data) {
case ND_OB_ACTIVE:
@ -152,8 +158,6 @@ static void outliner_main_region_listener(wmWindow *UNUSED(win),
case NC_OBJECT:
switch (wmn->data) {
case ND_TRANSFORM:
/* transform doesn't change outliner data */
break;
case ND_BONE_ACTIVE:
case ND_BONE_SELECT:
case ND_DRAW:

View File

@ -145,12 +145,14 @@ void ED_undo_push(bContext *C, const char *str)
}
}
UndoPushReturn push_retval;
/* Only apply limit if this is the last undo step. */
if (wm->undo_stack->step_active && (wm->undo_stack->step_active->next == NULL)) {
BKE_undosys_stack_limit_steps_and_memory(wm->undo_stack, steps - 1, 0);
}
BKE_undosys_step_push(wm->undo_stack, C, str);
push_retval = BKE_undosys_step_push(wm->undo_stack, C, str);
if (U.undomemory != 0) {
const size_t memory_limit = (size_t)U.undomemory * 1024 * 1024;
@ -160,6 +162,10 @@ void ED_undo_push(bContext *C, const char *str)
if (CLOG_CHECK(&LOG, 1)) {
BKE_undosys_print(wm->undo_stack);
}
if (push_retval & UNDO_PUSH_RET_OVERRIDE_CHANGED) {
WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL);
}
}
/**

View File

@ -117,6 +117,8 @@ enum {
#define TSE_SCENE_OBJECTS_BASE 41
#define TSE_GPENCIL_EFFECT_BASE 42
#define TSE_GPENCIL_EFFECT 43
#define TSE_LIBRARY_OVERRIDE_BASE 44
#define TSE_LIBRARY_OVERRIDE 45
/* Check whether given TreeStoreElem should have a real ID in its ->id member. */
#define TSE_IS_REAL_ID(_tse) \

View File

@ -305,8 +305,9 @@ typedef enum eSpaceOutliner_Flag {
/* SpaceOutliner.filter */
typedef enum eSpaceOutliner_Filter {
SO_FILTER_SEARCH = (1 << 0), /* Run-time flag. */
SO_FILTER_UNUSED_1 = (1 << 1), /* cleared */
SO_FILTER_SEARCH = (1 << 0), /* Run-time flag. */
SO_FILTER_CLEARED_1 = (1 << 1),
SO_FILTER_NO_LIB_OVERRIDE = SO_FILTER_CLEARED_1, /* re-use */
SO_FILTER_NO_OBJECT = (1 << 2),
SO_FILTER_NO_OB_CONTENT = (1 << 3), /* Not only mesh, but modifiers, constraints, ... */
SO_FILTER_NO_CHILDREN = (1 << 4),
@ -339,7 +340,7 @@ typedef enum eSpaceOutliner_Filter {
#define SO_FILTER_ANY \
(SO_FILTER_NO_OB_CONTENT | SO_FILTER_NO_CHILDREN | SO_FILTER_OB_TYPE | SO_FILTER_OB_STATE | \
SO_FILTER_NO_COLLECTION)
SO_FILTER_NO_COLLECTION | SO_FILTER_NO_LIB_OVERRIDE)
/* SpaceOutliner.filter_state */
typedef enum eSpaceOutliner_StateFilter {

View File

@ -3442,6 +3442,13 @@ static void rna_def_space_outliner(BlenderRNA *brna)
RNA_def_property_enum_items(prop, rna_enum_id_type_items);
RNA_def_property_ui_text(prop, "Filter by Type", "Data-block type to show");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_ID);
prop = RNA_def_property(srna, "use_filter_lib_override", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_negative_sdna(prop, NULL, "filter", SO_FILTER_NO_LIB_OVERRIDE);
RNA_def_property_ui_text(prop,
"Show Library Overrides",
"For libraries with overrides created, show the overriden values");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_OUTLINER, NULL);
}
static void rna_def_space_view3d_shading(BlenderRNA *brna)

View File

@ -312,6 +312,7 @@ typedef struct wmNotifier {
#define ND_JOB (5 << 16)
#define ND_UNDO (6 << 16)
#define ND_XR_DATA_CHANGED (7 << 16)
#define ND_LIB_OVERRIDE_CHANGED (8 << 16)
/* NC_SCREEN */
#define ND_LAYOUTBROWSE (1 << 16)