Fix #106246: Outliner: Crash on deleting the multi-level objects hierarchy.
Existing code was intrisically wrong, and also potentially very innefficient. Now tagging objects for deletion and removal is separated from actual collection removal and object deletion. This makes the code safer, simpler and more efficient. NOTE: There is still an unsolved (already existing in original code) issue with checks on objects that should not be deleted, noted in code. Pull Request: https://projects.blender.org/blender/blender/pulls/106821
This commit is contained in:
@@ -2167,39 +2167,18 @@ static void outliner_do_data_operation(
|
||||
});
|
||||
}
|
||||
|
||||
static Base *outliner_batch_delete_hierarchy(
|
||||
ReportList *reports, Main *bmain, ViewLayer *view_layer, Scene *scene, Base *base)
|
||||
static void outliner_batch_delete_object_tag(ReportList *reports,
|
||||
Main *bmain,
|
||||
Scene *scene,
|
||||
Object *object)
|
||||
{
|
||||
Base *child_base, *base_next;
|
||||
Object *object, *parent;
|
||||
|
||||
if (!base) {
|
||||
return nullptr;
|
||||
}
|
||||
BKE_view_layer_synced_ensure(scene, view_layer);
|
||||
object = base->object;
|
||||
for (child_base = static_cast<Base *>(BKE_view_layer_object_bases_get(view_layer)->first);
|
||||
child_base;
|
||||
child_base = base_next) {
|
||||
base_next = child_base->next;
|
||||
for (parent = child_base->object->parent; parent && (parent != object);
|
||||
parent = parent->parent) {
|
||||
/* pass */
|
||||
}
|
||||
if (parent) {
|
||||
base_next = outliner_batch_delete_hierarchy(reports, bmain, view_layer, scene, child_base);
|
||||
}
|
||||
}
|
||||
|
||||
base_next = base->next;
|
||||
|
||||
if (object->id.tag & LIB_TAG_INDIRECT) {
|
||||
BKE_reportf(reports,
|
||||
RPT_WARNING,
|
||||
"Cannot delete indirectly linked object '%s'",
|
||||
base->object->id.name + 2);
|
||||
return base_next;
|
||||
BKE_reportf(
|
||||
reports, RPT_WARNING, "Cannot delete indirectly linked object '%s'", object->id.name + 2);
|
||||
BLI_assert((object->id.tag & LIB_TAG_DOIT) == 0);
|
||||
}
|
||||
/* FIXME: This code checking object usercount won't work as expected if a same object belongs to
|
||||
* more than one collection in the scene. */
|
||||
if (ID_REAL_USERS(object) <= 1 && ID_EXTRA_USERS(object) == 0 &&
|
||||
BKE_library_ID_is_indirectly_used(bmain, object)) {
|
||||
BKE_reportf(reports,
|
||||
@@ -2208,41 +2187,105 @@ static Base *outliner_batch_delete_hierarchy(
|
||||
"one user",
|
||||
object->id.name + 2,
|
||||
scene->id.name + 2);
|
||||
return base_next;
|
||||
BLI_assert((object->id.tag & LIB_TAG_DOIT) == 0);
|
||||
}
|
||||
|
||||
DEG_id_tag_update_ex(bmain, &object->id, ID_RECALC_BASE_FLAGS);
|
||||
BKE_scene_collections_object_remove(bmain, scene, object, false);
|
||||
|
||||
if (object->id.us == 0) {
|
||||
object->id.tag |= LIB_TAG_DOIT;
|
||||
}
|
||||
|
||||
return base_next;
|
||||
object->id.tag |= LIB_TAG_DOIT;
|
||||
}
|
||||
|
||||
static void object_batch_delete_hierarchy_fn(bContext *C,
|
||||
ReportList *reports,
|
||||
Scene *scene,
|
||||
Object *ob)
|
||||
static void outliner_batch_delete_object_hierarchy_tag(
|
||||
ReportList *reports, Main *bmain, ViewLayer *view_layer, Scene *scene, Base *base)
|
||||
{
|
||||
Object *object = base->object;
|
||||
BLI_assert(object != nullptr);
|
||||
|
||||
outliner_batch_delete_object_tag(reports, bmain, scene, object);
|
||||
|
||||
/* Even though the object itself may not be deletable, some of its children may still be
|
||||
* deletable. */
|
||||
for (Base *base_iter = static_cast<Base *>(BKE_view_layer_object_bases_get(view_layer)->first);
|
||||
base_iter != nullptr;
|
||||
base_iter = base_iter->next) {
|
||||
Object *parent_ob_iter;
|
||||
for (parent_ob_iter = base_iter->object->parent;
|
||||
(parent_ob_iter != nullptr && parent_ob_iter != object &&
|
||||
(parent_ob_iter->id.tag & LIB_TAG_DOIT) == 0);
|
||||
parent_ob_iter = parent_ob_iter->parent) {
|
||||
/* pass */
|
||||
}
|
||||
if (parent_ob_iter != nullptr) {
|
||||
/* There is one or more parents to current iterated object that also need to be deleted,
|
||||
* process the parenting chain again to tag them as such.
|
||||
*
|
||||
* NOTE: Since objects that cannot be deleted are not tagged, the relevant 'parenting'
|
||||
* branches may be looped over more than once. Would not expect this to be a real issue in
|
||||
* practice though. */
|
||||
for (parent_ob_iter = base_iter->object;
|
||||
(parent_ob_iter != nullptr && parent_ob_iter != object &&
|
||||
(parent_ob_iter->id.tag & LIB_TAG_DOIT) == 0);
|
||||
parent_ob_iter = parent_ob_iter->parent) {
|
||||
outliner_batch_delete_object_tag(reports, bmain, scene, parent_ob_iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void object_batch_delete_hierarchy_tag_fn(bContext *C,
|
||||
ReportList *reports,
|
||||
Scene *scene,
|
||||
Object *ob)
|
||||
{
|
||||
if (ob->id.tag & LIB_TAG_DOIT) {
|
||||
/* Object has already been processed and tagged for removal as part of another parenting
|
||||
* hierarchy. */
|
||||
#ifndef NDEBUG
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
BKE_view_layer_synced_ensure(scene, view_layer);
|
||||
BLI_assert(BKE_view_layer_base_find(view_layer, ob) == nullptr);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
Object *obedit = CTX_data_edit_object(C);
|
||||
|
||||
BKE_view_layer_synced_ensure(scene, view_layer);
|
||||
Base *base = BKE_view_layer_base_find(view_layer, ob);
|
||||
|
||||
if (base) {
|
||||
/* Check also library later. */
|
||||
for (; obedit && (obedit != base->object); obedit = obedit->parent) {
|
||||
/* pass */
|
||||
}
|
||||
if (obedit == base->object) {
|
||||
ED_object_editmode_exit(C, EM_FREEDATA);
|
||||
if (base == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Exit Edit mode if the active object or one of its children are being edited. */
|
||||
for (; obedit && (obedit != base->object); obedit = obedit->parent) {
|
||||
/* pass */
|
||||
}
|
||||
if (obedit == base->object) {
|
||||
ED_object_editmode_exit(C, EM_FREEDATA);
|
||||
}
|
||||
|
||||
Main *bmain = CTX_data_main(C);
|
||||
outliner_batch_delete_object_hierarchy_tag(reports, bmain, view_layer, scene, base);
|
||||
}
|
||||
|
||||
static void outliner_batch_delete_object_hierarchy(Main *bmain, Scene *scene)
|
||||
{
|
||||
LISTBASE_FOREACH (Object *, ob_iter, &bmain->objects) {
|
||||
if ((ob_iter->id.tag & LIB_TAG_DOIT) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
outliner_batch_delete_hierarchy(reports, CTX_data_main(C), view_layer, scene, base);
|
||||
BKE_scene_collections_object_remove(bmain, scene, ob_iter, false);
|
||||
|
||||
/* Check on all objects tagged for deletion, these that are still in use (e.g. in collections
|
||||
* from another scene) should not be deleted. They also need to be tagged for depsgraph update.
|
||||
*/
|
||||
if (ob_iter->id.us != 0) {
|
||||
ob_iter->id.tag &= ~LIB_TAG_DOIT;
|
||||
DEG_id_tag_update_ex(bmain, &ob_iter->id, ID_RECALC_BASE_FLAGS);
|
||||
}
|
||||
}
|
||||
|
||||
BKE_id_multi_tagged_delete(bmain);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
@@ -2475,10 +2518,17 @@ static int outliner_delete_exec(bContext *C, wmOperator *op)
|
||||
if (delete_hierarchy) {
|
||||
BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false);
|
||||
|
||||
outliner_do_object_delete(
|
||||
C, op->reports, scene, object_delete_data.objects_set, object_batch_delete_hierarchy_fn);
|
||||
BKE_view_layer_synced_ensure(scene, view_layer);
|
||||
|
||||
BKE_id_multi_tagged_delete(bmain);
|
||||
/* #object_batch_delete_hierarchy_fn callback will only remove objects from collections and tag
|
||||
* them for deletion. */
|
||||
outliner_do_object_delete(C,
|
||||
op->reports,
|
||||
scene,
|
||||
object_delete_data.objects_set,
|
||||
object_batch_delete_hierarchy_tag_fn);
|
||||
|
||||
outliner_batch_delete_object_hierarchy(bmain, scene);
|
||||
}
|
||||
else {
|
||||
outliner_do_object_delete(
|
||||
|
||||
Reference in New Issue
Block a user