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
This commit is contained in:
Jacques Lucke
2025-09-27 10:09:48 +02:00
parent db88381e75
commit b186e60759
16 changed files with 456 additions and 69 deletions

View File

@@ -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':

View File

@@ -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")

View File

@@ -185,11 +185,10 @@ class bNodeTreeRuntime : NonCopyable, NonMovable {
std::unique_ptr<nodes::StructureTypeInterface> 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<nodes::socket_usage_inference::SocketUsage> inferenced_input_socket_usage;
blender::Array<nodes::socket_usage_inference::SocketUsage> 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

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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);

View File

@@ -445,6 +445,8 @@ std::unique_ptr<LazyFunction> get_menu_switch_node_lazy_function(
const bNode &node, GeometryNodesLazyFunctionGraphInfo &lf_graph_info);
std::unique_ptr<LazyFunction> get_menu_switch_node_socket_usage_lazy_function(const bNode &node);
std::unique_ptr<LazyFunction> get_warning_node_lazy_function(const bNode &node);
std::unique_ptr<LazyFunction> 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

View File

@@ -4,6 +4,8 @@
#pragma once
#include <optional>
#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<SocketUsage> infer_all_input_sockets_usage(const bNodeTree &tree);
Array<SocketUsage> 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<InferenceValue> group_input_values,
MutableSpan<SocketUsage> r_input_usages);
void infer_group_interface_usage(
const bNodeTree &group,
Span<InferenceValue> group_input_values,
MutableSpan<SocketUsage> r_input_usages,
std::optional<MutableSpan<SocketUsage>> 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<SocketUsage> r_input_usages);
void infer_group_interface_usage(
const bNodeTree &group,
const IDProperty *properties,
MutableSpan<SocketUsage> r_input_usages,
std::optional<MutableSpan<SocketUsage>> r_output_usages = std::nullopt);
} // namespace blender::nodes::socket_usage_inference

View File

@@ -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

View File

@@ -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<decl::Bool>("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<int> 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<SocketValueVariant>());
r_lf_index_by_bsocket[node.input_socket(1).index_in_tree()] = inputs_.append_and_get_index_as(
"Value", CPPType::get<SocketValueVariant>(), lf::ValueUsage::Maybe);
r_lf_index_by_bsocket[node.output_socket(0).index_in_tree()] =
outputs_.append_and_get_index_as("Value", CPPType::get<SocketValueVariant>());
}
void execute_impl(lf::Params &params, const lf::Context & /*context*/) const override
{
const bke::SocketValueVariant enable_variant = params.get_input<bke::SocketValueVariant>(0);
if (!enable_variant.is_single()) {
set_default_remaining_node_outputs(params, node_);
return;
}
const bool keep = enable_variant.get<bool>();
if (!keep) {
set_default_remaining_node_outputs(params, node_);
return;
}
const bke::SocketValueVariant *value_variant =
params.try_get_input_data_ptr_or_request<bke::SocketValueVariant>(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<bool>(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 &params)
{
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<bNodeTree *>(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<LazyFunction> 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<LazyFunctionForEnableOutputNode>(
node, own_lf_graph_info.mapping.lf_index_by_bsocket);
}
} // namespace blender::nodes

View File

@@ -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<nodes::socket_usage_inference::SocketUsage> input_usages;
Array<nodes::socket_usage_inference::SocketUsage> output_usages;
bool use_name_for_ids = false;
std::function<PanelOpenProperty(const bNodeTreeInterfacePanel &)> panel_open_property_fn;
std::function<SocketSearchData(const bNodeTreeInterfaceSocket &)> 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<const bNodeTreeInterfaceSocket *> 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);
}

View File

@@ -382,7 +382,7 @@ static void foreach_active_gizmo_exposed_to_modifier(
tree.ensure_interface_cache();
Array<nodes::socket_usage_inference::SocketUsage> 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);

View File

@@ -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<SocketValueVariant>());
outputs_.append_as("Usage", CPPType::get<bool>());
}
void execute_impl(lf::Params &params, const lf::Context & /*context*/) const override
{
const SocketValueVariant &keep_variant = params.get_input<SocketValueVariant>(0);
if (keep_variant.is_single()) {
if (keep_variant.get<bool>() == 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<LazyFunction> 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<LazyFunction> lazy_function = get_index_switch_node_lazy_function(

View File

@@ -2,6 +2,7 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <optional>
#include <regex>
#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<bool> 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<SocketUsage> infer_all_input_sockets_usage(const bNodeTree &tree)
Array<SocketUsage> infer_all_sockets_usage(const bNodeTree &tree)
{
tree.ensure_topology_cache();
const Span<const bNodeSocket *> all_input_sockets = tree.all_input_sockets();
Array<SocketUsage> all_usages(all_input_sockets.size());
const Span<const bNodeSocket *> all_output_sockets = tree.all_output_sockets();
Array<SocketUsage> all_usages(tree.all_sockets().size());
ResourceScope scope;
bke::ComputeContextCache compute_context_cache;
@@ -568,9 +611,8 @@ Array<SocketUsage> 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<SocketUsage> 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<InferenceValue> group_input_values,
const MutableSpan<SocketUsage> r_input_usages)
void infer_group_interface_usage(const bNodeTree &group,
const Span<InferenceValue> group_input_values,
const MutableSpan<SocketUsage> r_input_usages,
const std::optional<MutableSpan<SocketUsage>> 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<InferenceValue, 32> 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<SocketUsage> r_input_usages)
void infer_group_interface_usage(const bNodeTree &group,
const IDProperty *properties,
MutableSpan<SocketUsage> r_input_usages,
std::optional<MutableSpan<SocketUsage>> r_output_usages)
{
ResourceScope scope;
const Vector<InferenceValue> 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,

View File

@@ -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<InferenceValue> 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<bool>();
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<InferenceValue> 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.