Refactor: Simplify spreadsheet handling of cell values

Previously we used a `CellValue` class to hold the data for a cell,
and called a function to fill it whenever necessary. This is an
unnecessary complication when we have virtual generic arrays
and most data is already easily accessible that way anyway.
This patch removes `CellValue` and uses `fn::GVArray` to provide
access to data instead.

In the future, if rows have different types within a single column,
we can use a `GVArray` of `blender::Any` to interface with the drawing.

Along with that, the use of virtual arrays made it easy to do a
few other cleanups:
 - Use selection domain interpolations from rB5841f8656d95
   for the mesh selection filter.
 - Change the row filter to only calculate for necessary indices.

Differential Revision: https://developer.blender.org/D13478
This commit is contained in:
2021-12-15 09:34:13 -06:00
parent d79868c4e6
commit 474adc6f88
15 changed files with 463 additions and 728 deletions

View File

@@ -31,6 +31,8 @@
#include "attribute_access_intern.hh"
#include "FN_cpp_type_make.hh"
using blender::float4x4;
using blender::Map;
using blender::MutableSpan;
@@ -39,6 +41,8 @@ using blender::Span;
using blender::VectorSet;
using blender::fn::GSpan;
MAKE_CPP_TYPE(InstanceReference, InstanceReference, CPPTypeFlags::None)
/* -------------------------------------------------------------------- */
/** \name Geometry Component Implementation
* \{ */

View File

@@ -49,7 +49,6 @@ set(SRC
spreadsheet_row_filter_ui.cc
spreadsheet_cache.hh
spreadsheet_cell_value.hh
spreadsheet_column.hh
spreadsheet_column_values.hh
spreadsheet_context.hh

View File

@@ -323,6 +323,8 @@ static float get_default_column_width(const ColumnValues &values)
return 8.0f;
case SPREADSHEET_VALUE_TYPE_STRING:
return 5.0f;
case SPREADSHEET_VALUE_TYPE_UNKNOWN:
return 2.0f;
}
return float_width;
}

View File

@@ -1,64 +0,0 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
#include <optional>
#include "BLI_color.hh"
#include "BLI_float2.hh"
#include "BLI_float3.hh"
struct Collection;
struct Object;
namespace blender::ed::spreadsheet {
struct ObjectCellValue {
const Object *object;
};
struct CollectionCellValue {
const Collection *collection;
};
struct GeometrySetCellValue {
const GeometrySet *geometry_set;
};
/**
* This is a type that can hold the value of a cell in a spreadsheet. This type allows us to
* decouple the drawing of individual cells from the code that generates the data to be displayed.
*/
class CellValue {
public:
/* The implementation just uses a bunch of `std::option` for now. Unfortunately, we cannot use
* `std::variant` yet, due to missing compiler support. This type can really be optimized more,
* but it does not really matter too much currently. */
std::optional<int> value_int;
std::optional<float> value_float;
std::optional<bool> value_bool;
std::optional<float2> value_float2;
std::optional<float3> value_float3;
std::optional<ColorGeometry4f> value_color;
std::optional<ObjectCellValue> value_object;
std::optional<CollectionCellValue> value_collection;
std::optional<GeometrySetCellValue> value_geometry_set;
std::optional<std::string> value_string;
};
} // namespace blender::ed::spreadsheet

View File

@@ -18,14 +18,52 @@
#include "MEM_guardedalloc.h"
#include "BLI_color.hh"
#include "BLI_float2.hh"
#include "BLI_float3.hh"
#include "BLI_hash.hh"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BKE_geometry_set.hh"
#include "FN_cpp_type.hh"
#include "spreadsheet_column.hh"
#include "spreadsheet_column_values.hh"
namespace blender::ed::spreadsheet {
eSpreadsheetColumnValueType cpp_type_to_column_type(const fn::CPPType &type)
{
if (type.is<bool>()) {
return SPREADSHEET_VALUE_TYPE_BOOL;
}
if (type.is<int>()) {
return SPREADSHEET_VALUE_TYPE_INT32;
}
if (type.is<float>()) {
return SPREADSHEET_VALUE_TYPE_FLOAT;
}
if (type.is<float2>()) {
return SPREADSHEET_VALUE_TYPE_FLOAT2;
}
if (type.is<float3>()) {
return SPREADSHEET_VALUE_TYPE_FLOAT3;
}
if (type.is<ColorGeometry4f>()) {
return SPREADSHEET_VALUE_TYPE_COLOR;
}
if (type.is<std::string>()) {
return SPREADSHEET_VALUE_TYPE_STRING;
}
if (type.is<InstanceReference>()) {
return SPREADSHEET_VALUE_TYPE_INSTANCES;
}
return SPREADSHEET_VALUE_TYPE_UNKNOWN;
}
SpreadsheetColumnID *spreadsheet_column_id_new()
{
SpreadsheetColumnID *column_id = (SpreadsheetColumnID *)MEM_callocN(sizeof(SpreadsheetColumnID),

View File

@@ -20,33 +20,36 @@
#include "BLI_string_ref.hh"
#include "spreadsheet_cell_value.hh"
#include "FN_generic_virtual_array.hh"
namespace blender::ed::spreadsheet {
struct CellDrawParams;
eSpreadsheetColumnValueType cpp_type_to_column_type(const fn::CPPType &type);
/**
* This represents a column in a spreadsheet. It has a name and provides a value for all the cells
* in the column.
*/
class ColumnValues {
class ColumnValues final {
protected:
eSpreadsheetColumnValueType type_;
std::string name_;
int size_;
fn::GVArray data_;
public:
ColumnValues(const eSpreadsheetColumnValueType type, std::string name, const int size)
: type_(type), name_(std::move(name)), size_(size)
ColumnValues(std::string name, fn::GVArray data) : name_(std::move(name)), data_(std::move(data))
{
/* The array should not be empty. */
BLI_assert(data_);
}
virtual ~ColumnValues() = default;
virtual void get_value(int index, CellValue &r_cell_value) const = 0;
eSpreadsheetColumnValueType type() const
{
return type_;
return cpp_type_to_column_type(data_.type());
}
StringRefNull name() const
@@ -56,45 +59,16 @@ class ColumnValues {
int size() const
{
return size_;
return data_.size();
}
const fn::GVArray &data() const
{
return data_;
}
/* The default width of newly created columns, in UI units. */
float default_width = 0.0f;
};
/* Utility class for the function below. */
template<typename GetValueF> class LambdaColumnValues : public ColumnValues {
private:
GetValueF get_value_;
public:
LambdaColumnValues(const eSpreadsheetColumnValueType type,
std::string name,
int size,
GetValueF get_value)
: ColumnValues(type, std::move(name), size), get_value_(std::move(get_value))
{
}
void get_value(int index, CellValue &r_cell_value) const final
{
get_value_(index, r_cell_value);
}
};
/* Utility function that simplifies creating a spreadsheet column from a lambda function. */
template<typename GetValueF>
std::unique_ptr<ColumnValues> column_values_from_function(const eSpreadsheetColumnValueType type,
std::string name,
const int size,
GetValueF get_value,
const float default_width = 0.0f)
{
std::unique_ptr<ColumnValues> column_values = std::make_unique<LambdaColumnValues<GetValueF>>(
type, std::move(name), size, std::move(get_value));
column_values->default_width = default_width;
return column_values;
}
} // namespace blender::ed::spreadsheet

View File

@@ -14,6 +14,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "BLI_virtual_array.hh"
#include "BKE_context.h"
#include "BKE_editmesh.h"
#include "BKE_lib_id.h"
@@ -51,30 +53,6 @@ using blender::fn::GField;
namespace blender::ed::spreadsheet {
static std::optional<eSpreadsheetColumnValueType> cpp_type_to_column_value_type(
const fn::CPPType &type)
{
if (type.is<bool>()) {
return SPREADSHEET_VALUE_TYPE_BOOL;
}
if (type.is<int>()) {
return SPREADSHEET_VALUE_TYPE_INT32;
}
if (type.is<float>()) {
return SPREADSHEET_VALUE_TYPE_FLOAT;
}
if (type.is<float2>()) {
return SPREADSHEET_VALUE_TYPE_FLOAT2;
}
if (type.is<float3>()) {
return SPREADSHEET_VALUE_TYPE_FLOAT3;
}
if (type.is<ColorGeometry4f>()) {
return SPREADSHEET_VALUE_TYPE_COLOR;
}
return std::nullopt;
}
void ExtraColumns::foreach_default_column_ids(
FunctionRef<void(const SpreadsheetColumnID &, bool is_extra)> fn) const
{
@@ -92,39 +70,7 @@ std::unique_ptr<ColumnValues> ExtraColumns::get_column_values(
if (values == nullptr) {
return {};
}
eSpreadsheetColumnValueType column_type = *cpp_type_to_column_value_type(values->type());
return column_values_from_function(column_type,
column_id.name,
values->size(),
[column_type, values](int index, CellValue &r_cell_value) {
const void *value = (*values)[index];
switch (column_type) {
case SPREADSHEET_VALUE_TYPE_BOOL:
r_cell_value.value_bool = *(const bool *)value;
break;
case SPREADSHEET_VALUE_TYPE_INT32:
r_cell_value.value_int = *(const int *)value;
break;
case SPREADSHEET_VALUE_TYPE_FLOAT:
r_cell_value.value_float = *(const float *)value;
break;
case SPREADSHEET_VALUE_TYPE_FLOAT2:
r_cell_value.value_float2 = *(const float2 *)value;
break;
case SPREADSHEET_VALUE_TYPE_FLOAT3:
r_cell_value.value_float3 = *(const float3 *)value;
break;
case SPREADSHEET_VALUE_TYPE_COLOR:
r_cell_value.value_color = *(
const ColorGeometry4f *)value;
break;
case SPREADSHEET_VALUE_TYPE_STRING:
r_cell_value.value_string = *(const std::string *)value;
break;
case SPREADSHEET_VALUE_TYPE_INSTANCES:
break;
}
});
return std::make_unique<ColumnValues>(column_id.name, fn::GVArray::ForSpan(*values));
}
void GeometryDataSource::foreach_default_column_ids(
@@ -179,52 +125,25 @@ std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values(
if (STREQ(column_id.name, "Name")) {
Span<int> reference_handles = instances.instance_reference_handles();
Span<InstanceReference> references = instances.references();
std::unique_ptr<ColumnValues> values = column_values_from_function(
SPREADSHEET_VALUE_TYPE_INSTANCES,
"Name",
domain_size,
[reference_handles, references](int index, CellValue &r_cell_value) {
const InstanceReference &reference = references[reference_handles[index]];
switch (reference.type()) {
case InstanceReference::Type::Object: {
Object &object = reference.object();
r_cell_value.value_object = ObjectCellValue{&object};
break;
}
case InstanceReference::Type::Collection: {
Collection &collection = reference.collection();
r_cell_value.value_collection = CollectionCellValue{&collection};
break;
}
case InstanceReference::Type::GeometrySet: {
const GeometrySet &geometry_set = reference.geometry_set();
r_cell_value.value_geometry_set = GeometrySetCellValue{&geometry_set};
break;
}
case InstanceReference::Type::None: {
break;
}
}
});
return values;
return std::make_unique<ColumnValues>(
column_id.name,
VArray<InstanceReference>::ForFunc(domain_size,
[reference_handles, references](int64_t index) {
return references[reference_handles[index]];
}));
}
Span<float4x4> transforms = instances.instance_transforms();
if (STREQ(column_id.name, "Rotation")) {
return column_values_from_function(
SPREADSHEET_VALUE_TYPE_FLOAT3,
column_id.name,
domain_size,
[transforms](int index, CellValue &r_cell_value) {
r_cell_value.value_float3 = transforms[index].to_euler();
});
return std::make_unique<ColumnValues>(
column_id.name, VArray<float3>::ForFunc(domain_size, [transforms](int64_t index) {
return transforms[index].to_euler();
}));
}
if (STREQ(column_id.name, "Scale")) {
return column_values_from_function(SPREADSHEET_VALUE_TYPE_FLOAT3,
column_id.name,
domain_size,
[transforms](int index, CellValue &r_cell_value) {
r_cell_value.value_float3 = transforms[index].scale();
});
return std::make_unique<ColumnValues>(
column_id.name, VArray<float3>::ForFunc(domain_size, [transforms](int64_t index) {
return transforms[index].scale();
}));
}
}
@@ -237,71 +156,7 @@ std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values(
return {};
}
const CustomDataType type = bke::cpp_type_to_custom_data_type(varray.type());
switch (type) {
case CD_PROP_FLOAT:
return column_values_from_function(SPREADSHEET_VALUE_TYPE_FLOAT,
column_id.name,
domain_size,
[varray](int index, CellValue &r_cell_value) {
float value;
varray.get(index, &value);
r_cell_value.value_float = value;
});
case CD_PROP_INT32:
return column_values_from_function(
SPREADSHEET_VALUE_TYPE_INT32,
column_id.name,
domain_size,
[varray](int index, CellValue &r_cell_value) {
int value;
varray.get(index, &value);
r_cell_value.value_int = value;
},
STREQ(column_id.name, "id") ? 5.5f : 0.0f);
case CD_PROP_BOOL:
return column_values_from_function(SPREADSHEET_VALUE_TYPE_BOOL,
column_id.name,
domain_size,
[varray](int index, CellValue &r_cell_value) {
bool value;
varray.get(index, &value);
r_cell_value.value_bool = value;
});
case CD_PROP_FLOAT2: {
return column_values_from_function(SPREADSHEET_VALUE_TYPE_FLOAT2,
column_id.name,
domain_size,
[varray](int index, CellValue &r_cell_value) {
float2 value;
varray.get(index, &value);
r_cell_value.value_float2 = value;
});
}
case CD_PROP_FLOAT3: {
return column_values_from_function(SPREADSHEET_VALUE_TYPE_FLOAT3,
column_id.name,
domain_size,
[varray](int index, CellValue &r_cell_value) {
float3 value;
varray.get(index, &value);
r_cell_value.value_float3 = value;
});
}
case CD_PROP_COLOR: {
return column_values_from_function(SPREADSHEET_VALUE_TYPE_COLOR,
column_id.name,
domain_size,
[varray](int index, CellValue &r_cell_value) {
ColorGeometry4f value;
varray.get(index, &value);
r_cell_value.value_color = value;
});
}
default:
break;
}
return {};
return std::make_unique<ColumnValues>(column_id.name, std::move(varray));
}
int GeometryDataSource::tot_rows() const
@@ -309,90 +164,9 @@ int GeometryDataSource::tot_rows() const
return component_->attribute_domain_size(domain_);
}
using IsVertexSelectedFn = FunctionRef<bool(int vertex_index)>;
static void get_selected_vertex_indices(const Mesh &mesh,
const IsVertexSelectedFn is_vertex_selected_fn,
MutableSpan<bool> selection)
{
for (const int i : IndexRange(mesh.totvert)) {
if (!selection[i]) {
continue;
}
if (!is_vertex_selected_fn(i)) {
selection[i] = false;
}
}
}
static void get_selected_corner_indices(const Mesh &mesh,
const IsVertexSelectedFn is_vertex_selected_fn,
MutableSpan<bool> selection)
{
for (const int i : IndexRange(mesh.totloop)) {
const MLoop &loop = mesh.mloop[i];
if (!selection[i]) {
continue;
}
if (!is_vertex_selected_fn(loop.v)) {
selection[i] = false;
}
}
}
static void get_selected_face_indices(const Mesh &mesh,
const IsVertexSelectedFn is_vertex_selected_fn,
MutableSpan<bool> selection)
{
for (const int poly_index : IndexRange(mesh.totpoly)) {
if (!selection[poly_index]) {
continue;
}
const MPoly &poly = mesh.mpoly[poly_index];
for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) {
const MLoop &loop = mesh.mloop[loop_index];
if (!is_vertex_selected_fn(loop.v)) {
selection[poly_index] = false;
break;
}
}
}
}
static void get_selected_edge_indices(const Mesh &mesh,
const IsVertexSelectedFn is_vertex_selected_fn,
MutableSpan<bool> selection)
{
for (const int i : IndexRange(mesh.totedge)) {
if (!selection[i]) {
continue;
}
const MEdge &edge = mesh.medge[i];
if (!is_vertex_selected_fn(edge.v1) || !is_vertex_selected_fn(edge.v2)) {
selection[i] = false;
}
}
}
static void get_selected_indices_on_domain(const Mesh &mesh,
const AttributeDomain domain,
const IsVertexSelectedFn is_vertex_selected_fn,
MutableSpan<bool> selection)
{
switch (domain) {
case ATTR_DOMAIN_POINT:
return get_selected_vertex_indices(mesh, is_vertex_selected_fn, selection);
case ATTR_DOMAIN_FACE:
return get_selected_face_indices(mesh, is_vertex_selected_fn, selection);
case ATTR_DOMAIN_CORNER:
return get_selected_corner_indices(mesh, is_vertex_selected_fn, selection);
case ATTR_DOMAIN_EDGE:
return get_selected_edge_indices(mesh, is_vertex_selected_fn, selection);
default:
return;
}
}
/**
* Only data sets corresponding to mesh objects in edit mode currently support selection filtering.
*/
bool GeometryDataSource::has_selection_filter() const
{
Object *object_orig = DEG_get_original_object(object_eval_);
@@ -409,7 +183,18 @@ bool GeometryDataSource::has_selection_filter() const
return true;
}
void GeometryDataSource::apply_selection_filter(MutableSpan<bool> rows_included) const
static IndexMask index_mask_from_bool_array(const VArray<bool> &selection,
Vector<int64_t> &indices)
{
for (const int i : selection.index_range()) {
if (selection[i]) {
indices.append(i);
}
}
return IndexMask(indices);
}
IndexMask GeometryDataSource::apply_selection_filter(Vector<int64_t> &indices) const
{
std::lock_guard lock{mutex_};
@@ -425,27 +210,38 @@ void GeometryDataSource::apply_selection_filter(MutableSpan<bool> rows_included)
int *orig_indices = (int *)CustomData_get_layer(&mesh_eval->vdata, CD_ORIGINDEX);
if (orig_indices != nullptr) {
/* Use CD_ORIGINDEX layer if it exists. */
auto is_vertex_selected = [&](int vertex_index) -> bool {
const int i_orig = orig_indices[vertex_index];
if (i_orig < 0) {
return false;
}
if (i_orig >= bm->totvert) {
return false;
}
BMVert *vert = bm->vtable[i_orig];
return BM_elem_flag_test(vert, BM_ELEM_SELECT);
};
get_selected_indices_on_domain(*mesh_eval, domain_, is_vertex_selected, rows_included);
VArray<bool> selection = mesh_component->attribute_try_adapt_domain<bool>(
VArray<bool>::ForFunc(mesh_eval->totvert,
[bm, orig_indices](int vertex_index) -> bool {
const int i_orig = orig_indices[vertex_index];
if (i_orig < 0) {
return false;
}
if (i_orig >= bm->totvert) {
return false;
}
BMVert *vert = bm->vtable[i_orig];
return BM_elem_flag_test(vert, BM_ELEM_SELECT);
}),
ATTR_DOMAIN_POINT,
domain_);
return index_mask_from_bool_array(selection, indices);
}
else if (mesh_eval->totvert == bm->totvert) {
if (mesh_eval->totvert == bm->totvert) {
/* Use a simple heuristic to match original vertices to evaluated ones. */
auto is_vertex_selected = [&](int vertex_index) -> bool {
BMVert *vert = bm->vtable[vertex_index];
return BM_elem_flag_test(vert, BM_ELEM_SELECT);
};
get_selected_indices_on_domain(*mesh_eval, domain_, is_vertex_selected, rows_included);
VArray<bool> selection = mesh_component->attribute_try_adapt_domain<bool>(
VArray<bool>::ForFunc(mesh_eval->totvert,
[bm](int vertex_index) -> bool {
BMVert *vert = bm->vtable[vertex_index];
return BM_elem_flag_test(vert, BM_ELEM_SELECT);
}),
ATTR_DOMAIN_POINT,
domain_);
return index_mask_from_bool_array(selection, indices);
}
return IndexMask(mesh_eval->totvert);
}
void VolumeDataSource::foreach_default_column_ids(
@@ -472,50 +268,36 @@ std::unique_ptr<ColumnValues> VolumeDataSource::get_column_values(
#ifdef WITH_OPENVDB
const int size = this->tot_rows();
if (STREQ(column_id.name, "Grid Name")) {
return column_values_from_function(
SPREADSHEET_VALUE_TYPE_STRING,
IFACE_("Grid Name"),
size,
[volume](int index, CellValue &r_cell_value) {
return std::make_unique<ColumnValues>(
IFACE_("Grid Name"), VArray<std::string>::ForFunc(size, [volume](int64_t index) {
const VolumeGrid *volume_grid = BKE_volume_grid_get_for_read(volume, index);
r_cell_value.value_string = BKE_volume_grid_name(volume_grid);
},
6.0f);
return BKE_volume_grid_name(volume_grid);
}));
}
if (STREQ(column_id.name, "Data Type")) {
return column_values_from_function(
SPREADSHEET_VALUE_TYPE_STRING,
IFACE_("Type"),
size,
[volume](int index, CellValue &r_cell_value) {
return std::make_unique<ColumnValues>(
IFACE_("Data Type"), VArray<std::string>::ForFunc(size, [volume](int64_t index) {
const VolumeGrid *volume_grid = BKE_volume_grid_get_for_read(volume, index);
const VolumeGridType type = BKE_volume_grid_type(volume_grid);
const char *name = nullptr;
RNA_enum_name_from_value(rna_enum_volume_grid_data_type_items, type, &name);
r_cell_value.value_string = IFACE_(name);
},
5.0f);
return IFACE_(name);
}));
}
if (STREQ(column_id.name, "Class")) {
return column_values_from_function(
SPREADSHEET_VALUE_TYPE_STRING,
IFACE_("Class"),
size,
[volume](int index, CellValue &r_cell_value) {
return std::make_unique<ColumnValues>(
IFACE_("Class"), VArray<std::string>::ForFunc(size, [volume](int64_t index) {
const VolumeGrid *volume_grid = BKE_volume_grid_get_for_read(volume, index);
openvdb::GridBase::ConstPtr grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid);
openvdb::GridClass grid_class = grid->getGridClass();
if (grid_class == openvdb::GridClass::GRID_FOG_VOLUME) {
r_cell_value.value_string = IFACE_("Fog Volume");
return IFACE_("Fog Volume");
}
else if (grid_class == openvdb::GridClass::GRID_LEVEL_SET) {
r_cell_value.value_string = IFACE_("Level Set");
if (grid_class == openvdb::GridClass::GRID_LEVEL_SET) {
return IFACE_("Level Set");
}
else {
r_cell_value.value_string = IFACE_("Unknown");
}
},
5.0f);
return IFACE_("Unknown");
}));
}
#else
UNUSED_VARS(column_id);

View File

@@ -87,7 +87,7 @@ class GeometryDataSource : public DataSource {
* filtering.
*/
bool has_selection_filter() const override;
void apply_selection_filter(MutableSpan<bool> rows_included) const;
IndexMask apply_selection_filter(Vector<int64_t> &indices) const;
void foreach_default_column_ids(
FunctionRef<void(const SpreadsheetColumnID &, bool is_extra)> fn) const override;

View File

@@ -17,8 +17,16 @@
#include <iomanip>
#include <sstream>
#include "BLI_float2.hh"
#include "BLI_float3.hh"
#include "BKE_geometry_set.hh"
#include "spreadsheet_column_values.hh"
#include "spreadsheet_layout.hh"
#include "DNA_collection_types.h"
#include "DNA_object_types.h"
#include "DNA_userdef_types.h"
#include "UI_interface.h"
@@ -92,13 +100,14 @@ class SpreadsheetLayoutDrawer : public SpreadsheetDrawer {
{
const int real_index = spreadsheet_layout_.row_indices[row_index];
const ColumnValues &column = *spreadsheet_layout_.columns[column_index].values;
CellValue cell_value;
if (real_index < column.size()) {
column.get_value(real_index, cell_value);
if (real_index > column.size()) {
return;
}
if (cell_value.value_int.has_value()) {
const int value = *cell_value.value_int;
const fn::GVArray &data = column.data();
if (data.type().is<int>()) {
const int value = data.get<int>(real_index);
const std::string value_str = std::to_string(value);
uiBut *but = uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
@@ -119,8 +128,8 @@ class SpreadsheetLayoutDrawer : public SpreadsheetDrawer {
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
UI_but_drawflag_enable(but, UI_BUT_TEXT_RIGHT);
}
else if (cell_value.value_float.has_value()) {
const float value = *cell_value.value_float;
else if (data.type().is<float>()) {
const float value = data.get<float>(real_index);
std::stringstream ss;
ss << std::fixed << std::setprecision(3) << value;
const std::string value_str = ss.str();
@@ -143,8 +152,8 @@ class SpreadsheetLayoutDrawer : public SpreadsheetDrawer {
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
UI_but_drawflag_enable(but, UI_BUT_TEXT_RIGHT);
}
else if (cell_value.value_bool.has_value()) {
const bool value = *cell_value.value_bool;
else if (data.type().is<bool>()) {
const bool value = data.get<bool>(real_index);
const int icon = value ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT;
uiBut *but = uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
@@ -163,87 +172,81 @@ class SpreadsheetLayoutDrawer : public SpreadsheetDrawer {
nullptr);
UI_but_drawflag_disable(but, UI_BUT_ICON_LEFT);
}
else if (cell_value.value_float2.has_value()) {
const float2 value = *cell_value.value_float2;
else if (data.type().is<float2>()) {
const float2 value = data.get<float2>(real_index);
this->draw_float_vector(params, Span(&value.x, 2));
}
else if (cell_value.value_float3.has_value()) {
const float3 value = *cell_value.value_float3;
else if (data.type().is<float3>()) {
const float3 value = data.get<float3>(real_index);
this->draw_float_vector(params, Span(&value.x, 3));
}
else if (cell_value.value_color.has_value()) {
const ColorGeometry4f value = *cell_value.value_color;
else if (data.type().is<ColorGeometry4f>()) {
const ColorGeometry4f value = data.get<ColorGeometry4f>(real_index);
this->draw_float_vector(params, Span(&value.r, 4));
}
else if (cell_value.value_object.has_value()) {
const ObjectCellValue value = *cell_value.value_object;
uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
ICON_OBJECT_DATA,
reinterpret_cast<const ID *const>(value.object)->name + 2,
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
}
else if (cell_value.value_collection.has_value()) {
const CollectionCellValue value = *cell_value.value_collection;
uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
ICON_OUTLINER_COLLECTION,
reinterpret_cast<const ID *const>(value.collection)->name + 2,
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
}
else if (cell_value.value_geometry_set.has_value()) {
uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
ICON_MESH_DATA,
"Geometry",
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
}
else if (cell_value.value_string.has_value()) {
uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
ICON_NONE,
cell_value.value_string->c_str(),
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
else if (data.type().is<InstanceReference>()) {
const InstanceReference value = data.get<InstanceReference>(real_index);
switch (value.type()) {
case InstanceReference::Type::Object: {
const Object &object = value.object();
uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
ICON_OBJECT_DATA,
object.id.name + 2,
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
break;
}
case InstanceReference::Type::Collection: {
Collection &collection = value.collection();
uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
ICON_OUTLINER_COLLECTION,
collection.id.name + 2,
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
break;
}
case InstanceReference::Type::GeometrySet: {
uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
ICON_MESH_DATA,
"Geometry",
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
break;
}
case InstanceReference::Type::None: {
break;
}
}
}
}

View File

@@ -32,7 +32,7 @@ struct ColumnLayout {
/* Layout information for the entire spreadsheet. */
struct SpreadsheetLayout {
Vector<ColumnLayout> columns;
Span<int64_t> row_indices;
IndexMask row_indices;
int index_column_width = 100;
};

View File

@@ -18,7 +18,6 @@
#include "BLI_listbase.h"
#include "DNA_collection_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
@@ -38,238 +37,197 @@
namespace blender::ed::spreadsheet {
template<typename OperationFn>
static void apply_filter_operation(const ColumnValues &values,
template<typename T, typename OperationFn>
static void apply_filter_operation(const VArray<T> &data,
OperationFn check_fn,
MutableSpan<bool> rows_included)
const IndexMask mask,
Vector<int64_t> &new_indices)
{
for (const int i : rows_included.index_range()) {
if (!rows_included[i]) {
continue;
}
CellValue cell_value;
values.get_value(i, cell_value);
if (!check_fn(cell_value)) {
rows_included[i] = false;
for (const int64_t i : mask) {
if (check_fn(data[i])) {
new_indices.append(i);
}
}
}
static void apply_row_filter(const SpreadsheetLayout &spreadsheet_layout,
const SpreadsheetRowFilter &row_filter,
MutableSpan<bool> rows_included)
static void apply_row_filter(const SpreadsheetRowFilter &row_filter,
const Map<StringRef, const ColumnValues *> &columns,
const IndexMask prev_mask,
Vector<int64_t> &new_indices)
{
for (const ColumnLayout &column : spreadsheet_layout.columns) {
const ColumnValues &values = *column.values;
if (values.name() != row_filter.column_name) {
continue;
}
switch (values.type()) {
case SPREADSHEET_VALUE_TYPE_INT32: {
const int value = row_filter.value_int;
switch (row_filter.operation) {
case SPREADSHEET_ROW_FILTER_EQUAL: {
apply_filter_operation(
values,
[value](const CellValue &cell_value) -> bool {
return *cell_value.value_int == value;
},
rows_included);
break;
}
case SPREADSHEET_ROW_FILTER_GREATER: {
apply_filter_operation(
values,
[value](const CellValue &cell_value) -> bool {
return *cell_value.value_int > value;
},
rows_included);
break;
}
case SPREADSHEET_ROW_FILTER_LESS: {
apply_filter_operation(
values,
[value](const CellValue &cell_value) -> bool {
return *cell_value.value_int < value;
},
rows_included);
break;
}
}
break;
}
case SPREADSHEET_VALUE_TYPE_FLOAT: {
const float value = row_filter.value_float;
switch (row_filter.operation) {
case SPREADSHEET_ROW_FILTER_EQUAL: {
const float threshold = row_filter.threshold;
apply_filter_operation(
values,
[value, threshold](const CellValue &cell_value) -> bool {
return std::abs(*cell_value.value_float - value) < threshold;
},
rows_included);
break;
}
case SPREADSHEET_ROW_FILTER_GREATER: {
apply_filter_operation(
values,
[value](const CellValue &cell_value) -> bool {
return *cell_value.value_float > value;
},
rows_included);
break;
}
case SPREADSHEET_ROW_FILTER_LESS: {
apply_filter_operation(
values,
[value](const CellValue &cell_value) -> bool {
return *cell_value.value_float < value;
},
rows_included);
break;
}
}
break;
}
case SPREADSHEET_VALUE_TYPE_FLOAT2: {
const float2 value = row_filter.value_float2;
switch (row_filter.operation) {
case SPREADSHEET_ROW_FILTER_EQUAL: {
const float threshold_squared = row_filter.threshold * row_filter.threshold;
apply_filter_operation(
values,
[value, threshold_squared](const CellValue &cell_value) -> bool {
return float2::distance_squared(*cell_value.value_float2, value) <
threshold_squared;
},
rows_included);
break;
}
case SPREADSHEET_ROW_FILTER_GREATER: {
apply_filter_operation(
values,
[value](const CellValue &cell_value) -> bool {
return cell_value.value_float2->x > value.x &&
cell_value.value_float2->y > value.y;
},
rows_included);
break;
}
case SPREADSHEET_ROW_FILTER_LESS: {
apply_filter_operation(
values,
[value](const CellValue &cell_value) -> bool {
return cell_value.value_float2->x < value.x &&
cell_value.value_float2->y < value.y;
},
rows_included);
break;
}
}
break;
}
case SPREADSHEET_VALUE_TYPE_FLOAT3: {
const float3 value = row_filter.value_float3;
switch (row_filter.operation) {
case SPREADSHEET_ROW_FILTER_EQUAL: {
const float threshold_squared = row_filter.threshold * row_filter.threshold;
apply_filter_operation(
values,
[value, threshold_squared](const CellValue &cell_value) -> bool {
return float3::distance_squared(*cell_value.value_float3, value) <
threshold_squared;
},
rows_included);
break;
}
case SPREADSHEET_ROW_FILTER_GREATER: {
apply_filter_operation(
values,
[value](const CellValue &cell_value) -> bool {
return cell_value.value_float3->x > value.x &&
cell_value.value_float3->y > value.y &&
cell_value.value_float3->z > value.z;
},
rows_included);
break;
}
case SPREADSHEET_ROW_FILTER_LESS: {
apply_filter_operation(
values,
[value](const CellValue &cell_value) -> bool {
return cell_value.value_float3->x < value.x &&
cell_value.value_float3->y < value.y &&
cell_value.value_float3->z < value.z;
},
rows_included);
break;
}
}
break;
}
case SPREADSHEET_VALUE_TYPE_COLOR: {
const ColorGeometry4f value = row_filter.value_color;
switch (row_filter.operation) {
case SPREADSHEET_ROW_FILTER_EQUAL: {
const float threshold_squared = row_filter.threshold * row_filter.threshold;
apply_filter_operation(
values,
[value, threshold_squared](const CellValue &cell_value) -> bool {
return len_squared_v4v4(value, *cell_value.value_color) < threshold_squared;
},
rows_included);
break;
}
}
break;
}
case SPREADSHEET_VALUE_TYPE_BOOL: {
const bool value = (row_filter.flag & SPREADSHEET_ROW_FILTER_BOOL_VALUE) != 0;
const ColumnValues &column = *columns.lookup(row_filter.column_name);
const fn::GVArray &column_data = column.data();
if (column_data.type().is<float>()) {
const float value = row_filter.value_float;
switch (row_filter.operation) {
case SPREADSHEET_ROW_FILTER_EQUAL: {
const float threshold = row_filter.threshold;
apply_filter_operation(
values,
[value](const CellValue &cell_value) -> bool {
return *cell_value.value_bool == value;
},
rows_included);
column_data.typed<float>(),
[&](const float cell) { return std::abs(cell - value) < threshold; },
prev_mask,
new_indices);
break;
}
case SPREADSHEET_VALUE_TYPE_INSTANCES: {
const StringRef value = row_filter.value_string;
case SPREADSHEET_ROW_FILTER_GREATER: {
apply_filter_operation(
values,
[value](const CellValue &cell_value) -> bool {
const ID *id = nullptr;
if (cell_value.value_object) {
id = &cell_value.value_object->object->id;
}
else if (cell_value.value_collection) {
id = &cell_value.value_collection->collection->id;
}
if (id == nullptr) {
return false;
}
return value == id->name + 2;
},
rows_included);
column_data.typed<float>(),
[&](const float cell) { return cell > value; },
prev_mask,
new_indices);
break;
}
default:
case SPREADSHEET_ROW_FILTER_LESS: {
apply_filter_operation(
column_data.typed<float>(),
[&](const float cell) { return cell < value; },
prev_mask,
new_indices);
break;
}
}
/* Only one column should have this name. */
break;
}
}
static void index_vector_from_bools(Span<bool> selection, Vector<int64_t> &indices)
{
for (const int i : selection.index_range()) {
if (selection[i]) {
indices.append(i);
else if (column_data.type().is<int>()) {
const int value = row_filter.value_int;
switch (row_filter.operation) {
case SPREADSHEET_ROW_FILTER_EQUAL: {
apply_filter_operation(
column_data.typed<int>(),
[&](const int cell) { return cell == value; },
prev_mask,
new_indices);
break;
}
case SPREADSHEET_ROW_FILTER_GREATER: {
apply_filter_operation(
column_data.typed<int>(),
[value](const int cell) { return cell > value; },
prev_mask,
new_indices);
break;
}
case SPREADSHEET_ROW_FILTER_LESS: {
apply_filter_operation(
column_data.typed<int>(),
[&](const int cell) { return cell < value; },
prev_mask,
new_indices);
break;
}
}
}
else if (column_data.type().is<float2>()) {
const float2 value = row_filter.value_float2;
switch (row_filter.operation) {
case SPREADSHEET_ROW_FILTER_EQUAL: {
const float threshold_sq = row_filter.threshold;
apply_filter_operation(
column_data.typed<float2>(),
[&](const float2 cell) {
return float2::distance_squared(cell, value) > threshold_sq;
},
prev_mask,
new_indices);
break;
}
case SPREADSHEET_ROW_FILTER_GREATER: {
apply_filter_operation(
column_data.typed<float2>(),
[&](const float2 cell) { return cell.x > value.x && cell.y > value.y; },
prev_mask,
new_indices);
break;
}
case SPREADSHEET_ROW_FILTER_LESS: {
apply_filter_operation(
column_data.typed<float2>(),
[&](const float2 cell) { return cell.x < value.x && cell.y < value.y; },
prev_mask,
new_indices);
break;
}
}
}
else if (column_data.type().is<float3>()) {
const float3 value = row_filter.value_float3;
switch (row_filter.operation) {
case SPREADSHEET_ROW_FILTER_EQUAL: {
const float threshold_sq = row_filter.threshold;
apply_filter_operation(
column_data.typed<float3>(),
[&](const float3 cell) {
return float3::distance_squared(cell, value) > threshold_sq;
},
prev_mask,
new_indices);
break;
}
case SPREADSHEET_ROW_FILTER_GREATER: {
apply_filter_operation(
column_data.typed<float3>(),
[&](const float3 cell) {
return cell.x > value.x && cell.y > value.y && cell.z > value.z;
},
prev_mask,
new_indices);
break;
}
case SPREADSHEET_ROW_FILTER_LESS: {
apply_filter_operation(
column_data.typed<float3>(),
[&](const float3 cell) {
return cell.x < value.x && cell.y < value.y && cell.z < value.z;
},
prev_mask,
new_indices);
break;
}
}
}
else if (column_data.type().is<ColorGeometry4f>()) {
const ColorGeometry4f value = row_filter.value_color;
switch (row_filter.operation) {
case SPREADSHEET_ROW_FILTER_EQUAL: {
const float threshold_sq = row_filter.threshold;
apply_filter_operation(
column_data.typed<ColorGeometry4f>(),
[&](const ColorGeometry4f cell) {
return len_squared_v4v4(cell, value) > threshold_sq;
},
prev_mask,
new_indices);
break;
}
}
}
else if (column_data.type().is<InstanceReference>()) {
const StringRef value = row_filter.value_string;
switch (row_filter.operation) {
case SPREADSHEET_ROW_FILTER_EQUAL: {
apply_filter_operation(
column_data.typed<InstanceReference>(),
[&](const InstanceReference cell) {
switch (cell.type()) {
case InstanceReference::Type::Object: {
return value == (reinterpret_cast<ID &>(cell.object()).name + 2);
}
case InstanceReference::Type::Collection: {
return value == (reinterpret_cast<ID &>(cell.collection()).name + 2);
}
case InstanceReference::Type::GeometrySet: {
return false;
}
case InstanceReference::Type::None: {
return false;
}
}
BLI_assert_unreachable();
return false;
},
prev_mask,
new_indices);
break;
}
}
}
}
@@ -297,10 +255,10 @@ static bool use_selection_filter(const SpaceSpreadsheet &sspreadsheet,
return true;
}
Span<int64_t> spreadsheet_filter_rows(const SpaceSpreadsheet &sspreadsheet,
const SpreadsheetLayout &spreadsheet_layout,
const DataSource &data_source,
ResourceScope &scope)
IndexMask spreadsheet_filter_rows(const SpaceSpreadsheet &sspreadsheet,
const SpreadsheetLayout &spreadsheet_layout,
const DataSource &data_source,
ResourceScope &scope)
{
const int tot_rows = data_source.tot_rows();
@@ -309,29 +267,46 @@ Span<int64_t> spreadsheet_filter_rows(const SpaceSpreadsheet &sspreadsheet,
/* Avoid allocating an array if no row filtering is necessary. */
if (!(use_filters || use_selection)) {
return IndexRange(tot_rows).as_span();
return IndexMask(tot_rows);
}
Array<bool> rows_included(tot_rows, true);
IndexMask mask(tot_rows);
if (use_filters) {
LISTBASE_FOREACH (const SpreadsheetRowFilter *, row_filter, &sspreadsheet.row_filters) {
if (row_filter->flag & SPREADSHEET_ROW_FILTER_ENABLED) {
apply_row_filter(spreadsheet_layout, *row_filter, rows_included);
}
}
}
Vector<int64_t> mask_indices;
mask_indices.reserve(tot_rows);
if (use_selection) {
const GeometryDataSource *geometry_data_source = dynamic_cast<const GeometryDataSource *>(
&data_source);
geometry_data_source->apply_selection_filter(rows_included);
mask = geometry_data_source->apply_selection_filter(mask_indices);
}
Vector<int64_t> &indices = scope.construct<Vector<int64_t>>();
index_vector_from_bools(rows_included, indices);
if (use_filters) {
Map<StringRef, const ColumnValues *> columns;
for (const ColumnLayout &column : spreadsheet_layout.columns) {
columns.add(column.values->name(), column.values);
}
return indices;
LISTBASE_FOREACH (const SpreadsheetRowFilter *, row_filter, &sspreadsheet.row_filters) {
if (row_filter->flag & SPREADSHEET_ROW_FILTER_ENABLED) {
if (!columns.contains(row_filter->column_name)) {
continue;
}
Vector<int64_t> new_indices;
new_indices.reserve(mask_indices.size());
apply_row_filter(*row_filter, columns, mask, new_indices);
std::swap(new_indices, mask_indices);
mask = IndexMask(mask_indices);
}
}
}
if (mask_indices.is_empty()) {
BLI_assert(mask.is_empty() || mask.is_range());
return mask;
}
return IndexMask(scope.add_value(std::move(mask_indices)));
}
SpreadsheetRowFilter *spreadsheet_row_filter_new()

View File

@@ -23,10 +23,10 @@
namespace blender::ed::spreadsheet {
Span<int64_t> spreadsheet_filter_rows(const SpaceSpreadsheet &sspreadsheet,
const SpreadsheetLayout &spreadsheet_layout,
const DataSource &data_source,
ResourceScope &scope);
IndexMask spreadsheet_filter_rows(const SpaceSpreadsheet &sspreadsheet,
const SpreadsheetLayout &spreadsheet_layout,
const DataSource &data_source,
ResourceScope &scope);
SpreadsheetRowFilter *spreadsheet_row_filter_new();
SpreadsheetRowFilter *spreadsheet_row_filter_copy(const SpreadsheetRowFilter *src_row_filter);

View File

@@ -114,6 +114,8 @@ static std::string value_string(const SpreadsheetRowFilter &row_filter,
}
case SPREADSHEET_VALUE_TYPE_STRING:
return row_filter.value_string;
case SPREADSHEET_VALUE_TYPE_UNKNOWN:
return "";
}
BLI_assert_unreachable();
return "";
@@ -238,6 +240,10 @@ static void spreadsheet_filter_panel_draw(const bContext *C, Panel *panel)
uiItemR(layout, filter_ptr, "threshold", 0, nullptr, ICON_NONE);
break;
case SPREADSHEET_VALUE_TYPE_STRING:
uiItemR(layout, filter_ptr, "value_string", 0, IFACE_("Value"), ICON_NONE);
break;
case SPREADSHEET_VALUE_TYPE_UNKNOWN:
uiItemL(layout, IFACE_("Unkown column type"), ICON_ERROR);
break;
}
}

View File

@@ -174,6 +174,11 @@ class GVArrayCommon {
void get_internal_single_to_uninitialized(void *r_value) const;
void get(const int64_t index, void *r_value) const;
/**
* Returns a copy of the value at the given index. Usually a typed virtual array should
* be used instead, but sometimes this is simpler when only a few indices are needed.
*/
template<typename T> T get(const int64_t index) const;
void get_to_uninitialized(const int64_t index, void *r_value) const;
};
@@ -691,6 +696,16 @@ inline void GVArrayCommon::get(const int64_t index, void *r_value) const
impl_->get(index, r_value);
}
template<typename T> inline T GVArrayCommon::get(const int64_t index) const
{
BLI_assert(index >= 0);
BLI_assert(index < this->size());
BLI_assert(this->type().is<T>());
T value{};
impl_->get(index, &value);
return value;
}
/* Same as `get`, but `r_value` is expected to point to uninitialized memory. */
inline void GVArrayCommon::get_to_uninitialized(const int64_t index, void *r_value) const
{

View File

@@ -2006,6 +2006,7 @@ typedef enum eSpaceSpreadsheet_ContextType {
} eSpaceSpreadsheet_ContextType;
typedef enum eSpreadsheetColumnValueType {
SPREADSHEET_VALUE_TYPE_UNKNOWN = -1,
SPREADSHEET_VALUE_TYPE_BOOL = 0,
SPREADSHEET_VALUE_TYPE_INT32 = 1,
SPREADSHEET_VALUE_TYPE_FLOAT = 2,