diff --git a/scripts/startup/bl_ui/node_add_menu_compositor.py b/scripts/startup/bl_ui/node_add_menu_compositor.py index 65653d2f41f..80f1bc67802 100644 --- a/scripts/startup/bl_ui/node_add_menu_compositor.py +++ b/scripts/startup/bl_ui/node_add_menu_compositor.py @@ -62,6 +62,7 @@ class NODE_MT_compositor_node_output_base(node_add_menu.NodeMenu): def draw(self, context): layout = self.layout + self.node_operator(layout, "NodeEnableOutput") self.node_operator(layout, "NodeGroupOutput") self.node_operator(layout, "CompositorNodeViewer") if context.space_data.node_tree_sub_type == 'SCENE': diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index fcdaf7c58e4..fd7f96ae64c 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -595,6 +595,7 @@ class NODE_MT_gn_output_base(node_add_menu.NodeMenu): def draw(self, context): layout = self.layout + self.node_operator(layout, "NodeEnableOutput") self.node_operator(layout, "NodeGroupOutput") self.node_operator(layout, "GeometryNodeViewer") self.node_operator_with_searchable_enum(context, layout, "GeometryNodeWarning", "warning_type") diff --git a/source/blender/blenkernel/BKE_node_runtime.hh b/source/blender/blenkernel/BKE_node_runtime.hh index 15a091a9eb4..fc146e626a8 100644 --- a/source/blender/blenkernel/BKE_node_runtime.hh +++ b/source/blender/blenkernel/BKE_node_runtime.hh @@ -185,11 +185,10 @@ class bNodeTreeRuntime : NonCopyable, NonMovable { std::unique_ptr structure_type_interface; /** - * A bool for each input socket (indexed by `index_in_all_inputs()`) that indicates whether this - * socket is used by the node it belongs to. Sockets for which this is false may e.g. be grayed - * out. + * Indexed by #bNodeSocket::index_in_tree(). Contains information about whether the socket is + * used or visible. */ - blender::Array inferenced_input_socket_usage; + blender::Array inferenced_socket_usage; CacheMutex inferenced_input_socket_usage_mutex; /** @@ -1022,8 +1021,7 @@ inline bool bNodeSocket::is_panel_collapsed() const inline bool bNodeSocket::is_visible() const { - return !this->is_user_hidden() && this->is_available() && - (this->is_output() || this->inferred_input_socket_visibility()); + return !this->is_user_hidden() && this->is_available() && this->inferred_socket_visibility(); } inline bool bNodeSocket::is_icon_visible() const diff --git a/source/blender/blenkernel/intern/cpp_types.cc b/source/blender/blenkernel/intern/cpp_types.cc index 01a08ca9340..b824c57f650 100644 --- a/source/blender/blenkernel/intern/cpp_types.cc +++ b/source/blender/blenkernel/intern/cpp_types.cc @@ -23,7 +23,8 @@ struct Tex; struct Image; struct Material; -BLI_CPP_TYPE_MAKE(blender::bke::GeometrySet, CPPTypeFlags::Printable); +BLI_CPP_TYPE_MAKE(blender::bke::GeometrySet, + CPPTypeFlags::Printable | CPPTypeFlags::EqualityComparable); BLI_CPP_TYPE_MAKE(blender::bke::InstanceReference, CPPTypeFlags::None) BLI_VECTOR_CPP_TYPE_MAKE(blender::bke::GeometrySet); @@ -35,10 +36,10 @@ BLI_CPP_TYPE_MAKE(Image *, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(Material *, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(MStringProperty, CPPTypeFlags::None); -BLI_CPP_TYPE_MAKE(blender::nodes::MenuValue, CPPTypeFlags::None); -BLI_CPP_TYPE_MAKE(blender::nodes::BundlePtr, CPPTypeFlags::None); -BLI_CPP_TYPE_MAKE(blender::nodes::ClosurePtr, CPPTypeFlags::None); -BLI_CPP_TYPE_MAKE(blender::nodes::ListPtr, CPPTypeFlags::None); +BLI_CPP_TYPE_MAKE(blender::nodes::MenuValue, CPPTypeFlags::EqualityComparable); +BLI_CPP_TYPE_MAKE(blender::nodes::BundlePtr, CPPTypeFlags::EqualityComparable); +BLI_CPP_TYPE_MAKE(blender::nodes::ClosurePtr, CPPTypeFlags::EqualityComparable); +BLI_CPP_TYPE_MAKE(blender::nodes::ListPtr, CPPTypeFlags::EqualityComparable); BLI_CPP_TYPE_MAKE(blender::bke::GeometryNodesReferenceSet, CPPTypeFlags::None); BLI_CPP_TYPE_MAKE(blender::bke::SocketValueVariant, CPPTypeFlags::Printable); diff --git a/source/blender/blenkernel/intern/node_runtime.cc b/source/blender/blenkernel/intern/node_runtime.cc index 0390dde120d..edd4ee49fbb 100644 --- a/source/blender/blenkernel/intern/node_runtime.cc +++ b/source/blender/blenkernel/intern/node_runtime.cc @@ -704,8 +704,8 @@ bNodeSocket &bNode::socket_by_decl(const blender::nodes::SocketDeclaration &decl static void ensure_inference_usage_cache(const bNodeTree &tree) { tree.runtime->inferenced_input_socket_usage_mutex.ensure([&]() { - tree.runtime->inferenced_input_socket_usage = - blender::nodes::socket_usage_inference::infer_all_input_sockets_usage(tree); + tree.runtime->inferenced_socket_usage = + blender::nodes::socket_usage_inference::infer_all_sockets_usage(tree); }); } @@ -715,12 +715,11 @@ bool bNodeSocket::affects_node_output() const BLI_assert(blender::bke::node_tree_runtime::topology_cache_is_available(*this)); const bNodeTree &tree = this->owner_tree(); ensure_inference_usage_cache(tree); - return tree.runtime->inferenced_input_socket_usage[this->index_in_all_inputs()].is_used; + return tree.runtime->inferenced_socket_usage[this->index_in_tree()].is_used; } -bool bNodeSocket::inferred_input_socket_visibility() const +bool bNodeSocket::inferred_socket_visibility() const { - BLI_assert(this->is_input()); BLI_assert(blender::bke::node_tree_runtime::topology_cache_is_available(*this)); const bNode &node = this->owner_node(); if (node.typeinfo->ignore_inferred_input_socket_visibility) { @@ -729,5 +728,5 @@ bool bNodeSocket::inferred_input_socket_visibility() const const bNodeTree &tree = this->owner_tree(); ensure_inference_usage_cache(tree); - return tree.runtime->inferenced_input_socket_usage[this->index_in_all_inputs()].is_visible; + return tree.runtime->inferenced_socket_usage[this->index_in_tree()].is_visible; } diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 5cb15d5a74e..04a7098ab23 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -236,11 +236,13 @@ typedef struct bNodeSocket { */ bool affects_node_output() const; /** - * This becomes false when it is detected that the input socket is currently not used and its - * usage depends on a menu (as opposed to e.g. a boolean input). By convention, sockets whose - * visibility is controlled by a menu should be hidden. + * This becomes false when it is detected that the socket is unused and should be hidden. + * Inputs: An input should be hidden if it's unused and its usage depends on a menu input (as + * opposed to e.g. a boolean input). + * Outputs: An output is unused if it outputs the socket types fallback value as a constant given + * the current set of menu inputs and its value depends on a menu input. */ - bool inferred_input_socket_visibility() const; + bool inferred_socket_visibility() const; /** * True when the value of this socket may be a field. This is inferred during structure type * inferencing. diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 71b4a307266..4f14f8356df 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -9644,6 +9644,7 @@ static void rna_def_nodes(BlenderRNA *brna) define("NodeInternal", "NodeClosureInput", def_closure_input); define("NodeInternal", "NodeClosureOutput", def_closure_output); define("NodeInternal", "NodeCombineBundle", def_combine_bundle); + define("NodeInternal", "NodeEnableOutput"); define("NodeInternal", "NodeEvaluateClosure", def_evaluate_closure); define("NodeInternal", "NodeSeparateBundle", def_separate_bundle); diff --git a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh index c1c201b311d..cc314d2a524 100644 --- a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh +++ b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh @@ -445,6 +445,8 @@ 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); +std::unique_ptr get_enable_output_node_lazy_function( + const bNode &node, GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info); /** * Outputs the default value of each output socket that has not been output yet. This needs the diff --git a/source/blender/nodes/NOD_socket_usage_inference.hh b/source/blender/nodes/NOD_socket_usage_inference.hh index ba89abc405c..c35a3855203 100644 --- a/source/blender/nodes/NOD_socket_usage_inference.hh +++ b/source/blender/nodes/NOD_socket_usage_inference.hh @@ -4,6 +4,8 @@ #pragma once +#include + #include "BLI_array.hh" #include "BLI_generic_pointer.hh" @@ -57,24 +59,27 @@ class InputSocketUsageParams { }; /** - * Get a boolean value for each input socket in the given tree that indicates whether that input is - * used. It is assumed that all output sockets in the tree are used. + * Determine which sockets in the tree are currently used and thus which should be grayed out or + * made invisible. */ -Array infer_all_input_sockets_usage(const bNodeTree &tree); +Array infer_all_sockets_usage(const bNodeTree &tree); /** - * Get a boolean value for each node group input that indicates whether that input is used by the - * outputs. The result can be used to e.g. gray out or hide individual inputs that are unused. + * Get usage of the inputs and outputs of the node group given the set of input values. The result + * can be used to e.g. gray out or hide individual inputs that are unused. * * \param group: The node group that is called. * \param group_input_values: An optional input value for each node group input. The type is * expected to be `bNodeSocketType::base_cpp_type`. If the input value for a socket is not known * or can't be represented as base type, null has to be passed instead. - * \param r_input_usages: The destination array where the inferred usages are written. + * \param r_input_usages: The destination array where the inferred input usages are written. + * \param r_output_usages: The destination array where the inferred output usages are written. */ -void infer_group_interface_inputs_usage(const bNodeTree &group, - Span group_input_values, - MutableSpan r_input_usages); +void infer_group_interface_usage( + const bNodeTree &group, + Span group_input_values, + MutableSpan r_input_usages, + std::optional> r_output_usages = std::nullopt); /** * Same as above, but automatically retrieves the input values from the given sockets.. @@ -88,8 +93,10 @@ void infer_group_interface_inputs_usage(const bNodeTree &group, * Same as above, but automatically retrieves the input values from the given properties. * This is used with the geometry nodes modifier and node tools. */ -void infer_group_interface_inputs_usage(const bNodeTree &group, - const IDProperty *properties, - MutableSpan r_input_usages); +void infer_group_interface_usage( + const bNodeTree &group, + const IDProperty *properties, + MutableSpan r_input_usages, + std::optional> r_output_usages = std::nullopt); } // namespace blender::nodes::socket_usage_inference diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 3c064301f54..66c1d94ab32 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -79,6 +79,7 @@ set(SRC nodes/node_geo_edge_paths_to_selection.cc nodes/node_geo_edge_split.cc nodes/node_geo_edges_to_face_groups.cc + nodes/node_geo_enable_output.cc nodes/node_geo_evaluate_at_index.cc nodes/node_geo_evaluate_closure.cc nodes/node_geo_evaluate_on_domain.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_enable_output.cc b/source/blender/nodes/geometry/nodes/node_geo_enable_output.cc new file mode 100644 index 00000000000..c9b5bbc03eb --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_enable_output.cc @@ -0,0 +1,199 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "node_geometry_util.hh" + +#include "NOD_node_extra_info.hh" +#include "NOD_rna_define.hh" + +#include "UI_interface_layout.hh" +#include "UI_resources.hh" + +#include "RNA_enum_types.hh" + +#include "COM_node_operation.hh" +#include "COM_result.hh" + +namespace blender::nodes::node_geo_enable_output_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.use_custom_socket_order(); + b.allow_any_socket_order(); + + b.add_default_layout(); + b.add_input("Enable").default_value(false).structure_type(StructureType::Single); + + const bNode *node = b.node_or_null(); + if (!node) { + return; + } + const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1); + b.add_input(data_type, "Value").hide_value().structure_type(StructureType::Dynamic); + b.add_output(data_type, "Value").align_with_previous().structure_type(StructureType::Dynamic); +} + +static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + layout->use_property_split_set(true); + layout->use_property_decorate_set(false); + layout->prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE); +} + +class LazyFunctionForEnableOutputNode : public LazyFunction { + const bNode &node_; + + public: + LazyFunctionForEnableOutputNode(const bNode &node, MutableSpan r_lf_index_by_bsocket) + : node_(node) + { + r_lf_index_by_bsocket[node.input_socket(0).index_in_tree()] = inputs_.append_and_get_index_as( + "Enable", CPPType::get()); + r_lf_index_by_bsocket[node.input_socket(1).index_in_tree()] = inputs_.append_and_get_index_as( + "Value", CPPType::get(), lf::ValueUsage::Maybe); + r_lf_index_by_bsocket[node.output_socket(0).index_in_tree()] = + outputs_.append_and_get_index_as("Value", CPPType::get()); + } + + void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override + { + const bke::SocketValueVariant enable_variant = params.get_input(0); + if (!enable_variant.is_single()) { + set_default_remaining_node_outputs(params, node_); + return; + } + const bool keep = enable_variant.get(); + if (!keep) { + set_default_remaining_node_outputs(params, node_); + return; + } + const bke::SocketValueVariant *value_variant = + params.try_get_input_data_ptr_or_request(1); + if (!value_variant) { + /* Wait until the value is available. */ + return; + } + params.set_output(0, std::move(*value_variant)); + } +}; + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + node->custom1 = SOCK_FLOAT; +} + +using namespace blender::compositor; + +class EnableOutputOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + const bool keep = this->get_input("Enable").get_single_value_default(true); + Result &output = this->get_result("Value"); + if (keep) { + const Result &input = this->get_input("Value"); + output.share_data(input); + } + else { + output.allocate_invalid(); + } + } +}; + +static NodeOperation *node_get_compositor_operation(Context &context, DNode node) +{ + return new EnableOutputOperation(context, node); +} + +static void node_extra_info(NodeExtraInfoParams ¶ms) +{ + params.tree.ensure_topology_cache(); + const bNodeSocket &output_socket = params.node.output_socket(0); + if (!output_socket.is_directly_linked()) { + return; + } + for (const bNodeSocket *target_socket : output_socket.logically_linked_sockets()) { + const bNode &target_node = target_socket->owner_node(); + if (!target_node.is_group_output() && !target_node.is_reroute()) { + NodeExtraInfoRow row; + row.text = RPT_("Invalid Output Link"); + row.tooltip = TIP_("This node should be linked to the group output node"); + row.icon = ICON_ERROR; + params.rows.append(std::move(row)); + return; + } + } +} + +static const EnumPropertyItem *data_type_items_callback(bContext * /*C*/, + PointerRNA *ptr, + PropertyRNA * /*prop*/, + bool *r_free) +{ + *r_free = true; + const bNodeTree &ntree = *reinterpret_cast(ptr->owner_id); + blender::bke::bNodeTreeType *ntree_type = ntree.typeinfo; + return enum_items_filter( + rna_enum_node_socket_data_type_items, [&](const EnumPropertyItem &item) -> bool { + bke::bNodeSocketType *socket_type = bke::node_socket_type_find_static(item.value); + return ntree_type->valid_socket_type(ntree_type, socket_type); + }); +} + +static void node_rna(StructRNA *srna) +{ + RNA_def_node_enum(srna, + "data_type", + "Data Type", + "", + rna_enum_node_socket_data_type_items, + NOD_inline_enum_accessors(custom1), + SOCK_FLOAT, + data_type_items_callback); +} + +static const bNodeSocket *node_internally_linked_input(const bNodeTree & /*tree*/, + const bNode &node, + const bNodeSocket &output_socket) +{ + /* Internal links should always map corresponding input and output sockets. */ + return node.input_by_identifier(output_socket.identifier); +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + + geo_cmp_node_type_base(&ntype, "NodeEnableOutput"); + ntype.ui_name = "Enable Output"; + ntype.ui_description = "Either pass through the input value or output the fallback value"; + ntype.nclass = NODE_CLASS_INTERFACE; + ntype.ignore_inferred_input_socket_visibility = true; + ntype.initfunc = node_init; + ntype.draw_buttons = node_layout; + ntype.declare = node_declare; + ntype.get_compositor_operation = node_get_compositor_operation; + ntype.get_extra_info = node_extra_info; + ntype.internally_linked_input = node_internally_linked_input; + blender::bke::node_register_type(ntype); + + node_rna(ntype.rna_ext.srna); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_enable_output_cc + +namespace blender::nodes { + +std::unique_ptr get_enable_output_node_lazy_function( + const bNode &node, GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info) +{ + using namespace node_geo_enable_output_cc; + return std::make_unique( + node, own_lf_graph_info.mapping.lf_index_by_bsocket); +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/intern/geometry_nodes_caller_ui.cc b/source/blender/nodes/intern/geometry_nodes_caller_ui.cc index ee4afec83b3..d478efc7034 100644 --- a/source/blender/nodes/intern/geometry_nodes_caller_ui.cc +++ b/source/blender/nodes/intern/geometry_nodes_caller_ui.cc @@ -21,6 +21,7 @@ #include "BLT_translation.hh" #include "DNA_modifier_types.h" +#include "DNA_node_tree_interface_types.h" #include "ED_object.hh" #include "ED_screen.hh" @@ -85,6 +86,7 @@ struct DrawGroupInputsContext { PointerRNA *properties_ptr; PointerRNA *bmain_ptr; Array input_usages; + Array output_usages; bool use_name_for_ids = false; std::function panel_open_property_fn; std::function socket_search_data_fn; @@ -808,11 +810,16 @@ static void draw_output_attributes_panel(DrawGroupInputsContext &ctx, uiLayout * if (!ctx.tree || !ctx.properties) { return; } - for (const bNodeTreeInterfaceSocket *socket : ctx.tree->interface_outputs()) { - const bke::bNodeSocketType *typeinfo = socket->socket_typeinfo(); + const Span interface_outputs = ctx.tree->interface_outputs(); + for (const int i : interface_outputs.index_range()) { + const bNodeTreeInterfaceSocket &socket = *interface_outputs[i]; + const bke::bNodeSocketType *typeinfo = socket.socket_typeinfo(); const eNodeSocketDatatype type = typeinfo ? typeinfo->type : SOCK_CUSTOM; + if (!ctx.output_usages[i].is_visible) { + continue; + } if (nodes::socket_type_has_attribute_toggle(type)) { - draw_property_for_output_socket(ctx, layout, *socket); + draw_property_for_output_socket(ctx, layout, socket); } } } @@ -967,8 +974,9 @@ void draw_geometry_nodes_modifier_ui(const bContext &C, PointerRNA *modifier_ptr if (nmd.node_group != nullptr && nmd.settings.properties != nullptr) { nmd.node_group->ensure_interface_cache(); ctx.input_usages.reinitialize(nmd.node_group->interface_inputs().size()); - nodes::socket_usage_inference::infer_group_interface_inputs_usage( - *nmd.node_group, ctx.properties, ctx.input_usages); + ctx.output_usages.reinitialize(nmd.node_group->interface_outputs().size()); + nodes::socket_usage_inference::infer_group_interface_usage( + *nmd.node_group, ctx.properties, ctx.input_usages, ctx.output_usages); draw_interface_panel_content(ctx, &layout, nmd.node_group->tree_interface.root_panel); } @@ -1037,8 +1045,9 @@ void draw_geometry_nodes_operator_redo_ui(const bContext &C, tree.ensure_interface_cache(); ctx.input_usages.reinitialize(tree.interface_inputs().size()); - nodes::socket_usage_inference::infer_group_interface_inputs_usage( - tree, ctx.properties, ctx.input_usages); + ctx.output_usages.reinitialize(tree.interface_outputs().size()); + nodes::socket_usage_inference::infer_group_interface_usage( + tree, ctx.properties, ctx.input_usages, ctx.output_usages); draw_interface_panel_content(ctx, &layout, tree.tree_interface.root_panel); } diff --git a/source/blender/nodes/intern/geometry_nodes_gizmos.cc b/source/blender/nodes/intern/geometry_nodes_gizmos.cc index 55163c2e1bb..143f9517841 100644 --- a/source/blender/nodes/intern/geometry_nodes_gizmos.cc +++ b/source/blender/nodes/intern/geometry_nodes_gizmos.cc @@ -382,7 +382,7 @@ static void foreach_active_gizmo_exposed_to_modifier( tree.ensure_interface_cache(); Array input_usages(tree.interface_inputs().size()); - nodes::socket_usage_inference::infer_group_interface_inputs_usage( + nodes::socket_usage_inference::infer_group_interface_usage( tree, nmd.settings.properties, input_usages); const ComputeContext &root_compute_context = compute_context_cache.for_modifier(nullptr, nmd); diff --git a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc index 053151d243b..886d0cb88b2 100644 --- a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -1285,6 +1285,28 @@ class LazyFunctionForSwitchSocketUsage : public lf::LazyFunction { } }; +class LazyFunctionForEnableOutputSocketUsage : public lf::LazyFunction { + public: + LazyFunctionForEnableOutputSocketUsage() + { + debug_name_ = "Enable Output Socket Usage"; + inputs_.append_as("Enable", CPPType::get()); + outputs_.append_as("Usage", CPPType::get()); + } + + void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override + { + const SocketValueVariant &keep_variant = params.get_input(0); + if (keep_variant.is_single()) { + if (keep_variant.get() == true) { + params.set_output(0, true); + return; + } + } + params.set_output(0, false); + } +}; + /** * Outputs booleans that indicate which inputs of a switch node are used. Note that it's possible * that all inputs are used when the index input is a field. @@ -2870,6 +2892,10 @@ struct GeometryNodesLazyFunctionBuilder { this->build_multi_function_node(bnode, fn_item, graph_params); break; } + if (bnode.is_type("NodeEnableOutput")) { + this->build_enable_output_node(bnode, graph_params); + break; + } if (bnode.is_undefined()) { this->build_undefined_node(bnode, graph_params); break; @@ -3446,6 +3472,42 @@ struct GeometryNodesLazyFunctionBuilder { } } + void build_enable_output_node(const bNode &bnode, BuildGraphParams &graph_params) + { + std::unique_ptr lazy_function = get_enable_output_node_lazy_function( + bnode, *lf_graph_info_); + lf::FunctionNode &lf_node = graph_params.lf_graph.add_function(*lazy_function); + scope_.add(std::move(lazy_function)); + + for (const int i : bnode.input_sockets().index_range()) { + graph_params.lf_inputs_by_bsocket.add(&bnode.input_socket(i), &lf_node.input(i)); + mapping_->bsockets_by_lf_socket_map.add(&lf_node.input(i), &bnode.input_socket(i)); + } + for (const int i : bnode.output_sockets().index_range()) { + graph_params.lf_output_by_bsocket.add(&bnode.output_socket(i), &lf_node.output(i)); + mapping_->bsockets_by_lf_socket_map.add(&lf_node.output(i), &bnode.output_socket(i)); + } + + this->build_enable_output_node_socket_usage(bnode, graph_params); + } + + void build_enable_output_node_socket_usage(const bNode &bnode, BuildGraphParams &graph_params) + { + const bNodeSocket &enable_bsocket = *bnode.input_by_identifier("Enable"); + const bNodeSocket &value_input_bsocket = *bnode.input_by_identifier("Value"); + const bNodeSocket &output_bsocket = bnode.output_socket(0); + lf::OutputSocket *output_is_used_socket = graph_params.usage_by_bsocket.lookup_default( + &output_bsocket, nullptr); + if (!output_is_used_socket) { + return; + } + static LazyFunctionForEnableOutputSocketUsage socket_usage_fn; + lf::Node &lf_node = graph_params.lf_graph.add_function(socket_usage_fn); + graph_params.lf_inputs_by_bsocket.add(&enable_bsocket, &lf_node.input(0)); + graph_params.usage_by_bsocket.add(&enable_bsocket, output_is_used_socket); + graph_params.usage_by_bsocket.add(&value_input_bsocket, &lf_node.output(0)); + } + void build_index_switch_node(const bNode &bnode, BuildGraphParams &graph_params) { std::unique_ptr lazy_function = get_index_switch_node_lazy_function( diff --git a/source/blender/nodes/intern/socket_usage_inference.cc b/source/blender/nodes/intern/socket_usage_inference.cc index cdc22d35192..d102e0b5aa1 100644 --- a/source/blender/nodes/intern/socket_usage_inference.cc +++ b/source/blender/nodes/intern/socket_usage_inference.cc @@ -2,6 +2,7 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#include #include #include "NOD_geometry_nodes_execute.hh" @@ -105,6 +106,16 @@ struct SocketUsageInferencer { return false; } + bool group_output_has_default_value(const int output_i) + { + const bNode *group_output_node = root_tree_.group_output_node(); + if (!group_output_node) { + return true; + } + const SocketInContext socket{nullptr, &group_output_node->input_socket(output_i)}; + return this->socket_has_default_value(socket); + } + bool is_socket_used(const SocketInContext &socket) { const std::optional is_used = all_socket_usages_.lookup_try(socket); @@ -139,6 +150,19 @@ struct SocketUsageInferencer { return value_inferencer_.get_socket_value(socket); } + bool socket_has_default_value(const SocketInContext &socket) + { + const InferenceValue value = this->get_socket_value(socket); + if (!value.is_primitive_value()) { + return false; + } + const CPPType &type = *socket->typeinfo->base_cpp_type; + if (!type.is_equality_comparable()) { + return false; + } + return type.is_equal(value.get_primitive_ptr(), type.default_value()); + } + private: void usage_task(const SocketInContext &socket) { @@ -245,6 +269,10 @@ struct SocketUsageInferencer { break; } default: { + if (node->is_type("NodeEnableOutput")) { + this->usage_task__input__enable_output(socket); + break; + } this->usage_task__input__fallback(socket); break; } @@ -411,6 +439,20 @@ struct SocketUsageInferencer { socket, {&node->output_socket(socket->index())}, {}, socket.context); } + void usage_task__input__enable_output(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + const SocketInContext enable_socket = node.input_socket(0); + const SocketInContext output_socket = node.output_socket(0); + if (socket == enable_socket) { + this->usage_task__with_dependent_sockets(socket, {&*output_socket}, {}, socket.context); + } + else { + this->usage_task__with_dependent_sockets( + socket, {&*output_socket}, {&*enable_socket}, socket.context); + } + } + void usage_task__input__fallback(const SocketInContext &socket) { const SocketDeclaration *socket_decl = socket->runtime->declaration; @@ -548,11 +590,12 @@ static bool input_may_affect_visibility(const bNodeSocket &socket) return socket.type == SOCK_MENU; } -Array infer_all_input_sockets_usage(const bNodeTree &tree) +Array infer_all_sockets_usage(const bNodeTree &tree) { tree.ensure_topology_cache(); const Span all_input_sockets = tree.all_input_sockets(); - Array all_usages(all_input_sockets.size()); + const Span all_output_sockets = tree.all_output_sockets(); + Array all_usages(tree.all_sockets().size()); ResourceScope scope; bke::ComputeContextCache compute_context_cache; @@ -568,9 +611,8 @@ Array infer_all_input_sockets_usage(const bNodeTree &tree) std::nullopt, ignore_top_level_node_muting}; inferencer.mark_top_level_node_outputs_as_used(); - for (const int i : all_input_sockets.index_range()) { - const bNodeSocket &socket = *all_input_sockets[i]; - all_usages[i].is_used = inferencer.is_socket_used({nullptr, &socket}); + for (const bNodeSocket *socket : all_input_sockets) { + all_usages[socket->index_in_tree()].is_used = inferencer.is_socket_used({nullptr, socket}); } } @@ -597,36 +639,58 @@ Array infer_all_input_sockets_usage(const bNodeTree &tree) ignore_top_level_node_muting}; inferencer_all_unknown.mark_top_level_node_outputs_as_used(); inferencer_only_controllers.mark_top_level_node_outputs_as_used(); - for (const int i : all_input_sockets.index_range()) { - if (all_usages[i].is_used) { + for (const bNodeSocket *socket : all_input_sockets) { + SocketUsage &usage = all_usages[socket->index_in_tree()]; + if (usage.is_used) { /* Used inputs are always visible. */ continue; } - const SocketInContext socket{nullptr, all_input_sockets[i]}; - if (inferencer_only_controllers.is_socket_used(socket)) { + const SocketInContext socket_ctx{nullptr, socket}; + if (inferencer_only_controllers.is_socket_used(socket_ctx)) { /* The input should be visible if it's used if only visibility-controlling inputs are * considered. */ continue; } - if (!inferencer_all_unknown.is_socket_used(socket)) { + if (!inferencer_all_unknown.is_socket_used(socket_ctx)) { /* The input should be visible if it's never used, regardless of any inputs. Its usage does * not depend on any visibility-controlling input. */ continue; } - all_usages[i].is_visible = false; + usage.is_visible = false; + } + for (const bNodeSocket *socket : all_output_sockets) { + const bNode &node = socket->owner_node(); + if (node.is_group_input()) { + continue; + } + const SocketInContext socket_ctx{nullptr, socket}; + if (inferencer_all_unknown.socket_has_default_value(socket_ctx)) { + /* The output always has the default value unconditionally. */ + continue; + } + if (!inferencer_only_controllers.socket_has_default_value(socket_ctx)) { + /* The output does not have the default value, so it's used. */ + continue; + } + SocketUsage &usage = all_usages[socket->index_in_tree()]; + usage.is_visible = false; } return all_usages; } -void infer_group_interface_inputs_usage(const bNodeTree &group, - const Span group_input_values, - const MutableSpan r_input_usages) +void infer_group_interface_usage(const bNodeTree &group, + const Span group_input_values, + const MutableSpan r_input_usages, + const std::optional> r_output_usages) { SocketUsage default_usage; default_usage.is_used = false; default_usage.is_visible = true; r_input_usages.fill(default_usage); + if (r_output_usages) { + r_output_usages->fill({true, true}); + } ResourceScope scope; bke::ComputeContextCache compute_context_cache; @@ -641,14 +705,6 @@ void infer_group_interface_inputs_usage(const bNodeTree &group, } } } - if (std::all_of(r_input_usages.begin(), r_input_usages.end(), [](const SocketUsage &usage) { - return usage.is_used; - })) - { - /* If all inputs are used, there is no need to infer visibility because all inputs should be - * visible. */ - return; - } bool visibility_controlling_input_exists = false; Array inputs_all_unknown(group_input_values.size(), InferenceValue::Unknown()); @@ -687,6 +743,19 @@ void infer_group_interface_inputs_usage(const bNodeTree &group, } r_input_usages[i].is_visible = false; } + if (r_output_usages) { + for (const int i : group.interface_outputs().index_range()) { + if (inferencer_all_unknown.group_output_has_default_value(i)) { + continue; + } + if (!inferencer_only_controllers.group_output_has_default_value(i)) { + continue; + } + SocketUsage &usage = (*r_output_usages)[i]; + usage.is_used = false; + usage.is_visible = false; + } + } } void infer_group_interface_inputs_usage(const bNodeTree &group, @@ -716,18 +785,19 @@ void infer_group_interface_inputs_usage(const bNodeTree &group, input_values[i] = InferenceValue::from_primitive(value); } - infer_group_interface_inputs_usage(group, input_values, r_input_usages); + infer_group_interface_usage(group, input_values, r_input_usages, {}); // TODO } -void infer_group_interface_inputs_usage(const bNodeTree &group, - const IDProperty *properties, - MutableSpan r_input_usages) +void infer_group_interface_usage(const bNodeTree &group, + const IDProperty *properties, + MutableSpan r_input_usages, + std::optional> r_output_usages) { ResourceScope scope; const Vector group_input_values = nodes::get_geometry_nodes_input_inference_values(group, properties, scope); - nodes::socket_usage_inference::infer_group_interface_inputs_usage( - group, group_input_values, r_input_usages); + nodes::socket_usage_inference::infer_group_interface_usage( + group, group_input_values, r_input_usages, r_output_usages); } InputSocketUsageParams::InputSocketUsageParams(SocketUsageInferencer &inferencer, diff --git a/source/blender/nodes/intern/socket_value_inference.cc b/source/blender/nodes/intern/socket_value_inference.cc index 6a7e4dc296c..fecb08b2fde 100644 --- a/source/blender/nodes/intern/socket_value_inference.cc +++ b/source/blender/nodes/intern/socket_value_inference.cc @@ -205,6 +205,10 @@ class SocketValueInferencerImpl { return; } default: { + if (node->is_type("NodeEnableOutput")) { + this->value_task__output__enable_output(socket); + return; + } if (node->typeinfo->build_multi_function) { this->value_task__output__multi_function_node(socket); return; @@ -497,6 +501,36 @@ class SocketValueInferencerImpl { } } + void value_task__output__enable_output(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + const SocketInContext enable_input_socket = node.input_socket(0); + const SocketInContext value_input_socket = node.input_socket(1); + + const std::optional keep_value = all_socket_values_.lookup_try( + enable_input_socket); + if (!keep_value.has_value()) { + this->push_value_task(enable_input_socket); + return; + } + if (!keep_value->is_primitive_value()) { + all_socket_values_.add_new(socket, InferenceValue::Unknown()); + return; + } + const bool keep = keep_value->get_primitive(); + if (!keep) { + const CPPType &type = *socket->typeinfo->base_cpp_type; + all_socket_values_.add_new(socket, InferenceValue::from_primitive(type.default_value())); + return; + } + const std::optional value = all_socket_values_.lookup_try(value_input_socket); + if (!value.has_value()) { + this->push_value_task(value_input_socket); + return; + } + all_socket_values_.add_new(socket, *value); + } + /** * Assumes that the first available input is a condition that selects one of the remaining inputs * which is then output.