Fix (studio-reported) crash when using 'Clear' on a liboverride hierarchy in the Outliner.

Usual issue of modifying the data hierarchy while iterating on it in its
outliner representation. And usual fix for this problem (only store data
to be processed during the iteration over Outliner data, and perform the
actual operation in a single call afterwards).

This commit also tries to improve a bit the 'Clear' process when applied
to a bunch of IDs, by iterating several times over the list of IDs to
clear, and only processing 'liboverrides leaves' first. That way, if
clearing a liboverride leaves turn other liboverrides into leaves, then
can also be processed that way, instead of just being reset.

Note that this is a naive and quite imperfect solution though
(dependency islands cannot be handled that way e.g.), this feature needs
a complete re-write at some point to be more complete and consistent.
Don't think this is high priority though, as it is not really that
useful in practice afaik.

Note: This should be backported to 3.6 LTS (and maybe 3.3 LTS if
applicable).
This commit is contained in:
Bastien Montagne
2023-06-28 18:39:35 +02:00
parent f89b32382a
commit a60c1e5bb8

View File

@@ -1441,54 +1441,97 @@ static void id_override_library_reset_fn(bContext *C,
}
}
static void id_override_library_clear_single_fn(bContext *C,
static void id_override_library_clear_single_process(bContext *C,
ReportList * /*reports*/,
OutlinerLibOverrideData &data)
{
Main *bmain = CTX_data_main(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Scene *scene = CTX_data_scene(C);
/* TODO: At some point this likely needs to be re-written as a BKE function instead, with better
* handling of hierarchies among other things. */
/* Try to process all potential leaves first (deleting some liboverride leaves will turn other
* liboverrides into leaves as well). */
bool do_process_leaves = true;
while (!data.id_hierarchy_roots.is_empty()) {
bool has_found_leaves = false;
for (auto &&id : data.id_hierarchy_roots.keys()) {
if (do_process_leaves) {
if (BKE_lib_override_library_is_hierarchy_leaf(bmain, id)) {
/* If given ID is not using any other override (it's a 'leaf' in the override hierarchy),
* delete it and remap its usages to its linked reference. Otherwise, keep it as a reset
* system override. */
bool do_remap_active = false;
BKE_view_layer_synced_ensure(scene, view_layer);
if (BKE_view_layer_active_object_get(view_layer) == reinterpret_cast<Object *>(id)) {
BLI_assert(GS(id->name) == ID_OB);
do_remap_active = true;
}
BKE_libblock_remap(
bmain, id, id->override_library->reference, ID_REMAP_SKIP_INDIRECT_USAGE);
if (do_remap_active) {
Object *ref_object = reinterpret_cast<Object *>(id->override_library->reference);
Base *basact = BKE_view_layer_base_find(view_layer, ref_object);
if (basact != nullptr) {
view_layer->basact = basact;
}
DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
}
BKE_id_delete(bmain, id);
data.id_hierarchy_roots.remove(id);
has_found_leaves = true;
}
}
else {
BLI_assert(!BKE_lib_override_library_is_hierarchy_leaf(bmain, id));
BKE_lib_override_library_id_reset(bmain, id, true);
data.id_hierarchy_roots.remove(id);
}
}
do_process_leaves = has_found_leaves;
}
DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS | ID_RECALC_COPY_ON_WRITE);
}
static void id_override_library_clear_single_fn(bContext * /*C*/,
ReportList *reports,
Scene *scene,
Scene * /*scene*/,
TreeElement * /*te*/,
TreeStoreElem * /*tsep*/,
TreeStoreElem *tselem,
void * /*user_data*/)
void *user_data)
{
OutlinerLibOverrideData *data = reinterpret_cast<OutlinerLibOverrideData *>(user_data);
BLI_assert(TSE_IS_REAL_ID(tselem));
Main *bmain = CTX_data_main(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
ID *id = tselem->id;
if (!ID_IS_OVERRIDE_LIBRARY_REAL(id) || ID_IS_LINKED(id)) {
if (!ID_IS_OVERRIDE_LIBRARY(id)) {
return;
}
if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
BKE_reportf(reports,
RPT_WARNING,
"Cannot clear embedded library override id '%s', only overrides of real "
"data-blocks can be directly deleted",
"Cannot clear embedded library override '%s', only overrides of real data-blocks "
"can be directly cleared",
id->name);
return;
}
/* If given ID is not using any other override (it's a 'leaf' in the override hierarchy),
* delete it and remap its usages to its linked reference. Otherwise, keep it as a reset system
* override. */
if (BKE_lib_override_library_is_hierarchy_leaf(bmain, id)) {
bool do_remap_active = false;
BKE_view_layer_synced_ensure(CTX_data_scene(C), view_layer);
if (BKE_view_layer_active_object_get(view_layer) == reinterpret_cast<Object *>(id)) {
BLI_assert(GS(id->name) == ID_OB);
do_remap_active = true;
}
BKE_libblock_remap(bmain, id, id->override_library->reference, ID_REMAP_SKIP_INDIRECT_USAGE);
if (do_remap_active) {
Object *ref_object = reinterpret_cast<Object *>(id->override_library->reference);
Base *basact = BKE_view_layer_base_find(view_layer, ref_object);
if (basact != nullptr) {
view_layer->basact = basact;
}
DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
}
BKE_id_delete(bmain, id);
}
else {
BKE_lib_override_library_id_reset(bmain, id, true);
if (ID_IS_LINKED(id)) {
BKE_reportf(
reports,
RPT_WARNING,
"Cannot clear linked library override '%s', only local overrides can be directly cleared",
id->name);
return;
}
DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS | ID_RECALC_COPY_ON_WRITE);
data->id_root_set(id);
}
static void id_override_library_resync_fn(bContext * /*C*/,
@@ -1895,14 +1938,21 @@ static int outliner_liboverride_operation_exec(bContext *C, wmOperator *op)
break;
}
case OUTLINER_LIBOVERRIDE_OP_CLEAR_SINGLE: {
OutlinerLibOverrideData override_data{};
override_data.do_hierarchy = false;
override_data.do_fully_editable = false;
outliner_do_libdata_operation_selection_set(C,
op->reports,
scene,
space_outliner,
id_override_library_clear_single_fn,
selection_set,
nullptr,
&override_data,
false);
id_override_library_clear_single_process(C, op->reports, override_data);
ED_undo_push(C, "Clear Overridden Data");
break;
}