Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--release/scripts/startup/bl_operators/__init__.py1
-rw-r--r--release/scripts/startup/bl_operators/spreadsheet.py52
-rw-r--r--release/scripts/startup/bl_ui/space_spreadsheet.py13
-rw-r--r--source/blender/blenkernel/intern/screen.c11
-rw-r--r--source/blender/editors/space_spreadsheet/CMakeLists.txt9
-rw-r--r--source/blender/editors/space_spreadsheet/space_spreadsheet.cc52
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_draw.cc304
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_draw.hh60
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_from_geometry.cc437
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_from_geometry.hh32
-rw-r--r--source/blender/makesdna/DNA_space_types.h11
-rw-r--r--source/blender/makesrna/intern/rna_screen.c4
-rw-r--r--source/blender/makesrna/intern/rna_space.c21
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(&region->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(&region->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 = &region->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 &params) const;
+
+ virtual void draw_left_column_cell(int row_index, const CellDrawParams &params) const;
+
+ virtual void draw_content_cell(int row_index,
+ int column_index,
+ const CellDrawParams &params) 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 &params) 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 &params) 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 &params) 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 &params) 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 &params) 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 &params, 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 &params, 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 &params, 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 &params) {
+ 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 &params) {
+ 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 &params) {
+ 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 &params) {
+ 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 &params) {
+ 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 &params) {
+ 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)