Spreadsheet: new spreadsheet editor

This implements the MVP for the new spreadsheet editor (T85879). The functionality
is still very limited, but it proved to be useful already. A more complete picture
of where we want to go with the new editor can be found in T86279.

Supported features:
* Show point attributes of evaluated meshes (no original data, no other domains,
  no other geometry types, yet). Since only meshes are supported right now, the
  output of the Point Distribute is not shown, because it is a point cloud.
* Only show data for selected vertices when the mesh is in edit mode.
  Different parts of Blender keep track of selection state and original-indices with
  varying degrees of success. Therefore, when the selected-only filter is used, the
  result might be a bit confusing when using some modifiers or nodes. This will
  be improved in the future.
* All data is readonly. Since only evaluated data is displayed currently, it has to
  be readonly. However, this is not an inherent limitation of the spreadsheet editor.
  In the future editable data will be displayed as well.

Some boilerplate code for the new editor has been committed before in
rB9cb5f0a2282a7a84f7f8636b43a32bdc04b51cd5.

It would be good to let the spreadsheet editor mature for a couple of weeks as part
of the geometry nodes project. Then other modules are invited to show their own data
in the new editor!

Differential Revision: https://developer.blender.org/D10566
This commit is contained in:
Jacques Lucke 2021-03-10 11:34:36 +01:00
parent f247a14468
commit 3dab6f8b7b
13 changed files with 1001 additions and 6 deletions

View File

@ -45,6 +45,7 @@ _modules = [
"rigidbody",
"screen_play_rendered_anim",
"sequencer",
"spreadsheet",
"userpref",
"uvcalc_follow_active",
"uvcalc_lightmap",

View File

@ -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)

View File

@ -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,

View File

@ -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;
}

View File

@ -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
)

View File

@ -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)
{
UI_ThemeClearColor(TH_BACK);
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)
{
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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)
* \{ */

View File

@ -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;

View File

@ -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)