diff options
-rw-r--r-- | source/blender/blenkernel/BKE_node_ui_storage.hh | 95 | ||||
-rw-r--r-- | source/blender/blenkernel/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/node.cc | 10 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/node_ui_storage.cc | 104 | ||||
-rw-r--r-- | source/blender/editors/space_node/node_draw.cc | 145 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_node_types.h | 3 | ||||
-rw-r--r-- | source/blender/modifiers/intern/MOD_nodes.cc | 31 | ||||
-rw-r--r-- | source/blender/nodes/NOD_geometry_exec.hh | 16 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc | 2 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc | 4 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_point_instance.cc | 6 | ||||
-rw-r--r-- | source/blender/nodes/intern/node_geometry_exec.cc | 37 |
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); |