Spreadsheet: support showing data of specific node

Previously, the spreadsheet editor could only show data of the original
and of the final evaluated object. Now it is possible to show the data
at some intermediate stages too.

For that the mode has to be set to "Node" in the spreadsheet editor.
Furthermore, the preview of a specific node has to be activated by
clicking the new icon in the header of geometry nodes.

The exact ui of this feature might be refined in upcoming commits.
It is already very useful for debugging node groups in it's current
state though.

Differential Revision: https://developer.blender.org/D10875
This commit is contained in:
Jacques Lucke 2021-04-08 17:35:06 +02:00
parent 5e77ff79cc
commit c6ff722a1f
Notes: blender-bot 2023-02-14 10:04:50 +01:00
Referenced by issue #87417, Crash when adding an object to asset with geometry nodes modifier
Referenced by issue #86584, Active node context data for spreadsheet
18 changed files with 338 additions and 21 deletions

View File

@ -141,6 +141,12 @@ class GeometryComponent {
/* The returned component should be of the same type as the type this is called on. */
virtual GeometryComponent *copy() const = 0;
/* Direct data is everything except for instances of objects/collections.
* If this returns true, the geometry set can be cached and is still valid after e.g. modifier
* evaluation ends. Instances can only be valid as long as the data they instance is valid. */
virtual bool owns_direct_data() const = 0;
virtual void ensure_owns_direct_data() = 0;
void user_add() const;
void user_remove() const;
bool is_mutable() const;
@ -315,6 +321,8 @@ struct GeometrySet {
void clear();
void ensure_owns_direct_data();
/* Utility methods for creation. */
static GeometrySet create_with_mesh(
Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
@ -374,6 +382,9 @@ class MeshComponent : public GeometryComponent {
bool is_empty() const final;
bool owns_direct_data() const override;
void ensure_owns_direct_data() override;
static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_MESH;
private:
@ -404,6 +415,9 @@ class PointCloudComponent : public GeometryComponent {
bool is_empty() const final;
bool owns_direct_data() const override;
void ensure_owns_direct_data() override;
static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_POINT_CLOUD;
private:
@ -444,6 +458,9 @@ class InstancesComponent : public GeometryComponent {
bool is_empty() const final;
bool owns_direct_data() const override;
void ensure_owns_direct_data() override;
static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_INSTANCES;
};
@ -466,5 +483,8 @@ class VolumeComponent : public GeometryComponent {
const Volume *get_for_read() const;
Volume *get_for_write();
bool owns_direct_data() const override;
void ensure_owns_direct_data() override;
static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_VOLUME;
};

View File

@ -34,6 +34,7 @@ struct Base;
struct BoundBox;
struct Curve;
struct Depsgraph;
struct GeometrySet;
struct GpencilModifierData;
struct HookGpencilModifierData;
struct HookModifierData;
@ -69,6 +70,8 @@ void BKE_object_free_curve_cache(struct Object *ob);
void BKE_object_free_derived_caches(struct Object *ob);
void BKE_object_free_caches(struct Object *object);
void BKE_object_set_preview_geometry_set(struct Object *ob, struct GeometrySet *geometry_set);
void BKE_object_modifier_hook_reset(struct Object *ob, struct HookModifierData *hmd);
void BKE_object_modifier_gpencil_hook_reset(struct Object *ob,
struct HookGpencilModifierData *hmd);

View File

@ -108,6 +108,18 @@ bool InstancesComponent::is_empty() const
return transforms_.size() == 0;
}
bool InstancesComponent::owns_direct_data() const
{
/* The object and collection instances are not direct data. Instance transforms are direct data
* and are always owned. Therefore, instance components always own all their direct data. */
return true;
}
void InstancesComponent::ensure_owns_direct_data()
{
BLI_assert(this->is_mutable());
}
static blender::Array<int> generate_unique_instance_ids(Span<int> original_ids)
{
using namespace blender;

View File

@ -157,6 +157,20 @@ bool MeshComponent::is_empty() const
return mesh_ == nullptr;
}
bool MeshComponent::owns_direct_data() const
{
return ownership_ == GeometryOwnershipType::Owned;
}
void MeshComponent::ensure_owns_direct_data()
{
BLI_assert(this->is_mutable());
if (ownership_ != GeometryOwnershipType::Owned) {
mesh_ = BKE_mesh_copy_for_eval(mesh_, false);
ownership_ = GeometryOwnershipType::Owned;
}
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -107,6 +107,20 @@ bool PointCloudComponent::is_empty() const
return pointcloud_ == nullptr;
}
bool PointCloudComponent::owns_direct_data() const
{
return ownership_ == GeometryOwnershipType::Owned;
}
void PointCloudComponent::ensure_owns_direct_data()
{
BLI_assert(this->is_mutable());
if (ownership_ != GeometryOwnershipType::Owned) {
pointcloud_ = BKE_pointcloud_copy_for_eval(pointcloud_, false);
ownership_ = GeometryOwnershipType::Owned;
}
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -97,4 +97,18 @@ Volume *VolumeComponent::get_for_write()
return volume_;
}
bool VolumeComponent::owns_direct_data() const
{
return ownership_ == GeometryOwnershipType::Owned;
}
void VolumeComponent::ensure_owns_direct_data()
{
BLI_assert(this->is_mutable());
if (ownership_ != GeometryOwnershipType::Owned) {
volume_ = BKE_volume_copy_for_eval(volume_, false);
ownership_ = GeometryOwnershipType::Owned;
}
}
/** \} */

View File

@ -207,6 +207,19 @@ void GeometrySet::clear()
components_.clear();
}
/* Make sure that the geometry can be cached. This does not ensure ownership of object/collection
* instances. */
void GeometrySet::ensure_owns_direct_data()
{
for (GeometryComponentType type : components_.keys()) {
const GeometryComponent *component = this->get_component_for_read(type);
if (!component->owns_direct_data()) {
GeometryComponent &component_for_write = this->get_component_for_write(type);
component_for_write.ensure_owns_direct_data();
}
}
}
/* Returns a read-only mesh or null. */
const Mesh *GeometrySet::get_mesh_for_read() const
{

View File

@ -1756,6 +1756,10 @@ void BKE_object_free_derived_caches(Object *ob)
BKE_geometry_set_free(ob->runtime.geometry_set_eval);
ob->runtime.geometry_set_eval = NULL;
}
if (ob->runtime.geometry_set_preview != NULL) {
BKE_geometry_set_free(ob->runtime.geometry_set_preview);
ob->runtime.geometry_set_preview = NULL;
}
}
void BKE_object_free_caches(Object *object)
@ -1806,6 +1810,18 @@ void BKE_object_free_caches(Object *object)
}
}
/* Can be called from multiple threads. */
void BKE_object_set_preview_geometry_set(Object *ob, struct GeometrySet *geometry_set)
{
static ThreadMutex mutex = BLI_MUTEX_INITIALIZER;
BLI_mutex_lock(&mutex);
if (ob->runtime.geometry_set_preview != NULL) {
BKE_geometry_set_free(ob->runtime.geometry_set_preview);
}
ob->runtime.geometry_set_preview = geometry_set;
BLI_mutex_unlock(&mutex);
}
/**
* Actual check for internal data, not context or flags.
*/

View File

@ -1404,6 +1404,28 @@ static void node_draw_basis(const bContext *C,
"");
UI_block_emboss_set(node->block, UI_EMBOSS);
}
if (ntree->type == NTREE_GEOMETRY) {
/* Active preview toggle. */
iconofs -= iconbutw;
UI_block_emboss_set(node->block, UI_EMBOSS_NONE);
int icon = (node->flag & NODE_ACTIVE_PREVIEW) ? ICON_RESTRICT_VIEW_OFF : ICON_RESTRICT_VIEW_ON;
uiBut *but = uiDefIconBut(node->block,
UI_BTYPE_BUT_TOGGLE,
0,
icon,
iconofs,
rct->ymax - NODE_DY,
iconbutw,
UI_UNIT_Y,
nullptr,
0,
0,
0,
0,
"Show this node's geometry output in the spreadsheet in Node mode");
UI_but_func_set(but, node_toggle_button_cb, node, (void *)"NODE_OT_active_preview_toggle");
UI_block_emboss_set(node->block, UI_EMBOSS);
}
node_add_error_message_button(C, *ntree, *node, *rct, iconofs);

View File

@ -41,6 +41,7 @@
#include "BKE_node.h"
#include "BKE_report.h"
#include "BKE_scene.h"
#include "BKE_workspace.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_build.h"
@ -1316,6 +1317,7 @@ static int node_duplicate_exec(bContext *C, wmOperator *op)
nodeSetSelected(node, false);
node->flag &= ~(NODE_ACTIVE | NODE_ACTIVE_TEXTURE);
nodeSetSelected(newnode, true);
newnode->flag &= ~NODE_ACTIVE_PREVIEW;
do_tag_update |= (do_tag_update || node_connected_to_output(bmain, ntree, newnode));
}
@ -1692,6 +1694,55 @@ void NODE_OT_hide_socket_toggle(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
static void disable_active_preview_on_all_nodes(bNodeTree *ntree)
{
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
node->flag &= ~NODE_ACTIVE_PREVIEW;
}
}
static int node_active_preview_toggle_exec(bContext *C, wmOperator *UNUSED(op))
{
SpaceNode *snode = CTX_wm_space_node(C);
Main *bmain = CTX_data_main(C);
bNodeTree *ntree = snode->edittree;
disable_active_preview_on_all_nodes(ntree);
bNode *active_node = nodeGetActive(ntree);
active_node->flag |= NODE_ACTIVE_PREVIEW;
/* Tag for update, so that dependent objects are reevaluated. This is necessary when a
* spreadsheet editor displays data from a node. */
LISTBASE_FOREACH (wmWindow *, window, &((wmWindowManager *)bmain->wm.first)->windows) {
bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook);
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
if (area->spacetype == SPACE_SPREADSHEET) {
SpaceSpreadsheet *sspreadsheet = area->spacedata.first;
if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_NODE) {
DEG_id_tag_update(&ntree->id, ID_RECALC_COPY_ON_WRITE);
ED_area_tag_redraw(area);
}
}
}
}
return OPERATOR_FINISHED;
}
void NODE_OT_active_preview_toggle(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Toggle Active Preview";
ot->description = "Toggle active preview state of node";
ot->idname = "NODE_OT_active_preview_toggle";
/* callbacks */
ot->exec = node_active_preview_toggle_exec;
ot->poll = ED_operator_node_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ****************** Mute operator *********************** */
static int node_mute_exec(bContext *C, wmOperator *UNUSED(op))

View File

@ -273,6 +273,7 @@ void NODE_OT_hide_toggle(struct wmOperatorType *ot);
void NODE_OT_hide_socket_toggle(struct wmOperatorType *ot);
void NODE_OT_preview_toggle(struct wmOperatorType *ot);
void NODE_OT_options_toggle(struct wmOperatorType *ot);
void NODE_OT_active_preview_toggle(struct wmOperatorType *ot);
void NODE_OT_node_copy_color(struct wmOperatorType *ot);
void NODE_OT_read_viewlayers(struct wmOperatorType *ot);

View File

@ -57,6 +57,7 @@ void node_operatortypes(void)
WM_operatortype_append(NODE_OT_preview_toggle);
WM_operatortype_append(NODE_OT_options_toggle);
WM_operatortype_append(NODE_OT_hide_socket_toggle);
WM_operatortype_append(NODE_OT_active_preview_toggle);
WM_operatortype_append(NODE_OT_node_copy_color);
WM_operatortype_append(NODE_OT_duplicate);

View File

@ -200,25 +200,7 @@ static GeometrySet get_display_geometry_set(SpaceSpreadsheet *sspreadsheet,
const GeometryComponentType used_component_type)
{
GeometrySet geometry_set;
if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_FINAL) {
if (used_component_type == GEO_COMPONENT_TYPE_MESH && object_eval->mode == OB_MODE_EDIT) {
Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(object_eval, false);
if (mesh == nullptr) {
return geometry_set;
}
BKE_mesh_wrapper_ensure_mdata(mesh);
MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
mesh_component.replace(mesh, GeometryOwnershipType::ReadOnly);
mesh_component.copy_vertex_group_names_from_object(*object_eval);
}
else {
if (object_eval->runtime.geometry_set_eval != nullptr) {
/* This does not copy the geometry data itself. */
geometry_set = *object_eval->runtime.geometry_set_eval;
}
}
}
else {
if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_ORIGINAL) {
Object *object_orig = DEG_get_original_object(object_eval);
if (object_orig->type == OB_MESH) {
MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
@ -247,6 +229,30 @@ static GeometrySet get_display_geometry_set(SpaceSpreadsheet *sspreadsheet,
pointcloud_component.replace(pointcloud, GeometryOwnershipType::ReadOnly);
}
}
else {
if (used_component_type == GEO_COMPONENT_TYPE_MESH && object_eval->mode == OB_MODE_EDIT) {
Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(object_eval, false);
if (mesh == nullptr) {
return geometry_set;
}
BKE_mesh_wrapper_ensure_mdata(mesh);
MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
mesh_component.replace(mesh, GeometryOwnershipType::ReadOnly);
mesh_component.copy_vertex_group_names_from_object(*object_eval);
}
else {
if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_NODE) {
if (object_eval->runtime.geometry_set_preview != nullptr) {
geometry_set = *object_eval->runtime.geometry_set_preview;
}
}
else if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_FINAL) {
if (object_eval->runtime.geometry_set_eval != nullptr) {
geometry_set = *object_eval->runtime.geometry_set_eval;
}
}
}
}
return geometry_set;
}
@ -377,7 +383,7 @@ static Span<int64_t> filter_mesh_elements_by_selection(const bContext *C,
static GeometryComponentType get_display_component_type(const bContext *C, Object *object_eval)
{
SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C);
if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_FINAL) {
if (sspreadsheet->object_eval_state != SPREADSHEET_OBJECT_EVAL_STATE_ORIGINAL) {
return (GeometryComponentType)sspreadsheet->geometry_component_type;
}
if (object_eval->type == OB_POINTCLOUD) {

View File

@ -348,6 +348,8 @@ typedef struct bNode {
* composite out nodes when editing tree
*/
#define NODE_DO_OUTPUT_RECALC (1 << 17)
/* A preview for the data in this node can be displayed in the spreadsheet editor. */
#define NODE_ACTIVE_PREVIEW (1 << 18)
/* node->update */
/* XXX NODE_UPDATE is a generic update flag. More fine-grained updates

View File

@ -168,6 +168,11 @@ typedef struct Object_Runtime {
*/
struct GeometrySet *geometry_set_eval;
/**
* Data from this geometry set is previewed in the spreadsheet editor.
*/
struct GeometrySet *geometry_set_preview;
/**
* Mesh structure created during object evaluation.
* It has deformation only modifiers applied on it.

View File

@ -1889,6 +1889,7 @@ typedef enum eSpaceSpreadsheet_FilterFlag {
typedef enum eSpaceSpreadsheet_ObjectEvalState {
SPREADSHEET_OBJECT_EVAL_STATE_FINAL = 0,
SPREADSHEET_OBJECT_EVAL_STATE_ORIGINAL = 1,
SPREADSHEET_OBJECT_EVAL_STATE_NODE = 2,
} eSpaceSpreadsheet_Context;
/* -------------------------------------------------------------------- */

View File

@ -7365,6 +7365,11 @@ static void rna_def_space_spreadsheet(BlenderRNA *brna)
ICON_NONE,
"Original",
"Use data from original object without any modifiers applied"},
{SPREADSHEET_OBJECT_EVAL_STATE_NODE,
"NODE",
ICON_NONE,
"Node",
"Use data from the first geometry output of the node tagged for preview"},
{0, NULL, 0, NULL, NULL},
};

View File

@ -43,17 +43,22 @@
#include "DNA_pointcloud_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "DNA_windowmanager_types.h"
#include "BKE_customdata.h"
#include "BKE_global.h"
#include "BKE_idprop.h"
#include "BKE_lib_query.h"
#include "BKE_main.h"
#include "BKE_mesh.h"
#include "BKE_modifier.h"
#include "BKE_node_ui_storage.hh"
#include "BKE_object.h"
#include "BKE_pointcloud.h"
#include "BKE_screen.h"
#include "BKE_simulation.h"
#include "BKE_workspace.h"
#include "BLO_read_write.h"
@ -1088,6 +1093,110 @@ static void reset_tree_ui_storage(Span<const blender::nodes::NodeTreeRef *> tree
}
}
static const DTreeContext *find_derived_tree_context_that_matches_tree_path(
const SpaceNode &snode, const DerivedNodeTree &tree)
{
const DTreeContext &root_context = tree.root_context();
bNodeTree *root_tree_eval = root_context.tree().btree();
bNodeTree *root_tree_orig = (bNodeTree *)DEG_get_original_id(&root_tree_eval->id);
if (snode.nodetree != root_tree_orig) {
return nullptr;
}
const DTreeContext *current_context = &root_context;
bool is_first = true;
LISTBASE_FOREACH (const bNodeTreePath *, path, &snode.treepath) {
if (is_first) {
is_first = false;
continue;
}
StringRef parent_node_name = path->node_name;
const NodeTreeRef &tree_ref = current_context->tree();
const NodeRef *parent_node_ref = nullptr;
for (const NodeRef *node_ref : tree_ref.nodes()) {
if (node_ref->name() == parent_node_name) {
parent_node_ref = node_ref;
break;
}
}
if (parent_node_ref == nullptr) {
return nullptr;
}
current_context = current_context->child_context(*parent_node_ref);
if (current_context == nullptr) {
return nullptr;
}
}
return current_context;
}
static DNode find_active_preview_node_in_node_editor(const SpaceNode &snode,
const DerivedNodeTree &tree)
{
const DTreeContext *context = find_derived_tree_context_that_matches_tree_path(snode, tree);
if (context == nullptr) {
return {};
}
const NodeTreeRef &tree_ref = context->tree();
for (const NodeRef *node_ref : tree_ref.nodes()) {
if (node_ref->bnode()->flag & NODE_ACTIVE_PREVIEW) {
return {context, node_ref};
}
}
return {};
}
static DNode find_active_preview_node_in_all_node_editors(Depsgraph *depsgraph,
const DerivedNodeTree &tree)
{
Main *bmain = DEG_get_bmain(depsgraph);
wmWindowManager *wm = (wmWindowManager *)bmain->wm.first;
LISTBASE_FOREACH (wmWindow *, window, &wm->windows) {
bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook);
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
SpaceLink *sl = (SpaceLink *)area->spacedata.first;
if (sl->spacetype != SPACE_NODE) {
continue;
}
SpaceNode *snode = (SpaceNode *)sl;
DNode preview_node = find_active_preview_node_in_node_editor(*snode, tree);
if (!preview_node) {
continue;
}
return preview_node;
}
}
return {};
}
static DSocket find_preview_socket_in_all_node_editors(Depsgraph *depsgraph,
const DerivedNodeTree &tree)
{
DNode preview_node = find_active_preview_node_in_all_node_editors(depsgraph, tree);
if (!preview_node) {
return {};
}
for (const SocketRef *socket : preview_node->outputs()) {
if (socket->bsocket()->type == SOCK_GEOMETRY) {
return {preview_node.context(), socket};
}
}
for (const SocketRef *socket : preview_node->inputs()) {
if (socket->bsocket()->type == SOCK_GEOMETRY &&
(socket->bsocket()->flag & SOCK_MULTI_INPUT) == 0) {
return {preview_node.context(), socket};
}
}
return {};
}
static void log_preview_socket_value(const Span<GPointer> values, Object *object)
{
GeometrySet geometry_set = *(const GeometrySet *)values[0].get();
geometry_set.ensure_owns_direct_data();
BKE_object_set_preview_geometry_set(object, new GeometrySet(std::move(geometry_set)));
}
/**
* Evaluate a node group to compute the output geometry.
* Currently, this uses a fairly basic and inefficient algorithm that might compute things more
@ -1143,6 +1252,14 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree,
Vector<DInputSocket> group_outputs;
group_outputs.append({root_context, &socket_to_compute});
const DSocket preview_socket = find_preview_socket_in_all_node_editors(ctx->depsgraph, tree);
auto log_socket_value = [&](const DSocket socket, const Span<GPointer> values) {
if (socket == preview_socket && nmd->modifier.flag & eModifierFlag_Active) {
log_preview_socket_value(values, ctx->object);
}
};
GeometryNodesEvaluator evaluator{group_inputs,
group_outputs,
mf_by_node,
@ -1150,7 +1267,7 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree,
ctx->object,
(ModifierData *)nmd,
ctx->depsgraph,
{}};
log_socket_value};
Vector<GMutablePointer> results = evaluator.execute();
BLI_assert(results.size() == 1);