Spreadsheet: persistent column storage and data source

A `DataSource` provides columns for the spreadsheet to display.
Every column has a SpreadsheetColumnID as identifier. Columns
are not generated eagerly anymore, instead the main spreadsheet
code can request a column from a data source with an column
identifier. The column identifiers can be stored in DNA and allow us
to store persistent data per column.

On the user level the only thing that changes is that columns are
not shown in alphabetical order anymore. Instead, new columns
are always added on the left. The behavior can be changed,
however I'd prefer not to automate this too much currently. I think
we should just add operators to hide/reorder/resize columns soonish.

Differential Revision: https://developer.blender.org/D10901
This commit is contained in:
Jacques Lucke 2021-04-09 10:20:46 +02:00
parent 22574f741c
commit 75491fe100
Notes: blender-bot 2023-02-14 07:08:26 +01:00
Referenced by issue #87048, Hide / reveal columns for spreadsheet editor
16 changed files with 1079 additions and 647 deletions

View File

@ -1350,6 +1350,13 @@ static void write_area(BlendWriter *writer, ScrArea *area)
}
else if (sl->spacetype == SPACE_SPREADSHEET) {
BLO_write_struct(writer, SpaceSpreadsheet, sl);
SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl;
LISTBASE_FOREACH (SpreadsheetColumn *, column, &sspreadsheet->columns) {
BLO_write_struct(writer, SpreadsheetColumn, column);
BLO_write_struct(writer, SpreadsheetColumnID, column->id);
BLO_write_string(writer, column->id->name);
}
}
}
}
@ -1702,6 +1709,12 @@ static void direct_link_area(BlendDataReader *reader, ScrArea *area)
SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl;
sspreadsheet->runtime = NULL;
BLO_read_list(reader, &sspreadsheet->columns);
LISTBASE_FOREACH (SpreadsheetColumn *, column, &sspreadsheet->columns) {
BLO_read_data_address(reader, &column->id);
BLO_read_data_address(reader, &column->id->name);
}
}
}

View File

@ -33,15 +33,21 @@ set(INC
set(SRC
space_spreadsheet.cc
spreadsheet_column_layout.cc
spreadsheet_column.cc
spreadsheet_data_source.cc
spreadsheet_data_source_geometry.cc
spreadsheet_draw.cc
spreadsheet_from_geometry.cc
spreadsheet_layout.cc
spreadsheet_ops.cc
spreadsheet_column_layout.hh
spreadsheet_cell_value.hh
spreadsheet_column.hh
spreadsheet_data_source.hh
spreadsheet_data_source_geometry.hh
spreadsheet_column_values.hh
spreadsheet_draw.hh
spreadsheet_from_geometry.hh
spreadsheet_intern.hh
spreadsheet_layout.hh
)
set(LIB

View File

@ -17,7 +17,6 @@
#include <cstring>
#include "BLI_listbase.h"
#include "BLI_resource_scope.hh"
#include "BKE_screen.h"
@ -41,12 +40,15 @@
#include "WM_api.h"
#include "WM_types.h"
#include "BLF_api.h"
#include "spreadsheet_intern.hh"
#include "spreadsheet_column_layout.hh"
#include "spreadsheet_from_geometry.hh"
#include "spreadsheet_data_source_geometry.hh"
#include "spreadsheet_intern.hh"
#include "spreadsheet_layout.hh"
using namespace blender;
using namespace blender::ed::spreadsheet;
static SpaceLink *spreadsheet_create(const ScrArea *UNUSED(area), const Scene *UNUSED(scene))
@ -85,6 +87,10 @@ static void spreadsheet_free(SpaceLink *sl)
{
SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl;
MEM_SAFE_FREE(sspreadsheet->runtime);
LISTBASE_FOREACH_MUTABLE (SpreadsheetColumn *, column, &sspreadsheet->columns) {
spreadsheet_column_free(column);
}
}
static void spreadsheet_init(wmWindowManager *UNUSED(wm), ScrArea *area)
@ -94,6 +100,10 @@ static void spreadsheet_init(wmWindowManager *UNUSED(wm), ScrArea *area)
sspreadsheet->runtime = (SpaceSpreadsheet_Runtime *)MEM_callocN(
sizeof(SpaceSpreadsheet_Runtime), __func__);
}
LISTBASE_FOREACH_MUTABLE (SpreadsheetColumn *, column, &sspreadsheet->columns) {
spreadsheet_column_free(column);
}
BLI_listbase_clear(&sspreadsheet->columns);
}
static SpaceLink *spreadsheet_duplicate(SpaceLink *sl)
@ -102,6 +112,12 @@ static SpaceLink *spreadsheet_duplicate(SpaceLink *sl)
SpaceSpreadsheet *sspreadsheet_new = (SpaceSpreadsheet *)MEM_dupallocN(sspreadsheet_old);
sspreadsheet_new->runtime = (SpaceSpreadsheet_Runtime *)MEM_dupallocN(sspreadsheet_old->runtime);
BLI_listbase_clear(&sspreadsheet_new->columns);
LISTBASE_FOREACH (SpreadsheetColumn *, src_column, &sspreadsheet_old->columns) {
SpreadsheetColumn *new_column = spreadsheet_column_copy(src_column);
BLI_addtail(&sspreadsheet_new->columns, new_column);
}
return (SpaceLink *)sspreadsheet_new;
}
@ -133,47 +149,122 @@ static ID *get_used_id(const bContext *C)
return (ID *)active_object;
}
class FallbackSpreadsheetDrawer : public SpreadsheetDrawer {
};
static void gather_spreadsheet_columns(const bContext *C,
SpreadsheetColumnLayout &column_layout,
blender::ResourceScope &scope)
static std::unique_ptr<DataSource> get_data_source(const bContext *C)
{
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
ID *used_id = get_used_id(C);
if (used_id == nullptr) {
return;
return {};
}
const ID_Type id_type = GS(used_id->name);
if (id_type != ID_OB) {
return;
return {};
}
Object *object_orig = (Object *)used_id;
if (!ELEM(object_orig->type, OB_MESH, OB_POINTCLOUD)) {
return;
return {};
}
Object *object_eval = DEG_get_evaluated_object(depsgraph, object_orig);
if (object_eval == nullptr) {
return;
return {};
}
return spreadsheet_columns_from_geometry(C, object_eval, column_layout, scope);
return data_source_from_geometry(C, object_eval);
}
static float get_column_width(const ColumnValues &values)
{
if (values.default_width > 0) {
return values.default_width * UI_UNIT_X;
}
const int fontid = UI_style_get()->widget.uifont_id;
const int widget_points = UI_style_get_dpi()->widget.points;
BLF_size(fontid, widget_points * U.pixelsize, U.dpi);
const StringRefNull name = values.name();
const float name_width = BLF_width(fontid, name.data(), name.size());
return std::max<float>(name_width + UI_UNIT_X, 3 * UI_UNIT_X);
}
static int get_index_column_width(const int tot_rows)
{
const int fontid = UI_style_get()->widget.uifont_id;
BLF_size(fontid, UI_style_get_dpi()->widget.points * U.pixelsize, U.dpi);
return std::to_string(std::max(0, tot_rows - 1)).size() * BLF_width(fontid, "0", 1) +
UI_UNIT_X * 0.75;
}
static void update_visible_columns(ListBase &columns, DataSource &data_source)
{
Set<SpreadsheetColumnID> used_ids;
LISTBASE_FOREACH_MUTABLE (SpreadsheetColumn *, column, &columns) {
std::unique_ptr<ColumnValues> values = data_source.get_column_values(*column->id);
/* Remove columns that don't exist anymore. */
if (!values) {
BLI_remlink(&columns, column);
spreadsheet_column_free(column);
continue;
}
used_ids.add(*column->id);
}
data_source.foreach_default_column_ids([&](const SpreadsheetColumnID &column_id) {
std::unique_ptr<ColumnValues> values = data_source.get_column_values(column_id);
if (values) {
if (used_ids.add(column_id)) {
SpreadsheetColumnID *new_id = spreadsheet_column_id_copy(&column_id);
SpreadsheetColumn *new_column = spreadsheet_column_new(new_id);
BLI_addtail(&columns, new_column);
}
}
});
}
static void spreadsheet_main_region_draw(const bContext *C, ARegion *region)
{
SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C);
blender::ResourceScope scope;
SpreadsheetColumnLayout column_layout;
gather_spreadsheet_columns(C, column_layout, scope);
std::unique_ptr<DataSource> data_source = get_data_source(C);
if (!data_source) {
data_source = std::make_unique<DataSource>();
}
sspreadsheet->runtime->visible_rows = column_layout.row_indices.size();
sspreadsheet->runtime->tot_columns = column_layout.columns.size();
sspreadsheet->runtime->tot_rows = column_layout.tot_rows;
update_visible_columns(sspreadsheet->columns, *data_source);
std::unique_ptr<SpreadsheetDrawer> drawer = spreadsheet_drawer_from_column_layout(column_layout);
SpreadsheetLayout spreadsheet_layout;
ResourceScope scope;
LISTBASE_FOREACH (SpreadsheetColumn *, column, &sspreadsheet->columns) {
std::unique_ptr<ColumnValues> values_ptr = data_source->get_column_values(*column->id);
/* Should have been removed before if it does not exist anymore. */
BLI_assert(values_ptr);
const ColumnValues *values = scope.add(std::move(values_ptr), __func__);
const int width = get_column_width(*values);
spreadsheet_layout.columns.append({values, width});
}
const int tot_rows = data_source->tot_rows();
spreadsheet_layout.index_column_width = get_index_column_width(tot_rows);
spreadsheet_layout.row_indices = IndexRange(tot_rows).as_span();
if (const GeometryDataSource *geometry_data_source = dynamic_cast<const GeometryDataSource *>(
data_source.get())) {
Object *object_eval = geometry_data_source->object_eval();
Object *object_orig = DEG_get_original_object(object_eval);
if (object_orig->type == OB_MESH) {
if (object_orig->mode == OB_MODE_EDIT) {
if (sspreadsheet->filter_flag & SPREADSHEET_FILTER_SELECTED_ONLY) {
spreadsheet_layout.row_indices = geometry_data_source->get_selected_element_indices();
}
}
}
}
sspreadsheet->runtime->tot_columns = spreadsheet_layout.columns.size();
sspreadsheet->runtime->tot_rows = tot_rows;
sspreadsheet->runtime->visible_rows = spreadsheet_layout.row_indices.size();
std::unique_ptr<SpreadsheetDrawer> drawer = spreadsheet_drawer_from_layout(spreadsheet_layout);
draw_spreadsheet_in_region(C, region, *drawer);
/* Tag footer for redraw, because the main region updates data for the footer. */

View File

@ -0,0 +1,51 @@
/*
* 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>
struct Object;
struct Collection;
namespace blender::ed::spreadsheet {
struct ObjectCellValue {
const Object *object;
};
struct CollectionCellValue {
const Collection *collection;
};
/**
* 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<ObjectCellValue> value_object;
std::optional<CollectionCellValue> value_collection;
};
} // namespace blender::ed::spreadsheet

View File

@ -0,0 +1,73 @@
/*
* 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.
*/
#include "DNA_space_types.h"
#include "MEM_guardedalloc.h"
#include "BLI_hash.hh"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "spreadsheet_column.hh"
namespace blender::ed::spreadsheet {
SpreadsheetColumnID *spreadsheet_column_id_new()
{
SpreadsheetColumnID *column_id = (SpreadsheetColumnID *)MEM_callocN(sizeof(SpreadsheetColumnID),
__func__);
return column_id;
}
SpreadsheetColumnID *spreadsheet_column_id_copy(const SpreadsheetColumnID *src_column_id)
{
SpreadsheetColumnID *new_column_id = spreadsheet_column_id_new();
new_column_id->name = BLI_strdup(src_column_id->name);
new_column_id->index = src_column_id->index;
return new_column_id;
}
void spreadsheet_column_id_free(SpreadsheetColumnID *column_id)
{
if (column_id->name != nullptr) {
MEM_freeN(column_id->name);
}
MEM_freeN(column_id);
}
SpreadsheetColumn *spreadsheet_column_new(SpreadsheetColumnID *column_id)
{
SpreadsheetColumn *column = (SpreadsheetColumn *)MEM_callocN(sizeof(SpreadsheetColumn),
__func__);
column->id = column_id;
return column;
}
SpreadsheetColumn *spreadsheet_column_copy(const SpreadsheetColumn *src_column)
{
SpreadsheetColumnID *new_column_id = spreadsheet_column_id_copy(src_column->id);
SpreadsheetColumn *new_column = spreadsheet_column_new(new_column_id);
return new_column;
}
void spreadsheet_column_free(SpreadsheetColumn *column)
{
spreadsheet_column_id_free(column->id);
MEM_freeN(column);
}
} // namespace blender::ed::spreadsheet

View File

@ -0,0 +1,48 @@
/*
* 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 "DNA_space_types.h"
#include "BLI_hash.hh"
namespace blender {
template<> struct DefaultHash<SpreadsheetColumnID> {
uint64_t operator()(const SpreadsheetColumnID &column_id) const
{
return get_default_hash_2(StringRef(column_id.name), column_id.index);
}
};
} // namespace blender
inline bool operator==(const SpreadsheetColumnID &a, const SpreadsheetColumnID &b)
{
using blender::StringRef;
return StringRef(a.name) == StringRef(b.name) && a.index == b.index;
}
namespace blender::ed::spreadsheet {
SpreadsheetColumnID *spreadsheet_column_id_new();
SpreadsheetColumnID *spreadsheet_column_id_copy(const SpreadsheetColumnID *src_column_id);
void spreadsheet_column_id_free(SpreadsheetColumnID *column_id);
SpreadsheetColumn *spreadsheet_column_new(SpreadsheetColumnID *column_id);
SpreadsheetColumn *spreadsheet_column_copy(const SpreadsheetColumn *src_column);
void spreadsheet_column_free(SpreadsheetColumn *column);
} // namespace blender::ed::spreadsheet

View File

@ -1,115 +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 "spreadsheet_draw.hh"
struct Object;
struct Collection;
namespace blender::ed::spreadsheet {
struct ObjectCellValue {
const Object *object;
};
struct CollectionCellValue {
const Collection *collection;
};
/**
* This is a small 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<ObjectCellValue> value_object;
std::optional<CollectionCellValue> value_collection;
};
/**
* This represents a column in a spreadsheet. It has a name and provides a value for all the cells
* in the column.
*/
class SpreadsheetColumn {
protected:
std::string name_;
public:
SpreadsheetColumn(std::string name) : name_(std::move(name))
{
}
virtual ~SpreadsheetColumn() = default;
virtual void get_value(int index, CellValue &r_cell_value) const = 0;
StringRefNull name() const
{
return name_;
}
/* 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 LambdaSpreadsheetColumn : public SpreadsheetColumn {
private:
GetValueF get_value_;
public:
LambdaSpreadsheetColumn(std::string name, GetValueF get_value)
: SpreadsheetColumn(std::move(name)), 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<SpreadsheetColumn> spreadsheet_column_from_function(std::string name,
GetValueF get_value)
{
return std::make_unique<LambdaSpreadsheetColumn<GetValueF>>(std::move(name),
std::move(get_value));
}
/* This contains information required to create a spreadsheet drawer from columns. */
struct SpreadsheetColumnLayout {
Vector<const SpreadsheetColumn *> columns;
Span<int64_t> row_indices;
int tot_rows = 0;
};
std::unique_ptr<SpreadsheetDrawer> spreadsheet_drawer_from_column_layout(
const SpreadsheetColumnLayout &column_layout);
} // namespace blender::ed::spreadsheet

View File

@ -0,0 +1,84 @@
/*
* 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 "BLI_string_ref.hh"
#include "spreadsheet_cell_value.hh"
namespace blender::ed::spreadsheet {
/**
* This represents a column in a spreadsheet. It has a name and provides a value for all the cells
* in the column.
*/
class ColumnValues {
protected:
std::string name_;
int size_;
public:
ColumnValues(std::string name, const int size) : name_(std::move(name)), size_(size)
{
}
virtual ~ColumnValues() = default;
virtual void get_value(int index, CellValue &r_cell_value) const = 0;
StringRefNull name() const
{
return name_;
}
int size() const
{
return size_;
}
/* 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(std::string name, int size, GetValueF get_value)
: ColumnValues(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(std::string name,
int size,
GetValueF get_value)
{
return std::make_unique<LambdaColumnValues<GetValueF>>(
std::move(name), size, std::move(get_value));
}
} // namespace blender::ed::spreadsheet

View File

@ -14,21 +14,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
#include "BKE_geometry_set.hh"
#include "BLI_resource_scope.hh"
#include "spreadsheet_column_layout.hh"
struct bContext;
#include "spreadsheet_data_source.hh"
namespace blender::ed::spreadsheet {
void spreadsheet_columns_from_geometry(const bContext *C,
Object *object_eval,
SpreadsheetColumnLayout &column_layout,
ResourceScope &scope);
/* Provide a "key function" for the linker. */
DataSource::~DataSource() = default;
} // namespace blender::ed::spreadsheet

View File

@ -0,0 +1,65 @@
/*
* 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 "BLI_function_ref.hh"
#include "spreadsheet_column.hh"
#include "spreadsheet_column_values.hh"
namespace blender::ed::spreadsheet {
/**
* This class is subclassed to implement different data sources for the spreadsheet. A data source
* provides the information that should be displayed. It is not concerned with how data is layed
* out in the spreadsheet editor exactly.
*/
class DataSource {
public:
virtual ~DataSource();
/**
* Calls the callback with all the column ids that should be displayed as long as the user does
* not manually add or remove columns. The column id can be stack allocated. Therefore, the
* callback should not keep a reference to it (and copy it instead).
*/
virtual void foreach_default_column_ids(FunctionRef<void(const SpreadsheetColumnID &)> fn) const
{
UNUSED_VARS(fn);
}
/**
* Returns the column values the given column id. If no data exists for this id, null is
* returned.
*/
virtual std::unique_ptr<ColumnValues> get_column_values(
const SpreadsheetColumnID &column_id) const
{
UNUSED_VARS(column_id);
return {};
}
/**
* Returns the number of rows in columns returned by #get_column_values.
*/
virtual int tot_rows() const
{
return 0;
}
};
} // namespace blender::ed::spreadsheet

View File

@ -0,0 +1,447 @@
/*
* 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.
*/
#include "BKE_context.h"
#include "BKE_editmesh.h"
#include "BKE_lib_id.h"
#include "BKE_mesh.h"
#include "BKE_mesh_wrapper.h"
#include "BKE_modifier.h"
#include "DNA_ID.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_space_types.h"
#include "DNA_userdef_types.h"
#include "DEG_depsgraph_query.h"
#include "bmesh.h"
#include "spreadsheet_data_source_geometry.hh"
#include "spreadsheet_intern.hh"
namespace blender::ed::spreadsheet {
void GeometryDataSource::foreach_default_column_ids(
FunctionRef<void(const SpreadsheetColumnID &)> fn) const
{
component_->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
if (meta_data.domain != domain_) {
return true;
}
SpreadsheetColumnID column_id;
column_id.name = (char *)name.c_str();
if (meta_data.data_type == CD_PROP_FLOAT3) {
for (const int i : {0, 1, 2}) {
column_id.index = i;
fn(column_id);
}
}
else if (meta_data.data_type == CD_PROP_FLOAT2) {
for (const int i : {0, 1}) {
column_id.index = i;
fn(column_id);
}
}
else if (meta_data.data_type == CD_PROP_COLOR) {
for (const int i : {0, 1, 2, 3}) {
column_id.index = i;
fn(column_id);
}
}
else {
column_id.index = -1;
fn(column_id);
}
return true;
});
}
std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values(
const SpreadsheetColumnID &column_id) const
{
std::lock_guard lock{mutex_};
bke::ReadAttributePtr attribute_ptr = component_->attribute_try_get_for_read(column_id.name);
if (!attribute_ptr) {
return {};
}
const bke::ReadAttribute *attribute = scope_.add(std::move(attribute_ptr), __func__);
if (attribute->domain() != domain_) {
return {};
}
int domain_size = attribute->size();
switch (attribute->custom_data_type()) {
case CD_PROP_FLOAT:
return column_values_from_function(
column_id.name, domain_size, [attribute](int index, CellValue &r_cell_value) {
float value;
attribute->get(index, &value);
r_cell_value.value_float = value;
});
case CD_PROP_INT32:
return column_values_from_function(
column_id.name, domain_size, [attribute](int index, CellValue &r_cell_value) {
int value;
attribute->get(index, &value);
r_cell_value.value_int = value;
});
case CD_PROP_BOOL:
return column_values_from_function(
column_id.name, domain_size, [attribute](int index, CellValue &r_cell_value) {
bool value;
attribute->get(index, &value);
r_cell_value.value_bool = value;
});
case CD_PROP_FLOAT2: {
if (column_id.index < 0 || column_id.index > 1) {
return {};
}
const std::array<const char *, 2> suffixes = {" X", " Y"};
const std::string name = StringRef(column_id.name) + suffixes[column_id.index];
return column_values_from_function(
name,
domain_size,
[attribute, axis = column_id.index](int index, CellValue &r_cell_value) {
float2 value;
attribute->get(index, &value);
r_cell_value.value_float = value[axis];
});
}
case CD_PROP_FLOAT3: {
if (column_id.index < 0 || column_id.index > 2) {
return {};
}
const std::array<const char *, 3> suffixes = {" X", " Y", " Z"};
const std::string name = StringRef(column_id.name) + suffixes[column_id.index];
return column_values_from_function(
name,
domain_size,
[attribute, axis = column_id.index](int index, CellValue &r_cell_value) {
float3 value;
attribute->get(index, &value);
r_cell_value.value_float = value[axis];
});
}
case CD_PROP_COLOR: {
if (column_id.index < 0 || column_id.index > 3) {
return {};
}
const std::array<const char *, 4> suffixes = {" R", " G", " B", " A"};
const std::string name = StringRef(column_id.name) + suffixes[column_id.index];
return column_values_from_function(
name,
domain_size,
[attribute, axis = column_id.index](int index, CellValue &r_cell_value) {
Color4f value;
attribute->get(index, &value);
r_cell_value.value_float = value[axis];
});
}
default:
break;
}
return {};
}
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,
Vector<int64_t> &r_vertex_indices)
{
for (const int i : IndexRange(mesh.totvert)) {
if (is_vertex_selected_fn(i)) {
r_vertex_indices.append(i);
}
}
}
static void get_selected_corner_indices(const Mesh &mesh,
const IsVertexSelectedFn is_vertex_selected_fn,
Vector<int64_t> &r_corner_indices)
{
for (const int i : IndexRange(mesh.totloop)) {
const MLoop &loop = mesh.mloop[i];
if (is_vertex_selected_fn(loop.v)) {
r_corner_indices.append(i);
}
}
}
static void get_selected_face_indices(const Mesh &mesh,
const IsVertexSelectedFn is_vertex_selected_fn,
Vector<int64_t> &r_face_indices)
{
for (const int poly_index : IndexRange(mesh.totpoly)) {
const MPoly &poly = mesh.mpoly[poly_index];
bool is_selected = true;
for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) {
const MLoop &loop = mesh.mloop[loop_index];
if (!is_vertex_selected_fn(loop.v)) {
is_selected = false;
break;
}
}
if (is_selected) {
r_face_indices.append(poly_index);
}
}
}
static void get_selected_edge_indices(const Mesh &mesh,
const IsVertexSelectedFn is_vertex_selected_fn,
Vector<int64_t> &r_edge_indices)
{
for (const int i : IndexRange(mesh.totedge)) {
const MEdge &edge = mesh.medge[i];
if (is_vertex_selected_fn(edge.v1) && is_vertex_selected_fn(edge.v2)) {
r_edge_indices.append(i);
}
}
}
static void get_selected_indices_on_domain(const Mesh &mesh,
const AttributeDomain domain,
const IsVertexSelectedFn is_vertex_selected_fn,
Vector<int64_t> &r_indices)
{
switch (domain) {
case ATTR_DOMAIN_POINT:
return get_selected_vertex_indices(mesh, is_vertex_selected_fn, r_indices);
case ATTR_DOMAIN_FACE:
return get_selected_face_indices(mesh, is_vertex_selected_fn, r_indices);
case ATTR_DOMAIN_CORNER:
return get_selected_corner_indices(mesh, is_vertex_selected_fn, r_indices);
case ATTR_DOMAIN_EDGE:
return get_selected_edge_indices(mesh, is_vertex_selected_fn, r_indices);
default:
return;
}
}
Span<int64_t> GeometryDataSource::get_selected_element_indices() const
{
std::lock_guard lock{mutex_};
BLI_assert(object_eval_->mode == OB_MODE_EDIT);
BLI_assert(component_->type() == GEO_COMPONENT_TYPE_MESH);
Object *object_orig = DEG_get_original_object(object_eval_);
Vector<int64_t> &indices = scope_.construct<Vector<int64_t>>(__func__);
const MeshComponent *mesh_component = static_cast<const MeshComponent *>(component_);
const Mesh *mesh_eval = mesh_component->get_for_read();
Mesh *mesh_orig = (Mesh *)object_orig->data;
BMesh *bm = mesh_orig->edit_mesh->bm;
BM_mesh_elem_table_ensure(bm, BM_VERT);
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, indices);
}
else 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, indices);
}
return indices;
}
void InstancesDataSource::foreach_default_column_ids(
FunctionRef<void(const SpreadsheetColumnID &)> fn) const
{
SpreadsheetColumnID column_id;
column_id.index = -1;
column_id.name = (char *)"Name";
fn(column_id);
for (const char *name : {"Position", "Rotation", "Scale"}) {
for (const int i : {0, 1, 2}) {
column_id.name = (char *)name;
column_id.index = i;
fn(column_id);
}
}
}
std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values(
const SpreadsheetColumnID &column_id) const
{
const std::array<const char *, 3> suffixes = {" X", " Y", " Z"};
const int size = this->tot_rows();
if (STREQ(column_id.name, "Name")) {
Span<InstancedData> instance_data = component_->instanced_data();
std::unique_ptr<ColumnValues> values = column_values_from_function(
"Name", size, [instance_data](int index, CellValue &r_cell_value) {
const InstancedData &data = instance_data[index];
if (data.type == INSTANCE_DATA_TYPE_OBJECT) {
if (data.data.object != nullptr) {
r_cell_value.value_object = ObjectCellValue{data.data.object};
}
}
else if (data.type == INSTANCE_DATA_TYPE_COLLECTION) {
if (data.data.collection != nullptr) {
r_cell_value.value_collection = CollectionCellValue{data.data.collection};
}
}
});
values->default_width = 8.0f;
return values;
}
if (column_id.index < 0 || column_id.index > 2) {
return {};
}
Span<float4x4> transforms = component_->transforms();
if (STREQ(column_id.name, "Position")) {
std::string name = StringRef("Position") + suffixes[column_id.index];
return column_values_from_function(
name, size, [transforms, axis = column_id.index](int index, CellValue &r_cell_value) {
r_cell_value.value_float = transforms[index].translation()[axis];
});
}
if (STREQ(column_id.name, "Rotation")) {
std::string name = StringRef("Rotation") + suffixes[column_id.index];
return column_values_from_function(
name, size, [transforms, axis = column_id.index](int index, CellValue &r_cell_value) {
r_cell_value.value_float = transforms[index].to_euler()[axis];
});
}
if (STREQ(column_id.name, "Scale")) {
std::string name = StringRef("Scale") + suffixes[column_id.index];
return column_values_from_function(
name, size, [transforms, axis = column_id.index](int index, CellValue &r_cell_value) {
r_cell_value.value_float = transforms[index].scale()[axis];
});
}
return {};
}
int InstancesDataSource::tot_rows() const
{
return component_->instances_amount();
}
static GeometrySet get_display_geometry_set(SpaceSpreadsheet *sspreadsheet,
Object *object_eval,
const GeometryComponentType used_component_type)
{
GeometrySet geometry_set;
if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_ORIGINAL) {
Object *object_orig = DEG_get_original_object(object_eval);
if (object_orig->type == OB_MESH) {
MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
if (object_orig->mode == OB_MODE_EDIT) {
Mesh *mesh = (Mesh *)object_orig->data;
BMEditMesh *em = mesh->edit_mesh;
if (em != nullptr) {
Mesh *new_mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr);
/* This is a potentially heavy operation to do on every redraw. The best solution here is
* to display the data directly from the bmesh without a conversion, which can be
* implemented a bit later. */
BM_mesh_bm_to_me_for_eval(em->bm, new_mesh, nullptr);
mesh_component.replace(new_mesh, GeometryOwnershipType::Owned);
}
}
else {
Mesh *mesh = (Mesh *)object_orig->data;
mesh_component.replace(mesh, GeometryOwnershipType::ReadOnly);
}
mesh_component.copy_vertex_group_names_from_object(*object_orig);
}
else if (object_orig->type == OB_POINTCLOUD) {
PointCloud *pointcloud = (PointCloud *)object_orig->data;
PointCloudComponent &pointcloud_component =
geometry_set.get_component_for_write<PointCloudComponent>();
pointcloud_component.replace(pointcloud, GeometryOwnershipType::ReadOnly);
}
}
else {
if (used_component_type == GEO_COMPONENT_TYPE_MESH && object_eval->mode == OB_MODE_EDIT) {
Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(object_eval, false);
if (mesh == nullptr) {
return geometry_set;
}
BKE_mesh_wrapper_ensure_mdata(mesh);
MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
mesh_component.replace(mesh, GeometryOwnershipType::ReadOnly);
mesh_component.copy_vertex_group_names_from_object(*object_eval);
}
else {
if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_NODE) {
if (object_eval->runtime.geometry_set_preview != nullptr) {
geometry_set = *object_eval->runtime.geometry_set_preview;
}
}
else if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_FINAL) {
if (object_eval->runtime.geometry_set_eval != nullptr) {
geometry_set = *object_eval->runtime.geometry_set_eval;
}
}
}
}
return geometry_set;
}
static GeometryComponentType get_display_component_type(const bContext *C, Object *object_eval)
{
SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C);
if (sspreadsheet->object_eval_state != SPREADSHEET_OBJECT_EVAL_STATE_ORIGINAL) {
return (GeometryComponentType)sspreadsheet->geometry_component_type;
}
if (object_eval->type == OB_POINTCLOUD) {
return GEO_COMPONENT_TYPE_POINT_CLOUD;
}
return GEO_COMPONENT_TYPE_MESH;
}
std::unique_ptr<DataSource> data_source_from_geometry(const bContext *C, Object *object_eval)
{
SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C);
const AttributeDomain domain = (AttributeDomain)sspreadsheet->attribute_domain;
const GeometryComponentType component_type = get_display_component_type(C, object_eval);
GeometrySet geometry_set = get_display_geometry_set(sspreadsheet, object_eval, component_type);
if (!geometry_set.has(component_type)) {
return {};
}
if (component_type == GEO_COMPONENT_TYPE_INSTANCES) {
return std::make_unique<InstancesDataSource>(geometry_set);
}
return std::make_unique<GeometryDataSource>(object_eval, geometry_set, component_type, domain);
}
} // namespace blender::ed::spreadsheet

View File

@ -0,0 +1,94 @@
/*
* 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 <mutex>
#include "BLI_resource_scope.hh"
#include "BKE_geometry_set.hh"
#include "spreadsheet_data_source.hh"
struct bContext;
namespace blender::ed::spreadsheet {
class GeometryDataSource : public DataSource {
private:
Object *object_eval_;
const GeometrySet geometry_set_;
const GeometryComponent *component_;
AttributeDomain domain_;
/* Some data is computed on the fly only when it is requested. Computing it does not change the
* logical state of this data source. Therefore, the corresponding methods are const and need to
* be protected with a mutex. */
mutable std::mutex mutex_;
mutable ResourceScope scope_;
public:
GeometryDataSource(Object *object_eval,
GeometrySet geometry_set,
const GeometryComponentType component_type,
const AttributeDomain domain)
: object_eval_(object_eval),
geometry_set_(std::move(geometry_set)),
component_(geometry_set_.get_component_for_read(component_type)),
domain_(domain)
{
}
Object *object_eval() const
{
return object_eval_;
}
Span<int64_t> get_selected_element_indices() const;
void foreach_default_column_ids(
FunctionRef<void(const SpreadsheetColumnID &)> fn) const override;
std::unique_ptr<ColumnValues> get_column_values(
const SpreadsheetColumnID &column_id) const override;
int tot_rows() const override;
};
class InstancesDataSource : public DataSource {
const GeometrySet geometry_set_;
const InstancesComponent *component_;
public:
InstancesDataSource(GeometrySet geometry_set)
: geometry_set_(std::move(geometry_set)),
component_(geometry_set_.get_component_for_read<InstancesComponent>())
{
}
void foreach_default_column_ids(
FunctionRef<void(const SpreadsheetColumnID &)> fn) const override;
std::unique_ptr<ColumnValues> get_column_values(
const SpreadsheetColumnID &column_id) const override;
int tot_rows() const override;
};
std::unique_ptr<DataSource> data_source_from_geometry(const bContext *C, Object *object_eval);
} // namespace blender::ed::spreadsheet

View File

@ -1,453 +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.
*/
#include "BKE_context.h"
#include "BKE_editmesh.h"
#include "BKE_lib_id.h"
#include "BKE_mesh.h"
#include "BKE_mesh_wrapper.h"
#include "BKE_modifier.h"
#include "DNA_ID.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_space_types.h"
#include "DNA_userdef_types.h"
#include "DEG_depsgraph_query.h"
#include "bmesh.h"
#include "spreadsheet_from_geometry.hh"
#include "spreadsheet_intern.hh"
namespace blender::ed::spreadsheet {
using blender::bke::ReadAttribute;
using blender::bke::ReadAttributePtr;
static void add_columns_for_instances(const InstancesComponent &instances_component,
SpreadsheetColumnLayout &column_layout,
ResourceScope &scope)
{
Span<InstancedData> instance_data = instances_component.instanced_data();
Span<float4x4> transforms = instances_component.transforms();
Vector<std::unique_ptr<SpreadsheetColumn>> &columns =
scope.construct<Vector<std::unique_ptr<SpreadsheetColumn>>>("columns");
columns.append(spreadsheet_column_from_function(
"Name", [instance_data](int index, CellValue &r_cell_value) {
const InstancedData &data = instance_data[index];
if (data.type == INSTANCE_DATA_TYPE_OBJECT) {
if (data.data.object != nullptr) {
r_cell_value.value_object = ObjectCellValue{data.data.object};
}
}
else if (data.type == INSTANCE_DATA_TYPE_COLLECTION) {
if (data.data.collection != nullptr) {
r_cell_value.value_collection = CollectionCellValue{data.data.collection};
}
}
}));
columns.last()->default_width = 8.0f;
static std::array<char, 3> axis_char = {'X', 'Y', 'Z'};
for (const int i : {0, 1, 2}) {
std::string name = std::string("Position ") + axis_char[i];
columns.append(spreadsheet_column_from_function(
name, [transforms, i](int index, CellValue &r_cell_value) {
r_cell_value.value_float = transforms[index].translation()[i];
}));
}
for (const int i : {0, 1, 2}) {
std::string name = std::string("Rotation ") + axis_char[i];
columns.append(spreadsheet_column_from_function(
name, [transforms, i](int index, CellValue &r_cell_value) {
r_cell_value.value_float = transforms[index].to_euler()[i];
}));
}
for (const int i : {0, 1, 2}) {
std::string name = std::string("Scale ") + axis_char[i];
columns.append(spreadsheet_column_from_function(
name, [transforms, i](int index, CellValue &r_cell_value) {
r_cell_value.value_float = transforms[index].scale()[i];
}));
}
for (std::unique_ptr<SpreadsheetColumn> &column : columns) {
column_layout.columns.append(column.get());
}
column_layout.row_indices = instance_data.index_range().as_span();
column_layout.tot_rows = instances_component.instances_amount();
}
static Vector<std::string> get_sorted_attribute_names_to_display(
const GeometryComponent &component, const AttributeDomain domain)
{
Vector<std::string> attribute_names;
component.attribute_foreach(
[&](const StringRef attribute_name, const AttributeMetaData &meta_data) {
if (meta_data.domain == domain) {
attribute_names.append(attribute_name);
}
return true;
});
std::sort(attribute_names.begin(),
attribute_names.end(),
[](const std::string &a, const std::string &b) {
return BLI_strcasecmp_natural(a.c_str(), b.c_str()) < 0;
});
return attribute_names;
}
static void add_columns_for_attribute(const ReadAttribute *attribute,
const StringRefNull attribute_name,
Vector<std::unique_ptr<SpreadsheetColumn>> &columns)
{
const CustomDataType data_type = attribute->custom_data_type();
switch (data_type) {
case CD_PROP_FLOAT: {
columns.append(spreadsheet_column_from_function(
attribute_name, [attribute](int index, CellValue &r_cell_value) {
float value;
attribute->get(index, &value);
r_cell_value.value_float = value;
}));
break;
}
case CD_PROP_FLOAT2: {
static std::array<char, 2> axis_char = {'X', 'Y'};
for (const int i : {0, 1}) {
std::string name = attribute_name + " " + axis_char[i];
columns.append(spreadsheet_column_from_function(
name, [attribute, i](int index, CellValue &r_cell_value) {
float2 value;
attribute->get(index, &value);
r_cell_value.value_float = value[i];
}));
}
break;
}
case CD_PROP_FLOAT3: {
static std::array<char, 3> axis_char = {'X', 'Y', 'Z'};
for (const int i : {0, 1, 2}) {
std::string name = attribute_name + " " + axis_char[i];
columns.append(spreadsheet_column_from_function(
name, [attribute, i](int index, CellValue &r_cell_value) {
float3 value;
attribute->get(index, &value);
r_cell_value.value_float = value[i];
}));
}
break;
}
case CD_PROP_COLOR: {
static std::array<char, 4> axis_char = {'R', 'G', 'B', 'A'};
for (const int i : {0, 1, 2, 3}) {
std::string name = attribute_name + " " + axis_char[i];
columns.append(spreadsheet_column_from_function(
name, [attribute, i](int index, CellValue &r_cell_value) {
Color4f value;
attribute->get(index, &value);
r_cell_value.value_float = value[i];
}));
}
break;
}
case CD_PROP_INT32: {
columns.append(spreadsheet_column_from_function(
attribute_name, [attribute](int index, CellValue &r_cell_value) {
int value;
attribute->get(index, &value);
r_cell_value.value_int = value;
}));
break;
}
case CD_PROP_BOOL: {
columns.append(spreadsheet_column_from_function(
attribute_name, [attribute](int index, CellValue &r_cell_value) {
bool value;
attribute->get(index, &value);
r_cell_value.value_bool = value;
}));
break;
}
default:
break;
}
}
static GeometrySet get_display_geometry_set(SpaceSpreadsheet *sspreadsheet,
Object *object_eval,
const GeometryComponentType used_component_type)
{
GeometrySet geometry_set;
if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_ORIGINAL) {
Object *object_orig = DEG_get_original_object(object_eval);
if (object_orig->type == OB_MESH) {
MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
if (object_orig->mode == OB_MODE_EDIT) {
Mesh *mesh = (Mesh *)object_orig->data;
BMEditMesh *em = mesh->edit_mesh;
if (em != nullptr) {
Mesh *new_mesh = (Mesh *)BKE_id_new_nomain(ID_ME, nullptr);
/* This is a potentially heavy operation to do on every redraw. The best solution here is
* to display the data directly from the bmesh without a conversion, which can be
* implemented a bit later. */
BM_mesh_bm_to_me_for_eval(em->bm, new_mesh, nullptr);
mesh_component.replace(new_mesh, GeometryOwnershipType::Owned);
}
}
else {
Mesh *mesh = (Mesh *)object_orig->data;
mesh_component.replace(mesh, GeometryOwnershipType::ReadOnly);
}
mesh_component.copy_vertex_group_names_from_object(*object_orig);
}
else if (object_orig->type == OB_POINTCLOUD) {
PointCloud *pointcloud = (PointCloud *)object_orig->data;
PointCloudComponent &pointcloud_component =
geometry_set.get_component_for_write<PointCloudComponent>();
pointcloud_component.replace(pointcloud, GeometryOwnershipType::ReadOnly);
}
}
else {
if (used_component_type == GEO_COMPONENT_TYPE_MESH && object_eval->mode == OB_MODE_EDIT) {
Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(object_eval, false);
if (mesh == nullptr) {
return geometry_set;
}
BKE_mesh_wrapper_ensure_mdata(mesh);
MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
mesh_component.replace(mesh, GeometryOwnershipType::ReadOnly);
mesh_component.copy_vertex_group_names_from_object(*object_eval);
}
else {
if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_NODE) {
if (object_eval->runtime.geometry_set_preview != nullptr) {
geometry_set = *object_eval->runtime.geometry_set_preview;
}
}
else if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_FINAL) {
if (object_eval->runtime.geometry_set_eval != nullptr) {
geometry_set = *object_eval->runtime.geometry_set_eval;
}
}
}
}
return geometry_set;
}
using IsVertexSelectedFn = FunctionRef<bool(int vertex_index)>;
static void get_selected_vertex_indices(const Mesh &mesh,
const IsVertexSelectedFn is_vertex_selected_fn,
Vector<int64_t> &r_vertex_indices)
{
for (const int i : IndexRange(mesh.totvert)) {
if (is_vertex_selected_fn(i)) {
r_vertex_indices.append(i);
}
}
}
static void get_selected_corner_indices(const Mesh &mesh,
const IsVertexSelectedFn is_vertex_selected_fn,
Vector<int64_t> &r_corner_indices)
{
for (const int i : IndexRange(mesh.totloop)) {
const MLoop &loop = mesh.mloop[i];
if (is_vertex_selected_fn(loop.v)) {
r_corner_indices.append(i);
}
}
}
static void get_selected_face_indices(const Mesh &mesh,
const IsVertexSelectedFn is_vertex_selected_fn,
Vector<int64_t> &r_face_indices)
{
for (const int poly_index : IndexRange(mesh.totpoly)) {
const MPoly &poly = mesh.mpoly[poly_index];
bool is_selected = true;
for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) {
const MLoop &loop = mesh.mloop[loop_index];
if (!is_vertex_selected_fn(loop.v)) {
is_selected = false;
break;
}
}
if (is_selected) {
r_face_indices.append(poly_index);
}
}
}
static void get_selected_edge_indices(const Mesh &mesh,
const IsVertexSelectedFn is_vertex_selected_fn,
Vector<int64_t> &r_edge_indices)
{
for (const int i : IndexRange(mesh.totedge)) {
const MEdge &edge = mesh.medge[i];
if (is_vertex_selected_fn(edge.v1) && is_vertex_selected_fn(edge.v2)) {
r_edge_indices.append(i);
}
}
}
static void get_selected_indices_on_domain(const Mesh &mesh,
const AttributeDomain domain,
const IsVertexSelectedFn is_vertex_selected_fn,
Vector<int64_t> &r_indices)
{
switch (domain) {
case ATTR_DOMAIN_POINT:
return get_selected_vertex_indices(mesh, is_vertex_selected_fn, r_indices);
case ATTR_DOMAIN_FACE:
return get_selected_face_indices(mesh, is_vertex_selected_fn, r_indices);
case ATTR_DOMAIN_CORNER:
return get_selected_corner_indices(mesh, is_vertex_selected_fn, r_indices);
case ATTR_DOMAIN_EDGE:
return get_selected_edge_indices(mesh, is_vertex_selected_fn, r_indices);
default:
return;
}
}
static Span<int64_t> filter_mesh_elements_by_selection(const bContext *C,
Object *object_eval,
const MeshComponent *component,
const AttributeDomain domain,
ResourceScope &scope)
{
SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C);
const bool show_only_selected = sspreadsheet->filter_flag & SPREADSHEET_FILTER_SELECTED_ONLY;
if (object_eval->mode == OB_MODE_EDIT && show_only_selected) {
Object *object_orig = DEG_get_original_object(object_eval);
Vector<int64_t> &visible_rows = scope.construct<Vector<int64_t>>("visible rows");
const Mesh *mesh_eval = component->get_for_read();
Mesh *mesh_orig = (Mesh *)object_orig->data;
BMesh *bm = mesh_orig->edit_mesh->bm;
BM_mesh_elem_table_ensure(bm, BM_VERT);
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, visible_rows);
}
else 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, visible_rows);
}
/* This is safe, because the vector lives in the resource collector. */
return visible_rows.as_span();
}
/* No filter is used. */
const int domain_size = component->attribute_domain_size(domain);
return IndexRange(domain_size).as_span();
}
static GeometryComponentType get_display_component_type(const bContext *C, Object *object_eval)
{
SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C);
if (sspreadsheet->object_eval_state != SPREADSHEET_OBJECT_EVAL_STATE_ORIGINAL) {
return (GeometryComponentType)sspreadsheet->geometry_component_type;
}
if (object_eval->type == OB_POINTCLOUD) {
return GEO_COMPONENT_TYPE_POINT_CLOUD;
}
return GEO_COMPONENT_TYPE_MESH;
}
void spreadsheet_columns_from_geometry(const bContext *C,
Object *object_eval,
SpreadsheetColumnLayout &column_layout,
ResourceScope &scope)
{
SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C);
const AttributeDomain domain = (AttributeDomain)sspreadsheet->attribute_domain;
const GeometryComponentType component_type = get_display_component_type(C, object_eval);
/* Create a resource collector that owns stuff that needs to live until drawing is done. */
GeometrySet &geometry_set = scope.add_value(
get_display_geometry_set(sspreadsheet, object_eval, component_type), "geometry set");
const GeometryComponent *component = geometry_set.get_component_for_read(component_type);
if (component == nullptr) {
return;
}
if (component_type == GEO_COMPONENT_TYPE_INSTANCES) {
add_columns_for_instances(
*static_cast<const InstancesComponent *>(component), column_layout, scope);
return;
}
if (!component->attribute_domain_supported(domain)) {
return;
}
Vector<std::string> attribute_names = get_sorted_attribute_names_to_display(*component, domain);
Vector<std::unique_ptr<SpreadsheetColumn>> &columns =
scope.construct<Vector<std::unique_ptr<SpreadsheetColumn>>>("columns");
for (StringRefNull attribute_name : attribute_names) {
ReadAttributePtr attribute_ptr = component->attribute_try_get_for_read(attribute_name);
ReadAttribute &attribute = *attribute_ptr;
scope.add(std::move(attribute_ptr), "attribute");
add_columns_for_attribute(&attribute, attribute_name, columns);
}
for (std::unique_ptr<SpreadsheetColumn> &column : columns) {
column_layout.columns.append(column.get());
}
/* The filter below only works for mesh vertices currently. */
Span<int64_t> visible_rows;
if (component_type == GEO_COMPONENT_TYPE_MESH) {
visible_rows = filter_mesh_elements_by_selection(
C, object_eval, static_cast<const MeshComponent *>(component), domain, scope);
}
else {
visible_rows = IndexRange(component->attribute_domain_size(domain)).as_span();
}
const int domain_size = component->attribute_domain_size(domain);
column_layout.row_indices = visible_rows;
column_layout.tot_rows = domain_size;
}
} // namespace blender::ed::spreadsheet

View File

@ -17,7 +17,7 @@
#include <iomanip>
#include <sstream>
#include "spreadsheet_column_layout.hh"
#include "spreadsheet_layout.hh"
#include "DNA_userdef_types.h"
@ -28,45 +28,22 @@
namespace blender::ed::spreadsheet {
class ColumnLayoutDrawer : public SpreadsheetDrawer {
class SpreadsheetLayoutDrawer : public SpreadsheetDrawer {
private:
const SpreadsheetColumnLayout &column_layout_;
Vector<int> column_widths_;
const SpreadsheetLayout &spreadsheet_layout_;
public:
ColumnLayoutDrawer(const SpreadsheetColumnLayout &column_layout) : column_layout_(column_layout)
SpreadsheetLayoutDrawer(const SpreadsheetLayout &spreadsheet_layout)
: spreadsheet_layout_(spreadsheet_layout)
{
tot_columns = column_layout.columns.size();
tot_rows = column_layout.row_indices.size();
const int fontid = UI_style_get()->widget.uifont_id;
/* Use a consistent font size for the width calculation. */
BLF_size(fontid, UI_style_get_dpi()->widget.points * U.pixelsize, U.dpi);
/* The width of the index column depends on the maximum row index. */
left_column_width = std::to_string(std::max(0, column_layout_.tot_rows - 1)).size() *
BLF_width(fontid, "0", 1) +
UI_UNIT_X * 0.75;
/* The column widths depend on the column name widths. */
const int minimum_column_width = 3 * UI_UNIT_X;
const int header_name_padding = UI_UNIT_X;
for (const SpreadsheetColumn *column : column_layout_.columns) {
if (column->default_width == 0.0f) {
StringRefNull name = column->name();
const int name_width = BLF_width(fontid, name.data(), name.size());
const int width = std::max(name_width + header_name_padding, minimum_column_width);
column_widths_.append(width);
}
else {
column_widths_.append(column->default_width * UI_UNIT_X);
}
}
tot_columns = spreadsheet_layout.columns.size();
tot_rows = spreadsheet_layout.row_indices.size();
left_column_width = spreadsheet_layout.index_column_width;
}
void draw_top_row_cell(int column_index, const CellDrawParams &params) const final
{
const StringRefNull name = column_layout_.columns[column_index]->name();
const StringRefNull name = spreadsheet_layout_.columns[column_index].values->name();
uiBut *but = uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
@ -89,7 +66,7 @@ class ColumnLayoutDrawer : public SpreadsheetDrawer {
void draw_left_column_cell(int row_index, const CellDrawParams &params) const final
{
const int real_index = column_layout_.row_indices[row_index];
const int real_index = spreadsheet_layout_.row_indices[row_index];
std::string index_str = std::to_string(real_index);
uiBut *but = uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
@ -113,8 +90,8 @@ class ColumnLayoutDrawer : public SpreadsheetDrawer {
void draw_content_cell(int row_index, int column_index, const CellDrawParams &params) const final
{
const int real_index = column_layout_.row_indices[row_index];
const SpreadsheetColumn &column = *column_layout_.columns[column_index];
const int real_index = spreadsheet_layout_.row_indices[row_index];
const ColumnValues &column = *spreadsheet_layout_.columns[column_index].values;
CellValue cell_value;
column.get_value(real_index, cell_value);
@ -224,14 +201,14 @@ class ColumnLayoutDrawer : public SpreadsheetDrawer {
int column_width(int column_index) const final
{
return column_widths_[column_index];
return spreadsheet_layout_.columns[column_index].width;
}
};
std::unique_ptr<SpreadsheetDrawer> spreadsheet_drawer_from_column_layout(
const SpreadsheetColumnLayout &column_layout)
std::unique_ptr<SpreadsheetDrawer> spreadsheet_drawer_from_layout(
const SpreadsheetLayout &spreadsheet_layout)
{
return std::make_unique<ColumnLayoutDrawer>(column_layout);
return std::make_unique<SpreadsheetLayoutDrawer>(spreadsheet_layout);
}
} // namespace blender::ed::spreadsheet

View File

@ -0,0 +1,42 @@
/*
* 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 "spreadsheet_column_values.hh"
#include "spreadsheet_draw.hh"
namespace blender::ed::spreadsheet {
/* Layout information for a single column. */
struct ColumnLayout {
const ColumnValues *values;
int width;
};
/* Layout information for the entire spreadsheet. */
struct SpreadsheetLayout {
Vector<ColumnLayout> columns;
Span<int64_t> row_indices;
int index_column_width = 100;
};
std::unique_ptr<SpreadsheetDrawer> spreadsheet_drawer_from_layout(
const SpreadsheetLayout &spreadsheet_layout);
} // namespace blender::ed::spreadsheet

View File

@ -1854,6 +1854,22 @@ typedef struct SpaceStatusBar {
/** \name Spreadsheet
* \{ */
typedef struct SpreadsheetColumnID {
char *name;
int index;
char _pad[4];
} SpreadsheetColumnID;
typedef struct SpreadsheetColumn {
struct SpreadsheetColumn *next, *prev;
/**
* Identifies the data in the column.
* This is a pointer instead of a struct to make it easier if we want to "subclass"
* #SpreadsheetColumnID in the future for different kinds of ids.
*/
SpreadsheetColumnID *id;
} SpreadsheetColumn;
typedef struct SpaceSpreadsheet {
SpaceLink *next, *prev;
/** Storage of regions for inactive spaces. */
@ -1863,6 +1879,9 @@ typedef struct SpaceSpreadsheet {
char _pad0[6];
/* End 'SpaceLink' header. */
/* List of #SpreadsheetColumn. */
ListBase columns;
struct ID *pinned_id;
/* eSpaceSpreadsheet_FilterFlag. */
@ -1880,8 +1899,6 @@ typedef struct SpaceSpreadsheet {
SpaceSpreadsheet_Runtime *runtime;
} SpaceSpreadsheet;
/** \} */
typedef enum eSpaceSpreadsheet_FilterFlag {
SPREADSHEET_FILTER_SELECTED_ONLY = (1 << 0),
} eSpaceSpreadsheet_FilterFlag;
@ -1892,6 +1909,8 @@ typedef enum eSpaceSpreadsheet_ObjectEvalState {
SPREADSHEET_OBJECT_EVAL_STATE_NODE = 2,
} eSpaceSpreadsheet_Context;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Space Defines (eSpace_Type)
* \{ */