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:
authorHans Goudey <h.goudey@me.com>2021-02-17 02:15:08 +0300
committerHans Goudey <h.goudey@me.com>2021-02-17 02:15:08 +0300
commit461d4fc1aae200a6310a254b6e7c08070d9e94a7 (patch)
treef2ac642a29497855d879a022bc249a242a05b666
parentc9c4802c1c26d1125d9bb41ff9187b61e167cc42 (diff)
Geometry Nodes: Node error messages
This patch adds icons to the right side of nodes when they encounter a a problem. When hovered, a tooltip displays describing the encountered while evaluating the node. Some examples are: attribute doesn't exist, mesh has no faces, incorrect attribute type, etc. Exposing more messages to the system will be an ongoing process. Multiple warnings per node are supported. The system is implemented somewhat generically so that the basic structure can also be used to store more information from evaluation for the interface, like a list of available attributes. Currently the messages are just button tooltips. They could be styled differently in the future. Another limitation is that every instance of a node group in a parent node tree will have the same error messages, the "evaluation context" used to decide when to display the tooltips must be extended to support node tree paths. Differential Revision: https://developer.blender.org/D10290
-rw-r--r--source/blender/blenkernel/BKE_node_ui_storage.hh95
-rw-r--r--source/blender/blenkernel/CMakeLists.txt1
-rw-r--r--source/blender/blenkernel/intern/node.cc10
-rw-r--r--source/blender/blenkernel/intern/node_ui_storage.cc104
-rw-r--r--source/blender/editors/space_node/node_draw.cc145
-rw-r--r--source/blender/makesdna/DNA_node_types.h3
-rw-r--r--source/blender/modifiers/intern/MOD_nodes.cc31
-rw-r--r--source/blender/nodes/NOD_geometry_exec.hh16
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc4
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_instance.cc6
-rw-r--r--source/blender/nodes/intern/node_geometry_exec.cc37
12 files changed, 447 insertions, 7 deletions
diff --git a/source/blender/blenkernel/BKE_node_ui_storage.hh b/source/blender/blenkernel/BKE_node_ui_storage.hh
new file mode 100644
index 00000000000..0b8ef60a603
--- /dev/null
+++ b/source/blender/blenkernel/BKE_node_ui_storage.hh
@@ -0,0 +1,95 @@
+/*
+ * 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_hash.hh"
+#include "BLI_map.hh"
+#include "BLI_session_uuid.h"
+
+#include "DNA_ID.h"
+#include "DNA_modifier_types.h"
+#include "DNA_session_uuid_types.h"
+
+struct bNode;
+struct bNodeTree;
+struct Object;
+struct ModifierData;
+
+using blender::Map;
+
+/**
+ * Contains the context necessary to determine when to display settings for a certain node tree
+ * that may be used for multiple modifiers and objects. The object name and modifier session UUID
+ * are used instead of pointers because they are re-allocated between evaluations.
+ *
+ * \note This does not yet handle the context of nested node trees.
+ */
+class NodeTreeEvaluationContext {
+ private:
+ std::string object_name_;
+ SessionUUID modifier_session_uuid_;
+
+ public:
+ NodeTreeEvaluationContext(const Object &object, const ModifierData &modifier)
+ {
+ object_name_ = reinterpret_cast<const ID &>(object).name;
+ modifier_session_uuid_ = modifier.session_uuid;
+ }
+
+ uint64_t hash() const
+ {
+ const uint64_t hash1 = blender::DefaultHash<std::string>{}(object_name_);
+ const uint64_t hash2 = BLI_session_uuid_hash_uint64(&modifier_session_uuid_);
+ return hash1 ^ (hash2 * 33); /* Copied from DefaultHash for std::pair. */
+ }
+
+ bool operator==(const NodeTreeEvaluationContext &other) const
+ {
+ return other.object_name_ == object_name_ &&
+ BLI_session_uuid_is_equal(&other.modifier_session_uuid_, &modifier_session_uuid_);
+ }
+};
+
+enum class NodeWarningType {
+ Error,
+ Warning,
+ Info,
+};
+
+struct NodeWarning {
+ NodeWarningType type;
+ std::string message;
+};
+
+struct NodeUIStorage {
+ blender::Vector<NodeWarning> warnings;
+};
+
+struct NodeTreeUIStorage {
+ Map<NodeTreeEvaluationContext, Map<std::string, NodeUIStorage>> context_map;
+};
+
+void BKE_nodetree_ui_storage_free_for_context(bNodeTree &ntree,
+ const NodeTreeEvaluationContext &context);
+
+void BKE_nodetree_ui_storage_ensure(bNodeTree &ntree);
+
+void BKE_nodetree_error_message_add(bNodeTree &ntree,
+ const NodeTreeEvaluationContext &context,
+ const bNode &node,
+ const NodeWarningType type,
+ std::string message);
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index ead01dbd6cb..f288bf9aabc 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -200,6 +200,7 @@ set(SRC
intern/multires_versioning.c
intern/nla.c
intern/node.cc
+ intern/node_ui_storage.cc
intern/object.c
intern/object_deform.c
intern/object_dupli.c
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index e34afd1ce17..f455f83f5c5 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -49,6 +49,7 @@
#include "BLI_ghash.h"
#include "BLI_listbase.h"
+#include "BLI_map.hh"
#include "BLI_math.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
@@ -68,6 +69,7 @@
#include "BKE_lib_query.h"
#include "BKE_main.h"
#include "BKE_node.h"
+#include "BKE_node_ui_storage.hh"
#include "BLI_ghash.h"
#include "BLI_threads.h"
@@ -215,6 +217,10 @@ static void ntree_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, c
/* node tree will generate its own interface type */
ntree_dst->interface_type = nullptr;
+
+ /* Don't copy error messages in the runtime struct.
+ * They should be filled during execution anyway. */
+ ntree_dst->ui_storage = nullptr;
}
static void ntree_free_data(ID *id)
@@ -268,6 +274,8 @@ static void ntree_free_data(ID *id)
if (ntree->id.tag & LIB_TAG_LOCALIZED) {
BKE_libblock_free_data(&ntree->id, true);
}
+
+ delete ntree->ui_storage;
}
static void library_foreach_node_socket(LibraryForeachIDData *data, bNodeSocket *sock)
@@ -557,6 +565,7 @@ static void ntree_blend_write(BlendWriter *writer, ID *id, const void *id_addres
ntree->interface_type = nullptr;
ntree->progress = nullptr;
ntree->execdata = nullptr;
+ ntree->ui_storage = nullptr;
BLO_write_id_struct(writer, bNodeTree, id_address, &ntree->id);
@@ -588,6 +597,7 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree)
ntree->progress = nullptr;
ntree->execdata = nullptr;
+ ntree->ui_storage = nullptr;
BLO_read_data_address(reader, &ntree->adt);
BKE_animdata_blend_read_data(reader, ntree->adt);
diff --git a/source/blender/blenkernel/intern/node_ui_storage.cc b/source/blender/blenkernel/intern/node_ui_storage.cc
new file mode 100644
index 00000000000..4c8a5c824c4
--- /dev/null
+++ b/source/blender/blenkernel/intern/node_ui_storage.cc
@@ -0,0 +1,104 @@
+/*
+ * 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 "CLG_log.h"
+
+#include "BLI_map.hh"
+#include "BLI_string_ref.hh"
+#include "BLI_vector.hh"
+
+#include "DNA_node_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_node_ui_storage.hh"
+
+static CLG_LogRef LOG = {"bke.node_ui_storage"};
+
+using blender::Map;
+using blender::StringRef;
+using blender::Vector;
+
+void BKE_nodetree_ui_storage_ensure(bNodeTree &ntree)
+{
+ if (ntree.ui_storage == nullptr) {
+ ntree.ui_storage = new NodeTreeUIStorage();
+ }
+}
+
+/**
+ * Removes only the UI data associated with a particular evaluation context. The same node tree
+ * can be used for execution in multiple places, but the entire UI storage can't be removed when
+ * one execution starts, or all of the data associated with the node tree would be lost.
+ */
+void BKE_nodetree_ui_storage_free_for_context(bNodeTree &ntree,
+ const NodeTreeEvaluationContext &context)
+{
+ NodeTreeUIStorage *ui_storage = ntree.ui_storage;
+ if (ui_storage != nullptr) {
+ ui_storage->context_map.remove(context);
+ }
+}
+
+static void node_error_message_log(bNodeTree &ntree,
+ const bNode &node,
+ const StringRef message,
+ const NodeWarningType type)
+{
+ switch (type) {
+ case NodeWarningType::Error:
+ CLOG_ERROR(&LOG,
+ "Node Tree: \"%s\", Node: \"%s\", %s",
+ ntree.id.name + 2,
+ node.name,
+ message.data());
+ break;
+ case NodeWarningType::Warning:
+ CLOG_WARN(&LOG,
+ "Node Tree: \"%s\", Node: \"%s\", %s",
+ ntree.id.name + 2,
+ node.name,
+ message.data());
+ break;
+ case NodeWarningType::Info:
+ CLOG_INFO(&LOG,
+ 2,
+ "Node Tree: \"%s\", Node: \"%s\", %s",
+ ntree.id.name + 2,
+ node.name,
+ message.data());
+ break;
+ }
+}
+
+void BKE_nodetree_error_message_add(bNodeTree &ntree,
+ const NodeTreeEvaluationContext &context,
+ const bNode &node,
+ const NodeWarningType type,
+ std::string message)
+{
+ BLI_assert(ntree.ui_storage != nullptr);
+ NodeTreeUIStorage &ui_storage = *ntree.ui_storage;
+
+ node_error_message_log(ntree, node, message, type);
+
+ Map<std::string, NodeUIStorage> &node_tree_ui_storage =
+ ui_storage.context_map.lookup_or_add_default(context);
+
+ NodeUIStorage &node_ui_storage = node_tree_ui_storage.lookup_or_add_default_as(
+ StringRef(node.name));
+
+ node_ui_storage.warnings.append({type, std::move(message)});
+}
diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc
index 8662217961c..f85d29f99d5 100644
--- a/source/blender/editors/space_node/node_draw.cc
+++ b/source/blender/editors/space_node/node_draw.cc
@@ -27,6 +27,7 @@
#include "DNA_light_types.h"
#include "DNA_linestyle_types.h"
#include "DNA_material_types.h"
+#include "DNA_modifier_types.h"
#include "DNA_node_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
@@ -34,7 +35,11 @@
#include "DNA_world_types.h"
#include "BLI_blenlib.h"
+#include "BLI_map.hh"
#include "BLI_math.h"
+#include "BLI_span.hh"
+#include "BLI_string_ref.hh"
+#include "BLI_vector.hh"
#include "BLT_translation.h"
@@ -42,6 +47,8 @@
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_node.h"
+#include "BKE_node_ui_storage.hh"
+#include "BKE_object.h"
#include "DEG_depsgraph.h"
@@ -74,6 +81,11 @@
# include "COM_compositor.h"
#endif
+using blender::Map;
+using blender::Span;
+using blender::StringRef;
+using blender::Vector;
+
extern "C" {
/* XXX interface.h */
extern void ui_draw_dropshadow(
@@ -1178,6 +1190,135 @@ void node_draw_sockets(const View2D *v2d,
}
}
+static int node_error_type_to_icon(const NodeWarningType type)
+{
+ switch (type) {
+ case NodeWarningType::Error:
+ return ICON_ERROR;
+ case NodeWarningType::Warning:
+ return ICON_ERROR;
+ case NodeWarningType::Info:
+ return ICON_INFO;
+ }
+
+ BLI_assert(false);
+ return ICON_ERROR;
+}
+
+static uint8_t node_error_type_priority(const NodeWarningType type)
+{
+ switch (type) {
+ case NodeWarningType::Error:
+ return 3;
+ case NodeWarningType::Warning:
+ return 2;
+ case NodeWarningType::Info:
+ return 1;
+ }
+
+ BLI_assert(false);
+ return 0;
+}
+
+static NodeWarningType node_error_highest_priority(Span<NodeWarning> warnings)
+{
+ uint8_t highest_priority = 0;
+ NodeWarningType highest_priority_type = NodeWarningType::Info;
+ for (const NodeWarning &warning : warnings) {
+ const uint8_t priority = node_error_type_priority(warning.type);
+ if (priority > highest_priority) {
+ highest_priority = priority;
+ highest_priority_type = warning.type;
+ }
+ }
+ return highest_priority_type;
+}
+
+static char *node_errrors_tooltip_fn(bContext *UNUSED(C), void *argN, const char *UNUSED(tip))
+{
+ const NodeUIStorage **storage_pointer_alloc = static_cast<const NodeUIStorage **>(argN);
+ const NodeUIStorage *node_ui_storage = *storage_pointer_alloc;
+ Span<NodeWarning> warnings = node_ui_storage->warnings;
+
+ std::string complete_string;
+
+ for (const NodeWarning &warning : warnings.drop_back(1)) {
+ complete_string += warning.message;
+ complete_string += '\n';
+ }
+
+ complete_string += warnings.last().message;
+
+ /* Remove the last period-- the tooltip system adds this automatically. */
+ if (complete_string.back() == '.') {
+ complete_string.pop_back();
+ }
+
+ return BLI_strdupn(complete_string.c_str(), complete_string.size());
+}
+
+#define NODE_HEADER_ICON_SIZE (0.8f * U.widget_unit)
+
+static const NodeUIStorage *node_ui_storage_get_from_context(const bContext *C,
+ const bNodeTree &ntree,
+ const bNode &node)
+{
+ const NodeTreeUIStorage *ui_storage = ntree.ui_storage;
+ if (ui_storage == nullptr) {
+ return nullptr;
+ }
+
+ const Object *active_object = CTX_data_active_object(C);
+ const ModifierData *active_modifier = BKE_object_active_modifier(active_object);
+ if (active_object == nullptr || active_modifier == nullptr) {
+ return nullptr;
+ }
+
+ const NodeTreeEvaluationContext context(*active_object, *active_modifier);
+ const Map<std::string, NodeUIStorage> *storage = ui_storage->context_map.lookup_ptr(context);
+ if (storage == nullptr) {
+ return nullptr;
+ }
+
+ return storage->lookup_ptr_as(StringRef(node.name));
+}
+
+static void node_add_error_message_button(
+ const bContext *C, bNodeTree &ntree, bNode &node, const rctf &rect, float &icon_offset)
+{
+ const NodeUIStorage *node_ui_storage = node_ui_storage_get_from_context(C, ntree, node);
+ if (node_ui_storage == nullptr) {
+ return;
+ }
+
+ /* The UI API forces us to allocate memory for each error button, because the
+ * ownership of #UI_but_func_tooltip_set's argument is transferred to the button. */
+ const NodeUIStorage **storage_pointer_alloc = (const NodeUIStorage **)MEM_mallocN(
+ sizeof(NodeUIStorage *), __func__);
+ *storage_pointer_alloc = node_ui_storage;
+
+ const NodeWarningType display_type = node_error_highest_priority(node_ui_storage->warnings);
+
+ icon_offset -= NODE_HEADER_ICON_SIZE;
+ UI_block_emboss_set(node.block, UI_EMBOSS_NONE);
+ uiBut *but = uiDefIconBut(node.block,
+ UI_BTYPE_BUT,
+ 0,
+ node_error_type_to_icon(display_type),
+ icon_offset,
+ rect.ymax - NODE_DY,
+ NODE_HEADER_ICON_SIZE,
+ UI_UNIT_Y,
+ nullptr,
+ 0,
+ 0,
+ 0,
+ 0,
+ nullptr);
+ UI_but_func_tooltip_set(but, node_errrors_tooltip_fn, storage_pointer_alloc);
+ UI_block_emboss_set(node.block, UI_EMBOSS);
+}
+
static void node_draw_basis(const bContext *C,
const View2D *v2d,
const SpaceNode *snode,
@@ -1186,7 +1327,7 @@ static void node_draw_basis(const bContext *C,
bNodeInstanceKey key)
{
/* float socket_size = NODE_SOCKSIZE*U.dpi/72; */ /* UNUSED */
- const float iconbutw = 0.8f * U.widget_unit;
+ const float iconbutw = NODE_HEADER_ICON_SIZE;
/* skip if out of view */
if (BLI_rctf_isect(&node->totr, &v2d->cur, nullptr) == false) {
@@ -1297,6 +1438,8 @@ static void node_draw_basis(const bContext *C,
UI_block_emboss_set(node->block, UI_EMBOSS);
}
+ node_add_error_message_button(C, *ntree, *node, *rct, iconofs);
+
/* title */
if (node->flag & SELECT) {
UI_GetThemeColor4fv(TH_SELECT, color);
diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index a7b5be753d1..3f1f8328cef 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -43,6 +43,7 @@ struct bNodeLink;
struct bNodePreview;
struct bNodeTreeExec;
struct bNodeType;
+struct NodeTreeUIStorage;
struct uiBlock;
#define NODE_MAXSTR 64
@@ -501,6 +502,8 @@ typedef struct bNodeTree {
int (*test_break)(void *);
void (*update_draw)(void *);
void *tbh, *prh, *sdh, *udh;
+
+ struct NodeTreeUIStorage *ui_storage;
} bNodeTree;
/* ntree->type, index */
diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc
index 706ef8578ac..9ec7bdf3b80 100644
--- a/source/blender/modifiers/intern/MOD_nodes.cc
+++ b/source/blender/modifiers/intern/MOD_nodes.cc
@@ -50,6 +50,7 @@
#include "BKE_lib_query.h"
#include "BKE_mesh.h"
#include "BKE_modifier.h"
+#include "BKE_node_ui_storage.hh"
#include "BKE_pointcloud.h"
#include "BKE_screen.h"
#include "BKE_simulation.h"
@@ -244,6 +245,7 @@ class GeometryNodesEvaluator {
const blender::nodes::DataTypeConversions &conversions_;
const PersistentDataHandleMap &handle_map_;
const Object *self_object_;
+ const ModifierData *modifier_;
Depsgraph *depsgraph_;
public:
@@ -252,12 +254,14 @@ class GeometryNodesEvaluator {
blender::nodes::MultiFunctionByNode &mf_by_node,
const PersistentDataHandleMap &handle_map,
const Object *self_object,
+ const ModifierData *modifier,
Depsgraph *depsgraph)
: group_outputs_(std::move(group_outputs)),
mf_by_node_(mf_by_node),
conversions_(blender::nodes::get_implicit_type_conversions()),
handle_map_(handle_map),
self_object_(self_object),
+ modifier_(modifier),
depsgraph_(depsgraph)
{
for (auto item : group_input_data.items()) {
@@ -359,7 +363,7 @@ class GeometryNodesEvaluator {
/* Execute the node. */
GValueMap<StringRef> node_outputs_map{allocator_};
GeoNodeExecParams params{
- node, node_inputs_map, node_outputs_map, handle_map_, self_object_, depsgraph_};
+ node, node_inputs_map, node_outputs_map, handle_map_, self_object_, modifier_, depsgraph_};
this->execute_node(node, params);
/* Forward computed outputs to linked input sockets. */
@@ -946,6 +950,19 @@ static void fill_data_handle_map(const NodesModifierSettings &settings,
}
}
+static void reset_tree_ui_storage(Span<const blender::nodes::NodeTreeRef *> trees,
+ const Object &object,
+ const ModifierData &modifier)
+{
+ const NodeTreeEvaluationContext context = {object, modifier};
+
+ for (const blender::nodes::NodeTreeRef *tree : trees) {
+ bNodeTree *btree_cow = tree->btree();
+ bNodeTree *btree_original = (bNodeTree *)DEG_get_original_id((ID *)btree_cow);
+ BKE_nodetree_ui_storage_free_for_context(*btree_original, context);
+ }
+}
+
/**
* Evaluate a node group to compute the output geometry.
* Currently, this uses a fairly basic and inefficient algorithm that might compute things more
@@ -992,8 +1009,14 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree,
Vector<const DInputSocket *> group_outputs;
group_outputs.append(&socket_to_compute);
- GeometryNodesEvaluator evaluator{
- group_inputs, group_outputs, mf_by_node, handle_map, ctx->object, ctx->depsgraph};
+ GeometryNodesEvaluator evaluator{group_inputs,
+ group_outputs,
+ mf_by_node,
+ handle_map,
+ ctx->object,
+ (ModifierData *)nmd,
+ ctx->depsgraph};
+
Vector<GMutablePointer> results = evaluator.execute();
BLI_assert(results.size() == 1);
GMutablePointer result = results[0];
@@ -1091,6 +1114,8 @@ static void modifyGeometry(ModifierData *md,
return;
}
+ reset_tree_ui_storage(tree.used_node_tree_refs(), *ctx->object, *md);
+
geometry_set = compute_geometry(
tree, group_inputs, *group_outputs[0], std::move(geometry_set), nmd, ctx);
}
diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh
index 18de52ed6d4..d5fd3ff0abb 100644
--- a/source/blender/nodes/NOD_geometry_exec.hh
+++ b/source/blender/nodes/NOD_geometry_exec.hh
@@ -21,6 +21,7 @@
#include "BKE_attribute_access.hh"
#include "BKE_geometry_set.hh"
#include "BKE_geometry_set_instances.hh"
+#include "BKE_node_ui_storage.hh"
#include "BKE_persistent_data_handle.hh"
#include "DNA_node_types.h"
@@ -28,6 +29,7 @@
#include "NOD_derived_node_tree.hh"
struct Depsgraph;
+struct ModifierData;
namespace blender::nodes {
@@ -62,6 +64,7 @@ class GeoNodeExecParams {
GValueMap<StringRef> &output_values_;
const PersistentDataHandleMap &handle_map_;
const Object *self_object_;
+ const ModifierData *modifier_;
Depsgraph *depsgraph_;
public:
@@ -70,12 +73,14 @@ class GeoNodeExecParams {
GValueMap<StringRef> &output_values,
const PersistentDataHandleMap &handle_map,
const Object *self_object,
+ const ModifierData *modifier,
Depsgraph *depsgraph)
: node_(node),
input_values_(input_values),
output_values_(output_values),
handle_map_(handle_map),
self_object_(self_object),
+ modifier_(modifier),
depsgraph_(depsgraph)
{
}
@@ -199,8 +204,17 @@ class GeoNodeExecParams {
}
/**
+ * Add an error message displayed at the top of the node when displaying the node tree,
+ * and potentially elsewhere in Blender.
+ */
+ void error_message_add(const NodeWarningType type, std::string message) const;
+
+ /**
* Creates a read-only attribute based on node inputs. The method automatically detects which
- * input with the given name is available.
+ * input socket with the given name is available.
+ *
+ * \note This will add an error message if the string socket is active and
+ * the input attribute does not exist.
*/
ReadAttributePtr get_input_attribute(const StringRef name,
const GeometryComponent &component,
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc
index 8a098c366a0..d7b85953a44 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc
@@ -214,6 +214,8 @@ static void randomize_attribute_on_component(GeometryComponent &component,
* doesn't already exist, don't do the operation. */
if (operation != GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE) {
if (!component.attribute_exists(attribute_name)) {
+ params.error_message_add(NodeWarningType::Error,
+ "No attribute with name '" + attribute_name + "'.");
return;
}
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc
index 581c356742b..40187490c23 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc
@@ -426,6 +426,7 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params)
static_cast<GeometryNodePointDistributeMethod>(params.node().custom1);
if (!geometry_set.has_mesh()) {
+ params.error_message_add(NodeWarningType::Error, "Geometry must contain a mesh.");
params.set_output("Geometry", std::move(geometry_set_out));
return;
}
@@ -441,7 +442,8 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params)
const MeshComponent &mesh_component = *geometry_set.get_component_for_read<MeshComponent>();
const Mesh *mesh_in = mesh_component.get_for_read();
- if (mesh_in == nullptr || mesh_in->mpoly == nullptr) {
+ if (mesh_in->mpoly == nullptr) {
+ params.error_message_add(NodeWarningType::Error, "Mesh has no faces.");
params.set_output("Geometry", std::move(geometry_set_out));
return;
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc
index 11921acdb68..669b5ee4614 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc
@@ -101,6 +101,12 @@ static void get_instanced_data__collection(
return;
}
+ if (BLI_listbase_is_empty(&collection->children) &&
+ BLI_listbase_is_empty(&collection->gobject)) {
+ params.error_message_add(NodeWarningType::Info, "Collection is empty.");
+ return;
+ }
+
const bool use_whole_collection = (node_storage->flag &
GEO_NODE_POINT_INSTANCE_WHOLE_COLLECTION) != 0;
if (use_whole_collection) {
diff --git a/source/blender/nodes/intern/node_geometry_exec.cc b/source/blender/nodes/intern/node_geometry_exec.cc
index 7f4f75c294f..ebbb6f60b78 100644
--- a/source/blender/nodes/intern/node_geometry_exec.cc
+++ b/source/blender/nodes/intern/node_geometry_exec.cc
@@ -14,6 +14,12 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+#include "DNA_modifier_types.h"
+
+#include "BKE_node_ui_storage.hh"
+
+#include "DEG_depsgraph_query.h"
+
#include "NOD_derived_node_tree.hh"
#include "NOD_geometry_exec.hh"
#include "NOD_type_callbacks.hh"
@@ -22,6 +28,23 @@
namespace blender::nodes {
+void GeoNodeExecParams::error_message_add(const NodeWarningType type, std::string message) const
+{
+ bNodeTree *btree_cow = node_.node_ref().tree().btree();
+ BLI_assert(btree_cow != nullptr);
+ if (btree_cow == nullptr) {
+ return;
+ }
+ bNodeTree *btree_original = (bNodeTree *)DEG_get_original_id((ID *)btree_cow);
+
+ BKE_nodetree_ui_storage_ensure(*btree_original);
+
+ const NodeTreeEvaluationContext context(*self_object_, *modifier_);
+
+ BKE_nodetree_error_message_add(
+ *btree_original, context, *node_.bnode(), type, std::move(message));
+}
+
const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name) const
{
for (const DSocket *socket : node_.inputs()) {
@@ -47,7 +70,19 @@ ReadAttributePtr GeoNodeExecParams::get_input_attribute(const StringRef name,
if (found_socket->type == SOCK_STRING) {
const std::string name = this->get_input<std::string>(found_socket->identifier);
- return component.attribute_get_for_read(name, domain, type, default_value);
+ /* Try getting the attribute without the default value. */
+ ReadAttributePtr attribute = component.attribute_try_get_for_read(name, domain, type);
+ if (attribute) {
+ return attribute;
+ }
+
+ /* If the attribute doesn't exist, use the default value and output an error message
+ * (except when the field is empty, to avoid spamming error messages). */
+ if (!name.empty()) {
+ this->error_message_add(NodeWarningType::Error,
+ std::string("No attribute with name '") + name + "'.");
+ }
+ return component.attribute_get_constant_for_read(domain, type, default_value);
}
if (found_socket->type == SOCK_FLOAT) {
const float value = this->get_input<float>(found_socket->identifier);