diff options
Diffstat (limited to 'source/blender/editors/space_spreadsheet')
19 files changed, 1756 insertions, 841 deletions
diff --git a/source/blender/editors/space_spreadsheet/CMakeLists.txt b/source/blender/editors/space_spreadsheet/CMakeLists.txt index a77e74ffd93..b2a0905d4a2 100644 --- a/source/blender/editors/space_spreadsheet/CMakeLists.txt +++ b/source/blender/editors/space_spreadsheet/CMakeLists.txt @@ -17,9 +17,9 @@ set(INC ../include + ../../blenfont ../../blenkernel ../../blenlib - ../../blenfont ../../bmesh ../../depsgraph ../../functions @@ -33,15 +33,23 @@ set(INC set(SRC space_spreadsheet.cc - spreadsheet_column_layout.cc + spreadsheet_context.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_context.hh + spreadsheet_cell_value.hh + spreadsheet_column.hh + spreadsheet_column_values.hh + spreadsheet_data_source.hh + spreadsheet_data_source_geometry.hh spreadsheet_draw.hh - spreadsheet_column_layout.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 0f7709b464e..1f0b5d5d13e 100644 --- a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc +++ b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc @@ -17,12 +17,12 @@ #include <cstring> #include "BLI_listbase.h" -#include "BLI_resource_collector.hh" #include "BKE_screen.h" #include "ED_screen.h" #include "ED_space_api.h" +#include "ED_spreadsheet.h" #include "DNA_scene_types.h" #include "DNA_screen_types.h" @@ -41,12 +41,16 @@ #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_context.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 +89,13 @@ 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); + } + LISTBASE_FOREACH_MUTABLE (SpreadsheetContext *, context, &sspreadsheet->context_path) { + spreadsheet_context_free(context); + } } static void spreadsheet_init(wmWindowManager *UNUSED(wm), ScrArea *area) @@ -102,6 +113,18 @@ 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); + } + + BLI_listbase_clear(&sspreadsheet_new->context_path); + LISTBASE_FOREACH_MUTABLE (SpreadsheetContext *, src_context, &sspreadsheet_old->context_path) { + SpreadsheetContext *new_context = spreadsheet_context_copy(src_context); + BLI_addtail(&sspreadsheet_new->context_path, new_context); + } + return (SpaceLink *)sspreadsheet_new; } @@ -109,6 +132,24 @@ static void spreadsheet_keymap(wmKeyConfig *UNUSED(keyconf)) { } +static void spreadsheet_id_remap(ScrArea *UNUSED(area), SpaceLink *slink, ID *old_id, ID *new_id) +{ + SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)slink; + LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) { + if (context->type == SPREADSHEET_CONTEXT_OBJECT) { + SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)context; + if ((ID *)object_context->object == old_id) { + if (new_id && GS(new_id->name) == ID_OB) { + object_context->object = (Object *)new_id; + } + else { + object_context->object = nullptr; + } + } + } + } +} + static void spreadsheet_main_region_init(wmWindowManager *wm, ARegion *region) { region->v2d.scroll = V2D_SCROLL_RIGHT | V2D_SCROLL_BOTTOM; @@ -123,57 +164,212 @@ static void spreadsheet_main_region_init(wmWindowManager *wm, ARegion *region) WM_event_add_keymap_handler(®ion->handlers, keymap); } -static ID *get_used_id(const bContext *C) +ID *ED_spreadsheet_get_current_id(struct SpaceSpreadsheet *sspreadsheet) +{ + if (BLI_listbase_is_empty(&sspreadsheet->context_path)) { + return nullptr; + } + SpreadsheetContext *root_context = (SpreadsheetContext *)sspreadsheet->context_path.first; + if (root_context->type != SPREADSHEET_CONTEXT_OBJECT) { + return nullptr; + } + SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)root_context; + return (ID *)object_context->object; +} + +/* Check if the pinned context still exists. If it doesn't try to find a new context. */ +static void update_pinned_context_path_if_outdated(const bContext *C) { SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); - if (sspreadsheet->pinned_id != nullptr) { - return sspreadsheet->pinned_id; + + /* Currently, this only checks if the object has been deleted. In the future we can have a more + * sophisticated check for the entire context (including modifier and nodes). */ + LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) { + if (context->type == SPREADSHEET_CONTEXT_OBJECT) { + SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)context; + if (object_context->object == nullptr) { + ED_spreadsheet_context_path_clear(sspreadsheet); + break; + } + } + } + if (BLI_listbase_is_empty(&sspreadsheet->context_path)) { + Object *active_object = CTX_data_active_object(C); + if (active_object != nullptr) { + SpreadsheetContext *new_context = spreadsheet_context_new(SPREADSHEET_CONTEXT_OBJECT); + ((SpreadsheetContextObject *)new_context)->object = active_object; + BLI_addtail(&sspreadsheet->context_path, new_context); + } + } + + if (BLI_listbase_is_empty(&sspreadsheet->context_path)) { + /* Don't pin empty context_path, that could be annoying. */ + sspreadsheet->flag &= ~SPREADSHEET_FLAG_PINNED; } +} + +static void update_context_path_from_context(const bContext *C) +{ + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); Object *active_object = CTX_data_active_object(C); - return (ID *)active_object; + if (active_object == nullptr) { + ED_spreadsheet_context_path_clear(sspreadsheet); + return; + } + if (!BLI_listbase_is_empty(&sspreadsheet->context_path)) { + SpreadsheetContext *root_context = (SpreadsheetContext *)sspreadsheet->context_path.first; + if (root_context->type == SPREADSHEET_CONTEXT_OBJECT) { + SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)root_context; + if (object_context->object != active_object) { + ED_spreadsheet_context_path_clear(sspreadsheet); + } + } + } + if (BLI_listbase_is_empty(&sspreadsheet->context_path)) { + SpreadsheetContext *new_context = spreadsheet_context_new(SPREADSHEET_CONTEXT_OBJECT); + ((SpreadsheetContextObject *)new_context)->object = active_object; + BLI_addtail(&sspreadsheet->context_path, new_context); + } } -class FallbackSpreadsheetDrawer : public SpreadsheetDrawer { -}; +static void update_context_path(const bContext *C) +{ + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + if (sspreadsheet->flag & SPREADSHEET_FLAG_PINNED) { + update_pinned_context_path_if_outdated(C); + } + else { + update_context_path_from_context(C); + } +} -static void gather_spreadsheet_columns(const bContext *C, - SpreadsheetColumnLayout &column_layout, - blender::ResourceCollector &resources) +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); + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + ID *used_id = ED_spreadsheet_get_current_id(sspreadsheet); 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; + if (!ELEM(object_orig->type, OB_MESH, OB_POINTCLOUD, OB_VOLUME)) { + return {}; } Object *object_eval = DEG_get_evaluated_object(depsgraph, object_orig); if (object_eval == nullptr) { - return; + return {}; + } + + 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; } + const int fontid = UI_style_get()->widget.uifont_id; + BLF_size(fontid, UI_DEFAULT_TEXT_POINTS, 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 + 1.0f, 3.0f); +} - return spreadsheet_columns_from_geometry(C, object_eval, column_layout, resources); +static float get_column_width_in_pixels(const ColumnValues &values) +{ + return get_column_width(values) * SPREADSHEET_WIDTH_UNIT; +} + +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; + } + + if (!used_ids.add(*column->id)) { + /* Remove duplicate columns for now. */ + BLI_remlink(&columns, column); + spreadsheet_column_free(column); + continue; + } + } + + 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); + update_context_path(C); - blender::ResourceCollector resources; - SpreadsheetColumnLayout column_layout; - gather_spreadsheet_columns(C, column_layout, resources); + 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_in_pixels(*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. */ @@ -208,6 +404,7 @@ static void spreadsheet_main_region_listener(const wmRegionListenerParams *param } break; } + case NC_TEXTURE: case NC_GEOM: { ED_region_tag_redraw(region); break; @@ -222,6 +419,7 @@ static void spreadsheet_header_region_init(wmWindowManager *UNUSED(wm), ARegion static void spreadsheet_header_region_draw(const bContext *C, ARegion *region) { + update_context_path(C); ED_region_header(C, region); } @@ -327,6 +525,7 @@ void ED_spacetype_spreadsheet(void) st->duplicate = spreadsheet_duplicate; st->operatortypes = spreadsheet_operatortypes; st->keymap = spreadsheet_keymap; + st->id_remap = spreadsheet_id_remap; /* regions: main window */ art = (ARegionType *)MEM_callocN(sizeof(ARegionType), "spacetype spreadsheet region"); 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..c9b73aabf96 --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh @@ -0,0 +1,58 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <optional> + +#include "BLI_color.hh" +#include "BLI_float2.hh" +#include "BLI_float3.hh" + +struct 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<float2> value_float2; + std::optional<float3> value_float3; + std::optional<ColorGeometry4f> value_color; + 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..de40545fdae --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_column.cc @@ -0,0 +1,72 @@ +/* + * 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); + 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..bb245851d55 --- /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(StringRef(column_id.name)); + } +}; +} // namespace blender + +inline bool operator==(const SpreadsheetColumnID &a, const SpreadsheetColumnID &b) +{ + using blender::StringRef; + return StringRef(a.name) == StringRef(b.name); +} + +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.cc b/source/blender/editors/space_spreadsheet/spreadsheet_column_layout.cc deleted file mode 100644 index 46760c0dd4e..00000000000 --- a/source/blender/editors/space_spreadsheet/spreadsheet_column_layout.cc +++ /dev/null @@ -1,230 +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 <iomanip> -#include <sstream> - -#include "spreadsheet_column_layout.hh" - -#include "DNA_userdef_types.h" - -#include "UI_interface.h" -#include "UI_resources.h" - -#include "BLF_api.h" - -namespace blender::ed::spreadsheet { - -class ColumnLayoutDrawer : public SpreadsheetDrawer { - private: - const SpreadsheetColumnLayout &column_layout_; - Vector<int> column_widths_; - - public: - ColumnLayoutDrawer(const SpreadsheetColumnLayout &column_layout) : column_layout_(column_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, 11 * 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); - } - } - } - - void draw_top_row_cell(int column_index, const CellDrawParams ¶ms) const final - { - const StringRefNull name = column_layout_.columns[column_index]->name(); - uiBut *but = uiDefIconTextBut(params.block, - UI_BTYPE_LABEL, - 0, - ICON_NONE, - name.c_str(), - params.xmin, - params.ymin, - params.width, - params.height, - nullptr, - 0, - 0, - 0, - 0, - nullptr); - /* Center-align column headers. */ - UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT); - UI_but_drawflag_disable(but, UI_BUT_TEXT_RIGHT); - } - - void draw_left_column_cell(int row_index, const CellDrawParams ¶ms) const final - { - const int real_index = column_layout_.row_indices[row_index]; - std::string index_str = std::to_string(real_index); - uiBut *but = uiDefIconTextBut(params.block, - UI_BTYPE_LABEL, - 0, - ICON_NONE, - index_str.c_str(), - params.xmin, - params.ymin, - params.width, - params.height, - nullptr, - 0, - 0, - 0, - 0, - nullptr); - /* Right-align indices. */ - UI_but_drawflag_enable(but, UI_BUT_TEXT_RIGHT); - UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT); - } - - 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]; - CellValue cell_value; - column.get_value(real_index, cell_value); - - if (cell_value.value_int.has_value()) { - const int value = *cell_value.value_int; - const std::string value_str = std::to_string(value); - uiDefIconTextBut(params.block, - UI_BTYPE_LABEL, - 0, - ICON_NONE, - value_str.c_str(), - params.xmin, - params.ymin, - params.width, - params.height, - nullptr, - 0, - 0, - 0, - 0, - nullptr); - } - else if (cell_value.value_float.has_value()) { - const float value = *cell_value.value_float; - std::stringstream ss; - ss << std::fixed << std::setprecision(3) << value; - const std::string value_str = ss.str(); - uiDefIconTextBut(params.block, - UI_BTYPE_LABEL, - 0, - ICON_NONE, - value_str.c_str(), - params.xmin, - params.ymin, - params.width, - params.height, - nullptr, - 0, - 0, - 0, - 0, - nullptr); - } - else if (cell_value.value_bool.has_value()) { - const bool value = *cell_value.value_bool; - const int icon = value ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT; - uiDefIconTextBut(params.block, - UI_BTYPE_LABEL, - 0, - icon, - "", - params.xmin, - params.ymin, - params.width, - params.height, - nullptr, - 0, - 0, - 0, - 0, - nullptr); - } - else if (cell_value.value_object.has_value()) { - const ObjectCellValue value = *cell_value.value_object; - uiDefIconTextBut(params.block, - UI_BTYPE_LABEL, - 0, - ICON_OBJECT_DATA, - reinterpret_cast<const ID *const>(value.object)->name + 2, - params.xmin, - params.ymin, - params.width, - params.height, - nullptr, - 0, - 0, - 0, - 0, - nullptr); - } - else if (cell_value.value_collection.has_value()) { - const CollectionCellValue value = *cell_value.value_collection; - uiDefIconTextBut(params.block, - UI_BTYPE_LABEL, - 0, - ICON_OUTLINER_COLLECTION, - reinterpret_cast<const ID *const>(value.collection)->name + 2, - params.xmin, - params.ymin, - params.width, - params.height, - nullptr, - 0, - 0, - 0, - 0, - nullptr); - } - } - - int column_width(int column_index) const final - { - return column_widths_[column_index]; - } -}; - -std::unique_ptr<SpreadsheetDrawer> spreadsheet_drawer_from_column_layout( - const SpreadsheetColumnLayout &column_layout) -{ - return std::make_unique<ColumnLayoutDrawer>(column_layout); -} - -} // 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..373c988a41c --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh @@ -0,0 +1,92 @@ +/* + * 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, + const int size, + GetValueF get_value, + const float default_width = 0.0f) +{ + std::unique_ptr<ColumnValues> column_values = std::make_unique<LambdaColumnValues<GetValueF>>( + std::move(name), size, std::move(get_value)); + column_values->default_width = default_width; + return column_values; +} + +static constexpr float default_float_column_width = 3; +static constexpr float default_float2_column_width = 2 * default_float_column_width; +static constexpr float default_float3_column_width = 3 * default_float_column_width; +static constexpr float default_color_column_width = 4 * default_float_column_width; + +} // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_context.cc b/source/blender/editors/space_spreadsheet/spreadsheet_context.cc new file mode 100644 index 00000000000..3eb43338908 --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_context.cc @@ -0,0 +1,306 @@ +/* + * 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 "MEM_guardedalloc.h" + +#include "BLI_hash.h" +#include "BLI_hash.hh" +#include "BLI_hash_mm2a.h" +#include "BLI_listbase.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" +#include "BLI_vector.hh" + +#include "ED_spreadsheet.h" + +#include "DEG_depsgraph.h" + +#include "BKE_main.h" +#include "BKE_modifier.h" +#include "BKE_object.h" + +#include "spreadsheet_context.hh" + +namespace blender::ed::spreadsheet { + +static SpreadsheetContextObject *spreadsheet_context_object_new() +{ + SpreadsheetContextObject *context = (SpreadsheetContextObject *)MEM_callocN( + sizeof(SpreadsheetContextObject), __func__); + context->base.type = SPREADSHEET_CONTEXT_OBJECT; + return context; +} + +static SpreadsheetContextObject *spreadsheet_context_object_copy( + const SpreadsheetContextObject *src_context) +{ + SpreadsheetContextObject *new_context = spreadsheet_context_object_new(); + new_context->object = src_context->object; + return new_context; +} + +static void spreadsheet_context_object_hash(const SpreadsheetContextObject *context, + BLI_HashMurmur2A *mm2) +{ + BLI_hash_mm2a_add(mm2, (const uchar *)&context->object, sizeof(Object *)); +} + +static void spreadsheet_context_object_free(SpreadsheetContextObject *context) +{ + MEM_freeN(context); +} + +static SpreadsheetContextModifier *spreadsheet_context_modifier_new() +{ + SpreadsheetContextModifier *context = (SpreadsheetContextModifier *)MEM_callocN( + sizeof(SpreadsheetContextModifier), __func__); + context->base.type = SPREADSHEET_CONTEXT_MODIFIER; + return context; +} + +static SpreadsheetContextModifier *spreadsheet_context_modifier_copy( + const SpreadsheetContextModifier *src_context) +{ + SpreadsheetContextModifier *new_context = spreadsheet_context_modifier_new(); + if (src_context->modifier_name) { + new_context->modifier_name = BLI_strdup(src_context->modifier_name); + } + return new_context; +} + +static void spreadsheet_context_modifier_hash(const SpreadsheetContextModifier *context, + BLI_HashMurmur2A *mm2) +{ + if (context->modifier_name) { + BLI_hash_mm2a_add(mm2, (const uchar *)context->modifier_name, strlen(context->modifier_name)); + } +} + +static void spreadsheet_context_modifier_free(SpreadsheetContextModifier *context) +{ + if (context->modifier_name) { + MEM_freeN(context->modifier_name); + } + MEM_freeN(context); +} + +static SpreadsheetContextNode *spreadsheet_context_node_new() +{ + SpreadsheetContextNode *context = (SpreadsheetContextNode *)MEM_callocN( + sizeof(SpreadsheetContextNode), __func__); + context->base.type = SPREADSHEET_CONTEXT_NODE; + return context; +} + +static SpreadsheetContextNode *spreadsheet_context_node_copy( + const SpreadsheetContextNode *src_context) +{ + SpreadsheetContextNode *new_context = spreadsheet_context_node_new(); + if (src_context->node_name) { + new_context->node_name = BLI_strdup(src_context->node_name); + } + return new_context; +} + +static void spreadsheet_context_node_hash(const SpreadsheetContextNode *context, + BLI_HashMurmur2A *mm2) +{ + if (context->node_name) { + BLI_hash_mm2a_add(mm2, (const uchar *)context->node_name, strlen(context->node_name)); + } +} + +static void spreadsheet_context_node_free(SpreadsheetContextNode *context) +{ + if (context->node_name) { + MEM_freeN(context->node_name); + } + MEM_freeN(context); +} + +SpreadsheetContext *spreadsheet_context_new(eSpaceSpreadsheet_ContextType type) +{ + switch (type) { + case SPREADSHEET_CONTEXT_OBJECT: { + return (SpreadsheetContext *)spreadsheet_context_object_new(); + } + case SPREADSHEET_CONTEXT_MODIFIER: { + return (SpreadsheetContext *)spreadsheet_context_modifier_new(); + } + case SPREADSHEET_CONTEXT_NODE: { + return (SpreadsheetContext *)spreadsheet_context_node_new(); + } + } + BLI_assert_unreachable(); + return nullptr; +} + +SpreadsheetContext *spreadsheet_context_copy(const SpreadsheetContext *old_context) +{ + switch (old_context->type) { + case SPREADSHEET_CONTEXT_OBJECT: { + return (SpreadsheetContext *)spreadsheet_context_object_copy( + (const SpreadsheetContextObject *)old_context); + } + case SPREADSHEET_CONTEXT_MODIFIER: { + return (SpreadsheetContext *)spreadsheet_context_modifier_copy( + (const SpreadsheetContextModifier *)old_context); + } + case SPREADSHEET_CONTEXT_NODE: { + return (SpreadsheetContext *)spreadsheet_context_node_copy( + (const SpreadsheetContextNode *)old_context); + } + } + BLI_assert_unreachable(); + return nullptr; +} + +static void spreadsheet_context_hash(const SpreadsheetContext *context, BLI_HashMurmur2A *mm2) +{ + BLI_hash_mm2a_add_int(mm2, context->type); + switch (context->type) { + case SPREADSHEET_CONTEXT_OBJECT: { + spreadsheet_context_object_hash((const SpreadsheetContextObject *)context, mm2); + break; + } + case SPREADSHEET_CONTEXT_MODIFIER: { + spreadsheet_context_modifier_hash((const SpreadsheetContextModifier *)context, mm2); + break; + } + case SPREADSHEET_CONTEXT_NODE: { + spreadsheet_context_node_hash((const SpreadsheetContextNode *)context, mm2); + break; + } + } +} + +void spreadsheet_context_free(SpreadsheetContext *context) +{ + switch (context->type) { + case SPREADSHEET_CONTEXT_OBJECT: { + return spreadsheet_context_object_free((SpreadsheetContextObject *)context); + } + case SPREADSHEET_CONTEXT_MODIFIER: { + return spreadsheet_context_modifier_free((SpreadsheetContextModifier *)context); + } + case SPREADSHEET_CONTEXT_NODE: { + return spreadsheet_context_node_free((SpreadsheetContextNode *)context); + } + } + BLI_assert_unreachable(); +} + +/** + * Tag any data relevant to the spreadsheet's context for recalculation in order to collect + * information to display in the editor, which may be cached during evaluation. + */ +static void spreadsheet_context_update_tag(SpaceSpreadsheet *sspreadsheet) +{ + using namespace blender; + Vector<const SpreadsheetContext *> context_path = sspreadsheet->context_path; + if (context_path.is_empty()) { + return; + } + if (context_path[0]->type != SPREADSHEET_CONTEXT_OBJECT) { + return; + } + SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)context_path[0]; + Object *object = object_context->object; + if (object == nullptr) { + return; + } + if (context_path.size() == 1) { + /* No need to reevaluate, when the final or original object is viewed. */ + return; + } + + DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY); +} + +} // namespace blender::ed::spreadsheet + +SpreadsheetContext *ED_spreadsheet_context_new(int type) +{ + return blender::ed::spreadsheet::spreadsheet_context_new((eSpaceSpreadsheet_ContextType)type); +} + +void ED_spreadsheet_context_free(struct SpreadsheetContext *context) +{ + blender::ed::spreadsheet::spreadsheet_context_free(context); +} + +void ED_spreadsheet_context_path_clear(struct SpaceSpreadsheet *sspreadsheet) +{ + LISTBASE_FOREACH_MUTABLE (SpreadsheetContext *, context, &sspreadsheet->context_path) { + ED_spreadsheet_context_free(context); + } + BLI_listbase_clear(&sspreadsheet->context_path); +} + +void ED_spreadsheet_context_path_update_tag(SpaceSpreadsheet *sspreadsheet) +{ + blender::ed::spreadsheet::spreadsheet_context_update_tag(sspreadsheet); +} + +uint64_t ED_spreadsheet_context_path_hash(SpaceSpreadsheet *sspreadsheet) +{ + BLI_HashMurmur2A mm2; + BLI_hash_mm2a_init(&mm2, 1234); + LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) { + blender::ed::spreadsheet::spreadsheet_context_hash(context, &mm2); + } + return BLI_hash_mm2a_end(&mm2); +} + +void ED_spreadsheet_set_geometry_node_context(struct SpaceSpreadsheet *sspreadsheet, + struct SpaceNode *snode, + struct bNode *node) +{ + using namespace blender::ed::spreadsheet; + ED_spreadsheet_context_path_clear(sspreadsheet); + + Object *object = (Object *)snode->id; + ModifierData *modifier = BKE_object_active_modifier(object); + + { + SpreadsheetContextObject *context = spreadsheet_context_object_new(); + context->object = object; + BLI_addtail(&sspreadsheet->context_path, context); + } + { + SpreadsheetContextModifier *context = spreadsheet_context_modifier_new(); + context->modifier_name = BLI_strdup(modifier->name); + BLI_addtail(&sspreadsheet->context_path, context); + } + { + int i; + LISTBASE_FOREACH_INDEX (bNodeTreePath *, path, &snode->treepath, i) { + if (i == 0) { + continue; + } + SpreadsheetContextNode *context = spreadsheet_context_node_new(); + context->node_name = BLI_strdup(path->node_name); + BLI_addtail(&sspreadsheet->context_path, context); + } + } + { + SpreadsheetContextNode *context = spreadsheet_context_node_new(); + context->node_name = BLI_strdup(node->name); + BLI_addtail(&sspreadsheet->context_path, context); + } + + sspreadsheet->object_eval_state = SPREADSHEET_OBJECT_EVAL_STATE_EVALUATED; +} diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_from_geometry.hh b/source/blender/editors/space_spreadsheet/spreadsheet_context.hh index cef731517b9..d71769e42b3 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_from_geometry.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_context.hh @@ -16,19 +16,12 @@ #pragma once -#include "BKE_geometry_set.hh" - -#include "BLI_resource_collector.hh" - -#include "spreadsheet_column_layout.hh" - -struct bContext; +#include "DNA_space_types.h" namespace blender::ed::spreadsheet { -void spreadsheet_columns_from_geometry(const bContext *C, - Object *object_eval, - SpreadsheetColumnLayout &column_layout, - ResourceCollector &resources); +SpreadsheetContext *spreadsheet_context_new(eSpaceSpreadsheet_ContextType type); +SpreadsheetContext *spreadsheet_context_copy(const SpreadsheetContext *old_context); +void spreadsheet_context_free(SpreadsheetContext *context); } // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source.cc new file mode 100644 index 00000000000..09b8c6b1b54 --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source.cc @@ -0,0 +1,24 @@ +/* + * 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 "spreadsheet_data_source.hh" + +namespace blender::ed::spreadsheet { + +/* 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..de47109a144 --- /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 laid + * 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..452885959f6 --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -0,0 +1,445 @@ +/* + * 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 "ED_spreadsheet.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(); + fn(column_id); + return true; + }); +} + +std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values( + const SpreadsheetColumnID &column_id) const +{ + std::lock_guard lock{mutex_}; + + bke::ReadAttributeLookup attribute = component_->attribute_try_get_for_read(column_id.name); + if (!attribute) { + return {}; + } + const fn::GVArray *varray = scope_.add(std::move(attribute.varray), __func__); + if (attribute.domain != domain_) { + return {}; + } + int domain_size = varray->size(); + const CustomDataType type = bke::cpp_type_to_custom_data_type(varray->type()); + switch (type) { + case CD_PROP_FLOAT: + return column_values_from_function( + column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { + float value; + varray->get(index, &value); + r_cell_value.value_float = value; + }); + case CD_PROP_INT32: + return column_values_from_function( + column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { + int value; + varray->get(index, &value); + r_cell_value.value_int = value; + }); + case CD_PROP_BOOL: + return column_values_from_function( + column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { + bool value; + varray->get(index, &value); + r_cell_value.value_bool = value; + }); + case CD_PROP_FLOAT2: { + return column_values_from_function( + column_id.name, + domain_size, + [varray](int index, CellValue &r_cell_value) { + float2 value; + varray->get(index, &value); + r_cell_value.value_float2 = value; + }, + default_float2_column_width); + } + case CD_PROP_FLOAT3: { + return column_values_from_function( + column_id.name, + domain_size, + [varray](int index, CellValue &r_cell_value) { + float3 value; + varray->get(index, &value); + r_cell_value.value_float3 = value; + }, + default_float3_column_width); + } + case CD_PROP_COLOR: { + return column_values_from_function( + column_id.name, + domain_size, + [varray](int index, CellValue &r_cell_value) { + ColorGeometry4f value; + varray->get(index, &value); + r_cell_value.value_color = value; + }, + default_color_column_width); + } + 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 +{ + if (component_->instances_amount() == 0) { + return; + } + + SpreadsheetColumnID column_id; + column_id.name = (char *)"Name"; + fn(column_id); + for (const char *name : {"Position", "Rotation", "Scale", "ID"}) { + column_id.name = (char *)name; + fn(column_id); + } +} + +std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values( + const SpreadsheetColumnID &column_id) const +{ + if (component_->instances_amount() == 0) { + return {}; + } + + const int size = this->tot_rows(); + if (STREQ(column_id.name, "Name")) { + Span<int> reference_handles = component_->instance_reference_handles(); + Span<InstanceReference> references = component_->references(); + std::unique_ptr<ColumnValues> values = column_values_from_function( + "Name", size, [reference_handles, references](int index, CellValue &r_cell_value) { + const InstanceReference &reference = references[reference_handles[index]]; + switch (reference.type()) { + case InstanceReference::Type::Object: { + Object &object = reference.object(); + r_cell_value.value_object = ObjectCellValue{&object}; + break; + } + case InstanceReference::Type::Collection: { + Collection &collection = reference.collection(); + r_cell_value.value_collection = CollectionCellValue{&collection}; + break; + } + case InstanceReference::Type::None: { + break; + } + } + }); + values->default_width = 8.0f; + return values; + } + Span<float4x4> transforms = component_->instance_transforms(); + if (STREQ(column_id.name, "Position")) { + return column_values_from_function( + column_id.name, + size, + [transforms](int index, CellValue &r_cell_value) { + r_cell_value.value_float3 = transforms[index].translation(); + }, + default_float3_column_width); + } + if (STREQ(column_id.name, "Rotation")) { + return column_values_from_function( + column_id.name, + size, + [transforms](int index, CellValue &r_cell_value) { + r_cell_value.value_float3 = transforms[index].to_euler(); + }, + default_float3_column_width); + } + if (STREQ(column_id.name, "Scale")) { + return column_values_from_function( + column_id.name, + size, + [transforms](int index, CellValue &r_cell_value) { + r_cell_value.value_float3 = transforms[index].scale(); + }, + default_float3_column_width); + } + Span<int> ids = component_->instance_ids(); + if (STREQ(column_id.name, "ID")) { + /* Make the column a bit wider by default, since the IDs tend to be large numbers. */ + return column_values_from_function( + column_id.name, + size, + [ids](int index, CellValue &r_cell_value) { r_cell_value.value_int = ids[index]; }, + 5.5f); + } + 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 (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_EVALUATED) { + 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 (BLI_listbase_count(&sspreadsheet->context_path) == 1) { + /* Use final evaluated object. */ + if (object_eval->runtime.geometry_set_eval != nullptr) { + geometry_set = *object_eval->runtime.geometry_set_eval; + } + } + else { + if (object_eval->runtime.geometry_set_previews != nullptr) { + GHash *ghash = (GHash *)object_eval->runtime.geometry_set_previews; + const uint64_t key = ED_spreadsheet_context_path_hash(sspreadsheet); + GeometrySet *geometry_set_preview = (GeometrySet *)BLI_ghash_lookup_default( + ghash, POINTER_FROM_UINT(key), nullptr); + if (geometry_set_preview != nullptr) { + geometry_set = *geometry_set_preview; + } + } + } + } + } + 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_draw.cc b/source/blender/editors/space_spreadsheet/spreadsheet_draw.cc index d6379c740e8..b911c80fa63 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_draw.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_draw.cc @@ -142,7 +142,9 @@ static void draw_left_column_content(const int scroll_offset_y, ARegion *region, const SpreadsheetDrawer &drawer) { - GPU_scissor_test(true); + int old_scissor[4]; + GPU_scissor_get(old_scissor); + GPU_scissor(0, 0, drawer.left_column_width, region->winy - drawer.top_row_height); uiBlock *left_column_block = UI_block_begin(C, region, __func__, UI_EMBOSS_NONE); @@ -165,7 +167,7 @@ static void draw_left_column_content(const int scroll_offset_y, UI_block_end(C, left_column_block); UI_block_draw(C, left_column_block); - GPU_scissor_test(false); + GPU_scissor(UNPACK4(old_scissor)); } static void draw_top_row_content(const bContext *C, @@ -173,7 +175,9 @@ static void draw_top_row_content(const bContext *C, const SpreadsheetDrawer &drawer, const int scroll_offset_x) { - GPU_scissor_test(true); + int old_scissor[4]; + GPU_scissor_get(old_scissor); + GPU_scissor(drawer.left_column_width + 1, region->winy - drawer.top_row_height, region->winx - drawer.left_column_width, @@ -200,7 +204,7 @@ static void draw_top_row_content(const bContext *C, UI_block_end(C, first_row_block); UI_block_draw(C, first_row_block); - GPU_scissor_test(false); + GPU_scissor(UNPACK4(old_scissor)); } static void draw_cell_contents(const bContext *C, @@ -209,7 +213,9 @@ static void draw_cell_contents(const bContext *C, const int scroll_offset_x, const int scroll_offset_y) { - GPU_scissor_test(true); + int old_scissor[4]; + GPU_scissor_get(old_scissor); + GPU_scissor(drawer.left_column_width + 1, 0, region->winx - drawer.left_column_width, @@ -248,7 +254,7 @@ static void draw_cell_contents(const bContext *C, UI_block_end(C, cells_block); UI_block_draw(C, cells_block); - GPU_scissor_test(false); + GPU_scissor(UNPACK4(old_scissor)); } static void update_view2d_tot_rect(const SpreadsheetDrawer &drawer, diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_draw.hh b/source/blender/editors/space_spreadsheet/spreadsheet_draw.hh index 6828006f4a1..647587ec8b0 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_draw.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_draw.hh @@ -19,7 +19,6 @@ #include "BLI_vector.hh" struct uiBlock; -struct rcti; struct bContext; struct ARegion; 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 910bc0a34ec..00000000000 --- a/source/blender/editors/space_spreadsheet/spreadsheet_from_geometry.cc +++ /dev/null @@ -1,447 +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, - ResourceCollector &resources) -{ - Span<InstancedData> instance_data = instances_component.instanced_data(); - Span<float4x4> transforms = instances_component.transforms(); - - Vector<std::unique_ptr<SpreadsheetColumn>> &columns = - resources.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_FINAL) { - 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 (object_eval->runtime.geometry_set_eval != nullptr) { - /* This does not copy the geometry data itself. */ - geometry_set = *object_eval->runtime.geometry_set_eval; - } - } - } - else { - 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); - } - } - 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, - ResourceCollector &resources) -{ - 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 = resources.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_FINAL) { - 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, - ResourceCollector &resources) -{ - 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 = resources.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, resources); - 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 = - resources.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; - resources.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, resources); - } - 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_layout.cc b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc new file mode 100644 index 00000000000..8079763a339 --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc @@ -0,0 +1,256 @@ +/* + * 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 <iomanip> +#include <sstream> + +#include "spreadsheet_layout.hh" + +#include "DNA_userdef_types.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "BLF_api.h" + +namespace blender::ed::spreadsheet { + +class SpreadsheetLayoutDrawer : public SpreadsheetDrawer { + private: + const SpreadsheetLayout &spreadsheet_layout_; + + public: + SpreadsheetLayoutDrawer(const SpreadsheetLayout &spreadsheet_layout) + : spreadsheet_layout_(spreadsheet_layout) + { + 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 = spreadsheet_layout_.columns[column_index].values->name(); + uiBut *but = uiDefIconTextBut(params.block, + UI_BTYPE_LABEL, + 0, + ICON_NONE, + name.c_str(), + params.xmin, + params.ymin, + params.width, + params.height, + nullptr, + 0, + 0, + 0, + 0, + nullptr); + /* Center-align column headers. */ + UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT); + UI_but_drawflag_disable(but, UI_BUT_TEXT_RIGHT); + } + + void draw_left_column_cell(int row_index, const CellDrawParams ¶ms) const final + { + 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, + 0, + ICON_NONE, + index_str.c_str(), + params.xmin, + params.ymin, + params.width, + params.height, + nullptr, + 0, + 0, + 0, + 0, + nullptr); + /* Right-align indices. */ + UI_but_drawflag_enable(but, UI_BUT_TEXT_RIGHT); + UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT); + } + + void draw_content_cell(int row_index, int column_index, const CellDrawParams ¶ms) const final + { + 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); + + if (cell_value.value_int.has_value()) { + const int value = *cell_value.value_int; + const std::string value_str = std::to_string(value); + uiBut *but = uiDefIconTextBut(params.block, + UI_BTYPE_LABEL, + 0, + ICON_NONE, + value_str.c_str(), + params.xmin, + params.ymin, + params.width, + params.height, + nullptr, + 0, + 0, + 0, + 0, + nullptr); + /* Right-align Integers. */ + UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT); + UI_but_drawflag_enable(but, UI_BUT_TEXT_RIGHT); + } + else if (cell_value.value_float.has_value()) { + const float value = *cell_value.value_float; + std::stringstream ss; + ss << std::fixed << std::setprecision(3) << value; + const std::string value_str = ss.str(); + uiBut *but = uiDefIconTextBut(params.block, + UI_BTYPE_LABEL, + 0, + ICON_NONE, + value_str.c_str(), + params.xmin, + params.ymin, + params.width, + params.height, + nullptr, + 0, + 0, + 0, + 0, + nullptr); + /* Right-align Floats. */ + UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT); + UI_but_drawflag_enable(but, UI_BUT_TEXT_RIGHT); + } + else if (cell_value.value_bool.has_value()) { + const bool value = *cell_value.value_bool; + const int icon = value ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT; + uiBut *but = uiDefIconTextBut(params.block, + UI_BTYPE_LABEL, + 0, + icon, + "", + params.xmin, + params.ymin, + params.width, + params.height, + nullptr, + 0, + 0, + 0, + 0, + nullptr); + UI_but_drawflag_disable(but, UI_BUT_ICON_LEFT); + } + else if (cell_value.value_float2.has_value()) { + const float2 value = *cell_value.value_float2; + this->draw_float_vector(params, Span(&value.x, 2)); + } + else if (cell_value.value_float3.has_value()) { + const float3 value = *cell_value.value_float3; + this->draw_float_vector(params, Span(&value.x, 3)); + } + else if (cell_value.value_color.has_value()) { + const ColorGeometry4f value = *cell_value.value_color; + this->draw_float_vector(params, Span(&value.r, 4)); + } + else if (cell_value.value_object.has_value()) { + const ObjectCellValue value = *cell_value.value_object; + uiDefIconTextBut(params.block, + UI_BTYPE_LABEL, + 0, + ICON_OBJECT_DATA, + reinterpret_cast<const ID *const>(value.object)->name + 2, + params.xmin, + params.ymin, + params.width, + params.height, + nullptr, + 0, + 0, + 0, + 0, + nullptr); + } + else if (cell_value.value_collection.has_value()) { + const CollectionCellValue value = *cell_value.value_collection; + uiDefIconTextBut(params.block, + UI_BTYPE_LABEL, + 0, + ICON_OUTLINER_COLLECTION, + reinterpret_cast<const ID *const>(value.collection)->name + 2, + params.xmin, + params.ymin, + params.width, + params.height, + nullptr, + 0, + 0, + 0, + 0, + nullptr); + } + } + + void draw_float_vector(const CellDrawParams ¶ms, const Span<float> values) const + { + BLI_assert(!values.is_empty()); + const float segment_width = (float)params.width / values.size(); + for (const int i : values.index_range()) { + std::stringstream ss; + const float value = values[i]; + ss << std::fixed << std::setprecision(3) << value; + const std::string value_str = ss.str(); + uiBut *but = uiDefIconTextBut(params.block, + UI_BTYPE_LABEL, + 0, + ICON_NONE, + value_str.c_str(), + params.xmin + i * segment_width, + params.ymin, + segment_width, + params.height, + nullptr, + 0, + 0, + 0, + 0, + nullptr); + /* Right-align Floats. */ + UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT); + UI_but_drawflag_enable(but, UI_BUT_TEXT_RIGHT); + } + } + + int column_width(int column_index) const final + { + return spreadsheet_layout_.columns[column_index].width; + } +}; + +std::unique_ptr<SpreadsheetDrawer> spreadsheet_drawer_from_layout( + const SpreadsheetLayout &spreadsheet_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 |