diff options
16 files changed, 1079 insertions, 647 deletions
diff --git a/source/blender/blenkernel/intern/screen.c b/source/blender/blenkernel/intern/screen.c index 9617ef68a7d..d5540a3cfd3 100644 --- a/source/blender/blenkernel/intern/screen.c +++ b/source/blender/blenkernel/intern/screen.c @@ -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); + } } } diff --git a/source/blender/editors/space_spreadsheet/CMakeLists.txt b/source/blender/editors/space_spreadsheet/CMakeLists.txt index 1343ab8d851..5aa708b10c0 100644 --- a/source/blender/editors/space_spreadsheet/CMakeLists.txt +++ b/source/blender/editors/space_spreadsheet/CMakeLists.txt @@ -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 diff --git a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc index 403e6cd6206..d34b625293c 100644 --- a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc +++ b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc @@ -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>(); + } + + update_visible_columns(sspreadsheet->columns, *data_source); + + 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->visible_rows = column_layout.row_indices.size(); - sspreadsheet->runtime->tot_columns = column_layout.columns.size(); - sspreadsheet->runtime->tot_rows = column_layout.tot_rows; + 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_column_layout(column_layout); + 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. */ diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh b/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh new file mode 100644 index 00000000000..0627cff7abc --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh @@ -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 diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_column.cc b/source/blender/editors/space_spreadsheet/spreadsheet_column.cc new file mode 100644 index 00000000000..6c573ce7238 --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_column.cc @@ -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 diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_column.hh b/source/blender/editors/space_spreadsheet/spreadsheet_column.hh new file mode 100644 index 00000000000..0f74ee0141a --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_column.hh @@ -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 diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_column_layout.hh b/source/blender/editors/space_spreadsheet/spreadsheet_column_layout.hh deleted file mode 100644 index 611337df007..00000000000 --- a/source/blender/editors/space_spreadsheet/spreadsheet_column_layout.hh +++ /dev/null @@ -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 diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh b/source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh new file mode 100644 index 00000000000..58a2776e0fc --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh @@ -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 diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_from_geometry.hh b/source/blender/editors/space_spreadsheet/spreadsheet_data_source.cc index ff45b8517d1..09b8c6b1b54 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_from_geometry.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source.cc @@ -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 diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh b/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh new file mode 100644 index 00000000000..e012fae0edf --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh @@ -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 diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc new file mode 100644 index 00000000000..337e1e68f1a --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -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 diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh new file mode 100644 index 00000000000..273d39f27bf --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh @@ -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 diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_from_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_from_geometry.cc deleted file mode 100644 index 5e050517710..00000000000 --- a/source/blender/editors/space_spreadsheet/spreadsheet_from_geometry.cc +++ /dev/null @@ -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 diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_column_layout.cc b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc index 77c56c02d93..bca80cff773 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_column_layout.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc @@ -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 ¶ms) 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 ¶ms) 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 ¶ms) 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 diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_layout.hh b/source/blender/editors/space_spreadsheet/spreadsheet_layout.hh new file mode 100644 index 00000000000..1768af6ae09 --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_layout.hh @@ -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 diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 0ad66c63169..a90047b9daa 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -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) * \{ */ |