diff options
Diffstat (limited to 'source/blender/editors/space_outliner')
7 files changed, 544 insertions, 330 deletions
diff --git a/source/blender/editors/space_outliner/outliner_dragdrop.cc b/source/blender/editors/space_outliner/outliner_dragdrop.cc index e20958c1b1e..7435fa50a93 100644 --- a/source/blender/editors/space_outliner/outliner_dragdrop.cc +++ b/source/blender/editors/space_outliner/outliner_dragdrop.cc @@ -1124,8 +1124,7 @@ static Collection *collection_parent_from_ID(ID *id) return nullptr; } -static bool collection_drop_init( - bContext *C, wmDrag *drag, const int xy[2], const bool is_link, CollectionDrop *data) +static bool collection_drop_init(bContext *C, wmDrag *drag, const int xy[2], CollectionDrop *data) { /* Get collection to drop into. */ TreeElementInsertType insert_type; @@ -1157,9 +1156,6 @@ static bool collection_drop_init( /* Get collection to drag out of. */ ID *parent = drag_id->from_parent; Collection *from_collection = collection_parent_from_ID(parent); - if (is_link) { - from_collection = nullptr; - } /* Currently this should not be allowed, cannot edit items in an override of a Collection. */ if (from_collection != nullptr && ID_IS_OVERRIDE_LIBRARY(from_collection)) { @@ -1197,29 +1193,22 @@ static bool collection_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event bool changed = outliner_flag_set(*space_outliner, TSE_HIGHLIGHTED_ANY | TSE_DRAG_ANY, false); CollectionDrop data; - if (((event->modifier & KM_SHIFT) == 0) && - collection_drop_init(C, drag, event->xy, event->modifier & KM_CTRL, &data)) { + if (((event->modifier & KM_SHIFT) == 0) && collection_drop_init(C, drag, event->xy, &data)) { TreeElement *te = data.te; TreeStoreElem *tselem = TREESTORE(te); - if (!data.from || event->modifier & KM_CTRL) { - tselem->flag |= TSE_DRAG_INTO; - changed = true; - } - else { - switch (data.insert_type) { - case TE_INSERT_BEFORE: - tselem->flag |= TSE_DRAG_BEFORE; - changed = true; - break; - case TE_INSERT_AFTER: - tselem->flag |= TSE_DRAG_AFTER; - changed = true; - break; - case TE_INSERT_INTO: { - tselem->flag |= TSE_DRAG_INTO; - changed = true; - break; - } + switch (data.insert_type) { + case TE_INSERT_BEFORE: + tselem->flag |= TSE_DRAG_BEFORE; + changed = true; + break; + case TE_INSERT_AFTER: + tselem->flag |= TSE_DRAG_AFTER; + changed = true; + break; + case TE_INSERT_INTO: { + tselem->flag |= TSE_DRAG_INTO; + changed = true; + break; } } if (changed) { @@ -1242,30 +1231,49 @@ static char *collection_drop_tooltip(bContext *C, const wmEvent *event = win ? win->eventstate : nullptr; CollectionDrop data; - if (event && ((event->modifier & KM_SHIFT) == 0) && - collection_drop_init(C, drag, xy, event->modifier & KM_CTRL, &data)) { - TreeElement *te = data.te; - if (!data.from || event->modifier & KM_CTRL) { - return BLI_strdup(TIP_("Link inside Collection")); + if (event && ((event->modifier & KM_SHIFT) == 0) && collection_drop_init(C, drag, xy, &data)) { + const bool is_link = !data.from || (event->modifier & KM_CTRL); + + /* Test if we are moving within same parent collection. */ + bool same_level = false; + LISTBASE_FOREACH (CollectionParent *, parent, &data.to->parents) { + if (data.from == parent->collection) { + same_level = true; + } } + + /* Tooltips when not moving directly into another collection i.e. mouse on border of + * collections. Later we will decide which tooltip to return. */ + const bool tooltip_link = (is_link && !same_level); + const char *tooltip_before = tooltip_link ? TIP_("Link before collection") : + TIP_("Move before collection"); + const char *tooltip_between = tooltip_link ? TIP_("Link between collections") : + TIP_("Move between collections"); + const char *tooltip_after = tooltip_link ? TIP_("Link after collection") : + TIP_("Move after collection"); + + TreeElement *te = data.te; switch (data.insert_type) { case TE_INSERT_BEFORE: if (te->prev && outliner_is_collection_tree_element(te->prev)) { - return BLI_strdup(TIP_("Move between collections")); + return BLI_strdup(tooltip_between); } else { - return BLI_strdup(TIP_("Move before collection")); + return BLI_strdup(tooltip_before); } break; case TE_INSERT_AFTER: if (te->next && outliner_is_collection_tree_element(te->next)) { - return BLI_strdup(TIP_("Move between collections")); + return BLI_strdup(tooltip_between); } else { - return BLI_strdup(TIP_("Move after collection")); + return BLI_strdup(tooltip_after); } break; case TE_INSERT_INTO: { + if (is_link) { + return BLI_strdup(TIP_("Link inside collection")); + } /* Check the type of the drag IDs to avoid the incorrect "Shift to parent" * for collections. Checking the type of the first ID works fine here since @@ -1296,7 +1304,7 @@ static int collection_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmE wmDrag *drag = reinterpret_cast<wmDrag *>(lb->first); CollectionDrop data; - if (!collection_drop_init(C, drag, event->xy, event->modifier & KM_CTRL, &data)) { + if (!collection_drop_init(C, drag, event->xy, &data)) { return OPERATOR_CANCELLED; } @@ -1447,7 +1455,7 @@ static int outliner_item_drag_drop_invoke(bContext *C, TSE_GPENCIL_EFFECT_BASE); const int wm_drag_type = use_datastack_drag ? WM_DRAG_DATASTACK : WM_DRAG_ID; - wmDrag *drag = WM_event_start_drag(C, data.icon, wm_drag_type, nullptr, 0.0, WM_DRAG_NOP); + wmDrag *drag = WM_drag_data_create(C, data.icon, wm_drag_type, nullptr, 0.0, WM_DRAG_NOP); if (use_datastack_drag) { TreeElement *te_bone = nullptr; @@ -1537,6 +1545,8 @@ static int outliner_item_drag_drop_invoke(bContext *C, WM_drag_add_local_ID(drag, data.drag_id, data.drag_parent); } + WM_event_start_prepared_drag(C, drag); + ED_outliner_select_sync_from_outliner(C, space_outliner); return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH); diff --git a/source/blender/editors/space_outliner/outliner_draw.cc b/source/blender/editors/space_outliner/outliner_draw.cc index 753de83a10d..6bab0b938e8 100644 --- a/source/blender/editors/space_outliner/outliner_draw.cc +++ b/source/blender/editors/space_outliner/outliner_draw.cc @@ -37,6 +37,7 @@ #include "BKE_lib_override.h" #include "BKE_library.h" #include "BKE_main.h" +#include "BKE_main_namemap.h" #include "BKE_modifier.h" #include "BKE_node.h" #include "BKE_object.h" @@ -677,6 +678,7 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) TreeElement *te = outliner_find_tree_element(&space_outliner->tree, tselem); if (tselem->type == TSE_SOME_ID) { + BKE_main_namemap_remove_name(bmain, tselem->id, oldname); BLI_libblock_ensure_unique_name(bmain, tselem->id->name); WM_msg_publish_rna_prop(mbus, tselem->id, tselem->id, ID, name); @@ -742,6 +744,7 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) } case TSE_NLA_ACTION: { bAction *act = (bAction *)tselem->id; + BKE_main_namemap_remove_name(bmain, &act->id, oldname); BLI_libblock_ensure_unique_name(bmain, act->id.name); WM_msg_publish_rna_prop(mbus, &act->id, &act->id, ID, name); break; @@ -852,6 +855,7 @@ static void namebutton_fn(bContext *C, void *tsep, char *oldname) case TSE_LAYER_COLLECTION: { /* The ID is a #Collection, not a #LayerCollection */ Collection *collection = (Collection *)tselem->id; + BKE_main_namemap_remove_name(bmain, &collection->id, oldname); BLI_libblock_ensure_unique_name(bmain, collection->id.name); WM_msg_publish_rna_prop(mbus, &collection->id, &collection->id, ID, name); WM_event_add_notifier(C, NC_ID | NA_RENAME, nullptr); diff --git a/source/blender/editors/space_outliner/outliner_edit.cc b/source/blender/editors/space_outliner/outliner_edit.cc index c4a9398a5f7..32860bc2cff 100644 --- a/source/blender/editors/space_outliner/outliner_edit.cc +++ b/source/blender/editors/space_outliner/outliner_edit.cc @@ -447,7 +447,7 @@ void OUTLINER_OT_item_rename(wmOperatorType *ot) /** \name ID Delete Operator * \{ */ -static void id_delete(bContext *C, ReportList *reports, TreeElement *te, TreeStoreElem *tselem) +static void id_delete_tag(bContext *C, ReportList *reports, TreeElement *te, TreeStoreElem *tselem) { Main *bmain = CTX_data_main(C); ID *id = tselem->id; @@ -484,35 +484,39 @@ static void id_delete(bContext *C, ReportList *reports, TreeElement *te, TreeSto return; } if (te->idcode == ID_WS) { - BKE_workspace_id_tag_all_visible(bmain, LIB_TAG_DOIT); - if (id->tag & LIB_TAG_DOIT) { + BKE_workspace_id_tag_all_visible(bmain, LIB_TAG_PRE_EXISTING); + if (id->tag & LIB_TAG_PRE_EXISTING) { BKE_reportf( reports, RPT_WARNING, "Cannot delete currently visible workspace id '%s'", id->name); + BKE_main_id_tag_idcode(bmain, ID_WS, LIB_TAG_PRE_EXISTING, false); return; } + BKE_main_id_tag_idcode(bmain, ID_WS, LIB_TAG_PRE_EXISTING, false); } - BKE_id_delete(bmain, id); + id->tag |= LIB_TAG_DOIT; WM_event_add_notifier(C, NC_WINDOW, nullptr); } -void id_delete_fn(bContext *C, - ReportList *reports, - Scene *UNUSED(scene), - TreeElement *te, - TreeStoreElem *UNUSED(tsep), - TreeStoreElem *tselem, - void *UNUSED(user_data)) +void id_delete_tag_fn(bContext *C, + ReportList *reports, + Scene *UNUSED(scene), + TreeElement *te, + TreeStoreElem *UNUSED(tsep), + TreeStoreElem *tselem, + void *UNUSED(user_data)) { - id_delete(C, reports, te, tselem); + id_delete_tag(C, reports, te, tselem); } -static int outliner_id_delete_invoke_do(bContext *C, - ReportList *reports, - TreeElement *te, - const float mval[2]) +static int outliner_id_delete_tag(bContext *C, + ReportList *reports, + TreeElement *te, + const float mval[2]) { + int id_tagged_num = 0; + if (mval[1] > te->ys && mval[1] < te->ys + UI_UNIT_Y) { TreeStoreElem *tselem = TREESTORE(te); @@ -522,26 +526,27 @@ static int outliner_id_delete_invoke_do(bContext *C, RPT_ERROR_INVALID_INPUT, "Cannot delete indirectly linked library '%s'", ((Library *)tselem->id)->filepath_abs); - return OPERATOR_CANCELLED; } - id_delete(C, reports, te, tselem); - return OPERATOR_FINISHED; + else { + id_delete_tag(C, reports, te, tselem); + id_tagged_num++; + } } } else { LISTBASE_FOREACH (TreeElement *, te_sub, &te->subtree) { - int ret; - if ((ret = outliner_id_delete_invoke_do(C, reports, te_sub, mval))) { - return ret; + if ((id_tagged_num += outliner_id_delete_tag(C, reports, te_sub, mval)) != 0) { + break; } } } - return 0; + return id_tagged_num; } static int outliner_id_delete_invoke(bContext *C, wmOperator *op, const wmEvent *event) { + Main *bmain = CTX_data_main(C); ARegion *region = CTX_wm_region(C); SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); float fmval[2]; @@ -550,15 +555,21 @@ static int outliner_id_delete_invoke(bContext *C, wmOperator *op, const wmEvent UI_view2d_region_to_view(®ion->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]); + int id_tagged_num = 0; + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); LISTBASE_FOREACH (TreeElement *, te, &space_outliner->tree) { - int ret; - - if ((ret = outliner_id_delete_invoke_do(C, op->reports, te, fmval))) { - return ret; + if ((id_tagged_num += outliner_id_delete_tag(C, op->reports, te, fmval)) != 0) { + break; } } + if (id_tagged_num == 0) { + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + return OPERATOR_CANCELLED; + } - return OPERATOR_CANCELLED; + BKE_id_multi_tagged_delete(bmain); + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + return OPERATOR_FINISHED; } void OUTLINER_OT_id_delete(wmOperatorType *ot) diff --git a/source/blender/editors/space_outliner/outliner_intern.hh b/source/blender/editors/space_outliner/outliner_intern.hh index a0dcb49aa43..18173b37123 100644 --- a/source/blender/editors/space_outliner/outliner_intern.hh +++ b/source/blender/editors/space_outliner/outliner_intern.hh @@ -437,13 +437,13 @@ void lib_reload_fn(struct bContext *C, struct TreeStoreElem *tselem, void *user_data); -void id_delete_fn(struct bContext *C, - struct ReportList *reports, - struct Scene *scene, - struct TreeElement *te, - struct TreeStoreElem *tsep, - struct TreeStoreElem *tselem, - void *user_data); +void id_delete_tag_fn(struct bContext *C, + struct ReportList *reports, + struct Scene *scene, + struct TreeElement *te, + struct TreeStoreElem *tsep, + struct TreeStoreElem *tselem, + void *user_data); void id_remap_fn(struct bContext *C, struct ReportList *reports, struct Scene *scene, diff --git a/source/blender/editors/space_outliner/outliner_tools.cc b/source/blender/editors/space_outliner/outliner_tools.cc index ec19e8d5e5b..80ee3bbb6a1 100644 --- a/source/blender/editors/space_outliner/outliner_tools.cc +++ b/source/blender/editors/space_outliner/outliner_tools.cc @@ -31,8 +31,11 @@ #include "BLI_blenlib.h" #include "BLI_ghash.h" +#include "BLI_linklist.h" +#include "BLI_map.hh" #include "BLI_set.hh" #include "BLI_utildefines.h" +#include "BLI_vector.hh" #include "BKE_anim_data.h" #include "BKE_animsys.h" @@ -90,7 +93,9 @@ static CLG_LogRef LOG = {"ed.outliner.tools"}; using namespace blender::ed::outliner; +using blender::Map; using blender::Set; +using blender::Vector; /* -------------------------------------------------------------------- */ /** \name ID/Library/Data Set/Un-link Utilities @@ -777,6 +782,25 @@ static void id_local_fn(bContext *C, } } +struct OutlinerLiboverrideDataIDRoot { + /** The linked ID that was selected for override. */ + ID *id_root_reference; + + /** The root of the override hierarchy to which the override of `id_root` belongs, once + * known/created. */ + ID *id_hierarchy_root_override; + + /** The ID that was detected as being a good candidate as instanciation hint for newly overridden + * objects, may be null. + * + * \note Typically currently only used when the root ID to override is a collection instanced by + * an emtpy object. */ + ID *id_instance_hint; + + /** If this override comes from an instancing object (which would be `id_instance_hint` then). */ + bool is_override_instancing_object; +}; + struct OutlinerLibOverrideData { bool do_hierarchy; @@ -789,21 +813,44 @@ struct OutlinerLibOverrideData { * solving broken overrides while not losing *all* of your overrides. */ bool do_resync_hierarchy_enforce; - /** The override hierarchy root, when known/created. */ - ID *id_hierarchy_root_override; - - /** A hash of the selected tree elements' ID 'uuid'. Used to clear 'system override' flags on + /** A set of the selected tree elements' ID 'uuid'. Used to clear 'system override' flags on * their newly-created liboverrides in post-process step of override hierarchy creation. */ Set<uint> selected_id_uid; + + /** A mapping from the found hierarchy roots to a linked list of IDs to override for each of + * these roots. + * + * \note the key may be either linked (in which case it will be replaced by the newly created + * override), or an actual already existing override. */ + Map<ID *, Vector<OutlinerLiboverrideDataIDRoot>> id_hierarchy_roots; + + /** All 'session_uuid' of all hierarchy root IDs used or created by the operation. */ + Set<uint> id_hierarchy_roots_uid; + + void id_root_add(ID *id_hierarchy_root_reference, + ID *id_root_reference, + ID *id_instance_hint, + const bool is_override_instancing_object) + { + OutlinerLiboverrideDataIDRoot id_root_data; + id_root_data.id_root_reference = id_root_reference; + id_root_data.id_hierarchy_root_override = nullptr; + id_root_data.id_instance_hint = id_instance_hint; + id_root_data.is_override_instancing_object = is_override_instancing_object; + + Vector<OutlinerLiboverrideDataIDRoot> &value = id_hierarchy_roots.lookup_or_add_default( + id_hierarchy_root_reference); + value.append(id_root_data); + } }; /* Store 'UUID' of IDs of selected elements in the Outliner tree, before generating the override * hierarchy. */ -static void id_override_library_create_hierarchy_pre_process_fn(bContext *UNUSED(C), - ReportList *UNUSED(reports), +static void id_override_library_create_hierarchy_pre_process_fn(bContext *C, + ReportList *reports, Scene *UNUSED(scene), - TreeElement *UNUSED(te), - TreeStoreElem *UNUSED(tsep), + TreeElement *te, + TreeStoreElem *tsep, TreeStoreElem *tselem, void *user_data) { @@ -829,28 +876,6 @@ static void id_override_library_create_hierarchy_pre_process_fn(bContext *UNUSED } FOREACH_COLLECTION_OBJECT_RECURSIVE_END; } -} - -static void id_override_library_create_fn(bContext *C, - ReportList *reports, - Scene *scene, - TreeElement *te, - TreeStoreElem *tsep, - TreeStoreElem *tselem, - void *user_data) -{ - BLI_assert(TSE_IS_REAL_ID(tselem)); - - /* We can only safely apply this operation on one item at a time, so only do it on the active - * one. */ - if ((tselem->flag & TSE_ACTIVE) == 0) { - return; - } - - ID *id_root_reference = tselem->id; - OutlinerLibOverrideData *data = reinterpret_cast<OutlinerLibOverrideData *>(user_data); - const bool do_hierarchy = data->do_hierarchy; - bool success = false; ID *id_instance_hint = nullptr; bool is_override_instancing_object = false; @@ -866,172 +891,242 @@ static void id_override_library_create_fn(bContext *C, } } - if (ID_IS_OVERRIDABLE_LIBRARY(id_root_reference) || - (ID_IS_LINKED(id_root_reference) && do_hierarchy)) { - Main *bmain = CTX_data_main(C); + if (!ID_IS_OVERRIDABLE_LIBRARY(id_root_reference) && + !(ID_IS_LINKED(id_root_reference) && do_hierarchy)) { + return; + } - id_root_reference->tag |= LIB_TAG_DOIT; + Main *bmain = CTX_data_main(C); - /* For now, remap all local usages of linked ID to local override one here. */ - ID *id_iter; - FOREACH_MAIN_ID_BEGIN (bmain, id_iter) { - if (ID_IS_LINKED(id_iter)) { - id_iter->tag &= ~LIB_TAG_DOIT; - } - else { - id_iter->tag |= LIB_TAG_DOIT; + if (do_hierarchy) { + /* Tag all linked parents in tree hierarchy to be also overridden. */ + ID *id_hierarchy_root_reference = id_root_reference; + while ((te = te->parent) != nullptr) { + if (!TSE_IS_REAL_ID(te->store_elem)) { + continue; } - } - FOREACH_MAIN_ID_END; - if (do_hierarchy) { - /* Tag all linked parents in tree hierarchy to be also overridden. */ - ID *id_hierarchy_root_reference = id_root_reference; - while ((te = te->parent) != nullptr) { - if (!TSE_IS_REAL_ID(te->store_elem)) { + /* Tentative hierarchy root. */ + ID *id_current_hierarchy_root = te->store_elem->id; + + /* If the parent ID is from a different library than the reference root one, we are done + * with upwards tree processing in any case. */ + if (id_current_hierarchy_root->lib != id_root_reference->lib) { + if (ID_IS_OVERRIDE_LIBRARY_VIRTUAL(id_current_hierarchy_root)) { + /* Virtual overrides (i.e. embedded IDs), we can simply keep processing their parent to + * get an actual real override. */ continue; } - /* Tentative hierarchy root. */ - ID *id_current_hierarchy_root = te->store_elem->id; - - /* If the parent ID is from a different library than the reference root one, we are done - * with upwards tree processing in any case. */ - if (id_current_hierarchy_root->lib != id_root_reference->lib) { - if (ID_IS_OVERRIDE_LIBRARY_VIRTUAL(id_current_hierarchy_root)) { - /* Virtual overrides (i.e. embedded IDs), we can simply keep processing their parent to - * get an actual real override. */ - continue; - } - - /* If the parent ID is already an override, and is valid (i.e. local override), we can - * access its hierarchy root directly. */ - if (!ID_IS_LINKED(id_current_hierarchy_root) && - ID_IS_OVERRIDE_LIBRARY_REAL(id_current_hierarchy_root) && - id_current_hierarchy_root->override_library->reference->lib == - id_root_reference->lib) { - id_hierarchy_root_reference = - id_current_hierarchy_root->override_library->hierarchy_root; - BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference)); - break; - } - - if (ID_IS_LINKED(id_current_hierarchy_root)) { - /* No local 'anchor' was found for the hierarchy to override, do not proceed, as this - * would most likely generate invisible/confusing/hard to use and manage overrides. */ - BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); - BKE_reportf(reports, - RPT_WARNING, - "Invalid anchor ('%s') found, needed to create library override from " - "data-block '%s'", - id_current_hierarchy_root->name, - id_root_reference->name); - return; - } - - /* In all other cases, `id_current_hierarchy_root` cannot be a valid hierarchy root, so - * current `id_hierarchy_root_reference` is our best candidate. */ - + /* If the parent ID is already an override, and is valid (i.e. local override), we can + * access its hierarchy root directly. */ + if (!ID_IS_LINKED(id_current_hierarchy_root) && + ID_IS_OVERRIDE_LIBRARY_REAL(id_current_hierarchy_root) && + id_current_hierarchy_root->override_library->reference->lib == + id_root_reference->lib) { + id_hierarchy_root_reference = + id_current_hierarchy_root->override_library->hierarchy_root; + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference)); break; } - /* If some element in the tree needs to be overridden, but its ID is not overridable, - * abort. */ - if (!ID_IS_OVERRIDABLE_LIBRARY_HIERARCHY(id_current_hierarchy_root)) { + if (ID_IS_LINKED(id_current_hierarchy_root)) { + /* No local 'anchor' was found for the hierarchy to override, do not proceed, as this + * would most likely generate invisible/confusing/hard to use and manage overrides. */ BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); BKE_reportf(reports, RPT_WARNING, - "Could not create library override from data-block '%s', one of its parents " - "is not overridable ('%s')", - id_root_reference->name, - id_current_hierarchy_root->name); + "Invalid anchor ('%s') found, needed to create library override from " + "data-block '%s'", + id_current_hierarchy_root->name, + id_root_reference->name); return; } - id_current_hierarchy_root->tag |= LIB_TAG_DOIT; - id_hierarchy_root_reference = id_current_hierarchy_root; + + /* In all other cases, `id_current_hierarchy_root` cannot be a valid hierarchy root, so + * current `id_hierarchy_root_reference` is our best candidate. */ + + break; } - /* That case can happen when linked data is a complex mix involving several libraries and/or - * linked overrides. E.g. a mix of overrides from one library, and indirectly linked data - * from another library. Do not try to support such cases for now. */ - if (!((id_hierarchy_root_reference->lib == id_root_reference->lib) || - (!ID_IS_LINKED(id_hierarchy_root_reference) && - ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference) && - id_hierarchy_root_reference->override_library->reference->lib == - id_root_reference->lib))) { + /* If some element in the tree needs to be overridden, but its ID is not overridable, + * abort. */ + if (!ID_IS_OVERRIDABLE_LIBRARY_HIERARCHY(id_current_hierarchy_root)) { BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); BKE_reportf(reports, RPT_WARNING, - "Invalid hierarchy root ('%s') found, needed to create library override from " - "data-block '%s'", - id_hierarchy_root_reference->name, - id_root_reference->name); + "Could not create library override from data-block '%s', one of its parents " + "is not overridable ('%s')", + id_root_reference->name, + id_current_hierarchy_root->name); return; } + id_current_hierarchy_root->tag |= LIB_TAG_DOIT; + id_hierarchy_root_reference = id_current_hierarchy_root; + } + + /* That case can happen when linked data is a complex mix involving several libraries and/or + * linked overrides. E.g. a mix of overrides from one library, and indirectly linked data + * from another library. Do not try to support such cases for now. */ + if (!((id_hierarchy_root_reference->lib == id_root_reference->lib) || + (!ID_IS_LINKED(id_hierarchy_root_reference) && + ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference) && + id_hierarchy_root_reference->override_library->reference->lib == + id_root_reference->lib))) { + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + BKE_reportf(reports, + RPT_WARNING, + "Invalid hierarchy root ('%s') found, needed to create library override from " + "data-block '%s'", + id_hierarchy_root_reference->name, + id_root_reference->name); + return; + } + + data->id_root_add(id_hierarchy_root_reference, + id_root_reference, + id_instance_hint, + is_override_instancing_object); + } + else if (ID_IS_OVERRIDABLE_LIBRARY(id_root_reference)) { + data->id_root_add( + id_root_reference, id_root_reference, id_instance_hint, is_override_instancing_object); + } +} +static void id_override_library_create_hierarchy( + Main &bmain, + Scene *scene, + ViewLayer *view_layer, + OutlinerLibOverrideData &data, + ID *id_hierarchy_root_reference, + Vector<OutlinerLiboverrideDataIDRoot> &data_idroots, + bool &r_aggregated_success) +{ + BLI_assert(ID_IS_LINKED(id_hierarchy_root_reference) || + ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_reference)); + + const bool do_hierarchy = data.do_hierarchy; + + /* NOTE: This process is not the most efficient, but allows to re-use existing code. + * If this becomes a bottle-neck at some point, we need to implement a new + * `BKE_lib_override_library_hierarchy_create()` function able to process several roots inside of + * a same hierarchy in a single call. */ + for (OutlinerLiboverrideDataIDRoot &data_idroot : data_idroots) { + /* For now, remap all local usages of linked ID to local override one here. */ + ID *id_iter; + FOREACH_MAIN_ID_BEGIN (&bmain, id_iter) { + if (ID_IS_LINKED(id_iter) || ID_IS_OVERRIDE_LIBRARY(id_iter)) { + id_iter->tag &= ~LIB_TAG_DOIT; + } + else { + id_iter->tag |= LIB_TAG_DOIT; + } + } + FOREACH_MAIN_ID_END; + + bool success = false; + if (do_hierarchy) { ID *id_root_override = nullptr; - success = BKE_lib_override_library_create(bmain, - CTX_data_scene(C), - CTX_data_view_layer(C), + success = BKE_lib_override_library_create(&bmain, + scene, + view_layer, nullptr, - id_root_reference, + data_idroot.id_root_reference, id_hierarchy_root_reference, - id_instance_hint, + data_idroot.id_instance_hint, &id_root_override, - data->do_fully_editable); - - BLI_assert(id_root_override != nullptr); - BLI_assert(!ID_IS_LINKED(id_root_override)); - BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root_override)); - if (ID_IS_LINKED(id_hierarchy_root_reference)) { - BLI_assert( - id_root_override->override_library->hierarchy_root->override_library->reference == - id_hierarchy_root_reference); - data->id_hierarchy_root_override = id_root_override->override_library->hierarchy_root; - } - else { - BLI_assert(id_root_override->override_library->hierarchy_root == - id_hierarchy_root_reference); - data->id_hierarchy_root_override = id_root_override->override_library->hierarchy_root; + data.do_fully_editable); + + if (success) { + BLI_assert(id_root_override != nullptr); + BLI_assert(!ID_IS_LINKED(id_root_override)); + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root_override)); + + ID *id_hierarchy_root_override = id_root_override->override_library->hierarchy_root; + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_hierarchy_root_override)); + if (ID_IS_LINKED(id_hierarchy_root_reference)) { + BLI_assert(id_hierarchy_root_override->override_library->reference == + id_hierarchy_root_reference); + /* If the hierarchy root reference was a linked data, after the first iteration there is + * now a matching override, which shall be used for all further partial overrides with + * this same hierarchy. */ + id_hierarchy_root_reference = id_hierarchy_root_override; + } + else { + BLI_assert(id_hierarchy_root_override == id_hierarchy_root_reference); + } + data_idroot.id_hierarchy_root_override = id_hierarchy_root_override; + data.id_hierarchy_roots_uid.add(id_hierarchy_root_override->session_uuid); } } - else if (ID_IS_OVERRIDABLE_LIBRARY(id_root_reference)) { - success = BKE_lib_override_library_create_from_id(bmain, id_root_reference, true) != nullptr; + else if (ID_IS_OVERRIDABLE_LIBRARY(data_idroot.id_root_reference)) { + ID *id_root_override = BKE_lib_override_library_create_from_id( + &bmain, data_idroot.id_root_reference, true); + success = id_root_override != nullptr; + if (success) { + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root_override)); + id_root_override->override_library->flag &= ~IDOVERRIDE_LIBRARY_FLAG_SYSTEM_DEFINED; + } /* Cleanup. */ - BKE_main_id_newptr_and_tag_clear(bmain); - BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + BKE_main_id_newptr_and_tag_clear(&bmain); + BKE_main_id_tag_all(&bmain, LIB_TAG_DOIT, false); + } + else { + BLI_assert_unreachable(); } /* Remove the instance empty from this scene, the items now have an overridden collection * instead. */ - if (success && is_override_instancing_object) { - ED_object_base_free_and_unlink(bmain, scene, (Object *)id_instance_hint); + if (success && data_idroot.is_override_instancing_object) { + BLI_assert(GS(data_idroot.id_instance_hint) == ID_OB); + ED_object_base_free_and_unlink( + &bmain, scene, reinterpret_cast<Object *>(data_idroot.id_instance_hint)); } - } - if (!success) { - BKE_reportf(reports, - RPT_WARNING, - "Could not create library override from data-block '%s'", - id_root_reference->name); + + r_aggregated_success = r_aggregated_success && success; } } /* Clear system override flag from newly created overrides which linked reference were previously * selected in the Outliner tree. */ -static void id_override_library_create_hierarchy_post_process(bContext *C, - OutlinerLibOverrideData *data) +static void id_override_library_create_hierarchy_process(bContext *C, + ReportList *reports, + OutlinerLibOverrideData &data) { Main *bmain = CTX_data_main(C); - ID *id_hierarchy_root_override = data->id_hierarchy_root_override; + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + const bool do_hierarchy = data.do_hierarchy; + + bool success = true; + for (auto &&[id_hierarchy_root_reference, data_idroots] : data.id_hierarchy_roots.items()) { + id_override_library_create_hierarchy( + *bmain, scene, view_layer, data, id_hierarchy_root_reference, data_idroots, success); + } + + if (!success) { + BKE_reportf(reports, + RPT_WARNING, + "Could not create library override from one or more of the selected data-blocks"); + } + + if (!do_hierarchy) { + return; + } ID *id_iter; FOREACH_MAIN_ID_BEGIN (bmain, id_iter) { - if (ID_IS_LINKED(id_iter) || !ID_IS_OVERRIDE_LIBRARY_REAL(id_iter) || - id_iter->override_library->hierarchy_root != id_hierarchy_root_override) { + if (ID_IS_LINKED(id_iter) || !ID_IS_OVERRIDE_LIBRARY_REAL(id_iter)) { + continue; + } + if (!data.id_hierarchy_roots_uid.contains( + id_iter->override_library->hierarchy_root->session_uuid)) { continue; } - if (data->selected_id_uid.contains(id_iter->override_library->reference->session_uuid)) { + if (data.selected_id_uid.contains(id_iter->override_library->reference->session_uuid) || + data.selected_id_uid.contains(id_iter->session_uuid)) { id_iter->override_library->flag &= ~IDOVERRIDE_LIBRARY_FLAG_SYSTEM_DEFINED; } } @@ -1995,7 +2090,6 @@ enum eOutlinerIdOpTypes { OUTLINER_IDOP_LOCAL, OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE, OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY, - OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY_FULLY_EDITABLE, OUTLINER_IDOP_OVERRIDE_LIBRARY_MAKE_EDITABLE, OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET, OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET_HIERARCHY, @@ -2041,12 +2135,6 @@ static const EnumPropertyItem prop_id_op_types[] = { "Make Library Override Hierarchy", "Make a local override of this linked data-block, and its hierarchy of dependencies - only " "applies to active Outliner item"}, - {OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY_FULLY_EDITABLE, - "OVERRIDE_LIBRARY_CREATE_HIERARCHY_FULLY_EDITABLE", - 0, - "Make Library Override Hierarchy Fully Editable", - "Make a local override of this linked data-block, and its hierarchy of dependencies, making " - "them all fully user-editable - only applies to active Outliner item"}, {OUTLINER_IDOP_OVERRIDE_LIBRARY_MAKE_EDITABLE, "OVERRIDE_LIBRARY_MAKE_EDITABLE", 0, @@ -2126,7 +2214,6 @@ static bool outliner_id_operation_item_poll(bContext *C, } return false; case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY: - case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY_FULLY_EDITABLE: if (ID_IS_OVERRIDABLE_LIBRARY(tselem->id) || (ID_IS_LINKED(tselem->id))) { return true; } @@ -2185,6 +2272,7 @@ static const EnumPropertyItem *outliner_id_operation_itemf(bContext *C, static int outliner_id_operation_exec(bContext *C, wmOperator *op) { + Main *bmain = CTX_data_main(C); wmWindowManager *wm = CTX_wm_manager(C); Scene *scene = CTX_data_scene(C); SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); @@ -2261,42 +2349,36 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) } case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE: { OutlinerLibOverrideData override_data{}; - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, id_override_library_create_fn, &override_data); - ED_undo_push(C, "Overridden Data"); - break; - } - case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY: { - OutlinerLibOverrideData override_data{}; - override_data.do_hierarchy = true; + override_data.do_hierarchy = false; + override_data.do_fully_editable = true; + outliner_do_libdata_operation(C, op->reports, scene, space_outliner, id_override_library_create_hierarchy_pre_process_fn, &override_data); - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, id_override_library_create_fn, &override_data); - id_override_library_create_hierarchy_post_process(C, &override_data); - ED_undo_push(C, "Overridden Data Hierarchy"); + id_override_library_create_hierarchy_process(C, op->reports, override_data); + + ED_undo_push(C, "Overridden Data"); break; } - case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY_FULLY_EDITABLE: { + case OUTLINER_IDOP_OVERRIDE_LIBRARY_CREATE_HIERARCHY: { OutlinerLibOverrideData override_data{}; override_data.do_hierarchy = true; - override_data.do_fully_editable = true; + override_data.do_fully_editable = U.experimental.use_override_new_fully_editable; + outliner_do_libdata_operation(C, op->reports, scene, space_outliner, id_override_library_create_hierarchy_pre_process_fn, &override_data); - outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, id_override_library_create_fn, &override_data); - id_override_library_create_hierarchy_post_process(C, &override_data); - ED_undo_push(C, "Overridden Data Hierarchy Fully Editable"); + id_override_library_create_hierarchy_process(C, op->reports, override_data); + + ED_undo_push(C, "Overridden Data Hierarchy"); break; } case OUTLINER_IDOP_OVERRIDE_LIBRARY_MAKE_EDITABLE: { @@ -2381,8 +2463,10 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op) } case OUTLINER_IDOP_DELETE: { if (idlevel > 0) { + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); outliner_do_libdata_operation( - C, op->reports, scene, space_outliner, id_delete_fn, nullptr); + C, op->reports, scene, space_outliner, id_delete_tag_fn, nullptr); + BKE_id_multi_tagged_delete(bmain); ED_undo_push(C, "Delete"); } break; @@ -2507,6 +2591,7 @@ static const EnumPropertyItem outliner_lib_op_type_items[] = { static int outliner_lib_operation_exec(bContext *C, wmOperator *op) { + Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); int scenelevel = 0, objectlevel = 0, idlevel = 0, datalevel = 0; @@ -2522,7 +2607,10 @@ static int outliner_lib_operation_exec(bContext *C, wmOperator *op) eOutlinerLibOpTypes event = (eOutlinerLibOpTypes)RNA_enum_get(op->ptr, "type"); switch (event) { case OL_LIB_DELETE: { - outliner_do_libdata_operation(C, op->reports, scene, space_outliner, id_delete_fn, nullptr); + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + outliner_do_libdata_operation( + C, op->reports, scene, space_outliner, id_delete_tag_fn, nullptr); + BKE_id_multi_tagged_delete(bmain); ED_undo_push(C, "Delete Library"); break; } diff --git a/source/blender/editors/space_outliner/tree/tree_display.hh b/source/blender/editors/space_outliner/tree/tree_display.hh index 190e35c81d6..f8e35655c26 100644 --- a/source/blender/editors/space_outliner/tree/tree_display.hh +++ b/source/blender/editors/space_outliner/tree/tree_display.hh @@ -161,7 +161,6 @@ class TreeDisplayOverrideLibraryHierarchies final : public AbstractTreeDisplay { 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; }; /* -------------------------------------------------------------------- */ 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 index 67798e978ab..f8705c3f0ad 100644 --- 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 @@ -4,25 +4,23 @@ * \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_function_ref.hh" +#include "BLI_ghash.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 { @@ -42,8 +40,8 @@ ListBase TreeDisplayOverrideLibraryHierarchies::buildTree(const TreeSourceData & 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); { - 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. */ @@ -76,11 +74,49 @@ ListBase TreeDisplayOverrideLibraryHierarchies::buildTree(const TreeSourceData & return tree; } +/* -------------------------------------------------------------------- */ +/** \name Library override hierarchy building + * \{ */ + +class OverrideIDHierarchyBuilder { + SpaceOutliner &space_outliner_; + MainIDRelations &id_relations_; + + struct HierarchyBuildData { + const ID &override_root_id_; + /* The ancestor IDs leading to the current ID, to avoid IDs recursing into themselves. Changes + * with every level of recursion. */ + Set<const ID *> parent_ids{}; + /* The IDs that were already added to #parent_te, to avoid duplicates. Entirely new set with + * every level of recursion. */ + Set<const ID *> sibling_ids{}; + }; + + public: + OverrideIDHierarchyBuilder(SpaceOutliner &space_outliner, MainIDRelations &id_relations) + : space_outliner_(space_outliner), id_relations_(id_relations) + { + } + + void build_hierarchy_for_ID(ID &root_id, TreeElement &te_to_expand); + + private: + void build_hierarchy_for_ID_recursive(const ID &parent_id, + HierarchyBuildData &build_data, + TreeElement &te_to_expand); +}; + ListBase TreeDisplayOverrideLibraryHierarchies::build_hierarchy_for_lib_or_main( Main *bmain, TreeElement &parent_te, Library *lib) { ListBase tree = {nullptr}; + /* Ensure #Main.relations contains the latest mapping of relations. Must be freed before + * returning. */ + BKE_main_relations_create(bmain, 0); + + OverrideIDHierarchyBuilder builder(space_outliner_, *bmain->relations); + /* 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). */ @@ -109,108 +145,174 @@ ListBase TreeDisplayOverrideLibraryHierarchies::build_hierarchy_for_lib_or_main( TreeElement *new_id_te = outliner_add_element( &space_outliner_, &new_base_te->subtree, iter_id, new_base_te, TSE_SOME_ID, 0, false); - build_hierarchy_for_ID(bmain, *iter_id, *tree_element_cast<TreeElementID>(new_id_te)); + builder.build_hierarchy_for_ID(*iter_id, *new_id_te); } FOREACH_MAIN_ID_END; + BKE_main_relations_free(bmain); + 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{}; -}; +void OverrideIDHierarchyBuilder::build_hierarchy_for_ID(ID &override_root_id, + TreeElement &te_to_expand) +{ + HierarchyBuildData build_data{override_root_id}; + build_hierarchy_for_ID_recursive(override_root_id, build_data, te_to_expand); +} + +/* Helpers (defined below). */ +static void foreach_natural_hierarchy_child(const MainIDRelations &id_relations, + const ID &parent_id, + FunctionRef<void(ID &)> fn); +static bool id_is_in_override_hierarchy(const ID &id, + const ID &relationship_parent_id, + const ID &override_root_id); + +void OverrideIDHierarchyBuilder::build_hierarchy_for_ID_recursive(const ID &parent_id, + HierarchyBuildData &build_data, + TreeElement &te_to_expand) +{ + /* In case this isn't added to the parents yet (does nothing if already there). */ + build_data.parent_ids.add(&parent_id); + + foreach_natural_hierarchy_child(id_relations_, parent_id, [&](ID &id) { + if (!id_is_in_override_hierarchy(id, parent_id, build_data.override_root_id_)) { + return; + } + + /* 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; + } + + /* 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; + } + + TreeElement *new_te = outliner_add_element( + &space_outliner_, &te_to_expand.subtree, &id, &te_to_expand, TSE_SOME_ID, 0, false); + + build_data.sibling_ids.add(&id); + + /* Recurse into this ID. */ + HierarchyBuildData child_build_data{build_data.override_root_id_}; + child_build_data.parent_ids = build_data.parent_ids; + child_build_data.parent_ids.add(&id); + child_build_data.sibling_ids.reserve(10); + build_hierarchy_for_ID_recursive(id, child_build_data, *new_te); + }); +} -static int build_hierarchy_foreach_ID_cb(LibraryIDLinkCallbackData *cb_data) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Helpers for library override hierarchy building + * \{ */ + +/** + * Iterate over the IDs \a parent_id uses. E.g. the child collections and contained objects of a + * parent collection. Also does special handling for object parenting, so that: + * - When iterating over a child object, \a fn is executed for the parent instead. + * - When iterating over a parent object, \a fn is _additionally_ executed for all children. Given + * that the parent object isn't skipped, the caller has to ensure it's not added in the hierarchy + * twice. + * This allows us to build the hierarchy in the expected ("natural") order, where parent objects + * are actual parent elements in the hierarchy, even though in data, the relation goes the other + * way around (children point to or "use" the parent). + * + * Only handles regular object parenting, not cases like the "Child of" constraint. Other Outliner + * display modes don't show this as parent in the hierarchy either. + */ +static void foreach_natural_hierarchy_child(const MainIDRelations &id_relations, + const ID &parent_id, + FunctionRef<void(ID &)> fn) { - if (!*cb_data->id_pointer) { - return IDWALK_RET_NOP; + const MainIDRelationsEntry *relations_of_id = static_cast<MainIDRelationsEntry *>( + BLI_ghash_lookup(id_relations.relations_from_pointers, &parent_id)); + + /* Iterate over all IDs used by the parent ID (e.g. the child-collections of a collection). */ + for (MainIDRelationsEntryItem *to_id_entry = relations_of_id->to_ids; to_id_entry; + to_id_entry = to_id_entry->next) { + /* An ID pointed to (used) by the ID to recurse into. */ + ID &target_id = **to_id_entry->id_pointer.to; + + /* Don't walk up the hierarchy, e.g. ignore pointers to parent collections. */ + if (to_id_entry->usage_flag & IDWALK_CB_LOOPBACK) { + continue; + } + + /* Special case for objects: Process the parent object instead of the child object. Below the + * parent will add the child objects then. */ + if (GS(target_id.name) == ID_OB) { + const Object &potential_child_ob = reinterpret_cast<const Object &>(target_id); + if (potential_child_ob.parent) { + fn(potential_child_ob.parent->id); + continue; + } + } + + fn(target_id); + } + + /* If the ID is an object, find and iterate over any child objects. */ + if (GS(parent_id.name) == ID_OB) { + for (MainIDRelationsEntryItem *from_id_entry = relations_of_id->from_ids; from_id_entry; + from_id_entry = from_id_entry->next) { + ID &potential_child_id = *from_id_entry->id_pointer.from; + + if (GS(potential_child_id.name) != ID_OB) { + continue; + } + + Object &potential_child_ob = reinterpret_cast<Object &>(potential_child_id); + if (potential_child_ob.parent && &potential_child_ob.parent->id == &parent_id) { + fn(potential_child_id); + } + } } +} - 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; +static bool id_is_in_override_hierarchy(const ID &id, + const ID &relationship_parent_id, + const ID &override_root_id) +{ /* 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)) { + /* This assumes that the parent ID is always the owner of the 'embedded' one, I.e. that no + * other ID directly uses the embedded one. Should be true, but the debug code adds some checks + * to validate this assumption. */ + real_override_id = &relationship_parent_id; + +#ifndef NDEBUG if (GS(id.name) == ID_KE) { - Key *key = (Key *)&id; - real_override_id = key->from; + const Key *key = (Key *)&id; + BLI_assert(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; + else { + BLI_assert((id.flag & LIB_EMBEDDED_DATA) != 0); } +#endif } if (!ID_IS_OVERRIDE_LIBRARY(real_override_id)) { - return IDWALK_RET_NOP; + return false; } /* 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; + if (real_override_id->override_library->hierarchy_root != &override_root_id) { + return false; } - 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, - false); - 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; + return true; } -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 |