diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index 4d3f425d802..877a3f7a2b5 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -504,6 +504,7 @@ class NODE_MT_category_GEO_OUTPUT(Menu): layout = self.layout node_add_menu.add_node_type(layout, "NodeGroupOutput") node_add_menu.add_node_type(layout, "GeometryNodeViewer") + node_add_menu.add_node_type(layout, "GeometryNodeWarning") node_add_menu.draw_assets_for_catalog(layout, self.bl_label) diff --git a/source/blender/blenkernel/BKE_node.hh b/source/blender/blenkernel/BKE_node.hh index eb584581811..efc44b1f92c 100644 --- a/source/blender/blenkernel/BKE_node.hh +++ b/source/blender/blenkernel/BKE_node.hh @@ -1375,6 +1375,7 @@ void node_tree_remove_layer_n(bNodeTree *ntree, Scene *scene, int layer_index); #define GEO_NODE_CURVES_TO_GREASE_PENCIL 2144 #define GEO_NODE_GREASE_PENCIL_TO_CURVES 2145 #define GEO_NODE_IMPORT_PLY 2146 +#define GEO_NODE_WARNING 2147 /** \} */ diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc index e78c7651428..764a277630e 100644 --- a/source/blender/blenkernel/intern/node_tree_update.cc +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -1278,6 +1278,9 @@ class NodeTreeMainUpdater { if (node.type == NODE_GROUP_OUTPUT) { return true; } + if (node.type == GEO_NODE_WARNING) { + return true; + } if (nodes::gizmos::is_builtin_gizmo_node(node)) { return true; } diff --git a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh index 8c72a6e1485..0f0ecedd7bb 100644 --- a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh +++ b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh @@ -439,6 +439,7 @@ std::unique_ptr get_bake_lazy_function( std::unique_ptr get_menu_switch_node_lazy_function( const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info); std::unique_ptr get_menu_switch_node_socket_usage_lazy_function(const bNode &node); +std::unique_ptr get_warning_node_lazy_function(const bNode &node); /** * Outputs the default value of each output socket that has not been output yet. This needs the diff --git a/source/blender/nodes/NOD_geometry_nodes_log.hh b/source/blender/nodes/NOD_geometry_nodes_log.hh index 8a74d9db432..d2442482081 100644 --- a/source/blender/nodes/NOD_geometry_nodes_log.hh +++ b/source/blender/nodes/NOD_geometry_nodes_log.hh @@ -52,10 +52,11 @@ namespace blender::nodes::geo_eval_log { using fn::GField; +/** These values are also written to .blend files, so don't change them lightly. */ enum class NodeWarningType { - Error, - Warning, - Info, + Error = 0, + Warning = 1, + Info = 2, }; struct NodeWarning { diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index aec1ab31f24..78c2f2f329b 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -501,6 +501,7 @@ DefNode(GeometryNode, GEO_NODE_UV_UNWRAP, 0, "UV_UNWRAP", UVUnwrap, "UV Unwrap", DefNode(GeometryNode, GEO_NODE_VIEWER, 0, "VIEWER", Viewer, "Viewer", "Display the input data in the Spreadsheet Editor") DefNode(GeometryNode, GEO_NODE_VOLUME_CUBE, 0, "VOLUME_CUBE", VolumeCube, "Volume Cube", "Generate a dense volume with a field that controls the density at each grid voxel based on its position") DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, 0, "VOLUME_TO_MESH", VolumeToMesh, "Volume to Mesh", "Generate a mesh on the \"surface\" of a volume") +DefNode(GeometryNode, GEO_NODE_WARNING, 0, "WARNING", Warning, "Warning", "Create custom warnings in node groups") /* undefine macros */ #undef DefNode diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index b75c7ecfd16..e36165f859a 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -226,6 +226,7 @@ set(SRC nodes/node_geo_viewport_transform.cc nodes/node_geo_volume_cube.cc nodes/node_geo_volume_to_mesh.cc + nodes/node_geo_warning.cc include/NOD_geo_bake.hh include/NOD_geo_capture_attribute.hh diff --git a/source/blender/nodes/geometry/nodes/node_geo_warning.cc b/source/blender/nodes/geometry/nodes/node_geo_warning.cc new file mode 100644 index 00000000000..a25582eeefe --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_warning.cc @@ -0,0 +1,113 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "node_geometry_util.hh" + +#include "UI_interface.hh" +#include "UI_resources.hh" + +#include "NOD_rna_define.hh" + +namespace blender::nodes::node_geo_warning_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.use_custom_socket_order(); + b.allow_any_socket_order(); + + b.add_input("Show").default_value(true).hide_value(); + b.add_output("Show").align_with_previous(); + b.add_input("Message").hide_label(); +} + +class LazyFunctionForWarningNode : public LazyFunction { + const bNode &node_; + + public: + LazyFunctionForWarningNode(const bNode &node) : node_(node) + { + const CPPType &type = CPPType::get(); + inputs_.append_as("Show", type, lf::ValueUsage::Used); + inputs_.append_as("Message", type); + outputs_.append_as("Show", type); + } + + void execute_impl(lf::Params ¶ms, const lf::Context &context) const override + { + const SocketValueVariant show_variant = params.get_input(0); + const bool show = show_variant.get(); + if (!show) { + params.set_output(0, show_variant); + return; + } + SocketValueVariant *message_variant = + params.try_get_input_data_ptr_or_request(1); + if (!message_variant) { + /* Wait for the message to be computed. */ + return; + } + std::string message = message_variant->extract(); + GeoNodesLFUserData &user_data = *static_cast(context.user_data); + GeoNodesLFLocalUserData &local_user_data = *static_cast( + context.local_user_data); + if (geo_eval_log::GeoTreeLogger *tree_logger = local_user_data.try_get_tree_logger(user_data)) + { + tree_logger->node_warnings.append( + *tree_logger->allocator, + {node_.identifier, {NodeWarningType(node_.custom1), std::move(message)}}); + } + /* Only set output in the end so that this node is not finished before the warning is set. */ + params.set_output(0, show_variant); + } +}; + +static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "warning_type", UI_ITEM_NONE, "", ICON_NONE); +} + +static void node_rna(StructRNA *srna) +{ + static EnumPropertyItem warning_type_items[] = { + {int(NodeWarningType::Error), "ERROR", 0, "Error", ""}, + {int(NodeWarningType::Warning), "WARNING", 0, "Warning", ""}, + {int(NodeWarningType::Info), "INFO", 0, "Info", ""}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + RNA_def_node_enum(srna, + "warning_type", + "Warning Type", + "", + warning_type_items, + NOD_inline_enum_accessors(custom1)); +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_WARNING, "Warning", NODE_CLASS_INTERFACE); + ntype.declare = node_declare; + ntype.draw_buttons = node_layout; + blender::bke::node_register_type(&ntype); + + node_rna(ntype.rna_ext.srna); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_warning_cc + +namespace blender::nodes { + +std::unique_ptr get_warning_node_lazy_function(const bNode &node) +{ + using namespace node_geo_warning_cc; + BLI_assert(node.type == GEO_NODE_WARNING); + return std::make_unique(node); +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc index d4c8259cdfc..37921bc6354 100644 --- a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -1598,6 +1598,8 @@ struct BuildGraphParams { /** */ /** Cache to avoid building the same socket combinations multiple times. */ Map, lf::OutputSocket *> socket_usages_combination_cache; + + BuildGraphParams(lf::Graph &lf_graph) : lf_graph(lf_graph) {} }; struct ZoneFunctionIndices { @@ -2181,7 +2183,16 @@ class GeometryNodesLazyFunctionLogger : public lf::GraphExecutor::Logger { * another (depending on e.g. which tree path is currently viewed in the node editor). */ class GeometryNodesLazyFunctionSideEffectProvider : public lf::GraphExecutor::SideEffectProvider { + private: + Span local_side_effect_nodes_; + public: + GeometryNodesLazyFunctionSideEffectProvider( + Span local_side_effect_nodes = {}) + : local_side_effect_nodes_(local_side_effect_nodes) + { + } + Vector get_nodes_with_side_effects( const lf::Context &context) const override { @@ -2192,7 +2203,10 @@ class GeometryNodesLazyFunctionSideEffectProvider : public lf::GraphExecutor::Si return {}; } const ComputeContextHash &context_hash = user_data->compute_context->hash(); - return call_data.side_effect_nodes->nodes_by_context.lookup(context_hash); + Vector side_effect_nodes = + call_data.side_effect_nodes->nodes_by_context.lookup(context_hash); + side_effect_nodes.extend(local_side_effect_nodes_); + return side_effect_nodes; } }; @@ -2219,6 +2233,8 @@ struct GeometryNodesLazyFunctionBuilder { const bNodeTreeZones *tree_zones_; MutableSpan zone_build_infos_; + std::optional root_graph_build_params_; + /** * The inputs sockets in the graph. Multiple group input nodes are combined into one in the * lazy-function graph. @@ -2233,7 +2249,7 @@ struct GeometryNodesLazyFunctionBuilder { * Interface boolean sockets that have to be passed in from the outside and indicate whether a * specific output will be used. */ - Vector group_output_used_sockets_; + Vector group_output_used_sockets_; /** * Interface boolean sockets that can be used as group output that indicate whether a specific * input will be used (this may depend on the used outputs as well as other inputs). @@ -2720,7 +2736,7 @@ struct GeometryNodesLazyFunctionBuilder { lf_output_usages.append(&lf_socket); } - BuildGraphParams graph_params{lf_graph}; + BuildGraphParams &graph_params = root_graph_build_params_.emplace(lf_graph); if (const bNode *group_output_bnode = btree_.group_output_node()) { for (const bNodeSocket *bsocket : group_output_bnode->input_sockets().drop_back(1)) { graph_params.usage_by_bsocket.add(bsocket, lf_output_usages[bsocket->index()]); @@ -2790,12 +2806,33 @@ struct GeometryNodesLazyFunctionBuilder { function.outputs.input_usages = lf_graph_outputs.index_range().take_back( group_input_usage_sockets_.size()); + Vector &local_side_effect_nodes = + scope_.construct>(); + for (const bNode *bnode : btree_.nodes_by_type("GeometryNodeWarning")) { + if (bnode->output_socket(0).is_directly_linked()) { + /* The warning node is not a side-effect node. Instead, the user explicitly used the output + * socket to specify when the warning node should be used. */ + continue; + } + if (tree_zones_->get_zone_by_node(bnode->identifier)) { + /* "Global" warning nodes that are evaluated whenever the node group is evaluated must not + * be in a zone. */ + continue; + } + /* Add warning node as side-effect node so that it is always evaluated if the node group is + * evaluated. */ + const lf::Socket *lf_socket = root_graph_build_params_->lf_inputs_by_bsocket.lookup( + &bnode->input_socket(0))[0]; + const lf::FunctionNode &lf_node = static_cast(lf_socket->node()); + local_side_effect_nodes.append(&lf_node); + } + function.function = &scope_.construct( lf_graph_info_->graph, std::move(lf_graph_inputs), std::move(lf_graph_outputs), &scope_.construct(*lf_graph_info_), - &scope_.construct(), + &scope_.construct(local_side_effect_nodes), nullptr); } @@ -3237,6 +3274,10 @@ struct GeometryNodesLazyFunctionBuilder { this->build_index_switch_node(bnode, graph_params); break; } + case GEO_NODE_WARNING: { + this->build_warning_node(bnode, graph_params); + break; + } case GEO_NODE_GIZMO_LINEAR: case GEO_NODE_GIZMO_DIAL: case GEO_NODE_GIZMO_TRANSFORM: { @@ -3894,6 +3935,45 @@ struct GeometryNodesLazyFunctionBuilder { } } + void build_warning_node(const bNode &bnode, BuildGraphParams &graph_params) + { + auto lazy_function_ptr = get_warning_node_lazy_function(bnode); + LazyFunction &lazy_function = *lazy_function_ptr; + scope_.add(std::move(lazy_function_ptr)); + + lf::Node &lf_node = graph_params.lf_graph.add_function(lazy_function); + + for (const int i : bnode.input_sockets().index_range()) { + const bNodeSocket &bsocket = bnode.input_socket(i); + lf::InputSocket &lf_socket = lf_node.input(i); + graph_params.lf_inputs_by_bsocket.add(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + for (const int i : bnode.output_sockets().index_range()) { + const bNodeSocket &bsocket = bnode.output_socket(i); + lf::OutputSocket &lf_socket = lf_node.output(i); + graph_params.lf_output_by_bsocket.add(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + } + + const bNodeSocket &output_bsocket = bnode.output_socket(0); + + lf::OutputSocket *lf_usage = nullptr; + if (output_bsocket.is_directly_linked()) { + /* The warning node is only used if the output socket is used. */ + lf_usage = graph_params.usage_by_bsocket.lookup_default(&output_bsocket, nullptr); + } + else { + /* The warning node is used if any of the output sockets is used. */ + lf_usage = this->or_socket_usages(group_output_used_sockets_, graph_params); + } + if (lf_usage) { + for (const bNodeSocket *socket : bnode.input_sockets()) { + graph_params.usage_by_bsocket.add(socket, lf_usage); + } + } + } + void build_menu_switch_node(const bNode &bnode, BuildGraphParams &graph_params) { std::unique_ptr lazy_function = get_menu_switch_node_lazy_function(