From b186e6075917e28979d7fcb09a61bd963a9ec95f Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Sat, 27 Sep 2025 10:09:48 +0200 Subject: [PATCH] Nodes: support node output visibility inferencing Currently, only the visibility of input sockets can be changed dynamically based on other menu inputs. However, under some circumstances, it can also be useful to hide certain outputs. For example, the built-in Curve Arc primitive node does that. This patch adds support for automatic detection of unused outputs. How to detect unused outputs is less straight forward compared to inputs. This patch uses the rule that an output is unused if it always outputs a "fallback value" (typically 0) irrespective of the currently used inputs. If the output is independent of all inputs, it stays visible though. There is a new small utility node called "Enable Output". It replaces a value with it's fallback value unless it is disabled. This simplifies setting up unused outputs. In theory, a normal switch node can also be used, but that is less usable and the user will have to hardcode the fallback value for each type which is not something that is explicitly exposed yet. Supporting dynamic output visibility is also a prerequisite for exposing some menu node options as sockets (e.g. in the Arc node). Pull Request: https://projects.blender.org/blender/blender/pulls/140856 --- .../startup/bl_ui/node_add_menu_compositor.py | 1 + .../startup/bl_ui/node_add_menu_geometry.py | 1 + source/blender/blenkernel/BKE_node_runtime.hh | 10 +- source/blender/blenkernel/intern/cpp_types.cc | 11 +- .../blender/blenkernel/intern/node_runtime.cc | 11 +- source/blender/makesdna/DNA_node_types.h | 10 +- .../blender/makesrna/intern/rna_nodetree.cc | 1 + .../nodes/NOD_geometry_nodes_lazy_function.hh | 2 + .../nodes/NOD_socket_usage_inference.hh | 31 +-- source/blender/nodes/geometry/CMakeLists.txt | 1 + .../geometry/nodes/node_geo_enable_output.cc | 199 ++++++++++++++++++ .../nodes/intern/geometry_nodes_caller_ui.cc | 23 +- .../nodes/intern/geometry_nodes_gizmos.cc | 2 +- .../intern/geometry_nodes_lazy_function.cc | 62 ++++++ .../nodes/intern/socket_usage_inference.cc | 126 ++++++++--- .../nodes/intern/socket_value_inference.cc | 34 +++ 16 files changed, 456 insertions(+), 69 deletions(-) create mode 100644 source/blender/nodes/geometry/nodes/node_geo_enable_output.cc 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.