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
This commit is contained in:
Jacques Lucke
2024-07-29 20:42:08 +02:00
parent ae129da4f7
commit 071b18a3cc
9 changed files with 293 additions and 20 deletions

View File

@@ -115,6 +115,11 @@ class Instances {
CustomData attributes_;
/**
* Caches how often each reference is used.
*/
mutable SharedCache<Array<int>> 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<int> almost_unique_ids() const;
/**
* Get cached user counts for every reference.
*/
Span<int> 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();
}
};

View File

@@ -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<int> generate_unique_instance_ids(Span<int> original_ids)
return unique_ids;
}
Span<int> Instances::reference_user_counts() const
{
reference_user_counts_.ensure([&](Array<int> &r_data) {
const int references_num = references_.size();
r_data.reinitialize(references_num);
r_data.fill(0);
const Span<int> handles = this->reference_handles();
for (const int handle : handles) {
if (handle >= 0 && handle < references_num) {
r_data[handle]++;
}
}
});
return reference_user_counts_.data();
}
Span<int> Instances::almost_unique_ids() const
{
almost_unique_ids_cache_.ensure([&](Array<int> &r_data) {

View File

@@ -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<StringRef> search_string = {});
std::optional<StringRef> search_string = {},
bool add_box = true);
private:
static void ensure_min_rows_items(AbstractTreeView &tree_view);

View File

@@ -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<StringRef> search_string)
std::optional<StringRef> 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);
}

View File

@@ -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<SpreadsheetInstanceID *>(
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);
}

View File

@@ -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<SpreadsheetInstanceID> 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<bke::InstanceReference> 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<DataSource> 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<DataSource> data_source_from_geometry(const bContext *C, Object
if (component_type == bke::GeometryComponent::Type::Volume) {
return std::make_unique<VolumeDataSource>(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<GeometryDataSource>(
object_orig, std::move(geometry_set), component_type, domain, active_layer_index);
}

View File

@@ -113,4 +113,7 @@ int get_instance_reference_icon(const bke::InstanceReference &reference);
std::unique_ptr<DataSource> 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<SpreadsheetInstanceID> instance_ids);
} // namespace blender::ed::spreadsheet

View File

@@ -2,6 +2,8 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <fmt/format.h>
#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<bke::AttrDomain> 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<uiBut *>(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<uiBut *>(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<SpreadsheetInstanceID> &r_instance_ids) const;
void on_activate(bContext &C) override;
std::optional<bool> 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<RootGeometryViewItem>(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<bke::InstanceReference> references = instances.references();
for (const int reference_i : references.index_range()) {
auto &reference_item = parent.add_tree_item<InstanceReferenceViewItem>(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<GeometryDataSetTreeView &>(this->get_tree_view());
}
GeometryInstancesTreeView &InstancesTreeViewItem::get_tree() const
{
return static_cast<GeometryInstancesTreeView &>(this->get_tree_view());
}
void InstancesTreeViewItem::get_parent_instance_ids(
Vector<SpreadsheetInstanceID> &r_instance_ids) const
{
if (auto *reference_item = dynamic_cast<const InstanceReferenceViewItem *>(this)) {
r_instance_ids.append({reference_item->reference_index()});
}
this->foreach_parent([&](const ui::AbstractTreeViewItem &item) {
if (auto *reference_item = dynamic_cast<const InstanceReferenceViewItem *>(&item)) {
r_instance_ids.append({reference_item->reference_index()});
}
});
std::reverse(r_instance_ids.begin(), r_instance_ids.end());
}
std::optional<bool> InstancesTreeViewItem::should_be_active() const
{
GeometryInstancesTreeView &tree_view = this->get_tree();
SpaceSpreadsheet &sspreadsheet = tree_view.sspreadsheet_;
Vector<SpreadsheetInstanceID> 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<SpreadsheetInstanceID> 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<SpreadsheetInstanceID>(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<GeometryDataIdentifier> 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<GeometryDataSetTreeView>(
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<GeometryInstancesTreeView>(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<GeometryDataSetTreeView>(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

View File

@@ -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;