From 071b18a3cc57197d328fc6c4e0fefe8a9ade6f4a Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 29 Jul 2024 20:42:08 +0200 Subject: [PATCH] Spreadsheet: support navigating instance trees Previously, it was not possible to see detailed information about instances in the spreadsheet. Only the attributes on the top level instances were shown. Now, all nested instances can be inspected too. Combined with #114910 this will make inspecting more complex geometry with the spreadsheet much more feasible. It's also an important part of integrating grease pencil into geometry nodes because it makes it more obvious how layers are converted to curve instances. The data-selection is split into two separate tree views now. One that selects the geometry from the instance tree, and one that's used to select the geometry component and domain within that geometry. We found that this works better than combining both tree views into one (we tried that in #124186). Pull Request: https://projects.blender.org/blender/blender/pulls/125293 --- source/blender/blenkernel/BKE_instances.hh | 11 + source/blender/blenkernel/intern/instances.cc | 19 ++ .../blender/editors/include/UI_tree_view.hh | 4 +- .../editors/interface/views/tree_view.cc | 21 +- .../space_spreadsheet/space_spreadsheet.cc | 8 + .../spreadsheet_data_source_geometry.cc | 37 +++- .../spreadsheet_data_source_geometry.hh | 3 + .../spreadsheet_dataset_draw.cc | 198 ++++++++++++++++-- source/blender/makesdna/DNA_space_types.h | 12 +- 9 files changed, 293 insertions(+), 20 deletions(-) diff --git a/source/blender/blenkernel/BKE_instances.hh b/source/blender/blenkernel/BKE_instances.hh index 1dc4a242b9d..2c63c0d022a 100644 --- a/source/blender/blenkernel/BKE_instances.hh +++ b/source/blender/blenkernel/BKE_instances.hh @@ -115,6 +115,11 @@ class Instances { CustomData attributes_; + /** + * Caches how often each reference is used. + */ + mutable SharedCache> reference_user_counts_; + /* These almost unique ids are generated based on the `id` attribute, which might not contain * unique ids at all. They are *almost* unique, because under certain very unlikely * circumstances, they are not unique. Code using these ids should not crash when they are not @@ -186,6 +191,11 @@ class Instances { */ Span almost_unique_ids() const; + /** + * Get cached user counts for every reference. + */ + Span reference_user_counts() const; + bke::AttributeAccessor attributes() const; bke::MutableAttributeAccessor attributes_for_write(); @@ -200,6 +210,7 @@ class Instances { void tag_reference_handles_changed() { + reference_user_counts_.tag_dirty(); almost_unique_ids_cache_.tag_dirty(); } }; diff --git a/source/blender/blenkernel/intern/instances.cc b/source/blender/blenkernel/intern/instances.cc index 162b8b8fd06..7f1155190f1 100644 --- a/source/blender/blenkernel/intern/instances.cc +++ b/source/blender/blenkernel/intern/instances.cc @@ -128,6 +128,7 @@ Instances::Instances(Instances &&other) : references_(std::move(other.references_)), instances_num_(other.instances_num_), attributes_(other.attributes_), + reference_user_counts_(std::move(other.reference_user_counts_)), almost_unique_ids_cache_(std::move(other.almost_unique_ids_cache_)) { CustomData_reset(&other.attributes_); @@ -136,6 +137,7 @@ Instances::Instances(Instances &&other) Instances::Instances(const Instances &other) : references_(other.references_), instances_num_(other.instances_num_), + reference_user_counts_(std::move(other.reference_user_counts_)), almost_unique_ids_cache_(other.almost_unique_ids_cache_) { CustomData_copy(&other.attributes_, &attributes_, CD_MASK_ALL, other.instances_num_); @@ -443,6 +445,23 @@ static Array generate_unique_instance_ids(Span original_ids) return unique_ids; } +Span Instances::reference_user_counts() const +{ + reference_user_counts_.ensure([&](Array &r_data) { + const int references_num = references_.size(); + r_data.reinitialize(references_num); + r_data.fill(0); + + const Span handles = this->reference_handles(); + for (const int handle : handles) { + if (handle >= 0 && handle < references_num) { + r_data[handle]++; + } + } + }); + return reference_user_counts_.data(); +} + Span Instances::almost_unique_ids() const { almost_unique_ids_cache_.ensure([&](Array &r_data) { diff --git a/source/blender/editors/include/UI_tree_view.hh b/source/blender/editors/include/UI_tree_view.hh index c6900f676e1..2846dd9afa3 100644 --- a/source/blender/editors/include/UI_tree_view.hh +++ b/source/blender/editors/include/UI_tree_view.hh @@ -93,6 +93,7 @@ class TreeViewItemContainer { protected: void foreach_item_recursive(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const; + void foreach_parent(ItemIterFn iter_fn) const; }; ENUM_OPERATORS(TreeViewItemContainer::IterOptions, @@ -395,7 +396,8 @@ class TreeViewBuilder { public: static void build_tree_view(AbstractTreeView &tree_view, uiLayout &layout, - std::optional search_string = {}); + std::optional search_string = {}, + bool add_box = true); private: static void ensure_min_rows_items(AbstractTreeView &tree_view); diff --git a/source/blender/editors/interface/views/tree_view.cc b/source/blender/editors/interface/views/tree_view.cc index 597fca3c793..f2186d1423f 100644 --- a/source/blender/editors/interface/views/tree_view.cc +++ b/source/blender/editors/interface/views/tree_view.cc @@ -83,6 +83,13 @@ void TreeViewItemContainer::foreach_item_recursive(ItemIterFn iter_fn, IterOptio } } +void TreeViewItemContainer::foreach_parent(ItemIterFn iter_fn) const +{ + for (ui::AbstractTreeViewItem *item = parent_; item; item = item->parent_) { + iter_fn(*item); + } +} + /* ---------------------------------------------------------------------- */ /* Implementation for the base class virtual function. More specialized iterators below. */ @@ -620,6 +627,7 @@ bool AbstractTreeViewItem::matches(const AbstractViewItem &other) const class TreeViewLayoutBuilder { uiBlock &block_; + bool add_box_ = true; friend TreeViewBuilder; @@ -643,8 +651,13 @@ void TreeViewLayoutBuilder::build_from_tree(const AbstractTreeView &tree_view) { uiLayout &parent_layout = this->current_layout(); - uiLayout *box = uiLayoutBox(&parent_layout); - uiLayoutColumn(box, true); + if (add_box_) { + uiLayout *box = uiLayoutBox(&parent_layout); + uiLayoutColumn(box, true); + } + else { + uiLayoutColumn(&parent_layout, true); + } tree_view.foreach_item([this](AbstractTreeViewItem &item) { build_row(item); }, AbstractTreeView::IterOptions::SkipCollapsed | @@ -727,7 +740,8 @@ void TreeViewBuilder::ensure_min_rows_items(AbstractTreeView &tree_view) void TreeViewBuilder::build_tree_view(AbstractTreeView &tree_view, uiLayout &layout, - std::optional search_string) + std::optional search_string, + const bool add_box) { uiBlock &block = *uiLayoutGetBlock(&layout); @@ -742,6 +756,7 @@ void TreeViewBuilder::build_tree_view(AbstractTreeView &tree_view, UI_block_layout_set_current(&block, &layout); TreeViewLayoutBuilder builder(layout); + builder.add_box_ = add_box; builder.build_from_tree(tree_view); } diff --git a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc index 64f69d9b95f..6f250619946 100644 --- a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc +++ b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc @@ -107,6 +107,7 @@ static void spreadsheet_free(SpaceLink *sl) LISTBASE_FOREACH_MUTABLE (SpreadsheetColumn *, column, &sspreadsheet->columns) { spreadsheet_column_free(column); } + MEM_SAFE_FREE(sspreadsheet->instance_ids); BKE_viewer_path_clear(&sspreadsheet->viewer_path); } @@ -141,6 +142,8 @@ static SpaceLink *spreadsheet_duplicate(SpaceLink *sl) BLI_addtail(&sspreadsheet_new->columns, new_column); } + sspreadsheet_new->instance_ids = static_cast( + MEM_dupallocN(sspreadsheet_old->instance_ids)); BKE_viewer_path_copy(&sspreadsheet_new->viewer_path, &sspreadsheet_old->viewer_path); return (SpaceLink *)sspreadsheet_new; @@ -688,6 +691,9 @@ static void spreadsheet_blend_read_data(BlendDataReader *reader, SpaceLink *sl) BLO_read_string(reader, &column->display_name); } + BLO_read_struct_array( + reader, SpreadsheetInstanceID, sspreadsheet->instance_ids_num, &sspreadsheet->instance_ids); + BKE_viewer_path_blend_read_data(reader, &sspreadsheet->viewer_path); } @@ -711,6 +717,8 @@ static void spreadsheet_blend_write(BlendWriter *writer, SpaceLink *sl) BLO_write_string(writer, column->display_name); } + BLO_write_struct_array( + writer, SpreadsheetInstanceID, sspreadsheet->instance_ids_num, sspreadsheet->instance_ids); BKE_viewer_path_blend_write(writer, &sspreadsheet->viewer_path); } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index 46368cddbae..2f44fa80cf7 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -12,6 +12,7 @@ #include "BKE_editmesh.hh" #include "BKE_geometry_fields.hh" #include "BKE_geometry_set.hh" +#include "BKE_geometry_set_instances.hh" #include "BKE_global.hh" #include "BKE_grease_pencil.hh" #include "BKE_instances.hh" @@ -560,7 +561,7 @@ int get_instance_reference_icon(const bke::InstanceReference &reference) return ICON_OUTLINER_COLLECTION; } case bke::InstanceReference::Type::GeometrySet: { - return ICON_EMPTY_AXIS; + return ICON_GEOMETRY_SET; } case bke::InstanceReference::Type::None: { break; @@ -632,13 +633,41 @@ bke::GeometrySet spreadsheet_get_display_geometry_set(const SpaceSpreadsheet *ss return geometry_set; } +bke::GeometrySet get_geometry_set_for_instance_ids(const bke::GeometrySet &root_geometry, + const Span instance_ids) +{ + bke::GeometrySet geometry = root_geometry; + for (const SpreadsheetInstanceID &instance_id : instance_ids) { + const bke::Instances *instances = geometry.get_instances(); + if (!instances) { + /* Return the best available geometry. */ + return geometry; + } + const Span references = instances->references(); + if (instance_id.reference_index < 0 || instance_id.reference_index >= references.size()) { + /* Return the best available geometry. */ + return geometry; + } + const bke::InstanceReference &reference = references[instance_id.reference_index]; + bke::GeometrySet reference_geometry; + reference.to_geometry_set(reference_geometry); + geometry = reference_geometry; + } + return geometry; +} + std::unique_ptr data_source_from_geometry(const bContext *C, Object *object_eval) { SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + + const bke::GeometrySet root_geometry_set = spreadsheet_get_display_geometry_set(sspreadsheet, + object_eval); + const bke::GeometrySet geometry_set = get_geometry_set_for_instance_ids( + root_geometry_set, Span{sspreadsheet->instance_ids, sspreadsheet->instance_ids_num}); + const bke::AttrDomain domain = (bke::AttrDomain)sspreadsheet->attribute_domain; const auto component_type = bke::GeometryComponent::Type(sspreadsheet->geometry_component_type); const int active_layer_index = sspreadsheet->active_layer_index; - bke::GeometrySet geometry_set = spreadsheet_get_display_geometry_set(sspreadsheet, object_eval); if (!geometry_set.has(component_type)) { return {}; } @@ -646,7 +675,9 @@ std::unique_ptr data_source_from_geometry(const bContext *C, Object if (component_type == bke::GeometryComponent::Type::Volume) { return std::make_unique(std::move(geometry_set)); } - Object *object_orig = DEG_get_original_object(object_eval); + Object *object_orig = sspreadsheet->instance_ids_num == 0 ? + DEG_get_original_object(object_eval) : + nullptr; return std::make_unique( object_orig, std::move(geometry_set), component_type, domain, active_layer_index); } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh index 61470d996da..17ce9d2230d 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh @@ -113,4 +113,7 @@ int get_instance_reference_icon(const bke::InstanceReference &reference); std::unique_ptr data_source_from_geometry(const bContext *C, Object *object_eval); +bke::GeometrySet get_geometry_set_for_instance_ids(const bke::GeometrySet &root_geometry, + const Span instance_ids); + } // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc index b9221c0d4f3..a1b711868e3 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc @@ -2,6 +2,8 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#include + #include "BLI_string.h" #include "DNA_collection_types.h" @@ -24,6 +26,7 @@ #include "UI_interface.hh" #include "UI_tree_view.hh" +#include "WM_api.hh" #include "WM_types.hh" #include "BLT_translation.hh" @@ -37,6 +40,7 @@ namespace blender::ed::spreadsheet { class GeometryDataSetTreeView; +class GeometryInstancesTreeView; struct GeometryDataIdentifier { bke::GeometryComponent::Type component_type; @@ -44,13 +48,18 @@ struct GeometryDataIdentifier { std::optional domain; }; -static void draw_count(ui::AbstractTreeViewItem &view_item, const int count) +static void draw_row_suffix(ui::AbstractTreeViewItem &view_item, const StringRefNull str) { /* Using the tree row button instead of a separate right aligned button gives padding * to the right side of the number, which it didn't have with the button. */ + UI_but_hint_drawstr_set(reinterpret_cast(view_item.view_item_button()), str.c_str()); +} + +static void draw_count(ui::AbstractTreeViewItem &view_item, const int count) +{ char element_count[BLI_STR_FORMAT_INT32_DECIMAL_UNIT_SIZE]; BLI_str_format_decimal_unit(element_count, count); - UI_but_hint_drawstr_set(reinterpret_cast(view_item.view_item_button()), element_count); + draw_row_suffix(view_item, element_count); } static StringRefNull mesh_domain_to_label(const bke::AttrDomain domain) @@ -113,6 +122,102 @@ static BIFIconID curves_domain_to_icon(const bke::AttrDomain domain) } } +class InstancesTreeViewItem : public ui::AbstractTreeViewItem { + public: + GeometryInstancesTreeView &get_tree() const; + + void get_parent_instance_ids(Vector &r_instance_ids) const; + + void on_activate(bContext &C) override; + + std::optional should_be_active() const override; +}; + +class RootGeometryViewItem : public InstancesTreeViewItem { + public: + RootGeometryViewItem(const bke::GeometrySet &geometry) + { + label_ = geometry.name.empty() ? IFACE_("Geometry") : geometry.name; + } + + void build_row(uiLayout &row) override + { + uiItemL(&row, label_.c_str(), ICON_GEOMETRY_SET); + } +}; + +class InstanceReferenceViewItem : public InstancesTreeViewItem { + private: + const bke::InstanceReference &reference_; + int reference_index_; + int user_count_; + + public: + InstanceReferenceViewItem(const bke::Instances &instances, const int reference_index) + : reference_(instances.references()[reference_index]), reference_index_(reference_index) + { + label_ = std::to_string(reference_index); + user_count_ = instances.reference_user_counts()[reference_index]; + } + + void build_row(uiLayout &row) override + { + const int icon = get_instance_reference_icon(reference_); + StringRefNull name = reference_.name(); + if (name.is_empty()) { + name = IFACE_("Geometry"); + } + uiItemL(&row, name.c_str(), icon); + draw_count(*this, user_count_); + } + + int reference_index() const + { + return reference_index_; + } +}; + +class GeometryInstancesTreeView : public ui::AbstractTreeView { + private: + bke::GeometrySet root_geometry_set_; + SpaceSpreadsheet &sspreadsheet_; + bScreen &screen_; + + friend class InstancesTreeViewItem; + + public: + GeometryInstancesTreeView(bke::GeometrySet geometry_set, const bContext &C) + : root_geometry_set_(std::move(geometry_set)), + sspreadsheet_(*CTX_wm_space_spreadsheet(&C)), + screen_(*CTX_wm_screen(&C)) + { + } + + void build_tree() override + { + auto &root_item = this->add_tree_item(root_geometry_set_); + root_item.uncollapse_by_default(); + if (const bke::Instances *instances = root_geometry_set_.get_instances()) { + this->build_tree_for_instances(root_item, *instances); + } + } + + void build_tree_for_instances(ui::TreeViewItemContainer &parent, const bke::Instances &instances) + { + const Span references = instances.references(); + for (const int reference_i : references.index_range()) { + auto &reference_item = parent.add_tree_item(instances, + reference_i); + const bke::InstanceReference &reference = references[reference_i]; + bke::GeometrySet reference_geometry; + reference.to_geometry_set(reference_geometry); + if (const bke::Instances *child_instances = reference_geometry.get_instances()) { + this->build_tree_for_instances(reference_item, *child_instances); + } + } + } +}; + class DataSetViewItem : public ui::AbstractTreeViewItem { public: GeometryDataSetTreeView &get_tree() const; @@ -380,7 +485,7 @@ class InstancesViewItem : public DataSetViewItem { class GeometryDataSetTreeView : public ui::AbstractTreeView { private: - bke::GeometrySet root_geometry_set_; + bke::GeometrySet geometry_set_; SpaceSpreadsheet &sspreadsheet_; bScreen &screen_; @@ -388,7 +493,7 @@ class GeometryDataSetTreeView : public ui::AbstractTreeView { public: GeometryDataSetTreeView(bke::GeometrySet geometry_set, const bContext &C) - : root_geometry_set_(std::move(geometry_set)), + : geometry_set_(std::move(geometry_set)), sspreadsheet_(*CTX_wm_space_spreadsheet(&C)), screen_(*CTX_wm_screen(&C)) { @@ -396,7 +501,7 @@ class GeometryDataSetTreeView : public ui::AbstractTreeView { void build_tree() override { - this->build_tree_for_geometry(root_geometry_set_, *this); + this->build_tree_for_geometry(geometry_set_, *this); } void build_tree_for_geometry(const bke::GeometrySet &geometry, ui::TreeViewItemContainer &parent) @@ -482,6 +587,60 @@ GeometryDataSetTreeView &DataSetViewItem::get_tree() const return static_cast(this->get_tree_view()); } +GeometryInstancesTreeView &InstancesTreeViewItem::get_tree() const +{ + return static_cast(this->get_tree_view()); +} + +void InstancesTreeViewItem::get_parent_instance_ids( + Vector &r_instance_ids) const +{ + if (auto *reference_item = dynamic_cast(this)) { + r_instance_ids.append({reference_item->reference_index()}); + } + this->foreach_parent([&](const ui::AbstractTreeViewItem &item) { + if (auto *reference_item = dynamic_cast(&item)) { + r_instance_ids.append({reference_item->reference_index()}); + } + }); + std::reverse(r_instance_ids.begin(), r_instance_ids.end()); +} + +std::optional InstancesTreeViewItem::should_be_active() const +{ + GeometryInstancesTreeView &tree_view = this->get_tree(); + SpaceSpreadsheet &sspreadsheet = tree_view.sspreadsheet_; + + Vector instance_ids; + this->get_parent_instance_ids(instance_ids); + if (sspreadsheet.instance_ids_num != instance_ids.size()) { + return false; + } + for (const int i : instance_ids.index_range()) { + const SpreadsheetInstanceID &a = sspreadsheet.instance_ids[i]; + const SpreadsheetInstanceID &b = instance_ids[i]; + if (a.reference_index != b.reference_index) { + return false; + } + } + return true; +} + +void InstancesTreeViewItem::on_activate(bContext &C) +{ + Vector instance_ids; + this->get_parent_instance_ids(instance_ids); + + SpaceSpreadsheet &sspreadsheet = *CTX_wm_space_spreadsheet(&C); + + MEM_SAFE_FREE(sspreadsheet.instance_ids); + sspreadsheet.instance_ids = MEM_cnew_array(instance_ids.size(), __func__); + sspreadsheet.instance_ids_num = instance_ids.size(); + initialized_copy_n(instance_ids.data(), instance_ids.size(), sspreadsheet.instance_ids); + + WM_main_add_notifier(NC_SPACE | ND_SPACE_SPREADSHEET, nullptr); +} + void DataSetViewItem::on_activate(bContext &C) { std::optional data_id = this->get_geometry_data_id(); @@ -555,14 +714,29 @@ void spreadsheet_data_set_panel_draw(const bContext *C, Panel *panel) uiBlock *block = uiLayoutGetBlock(layout); UI_block_layout_set_current(block, layout); + const bke::GeometrySet root_geometry = spreadsheet_get_display_geometry_set(sspreadsheet, + object); - ui::AbstractTreeView *tree_view = UI_block_add_view( - *block, - "Data Set Tree View", - std::make_unique( - spreadsheet_get_display_geometry_set(sspreadsheet, object), *C)); - tree_view->set_context_menu_title("Spreadsheet"); - ui::TreeViewBuilder::build_tree_view(*tree_view, *layout); + if (uiLayout *panel = uiLayoutPanel(C, layout, "instance tree", false, IFACE_("Geometry"))) { + ui::AbstractTreeView *tree_view = UI_block_add_view( + *block, + "Instances Tree View", + std::make_unique(root_geometry, *C)); + tree_view->set_context_menu_title("Instance"); + ui::TreeViewBuilder::build_tree_view(*tree_view, *panel, {}, false); + } + if (uiLayout *panel = uiLayoutPanel( + C, layout, "geometry_domain_tree_view", false, IFACE_("Domain"))) + { + bke::GeometrySet instance_geometry = get_geometry_set_for_instance_ids( + root_geometry, {sspreadsheet->instance_ids, sspreadsheet->instance_ids_num}); + ui::AbstractTreeView *tree_view = UI_block_add_view( + *block, + "Data Set Tree View", + std::make_unique(std::move(instance_geometry), *C)); + tree_view->set_context_menu_title("Domain"); + ui::TreeViewBuilder::build_tree_view(*tree_view, *panel, {}, false); + } } } // namespace blender::ed::spreadsheet diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 30061447792..aec9148edfb 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -1953,6 +1953,10 @@ typedef struct SpreadsheetColumn { char *display_name; } SpreadsheetColumn; +typedef struct SpreadsheetInstanceID { + int reference_index; +} SpreadsheetInstanceID; + typedef struct SpaceSpreadsheet { SpaceLink *next, *prev; /** Storage of regions for inactive spaces. */ @@ -1975,6 +1979,13 @@ typedef struct SpaceSpreadsheet { */ ViewerPath viewer_path; + /** + * The "path" to the currently active instance reference. This is needed when viewing nested + * instances. + */ + SpreadsheetInstanceID *instance_ids; + int instance_ids_num; + /* eSpaceSpreadsheet_FilterFlag. */ uint8_t filter_flag; @@ -1989,7 +2000,6 @@ typedef struct SpaceSpreadsheet { /* eSpaceSpreadsheet_Flag. */ uint32_t flag; - char _pad1[4]; SpaceSpreadsheet_Runtime *runtime; } SpaceSpreadsheet;