diff options
13 files changed, 1001 insertions, 6 deletions
diff --git a/release/scripts/startup/bl_operators/__init__.py b/release/scripts/startup/bl_operators/__init__.py index 7e6f14a0a51..078b32f5e2a 100644 --- a/release/scripts/startup/bl_operators/__init__.py +++ b/release/scripts/startup/bl_operators/__init__.py @@ -45,6 +45,7 @@ _modules = [ "rigidbody", "screen_play_rendered_anim", "sequencer", + "spreadsheet", "userpref", "uvcalc_follow_active", "uvcalc_lightmap", diff --git a/release/scripts/startup/bl_operators/spreadsheet.py b/release/scripts/startup/bl_operators/spreadsheet.py new file mode 100644 index 00000000000..a2f9b2ad412 --- /dev/null +++ b/release/scripts/startup/bl_operators/spreadsheet.py @@ -0,0 +1,52 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# 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. +# +# ##### END GPL LICENSE BLOCK ##### + +from __future__ import annotations + +import bpy + +class SPREADSHEET_OT_toggle_pin(bpy.types.Operator): + '''Turn on or off pinning''' + bl_idname = "spreadsheet.toggle_pin" + bl_label = "Toggle Pin" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + space = context.space_data + return space and space.type == 'SPREADSHEET' + + def execute(self, context): + space = context.space_data + + if space.pinned_id: + space.pinned_id = None + else: + space.pinned_id = context.active_object + + return {'FINISHED'} + + +classes = ( + SPREADSHEET_OT_toggle_pin, +) + +if __name__ == "__main__": # Only for live edit. + from bpy.utils import register_class + for cls in classes: + register_class(cls) diff --git a/release/scripts/startup/bl_ui/space_spreadsheet.py b/release/scripts/startup/bl_ui/space_spreadsheet.py index e433ead070c..1d124019ce8 100644 --- a/release/scripts/startup/bl_ui/space_spreadsheet.py +++ b/release/scripts/startup/bl_ui/space_spreadsheet.py @@ -28,6 +28,19 @@ class SPREADSHEET_HT_header(bpy.types.Header): layout.template_header() + pinned_id = space.pinned_id + used_id = pinned_id if pinned_id else context.active_object + + if used_id: + layout.label(text=used_id.name, icon="OBJECT_DATA") + + layout.operator("spreadsheet.toggle_pin", text="", icon='PINNED' if pinned_id else 'UNPINNED', emboss=False) + + layout.separator_spacer() + + if isinstance(used_id, bpy.types.Object) and used_id.mode == 'EDIT': + layout.prop(space, "show_only_selected", text="Selected Only") + classes = ( SPREADSHEET_HT_header, diff --git a/source/blender/blenkernel/intern/screen.c b/source/blender/blenkernel/intern/screen.c index f0220373678..fd7f1acb456 100644 --- a/source/blender/blenkernel/intern/screen.c +++ b/source/blender/blenkernel/intern/screen.c @@ -224,6 +224,12 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area BKE_LIB_FOREACHID_PROCESS(data, sclip->mask_info.mask, IDWALK_CB_USER_ONE); break; } + case SPACE_SPREADSHEET: { + SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl; + + BKE_LIB_FOREACHID_PROCESS_ID(data, sspreadsheet->pinned_id, IDWALK_CB_NOP); + break; + } default: break; } @@ -1908,6 +1914,11 @@ void BKE_screen_area_blend_read_lib(BlendLibReader *reader, ID *parent_id, ScrAr BLO_read_id_address(reader, parent_id->lib, &sclip->mask_info.mask); break; } + case SPACE_SPREADSHEET: { + SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl; + BLO_read_id_address(reader, parent_id->lib, &sspreadsheet->pinned_id); + break; + } default: break; } diff --git a/source/blender/editors/space_spreadsheet/CMakeLists.txt b/source/blender/editors/space_spreadsheet/CMakeLists.txt index 8be5f506dd7..e270ce9676c 100644 --- a/source/blender/editors/space_spreadsheet/CMakeLists.txt +++ b/source/blender/editors/space_spreadsheet/CMakeLists.txt @@ -19,6 +19,11 @@ set(INC ../include ../../blenkernel ../../blenlib + ../../blenfont + ../../bmesh + ../../depsgraph + ../../functions + ../../gpu ../../makesdna ../../makesrna ../../windowmanager @@ -28,8 +33,12 @@ set(INC set(SRC space_spreadsheet.cc + spreadsheet_draw.cc + spreadsheet_from_geometry.cc spreadsheet_ops.cc + spreadsheet_draw.hh + spreadsheet_from_geometry.hh spreadsheet_intern.hh ) diff --git a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc index 27276b4bedc..53424c60d59 100644 --- a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc +++ b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc @@ -32,6 +32,8 @@ #include "UI_resources.h" #include "UI_view2d.h" +#include "DEG_depsgraph_query.h" + #include "RNA_access.h" #include "WM_api.h" @@ -39,6 +41,11 @@ #include "spreadsheet_intern.hh" +#include "spreadsheet_from_geometry.hh" +#include "spreadsheet_intern.hh" + +using namespace blender::ed::spreadsheet; + static SpaceLink *spreadsheet_create(const ScrArea *UNUSED(area), const Scene *UNUSED(scene)) { SpaceSpreadsheet *spreadsheet_space = (SpaceSpreadsheet *)MEM_callocN(sizeof(SpaceSpreadsheet), @@ -94,9 +101,49 @@ static void spreadsheet_main_region_init(wmWindowManager *wm, ARegion *region) WM_event_add_keymap_handler(®ion->handlers, keymap); } -static void spreadsheet_main_region_draw(const bContext *UNUSED(C), ARegion *UNUSED(region)) +static ID *get_used_id(const bContext *C) +{ + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + if (sspreadsheet->pinned_id != nullptr) { + return sspreadsheet->pinned_id; + } + Object *active_object = CTX_data_active_object(C); + return (ID *)active_object; +} + +class FallbackSpreadsheetDrawer : public SpreadsheetDrawer { +}; + +static std::unique_ptr<SpreadsheetDrawer> generate_spreadsheet_drawer(const bContext *C) +{ + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + ID *used_id = get_used_id(C); + if (used_id == nullptr) { + return {}; + } + const ID_Type id_type = GS(used_id->name); + if (id_type != ID_OB) { + return {}; + } + Object *object_orig = (Object *)used_id; + if (object_orig->type != OB_MESH) { + return {}; + } + Object *object_eval = DEG_get_evaluated_object(depsgraph, object_orig); + if (object_eval == nullptr) { + return {}; + } + + return spreadsheet_drawer_from_geometry_attributes(C, object_eval); +} + +static void spreadsheet_main_region_draw(const bContext *C, ARegion *region) { - UI_ThemeClearColor(TH_BACK); + std::unique_ptr<SpreadsheetDrawer> drawer = generate_spreadsheet_drawer(C); + if (!drawer) { + drawer = std::make_unique<FallbackSpreadsheetDrawer>(); + } + draw_spreadsheet_in_region(C, region, *drawer); } static void spreadsheet_main_region_listener(const wmRegionListenerParams *params) @@ -108,6 +155,7 @@ static void spreadsheet_main_region_listener(const wmRegionListenerParams *param case NC_SCENE: { switch (wmn->data) { case ND_MODE: + case ND_FRAME: case ND_OB_ACTIVE: { ED_region_tag_redraw(region); break; diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_draw.cc b/source/blender/editors/space_spreadsheet/spreadsheet_draw.cc new file mode 100644 index 00000000000..d6379c740e8 --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_draw.cc @@ -0,0 +1,304 @@ +/* + * 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 "UI_interface.h" +#include "UI_resources.h" +#include "UI_view2d.h" + +#include "GPU_immediate.h" + +#include "DNA_screen_types.h" +#include "DNA_userdef_types.h" + +#include "BLI_rect.h" + +#include "spreadsheet_draw.hh" + +namespace blender::ed::spreadsheet { + +SpreadsheetDrawer::SpreadsheetDrawer() +{ + left_column_width = UI_UNIT_X * 2; + top_row_height = UI_UNIT_Y * 1.1f; + row_height = UI_UNIT_Y; +} + +SpreadsheetDrawer::~SpreadsheetDrawer() = default; + +void SpreadsheetDrawer::draw_top_row_cell(int UNUSED(column_index), + const CellDrawParams &UNUSED(params)) const +{ +} + +void SpreadsheetDrawer::draw_left_column_cell(int UNUSED(row_index), + const CellDrawParams &UNUSED(params)) const +{ +} + +void SpreadsheetDrawer::draw_content_cell(int UNUSED(row_index), + int UNUSED(column_index), + const CellDrawParams &UNUSED(params)) const +{ +} + +int SpreadsheetDrawer::column_width(int UNUSED(column_index)) const +{ + return 5 * UI_UNIT_X; +} + +static void draw_index_column_background(const uint pos, + const ARegion *region, + const SpreadsheetDrawer &drawer) +{ + immUniformThemeColorShade(TH_BACK, 11); + immRecti(pos, 0, region->winy - drawer.top_row_height, drawer.left_column_width, 0); +} + +static void draw_alternating_row_overlay(const uint pos, + const int scroll_offset_y, + const ARegion *region, + const SpreadsheetDrawer &drawer) +{ + immUniformThemeColor(TH_ROW_ALTERNATE); + GPU_blend(GPU_BLEND_ALPHA); + BLI_assert(drawer.row_height > 0); + const int row_pair_height = drawer.row_height * 2; + const int row_top_y = region->winy - drawer.top_row_height - scroll_offset_y % row_pair_height; + for (const int i : IndexRange(region->winy / row_pair_height + 1)) { + int x_left = 0; + int x_right = region->winx; + int y_top = row_top_y - i * row_pair_height - drawer.row_height; + int y_bottom = y_top - drawer.row_height; + y_top = std::min(y_top, region->winy - drawer.top_row_height); + y_bottom = std::min(y_bottom, region->winy - drawer.top_row_height); + immRecti(pos, x_left, y_top, x_right, y_bottom); + } + GPU_blend(GPU_BLEND_NONE); +} + +static void draw_top_row_background(const uint pos, + const ARegion *region, + const SpreadsheetDrawer &drawer) +{ + immUniformThemeColorShade(TH_BACK, 11); + immRecti(pos, 0, region->winy, region->winx, region->winy - drawer.top_row_height); +} + +static void draw_separator_lines(const uint pos, + const int scroll_offset_x, + const ARegion *region, + const SpreadsheetDrawer &drawer) +{ + immUniformThemeColorShade(TH_BACK, -11); + + immBeginAtMost(GPU_PRIM_LINES, drawer.tot_columns * 2 + 4); + + /* Left column line. */ + immVertex2i(pos, drawer.left_column_width, region->winy); + immVertex2i(pos, drawer.left_column_width, 0); + + /* Top row line. */ + immVertex2i(pos, 0, region->winy - drawer.top_row_height); + immVertex2i(pos, region->winx, region->winy - drawer.top_row_height); + + /* Column separator lines. */ + int line_x = drawer.left_column_width - scroll_offset_x; + for (const int column_index : IndexRange(drawer.tot_columns)) { + const int column_width = drawer.column_width(column_index); + line_x += column_width; + if (line_x >= drawer.left_column_width) { + immVertex2i(pos, line_x, region->winy); + immVertex2i(pos, line_x, 0); + } + } + immEnd(); +} + +static void get_visible_rows(const SpreadsheetDrawer &drawer, + const ARegion *region, + const int scroll_offset_y, + int *r_first_row, + int *r_max_visible_rows) +{ + *r_first_row = -scroll_offset_y / drawer.row_height; + *r_max_visible_rows = region->winy / drawer.row_height + 1; +} + +static void draw_left_column_content(const int scroll_offset_y, + const bContext *C, + ARegion *region, + const SpreadsheetDrawer &drawer) +{ + GPU_scissor_test(true); + 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); + int first_row, max_visible_rows; + get_visible_rows(drawer, region, scroll_offset_y, &first_row, &max_visible_rows); + for (const int row_index : IndexRange(first_row, max_visible_rows)) { + if (row_index >= drawer.tot_rows) { + break; + } + CellDrawParams params; + params.block = left_column_block; + params.xmin = 0; + params.ymin = region->winy - drawer.top_row_height - (row_index + 1) * drawer.row_height - + scroll_offset_y; + params.width = drawer.left_column_width; + params.height = drawer.row_height; + drawer.draw_left_column_cell(row_index, params); + } + + UI_block_end(C, left_column_block); + UI_block_draw(C, left_column_block); + + GPU_scissor_test(false); +} + +static void draw_top_row_content(const bContext *C, + ARegion *region, + const SpreadsheetDrawer &drawer, + const int scroll_offset_x) +{ + GPU_scissor_test(true); + GPU_scissor(drawer.left_column_width + 1, + region->winy - drawer.top_row_height, + region->winx - drawer.left_column_width, + drawer.top_row_height); + + uiBlock *first_row_block = UI_block_begin(C, region, __func__, UI_EMBOSS_NONE); + + int left_x = drawer.left_column_width - scroll_offset_x; + for (const int column_index : IndexRange(drawer.tot_columns)) { + const int column_width = drawer.column_width(column_index); + const int right_x = left_x + column_width; + + CellDrawParams params; + params.block = first_row_block; + params.xmin = left_x; + params.ymin = region->winy - drawer.top_row_height; + params.width = column_width; + params.height = drawer.top_row_height; + drawer.draw_top_row_cell(column_index, params); + + left_x = right_x; + } + + UI_block_end(C, first_row_block); + UI_block_draw(C, first_row_block); + + GPU_scissor_test(false); +} + +static void draw_cell_contents(const bContext *C, + ARegion *region, + const SpreadsheetDrawer &drawer, + const int scroll_offset_x, + const int scroll_offset_y) +{ + GPU_scissor_test(true); + GPU_scissor(drawer.left_column_width + 1, + 0, + region->winx - drawer.left_column_width, + region->winy - drawer.top_row_height); + + uiBlock *cells_block = UI_block_begin(C, region, __func__, UI_EMBOSS_NONE); + + int first_row, max_visible_rows; + get_visible_rows(drawer, region, scroll_offset_y, &first_row, &max_visible_rows); + + int left_x = drawer.left_column_width - scroll_offset_x; + for (const int column_index : IndexRange(drawer.tot_columns)) { + const int column_width = drawer.column_width(column_index); + const int right_x = left_x + column_width; + + if (right_x >= drawer.left_column_width && left_x <= region->winx) { + for (const int row_index : IndexRange(first_row, max_visible_rows)) { + if (row_index >= drawer.tot_rows) { + break; + } + + CellDrawParams params; + params.block = cells_block; + params.xmin = left_x; + params.ymin = region->winy - drawer.top_row_height - (row_index + 1) * drawer.row_height - + scroll_offset_y; + params.width = column_width; + params.height = drawer.row_height; + drawer.draw_content_cell(row_index, column_index, params); + } + } + + left_x = right_x; + } + + UI_block_end(C, cells_block); + UI_block_draw(C, cells_block); + + GPU_scissor_test(false); +} + +static void update_view2d_tot_rect(const SpreadsheetDrawer &drawer, + ARegion *region, + const int row_amount) +{ + int column_width_sum = 0; + for (const int column_index : IndexRange(drawer.tot_columns)) { + column_width_sum += drawer.column_width(column_index); + } + + UI_view2d_totRect_set(®ion->v2d, + column_width_sum + drawer.left_column_width, + row_amount * drawer.row_height + drawer.top_row_height); +} + +void draw_spreadsheet_in_region(const bContext *C, + ARegion *region, + const SpreadsheetDrawer &drawer) +{ + update_view2d_tot_rect(drawer, region, drawer.tot_rows); + + UI_ThemeClearColor(TH_BACK); + + View2D *v2d = ®ion->v2d; + const int scroll_offset_y = v2d->cur.ymax; + const int scroll_offset_x = v2d->cur.xmin; + + GPUVertFormat *format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + draw_index_column_background(pos, region, drawer); + draw_alternating_row_overlay(pos, scroll_offset_y, region, drawer); + draw_top_row_background(pos, region, drawer); + draw_separator_lines(pos, scroll_offset_x, region, drawer); + + immUnbindProgram(); + + draw_left_column_content(scroll_offset_y, C, region, drawer); + draw_top_row_content(C, region, drawer, scroll_offset_x); + draw_cell_contents(C, region, drawer, scroll_offset_x, scroll_offset_y); + + rcti scroller_mask; + BLI_rcti_init(&scroller_mask, + drawer.left_column_width, + region->winx, + 0, + region->winy - drawer.top_row_height); + UI_view2d_scrollers_draw(v2d, &scroller_mask); +} + +} // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_draw.hh b/source/blender/editors/space_spreadsheet/spreadsheet_draw.hh new file mode 100644 index 00000000000..6828006f4a1 --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_draw.hh @@ -0,0 +1,60 @@ +/* + * 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_vector.hh" + +struct uiBlock; +struct rcti; +struct bContext; +struct ARegion; + +namespace blender::ed::spreadsheet { + +struct CellDrawParams { + uiBlock *block; + int xmin, ymin; + int width, height; +}; + +class SpreadsheetDrawer { + public: + int left_column_width; + int top_row_height; + int row_height; + int tot_rows = 0; + int tot_columns = 0; + + SpreadsheetDrawer(); + virtual ~SpreadsheetDrawer(); + + virtual void draw_top_row_cell(int column_index, const CellDrawParams ¶ms) const; + + virtual void draw_left_column_cell(int row_index, const CellDrawParams ¶ms) const; + + virtual void draw_content_cell(int row_index, + int column_index, + const CellDrawParams ¶ms) const; + + virtual int column_width(int column_index) const; +}; + +void draw_spreadsheet_in_region(const bContext *C, + ARegion *region, + const SpreadsheetDrawer &spreadsheet_drawer); + +} // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_from_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_from_geometry.cc new file mode 100644 index 00000000000..fa1a3549b86 --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_from_geometry.cc @@ -0,0 +1,437 @@ +/* + * 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 "UI_interface.h" +#include "UI_resources.h" + +#include "BLF_api.h" + +#include "BKE_context.h" +#include "BKE_editmesh.h" +#include "BKE_mesh_wrapper.h" +#include "BKE_modifier.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" + +namespace blender::ed::spreadsheet { + +using blender::bke::ReadAttribute; +using blender::bke::ReadAttributePtr; + +class AttributeColumn { + public: + std::string name; + int width; + + AttributeColumn(std::string column_name) : name(std::move(column_name)) + { + /* Compute the column width based on its name. */ + const int fontid = UI_style_get()->widget.uifont_id; + const int header_name_padding = UI_UNIT_X; + const int minimum_column_width = 3 * UI_UNIT_X; + /* Use a consistent font size for the width calculation. */ + BLF_size(fontid, 11 * U.pixelsize, U.dpi); + const int text_width = BLF_width(fontid, name.data(), name.size()); + width = std::max(text_width + header_name_padding, minimum_column_width); + } + + virtual ~AttributeColumn() = default; + virtual void draw(const int index, const CellDrawParams ¶ms) const = 0; +}; + +class GeometryAttributeSpreadsheetDrawer : public SpreadsheetDrawer { + private: + /* Contains resources that are used during drawing. They will be freed automatically. */ + std::unique_ptr<ResourceCollector> resources_; + /* Information about how to draw the individual columns. */ + Vector<std::unique_ptr<AttributeColumn>> columns_; + /* This is used to filter the selected rows. The referenced data lives at least as long as the + * resource collector above. */ + Span<int64_t> visible_rows_; + + public: + GeometryAttributeSpreadsheetDrawer(std::unique_ptr<ResourceCollector> resources, + Vector<std::unique_ptr<AttributeColumn>> columns, + Span<int64_t> visible_rows, + const int domain_size) + : resources_(std::move(resources)), columns_(std::move(columns)), visible_rows_(visible_rows) + { + tot_columns = columns_.size(); + tot_rows = visible_rows.size(); + + /* Compute index column width based on number of digits. */ + const int fontid = UI_style_get()->widget.uifont_id; + left_column_width = std::to_string(std::max(domain_size - 1, 0)).size() * + BLF_width(fontid, "0", 1) + + UI_UNIT_X * 0.75; + } + + void draw_top_row_cell(int column_index, const CellDrawParams ¶ms) const final + { + uiBut *but = uiDefIconTextBut(params.block, + UI_BTYPE_LABEL, + 0, + ICON_NONE, + columns_[column_index]->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 = visible_rows_[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 = visible_rows_[row_index]; + columns_[column_index]->draw(real_index, params); + } + + int column_width(int column_index) const final + { + return columns_[column_index]->width; + } +}; + +/* Utility to make writing column drawing code more concise. */ +template<typename DrawF> class CustomAttributeColumn : public AttributeColumn { + private: + DrawF draw_; + + public: + CustomAttributeColumn(std::string attribute_name, DrawF draw) + : AttributeColumn(std::move(attribute_name)), draw_(std::move(draw)) + { + } + + void draw(const int index, const CellDrawParams ¶ms) const final + { + draw_(index, params); + } +}; + +template<typename DrawF> +std::unique_ptr<CustomAttributeColumn<DrawF>> create_attribute_column(std::string attribute_name, + DrawF draw) +{ + return std::make_unique<CustomAttributeColumn<DrawF>>(std::move(attribute_name), + std::move(draw)); +} + +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 draw_float_in_cell(const CellDrawParams ¶ms, const float value) +{ + 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); +} + +static void draw_int_in_cell(const CellDrawParams ¶ms, const int value) +{ + 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); +} + +static void draw_bool_in_cell(const CellDrawParams ¶ms, const bool value) +{ + 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); +} + +static void add_columns_for_attribute(const ReadAttribute *attribute, + const StringRefNull attribute_name, + Vector<std::unique_ptr<AttributeColumn>> &columns) +{ + const CustomDataType data_type = attribute->custom_data_type(); + switch (data_type) { + case CD_PROP_FLOAT: { + columns.append(create_attribute_column(attribute_name, + [attribute](int index, const CellDrawParams ¶ms) { + float value; + attribute->get(index, &value); + draw_float_in_cell(params, 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( + create_attribute_column(name, [attribute, i](int index, const CellDrawParams ¶ms) { + float2 value; + attribute->get(index, &value); + draw_float_in_cell(params, 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( + create_attribute_column(name, [attribute, i](int index, const CellDrawParams ¶ms) { + float3 value; + attribute->get(index, &value); + draw_float_in_cell(params, 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( + create_attribute_column(name, [attribute, i](int index, const CellDrawParams ¶ms) { + Color4f value; + attribute->get(index, &value); + draw_float_in_cell(params, value[i]); + })); + } + break; + } + case CD_PROP_INT32: { + columns.append(create_attribute_column(attribute_name, + [attribute](int index, const CellDrawParams ¶ms) { + int value; + attribute->get(index, &value); + draw_int_in_cell(params, value); + })); + break; + } + case CD_PROP_BOOL: { + columns.append(create_attribute_column(attribute_name, + [attribute](int index, const CellDrawParams ¶ms) { + bool value; + attribute->get(index, &value); + draw_bool_in_cell(params, value); + })); + break; + } + default: + break; + } +} + +static GeometrySet get_display_geometry_set(Object *object_eval) +{ + GeometrySet geometry_set; + if (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; + } + } + return geometry_set; +} + +static Span<int64_t> filter_visible_mesh_vertex_rows(const bContext *C, + Object *object_eval, + const MeshComponent *component, + 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. */ + for (const int i_eval : IndexRange(mesh_eval->totvert)) { + const int i_orig = orig_indices[i_eval]; + if (i_orig >= 0 && i_orig < bm->totvert) { + BMVert *vert = bm->vtable[i_orig]; + if (BM_elem_flag_test(vert, BM_ELEM_SELECT)) { + visible_rows.append(i_eval); + } + } + } + } + else if (mesh_eval->totvert == bm->totvert) { + /* Use a simple heuristic to match original vertices to evaluated ones. */ + for (const int i : IndexRange(mesh_eval->totvert)) { + BMVert *vert = bm->vtable[i]; + if (BM_elem_flag_test(vert, BM_ELEM_SELECT)) { + visible_rows.append(i); + } + } + } + /* 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(ATTR_DOMAIN_POINT); + return IndexRange(domain_size).as_span(); +} + +std::unique_ptr<SpreadsheetDrawer> spreadsheet_drawer_from_geometry_attributes(const bContext *C, + Object *object_eval) +{ + /* Create a resource collector that owns stuff that needs to live until drawing is done. */ + std::unique_ptr<ResourceCollector> resources = std::make_unique<ResourceCollector>(); + GeometrySet &geometry_set = resources->add_value(get_display_geometry_set(object_eval), + "geometry set"); + + const AttributeDomain domain = ATTR_DOMAIN_POINT; + const GeometryComponentType component_type = GeometryComponentType::Mesh; + const GeometryComponent *component = geometry_set.get_component_for_read(component_type); + if (component == nullptr) { + return {}; + } + + Vector<std::string> attribute_names = get_sorted_attribute_names_to_display(*component, domain); + + Vector<std::unique_ptr<AttributeColumn>> 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); + } + + /* The filter below only works for mesh vertices currently. */ + BLI_assert(domain == ATTR_DOMAIN_POINT && component_type == GeometryComponentType::Mesh); + Span<int64_t> visible_rows = filter_visible_mesh_vertex_rows( + C, object_eval, static_cast<const MeshComponent *>(component), *resources); + + const int domain_size = component->attribute_domain_size(domain); + return std::make_unique<GeometryAttributeSpreadsheetDrawer>( + std::move(resources), std::move(columns), visible_rows, domain_size); +} + +} // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_from_geometry.hh b/source/blender/editors/space_spreadsheet/spreadsheet_from_geometry.hh new file mode 100644 index 00000000000..32d18254439 --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_from_geometry.hh @@ -0,0 +1,32 @@ +/* + * 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 "BKE_geometry_set.hh" + +#include "BLI_resource_collector.hh" + +#include "spreadsheet_draw.hh" + +struct bContext; + +namespace blender::ed::spreadsheet { + +std::unique_ptr<SpreadsheetDrawer> spreadsheet_drawer_from_geometry_attributes( + const bContext *C, Object *object_eval); + +} // namespace blender::ed::spreadsheet diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 98f652b04f8..b18add0a826 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -1852,10 +1852,21 @@ typedef struct SpaceSpreadsheet { char link_flag; char _pad0[6]; /* End 'SpaceLink' header. */ + + struct ID *pinned_id; + + /* eSpaceSpreadsheet_FilterFlag. */ + uint8_t filter_flag; + + char _pad1[7]; } SpaceSpreadsheet; /** \} */ +typedef enum eSpaceSpreadsheet_FilterFlag { + SPREADSHEET_FILTER_SELECTED_ONLY = (1 << 0), +} eSpaceSpreadsheet_FilterFlag; + /* -------------------------------------------------------------------- */ /** \name Space Defines (eSpace_Type) * \{ */ diff --git a/source/blender/makesrna/intern/rna_screen.c b/source/blender/makesrna/intern/rna_screen.c index 58e446381ad..6cf1d7a923b 100644 --- a/source/blender/makesrna/intern/rna_screen.c +++ b/source/blender/makesrna/intern/rna_screen.c @@ -201,10 +201,6 @@ static const EnumPropertyItem *rna_Area_ui_type_itemf(bContext *C, if (ELEM(item_from->value, SPACE_TOPBAR, SPACE_STATUSBAR)) { continue; } - /* Hide spreadsheet editor until we want to expose it in the ui. */ - if (item_from->value == SPACE_SPREADSHEET) { - continue; - } SpaceType *st = item_from->identifier[0] ? BKE_spacetype_from_id(item_from->value) : NULL; int totitem_prev = totitem; diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index affb17c7d8c..574c4e98819 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -2983,6 +2983,14 @@ static void rna_SpaceFileBrowser_browse_mode_update(Main *UNUSED(bmain), ED_area_tag_refresh(area); } +static void rna_SpaceSpreadsheet_pinned_id_set(PointerRNA *ptr, + PointerRNA value, + struct ReportList *UNUSED(reports)) +{ + SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)ptr->data; + sspreadsheet->pinned_id = value.data; +} + #else static const EnumPropertyItem dt_uv_items[] = { @@ -7185,10 +7193,23 @@ static void rna_def_space_clip(BlenderRNA *brna) static void rna_def_space_spreadsheet(BlenderRNA *brna) { + PropertyRNA *prop; StructRNA *srna; srna = RNA_def_struct(brna, "SpaceSpreadsheet", "Space"); RNA_def_struct_ui_text(srna, "Space Spreadsheet", "Spreadsheet space data"); + + prop = RNA_def_property(srna, "pinned_id", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_pointer_funcs(prop, NULL, "rna_SpaceSpreadsheet_pinned_id_set", NULL, NULL); + RNA_def_property_ui_text(prop, "Pinned ID", "Data-block whose values are displayed"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); + + prop = RNA_def_property(srna, "show_only_selected", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "filter_flag", SPREADSHEET_FILTER_SELECTED_ONLY); + RNA_def_property_ui_text( + prop, "Show Only Selected", "Only include rows that correspond to selected elements"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL); } void RNA_def_space(BlenderRNA *brna) |