diff --git a/scripts/startup/bl_ui/space_userpref.py b/scripts/startup/bl_ui/space_userpref.py index dda9ee49a7c..8e3dd609a80 100644 --- a/scripts/startup/bl_ui/space_userpref.py +++ b/scripts/startup/bl_ui/space_userpref.py @@ -2848,6 +2848,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel): ({"property": "use_new_volume_nodes"}, ("blender/blender/issues/103248", "#103248")), ({"property": "use_shader_node_previews"}, ("blender/blender/issues/110353", "#110353")), ({"property": "use_bundle_and_closure_nodes"}, ("blender/blender/issues/134029", "#134029")), + ({"property": "use_socket_structure_type"}, ("blender/blender/issues/127106", "#127106")), ), ) diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 8108c479703..b686f849c55 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -27,7 +27,7 @@ /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 80 +#define BLENDER_FILE_SUBVERSION 81 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenkernel/BKE_node_runtime.hh b/source/blender/blenkernel/BKE_node_runtime.hh index c800c9688d0..bc97a71385e 100644 --- a/source/blender/blenkernel/BKE_node_runtime.hh +++ b/source/blender/blenkernel/BKE_node_runtime.hh @@ -36,6 +36,7 @@ struct FieldInferencingInterface; struct GeometryNodesEvalDependencies; class NodeDeclaration; struct GeometryNodesLazyFunctionGraphInfo; +struct StructureTypeInterface; namespace anonymous_attribute_lifetime { } namespace aal = anonymous_attribute_lifetime; @@ -175,6 +176,7 @@ class bNodeTreeRuntime : NonCopyable, NonMovable { /** Information about usage of anonymous attributes within the group. */ std::unique_ptr reference_lifetimes_info; std::unique_ptr gizmo_propagation; + std::unique_ptr structure_type_interface; /** * A bool for each input socket (indexed by `index_in_all_inputs()`) that indicates whether this @@ -459,6 +461,10 @@ namespace node_field_inferencing { bool update_field_inferencing(const bNodeTree &tree); } +namespace node_structure_type_inferencing { +bool update_structure_type_interface(bNodeTree &tree); +} + } // namespace blender::bke /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 116d22b66a6..6d34c21a2cf 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -239,6 +239,7 @@ set(SRC intern/node_tree_field_inferencing.cc intern/node_tree_interface.cc intern/node_tree_reference_lifetimes.cc + intern/node_tree_structure_type_inferencing.cc intern/node_tree_update.cc intern/node_tree_zones.cc intern/object.cc diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 83d7a929b45..0787506819b 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -203,6 +203,10 @@ static void ntree_copy_data(Main * /*bmain*/, dst_runtime.field_inferencing_interface = std::make_unique( *ntree_src->runtime->field_inferencing_interface); } + if (ntree_src->runtime->structure_type_interface) { + dst_runtime.structure_type_interface = std::make_unique( + *ntree_src->runtime->structure_type_interface); + } if (ntree_src->runtime->reference_lifetimes_info) { using namespace node_tree_reference_lifetimes; dst_runtime.reference_lifetimes_info = std::make_unique( diff --git a/source/blender/blenkernel/intern/node_tree_field_inferencing.cc b/source/blender/blenkernel/intern/node_tree_field_inferencing.cc index 9716b87cde6..280db898fe8 100644 --- a/source/blender/blenkernel/intern/node_tree_field_inferencing.cc +++ b/source/blender/blenkernel/intern/node_tree_field_inferencing.cc @@ -525,7 +525,7 @@ static void determine_group_input_states( else if (is_layer_selection_field(*group_input)) { new_inferencing_interface.inputs[index] = InputSocketFieldType::Implicit; } - else if (group_input->flag & NODE_INTERFACE_SOCKET_SINGLE_VALUE_ONLY) { + else if (group_input->structure_type == NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_SINGLE) { new_inferencing_interface.inputs[index] = InputSocketFieldType::None; } } diff --git a/source/blender/blenkernel/intern/node_tree_interface.cc b/source/blender/blenkernel/intern/node_tree_interface.cc index a445c77e356..81ff459f713 100644 --- a/source/blender/blenkernel/intern/node_tree_interface.cc +++ b/source/blender/blenkernel/intern/node_tree_interface.cc @@ -578,6 +578,12 @@ void item_write_struct(BlendWriter *writer, bNodeTreeInterfaceItem &item) { switch (NodeTreeInterfaceItemType(item.item_type)) { case NODE_INTERFACE_SOCKET: { + /* Forward compatible writing of older single value only flag. To be removed in 5.0. */ + bNodeTreeInterfaceSocket &socket = get_item_as(item); + SET_FLAG_FROM_TEST(socket.flag, + socket.structure_type == NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_SINGLE, + NODE_INTERFACE_SOCKET_SINGLE_VALUE_ONLY_LEGACY); + BLO_write_struct(writer, bNodeTreeInterfaceSocket, &item); break; } diff --git a/source/blender/blenkernel/intern/node_tree_structure_type_inferencing.cc b/source/blender/blenkernel/intern/node_tree_structure_type_inferencing.cc new file mode 100644 index 00000000000..feb624507ad --- /dev/null +++ b/source/blender/blenkernel/intern/node_tree_structure_type_inferencing.cc @@ -0,0 +1,729 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_array_utils.hh" +#include "BLI_stack.hh" +#include "BLI_utildefines.h" + +#include "BKE_node.hh" +#include "BKE_node_legacy_types.hh" +#include "BKE_node_runtime.hh" + +#include "DNA_node_tree_interface_types.h" +#include "DNA_node_types.h" + +#include "NOD_node_declaration.hh" + +namespace blender::bke::node_structure_type_inferencing { + +using nodes::StructureType; +namespace aal = nodes::anonymous_attribute_lifetime; + +static nodes::StructureTypeInterface calc_node_interface(const bNode &node) +{ + const Span input_sockets = node.input_sockets(); + const Span output_sockets = node.output_sockets(); + + nodes::StructureTypeInterface node_interface; + node_interface.inputs.reinitialize(input_sockets.size()); + node_interface.outputs.reinitialize(output_sockets.size()); + + if (node.is_undefined()) { + node_interface.inputs.fill(StructureType::Dynamic); + node_interface.outputs.fill( + nodes::StructureTypeInterface::OutputDependency{StructureType::Dynamic}); + return node_interface; + } + if (node.is_reroute()) { + node_interface.inputs.first() = StructureType::Dynamic; + node_interface.outputs.first() = {StructureType::Dynamic, {0}}; + return node_interface; + } + + for (const int i : input_sockets.index_range()) { + const nodes::SocketDeclaration &decl = *input_sockets[i]->runtime->declaration; + node_interface.inputs[i] = decl.structure_type; + } + + for (const int output : output_sockets.index_range()) { + const nodes::SocketDeclaration &decl = *output_sockets[output]->runtime->declaration; + nodes::StructureTypeInterface::OutputDependency &dependency = node_interface.outputs[output]; + dependency.type = decl.structure_type; + if (dependency.type != StructureType::Dynamic) { + continue; + } + + /* Currently the input sockets that influence the field status of an output are the same as the + * sockets that influence its structure type. Reuse that for the propagation of structure type + * until there is a more generic format of intra-node dependencies. */ + switch (decl.output_field_dependency.field_type()) { + case nodes::OutputSocketFieldType::None: + break; + case nodes::OutputSocketFieldType::FieldSource: + break; + case nodes::OutputSocketFieldType::DependentField: + dependency.linked_inputs.reinitialize(input_sockets.size()); + array_utils::fill_index_range(dependency.linked_inputs.as_mutable_span()); + break; + case nodes::OutputSocketFieldType::PartiallyDependent: + dependency.linked_inputs = decl.output_field_dependency.linked_input_indices(); + break; + } + } + + return node_interface; +} + +static Array calc_node_interfaces(const bNodeTree &tree) +{ + const Span nodes = tree.all_nodes(); + Array interfaces(nodes.size()); + for (const int i : nodes.index_range()) { + interfaces[i] = calc_node_interface(*nodes[i]); + } + return interfaces; +} + +enum class DataRequirement : int8_t { None, Field, Single, Grid, Invalid }; + +static DataRequirement merge(const DataRequirement a, const DataRequirement b) +{ + if (a == b) { + return a; + } + if (a == DataRequirement::None) { + return b; + } + if (b == DataRequirement::None) { + return a; + } + if ((a == DataRequirement::Field && b == DataRequirement::Single) || + (a == DataRequirement::Single && b == DataRequirement::Field)) + { + /* Single beats field, becasuse fields can accept single values too. */ + return DataRequirement::Single; + } + return DataRequirement::Invalid; +} + +static void init_input_requirements(const bNodeTree &tree, + MutableSpan input_requirements) +{ + const Span input_sockets = tree.all_input_sockets(); + for (const int i : input_sockets.index_range()) { + const bNodeSocket &socket = *input_sockets[i]; + const nodes::SocketDeclaration *declaration = socket.runtime->declaration; + if (!declaration) { + input_requirements[i] = DataRequirement::None; + continue; + } + switch (declaration->structure_type) { + case StructureType::Dynamic: { + input_requirements[i] = DataRequirement::None; + break; + } + case StructureType::Single: { + input_requirements[i] = DataRequirement::Single; + break; + } + case StructureType::Grid: { + input_requirements[i] = DataRequirement::Grid; + break; + } + case StructureType::Field: { + input_requirements[i] = DataRequirement::Field; + break; + } + } + } +} + +static DataRequirement calc_output_socket_requirement( + const bNodeSocket &output_socket, const Span input_requirements) +{ + DataRequirement requirement = DataRequirement::None; + if (!output_socket.is_available()) { + return requirement; + } + for (const bNodeSocket *socket : output_socket.directly_linked_sockets()) { + if (!socket->is_available()) { + continue; + } + requirement = merge(requirement, input_requirements[socket->index_in_all_inputs()]); + } + return requirement; +} + +static void store_group_input_structure_types(const bNodeTree &tree, + const Span input_requirements, + nodes::StructureTypeInterface &derived_interface) +{ + /* Merge usages from all group input nodes. */ + Array interface_requirements(tree.interface_inputs().size(), + DataRequirement::None); + for (const bNode *node : tree.group_input_nodes()) { + const Span output_sockets = node->output_sockets(); + for (const int i : output_sockets.index_range().drop_back(1)) { + const bNodeSocket &output = *output_sockets[i]; + interface_requirements[i] = merge( + interface_requirements[i], calc_output_socket_requirement(output, input_requirements)); + } + } + + /* Build derived interface structure types from group input nodes. */ + for (const int i : tree.interface_inputs().index_range()) { + const bNodeTreeInterfaceSocket &io_socket = *tree.interface_inputs()[i]; + if (io_socket.structure_type != NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_AUTO) { + derived_interface.inputs[i] = StructureType(io_socket.structure_type); + continue; + } + + const DataRequirement requirement = interface_requirements[i]; + switch (requirement) { + case DataRequirement::None: + derived_interface.inputs[i] = StructureType::Dynamic; + break; + case DataRequirement::Field: + derived_interface.inputs[i] = StructureType::Field; + break; + case DataRequirement::Single: + derived_interface.inputs[i] = StructureType::Single; + break; + case DataRequirement::Grid: + derived_interface.inputs[i] = StructureType::Grid; + break; + case DataRequirement::Invalid: + derived_interface.inputs[i] = StructureType::Dynamic; + break; + } + } +} + +enum class ZoneInOutChange { + None = 0, + In = (1 << 1), + Out = (1 << 2), +}; +ENUM_OPERATORS(ZoneInOutChange, ZoneInOutChange::Out); + +static ZoneInOutChange simulation_zone_requirements_propagate( + const bNode &input_node, + const bNode &output_node, + MutableSpan input_requirements) +{ + ZoneInOutChange change = ZoneInOutChange::None; + for (const int i : output_node.output_sockets().index_range()) { + /* First input node output is Delta Time which does not appear in the output node outputs. */ + const bNodeSocket &socket_input = input_node.input_socket(i); + const bNodeSocket &socket_output = output_node.output_socket(i); + const DataRequirement new_value = merge( + input_requirements[socket_input.index_in_all_inputs()], + calc_output_socket_requirement(socket_output, input_requirements)); + if (input_requirements[socket_input.index_in_all_inputs()] != new_value) { + input_requirements[socket_input.index_in_all_inputs()] = new_value; + change |= ZoneInOutChange::In; + } + if (input_requirements[socket_input.index_in_all_inputs()] != new_value) { + input_requirements[socket_input.index_in_all_inputs()] = new_value; + change |= ZoneInOutChange::In; + } + } + return change; +} + +static ZoneInOutChange repeat_zone_requirements_propagate( + const bNode &input_node, + const bNode &output_node, + MutableSpan input_requirements) +{ + ZoneInOutChange change = ZoneInOutChange::None; + for (const int i : output_node.output_sockets().index_range()) { + const bNodeSocket &socket_input = input_node.input_socket(i + 1); + const bNodeSocket &socket_output = output_node.output_socket(i); + const DataRequirement new_value = merge( + input_requirements[socket_input.index_in_all_inputs()], + calc_output_socket_requirement(socket_output, input_requirements)); + if (input_requirements[socket_input.index_in_all_inputs()] != new_value) { + input_requirements[socket_input.index_in_all_inputs()] = new_value; + change |= ZoneInOutChange::In; + } + if (input_requirements[socket_input.index_in_all_inputs()] != new_value) { + input_requirements[socket_input.index_in_all_inputs()] = new_value; + change |= ZoneInOutChange::In; + } + } + return change; +} + +static bool propagate_zone_data_requirements(const bNodeTree &tree, + const bNode &node, + MutableSpan input_requirements) +{ + /* Sync field state between zone nodes and schedule another pass if necessary. */ + switch (node.type_legacy) { + case GEO_NODE_SIMULATION_INPUT: { + const auto &data = *static_cast(node.storage); + if (const bNode *output_node = tree.node_by_id(data.output_node_id)) { + const ZoneInOutChange change = simulation_zone_requirements_propagate( + node, *output_node, input_requirements); + if ((change & ZoneInOutChange::Out) != ZoneInOutChange::None) { + return true; + } + } + return false; + } + case GEO_NODE_SIMULATION_OUTPUT: { + for (const bNode *input_node : tree.nodes_by_type("GeometryNodeSimulationInput")) { + const auto &data = *static_cast(input_node->storage); + if (node.identifier == data.output_node_id) { + const ZoneInOutChange change = simulation_zone_requirements_propagate( + *input_node, node, input_requirements); + if ((change & ZoneInOutChange::In) != ZoneInOutChange::None) { + return true; + } + } + } + return false; + } + case GEO_NODE_REPEAT_INPUT: { + const auto &data = *static_cast(node.storage); + if (const bNode *output_node = tree.node_by_id(data.output_node_id)) { + const ZoneInOutChange change = repeat_zone_requirements_propagate( + node, *output_node, input_requirements); + if ((change & ZoneInOutChange::Out) != ZoneInOutChange::None) { + return true; + } + } + return false; + } + case GEO_NODE_REPEAT_OUTPUT: { + for (const bNode *input_node : tree.nodes_by_type("GeometryNodeRepeatInput")) { + const auto &data = *static_cast(input_node->storage); + if (node.identifier == data.output_node_id) { + const ZoneInOutChange change = repeat_zone_requirements_propagate( + *input_node, node, input_requirements); + if ((change & ZoneInOutChange::In) != ZoneInOutChange::None) { + return true; + } + } + } + return false; + } + } + + return false; +} + +static void propagate_right_to_left(const bNodeTree &tree, + const Span node_interfaces, + MutableSpan input_requirements) +{ + while (true) { + bool need_update = false; + + for (const bNode *node : tree.toposort_right_to_left()) { + const Span input_sockets = node->input_sockets(); + const Span output_sockets = node->output_sockets(); + const nodes::StructureTypeInterface &node_interface = node_interfaces[node->index()]; + + for (const int output : node_interface.outputs.index_range()) { + const bNodeSocket &output_socket = *output_sockets[output]; + DataRequirement ouput_requirement = DataRequirement::None; + for (const bNodeSocket *socket : output_socket.directly_linked_sockets()) { + if (!socket->is_available()) { + continue; + } + ouput_requirement = merge(ouput_requirement, + input_requirements[socket->index_in_all_inputs()]); + } + + /* When a data requirement could be provided by multiple node inputs (i.e. only a single + * node input involved in a math operation has to be a volume grid for the output to be a + * grid), it's better to not propagate the data requirement than incorrectly saying that + * all of the inputs have it. */ + Vector inputs_with_links; + for (const int input : node_interface.outputs[output].linked_inputs) { + const bNodeSocket &input_socket = *input_sockets[input]; + if (input_socket.is_directly_linked()) { + inputs_with_links.append(input_socket.index_in_all_inputs()); + } + } + if (inputs_with_links.size() == 1) { + input_requirements[inputs_with_links.first()] = ouput_requirement; + } + else { + for (const int input : inputs_with_links) { + input_requirements[input] = DataRequirement::None; + } + } + } + + /* Find reverse dependencies and resolve conflicts, which may require another pass. */ + if (propagate_zone_data_requirements(tree, *node, input_requirements)) { + need_update = true; + } + } + + if (!need_update) { + break; + } + } +} + +static StructureType left_to_right_merge(const StructureType a, const StructureType b) +{ + if (a == b) { + return a; + } + if ((a == StructureType::Dynamic && b == StructureType::Single) || + (a == StructureType::Single && b == StructureType::Dynamic)) + { + return StructureType::Dynamic; + } + if ((a == StructureType::Dynamic && b == StructureType::Field) || + (a == StructureType::Field && b == StructureType::Dynamic)) + { + return StructureType::Field; + } + if ((a == StructureType::Dynamic && b == StructureType::Grid) || + (a == StructureType::Grid && b == StructureType::Dynamic)) + { + return StructureType::Grid; + } + if ((a == StructureType::Field && b == StructureType::Grid) || + (a == StructureType::Grid && b == StructureType::Field)) + { + return StructureType::Grid; + } + if ((a == StructureType::Single && b == StructureType::Field) || + (a == StructureType::Field && b == StructureType::Single)) + { + return StructureType::Field; + } + if ((a == StructureType::Single && b == StructureType::Grid) || + (a == StructureType::Grid && b == StructureType::Single)) + { + return StructureType::Grid; + } + /* Invalid combination. */ + return a; +} + +static ZoneInOutChange simulation_zone_status_propagate(const bNode &input_node, + const bNode &output_node, + MutableSpan structure_types) +{ + ZoneInOutChange change = ZoneInOutChange::None; + for (const int i : output_node.output_sockets().index_range()) { + /* First input node output is Delta Time which does not appear in the output node outputs. */ + const bNodeSocket &input = input_node.output_socket(i + 1); + const bNodeSocket &output = output_node.output_socket(i); + const StructureType new_value = left_to_right_merge(structure_types[input.index_in_tree()], + structure_types[output.index_in_tree()]); + if (structure_types[input.index_in_tree()] != new_value) { + structure_types[input.index_in_tree()] = new_value; + change |= ZoneInOutChange::In; + } + if (structure_types[output.index_in_tree()] != new_value) { + structure_types[output.index_in_tree()] = new_value; + change |= ZoneInOutChange::Out; + } + } + return change; +} + +static ZoneInOutChange repeat_zone_status_propagate(const bNode &input_node, + const bNode &output_node, + MutableSpan structure_types) +{ + ZoneInOutChange change = ZoneInOutChange::None; + for (const int i : output_node.output_sockets().index_range()) { + const bNodeSocket &input = input_node.output_socket(i + 1); + const bNodeSocket &output = output_node.output_socket(i); + const StructureType new_value = left_to_right_merge(structure_types[input.index_in_tree()], + structure_types[output.index_in_tree()]); + if (structure_types[input.index_in_tree()] != new_value) { + structure_types[input.index_in_tree()] = new_value; + change |= ZoneInOutChange::In; + } + if (structure_types[output.index_in_tree()] != new_value) { + structure_types[output.index_in_tree()] = new_value; + change |= ZoneInOutChange::Out; + } + } + return change; +} + +static bool propagate_zone_status(const bNodeTree &tree, + const bNode &node, + MutableSpan structure_types) +{ + /* Sync field state between zone nodes and schedule another pass if necessary. */ + switch (node.type_legacy) { + case GEO_NODE_SIMULATION_INPUT: { + const auto &data = *static_cast(node.storage); + if (const bNode *output_node = tree.node_by_id(data.output_node_id)) { + const ZoneInOutChange change = simulation_zone_status_propagate( + node, *output_node, structure_types); + if ((change & ZoneInOutChange::Out) != ZoneInOutChange::None) { + return true; + } + } + return false; + } + case GEO_NODE_SIMULATION_OUTPUT: { + for (const bNode *input_node : tree.nodes_by_type("GeometryNodeSimulationInput")) { + const auto &data = *static_cast(input_node->storage); + if (node.identifier == data.output_node_id) { + const ZoneInOutChange change = simulation_zone_status_propagate( + *input_node, node, structure_types); + if ((change & ZoneInOutChange::In) != ZoneInOutChange::None) { + return true; + } + } + } + return false; + } + case GEO_NODE_REPEAT_INPUT: { + const auto &data = *static_cast(node.storage); + if (const bNode *output_node = tree.node_by_id(data.output_node_id)) { + const ZoneInOutChange change = repeat_zone_status_propagate( + node, *output_node, structure_types); + if ((change & ZoneInOutChange::Out) != ZoneInOutChange::None) { + return true; + } + } + return false; + } + case GEO_NODE_REPEAT_OUTPUT: { + for (const bNode *input_node : tree.nodes_by_type("GeometryNodeRepeatInput")) { + const auto &data = *static_cast(input_node->storage); + if (node.identifier == data.output_node_id) { + const ZoneInOutChange change = repeat_zone_status_propagate( + *input_node, node, structure_types); + if ((change & ZoneInOutChange::In) != ZoneInOutChange::None) { + return true; + } + } + } + return false; + } + } + + return false; +} + +static StructureType get_unconnected_input_structure_type( + const nodes::SocketDeclaration &declaration) +{ + if (declaration.input_field_type == nodes::InputSocketFieldType::Implicit) { + return StructureType::Field; + } + return StructureType::Single; +} + +static void propagate_left_to_right(const bNodeTree &tree, + const Span node_interfaces, + const Span group_input_structure_types, + MutableSpan structure_types) +{ + for (const bNodeSocket *input : tree.all_input_sockets()) { + if (input->owner_node().is_undefined()) { + continue; + } + if (!input->is_directly_linked()) { + const nodes::SocketDeclaration &declaration = *input->runtime->declaration; + structure_types[input->index_in_tree()] = get_unconnected_input_structure_type(declaration); + } + } + while (true) { + bool need_update = false; + for (const bNode *node : tree.toposort_left_to_right()) { + if (node->is_undefined()) { + continue; + } + const Span input_sockets = node->input_sockets(); + const Span output_sockets = node->output_sockets(); + if (node->is_group_input()) { + for (const int i : output_sockets.index_range().drop_back(1)) { + structure_types[output_sockets[i]->index_in_tree()] = group_input_structure_types[i]; + } + continue; + } + + for (const bNodeSocket *input : input_sockets) { + if (!input->is_available()) { + continue; + } + + std::optional input_type; + for (const bNodeLink *link : input->directly_linked_links()) { + if (!link->is_used()) { + continue; + } + const StructureType new_type = structure_types[link->fromsock->index_in_tree()]; + if (input_type) { + input_type = left_to_right_merge(*input_type, new_type); + } + else { + input_type = new_type; + } + } + if (input_type) { + structure_types[input->index_in_tree()] = *input_type; + } + } + + const nodes::StructureTypeInterface &node_interface = node_interfaces[node->index()]; + + for (const int output_index : node_interface.outputs.index_range()) { + const bNodeSocket &output = *output_sockets[output_index]; + if (!output.is_available()) { + continue; + } + const nodes::SocketDeclaration &declaration = *output.runtime->declaration; + + std::optional output_type; + for (const int input_index : node_interface.outputs[output_index].linked_inputs) { + const bNodeSocket &input = node->input_socket(input_index); + if (!input.is_available()) { + continue; + } + const StructureType new_type = structure_types[input.index_in_tree()]; + if (output_type) { + output_type = left_to_right_merge(*output_type, new_type); + } + else { + output_type = new_type; + } + } + structure_types[output.index_in_tree()] = output_type.value_or(declaration.structure_type); + } + + if (propagate_zone_status(tree, *node, structure_types)) { + need_update = true; + } + } + + if (!need_update) { + break; + } + } +} + +static Vector find_dynamic_output_linked_inputs( + const bNodeSocket &group_output, const Span interface_by_node) +{ + /* Use a Set instead of an array indexed by socket because we may only look at a few sockets. */ + Set handled_sockets; + Stack sockets_to_check; + + handled_sockets.add(&group_output); + sockets_to_check.push(&group_output); + + Vector group_inputs; + + while (!sockets_to_check.is_empty()) { + const bNodeSocket *input_socket = sockets_to_check.pop(); + if (!input_socket->is_directly_linked()) { + continue; + } + + for (const bNodeSocket *origin_socket : input_socket->directly_linked_sockets()) { + const bNode &origin_node = origin_socket->owner_node(); + if (origin_node.is_group_input()) { + group_inputs.append_non_duplicates(origin_socket->index()); + continue; + } + + const nodes::StructureTypeInterface &node_interface = interface_by_node[origin_node.index()]; + for (const int input_index : node_interface.outputs[origin_socket->index()].linked_inputs) { + const bNodeSocket &input = origin_node.input_socket(input_index); + if (!input.is_available()) { + continue; + } + if (handled_sockets.add(&input)) { + sockets_to_check.push(&input); + } + } + } + } + + return group_inputs; +} + +static void store_group_output_structure_types( + const bNodeTree &tree, + const Span interface_by_node, + const Span structure_types, + nodes::StructureTypeInterface &interface) +{ + const bNode *group_output_node = tree.group_output_node(); + if (!group_output_node) { + for (nodes::StructureTypeInterface::OutputDependency &output : interface.outputs) { + output.type = StructureType::Dynamic; + } + return; + } + + const Span interface_outputs = tree.interface_outputs(); + const Span sockets = group_output_node->input_sockets().drop_back(1); + for (const int i : sockets.index_range()) { + if (interface_outputs[i]->structure_type != NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_AUTO) { + interface.outputs[i] = {StructureType(interface_outputs[i]->structure_type), {}}; + continue; + } + /* Update derived interface output structure types from output node socket usages. */ + interface.outputs[i].type = structure_types[sockets[i]->index_in_tree()]; + if (interface.outputs[i].type == StructureType::Dynamic) { + const Vector linked_inputs = find_dynamic_output_linked_inputs(*sockets[i], + interface_by_node); + interface.outputs[i] = {StructureType::Dynamic, linked_inputs.as_span()}; + } + } +} + +static std::unique_ptr calc_structure_type_interface( + const bNodeTree &tree) +{ + tree.ensure_topology_cache(); + tree.ensure_interface_cache(); + + auto derived_interface = std::make_unique(); + derived_interface->inputs.reinitialize(tree.interface_inputs().size()); + derived_interface->outputs.reinitialize(tree.interface_outputs().size()); + if (tree.has_available_link_cycle()) { + derived_interface->inputs.fill(StructureType::Dynamic); + derived_interface->outputs.fill({StructureType::Dynamic, {}}); + return derived_interface; + } + + Array node_interfaces = calc_node_interfaces(tree); + + Array data_requirements(tree.all_input_sockets().size()); + Array structure_types(tree.all_sockets().size(), StructureType::Dynamic); + + init_input_requirements(tree, data_requirements); + propagate_right_to_left(tree, node_interfaces, data_requirements); + store_group_input_structure_types(tree, data_requirements, *derived_interface); + propagate_left_to_right(tree, node_interfaces, derived_interface->inputs, structure_types); + store_group_output_structure_types(tree, node_interfaces, structure_types, *derived_interface); + + return derived_interface; +} + +bool update_structure_type_interface(bNodeTree &tree) +{ + std::unique_ptr new_interface = calc_structure_type_interface( + tree); + if (tree.runtime->structure_type_interface && + *tree.runtime->structure_type_interface == *new_interface) + { + return false; + } + tree.runtime->structure_type_interface = std::move(new_interface); + return true; +} + +} // namespace blender::bke::node_structure_type_inferencing diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc index 8def32c9a11..8fed5b011b4 100644 --- a/source/blender/blenkernel/intern/node_tree_update.cc +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -510,6 +510,9 @@ class NodeTreeMainUpdater { if (node_field_inferencing::update_field_inferencing(ntree)) { result.interface_changed = true; } + if (node_structure_type_inferencing::update_structure_type_interface(ntree)) { + result.interface_changed = true; + } this->update_from_field_inference(ntree); if (node_tree_reference_lifetimes::analyse_reference_lifetimes(ntree)) { result.interface_changed = true; @@ -860,21 +863,137 @@ class NodeTreeMainUpdater { } } + static bool socket_type_always_single(const SocketDeclaration &decl) + { + switch (decl.socket_type) { + case SOCK_OBJECT: + case SOCK_IMAGE: + case SOCK_GEOMETRY: + case SOCK_COLLECTION: + case SOCK_TEXTURE: + case SOCK_MATERIAL: + return true; + default: + return false; + } + } + + static int get_input_socket_shape(const SocketDeclaration &decl, + const StructureType structure_type) + { + if (decl.identifier == "__extend__") { + return SOCK_DISPLAY_SHAPE_CIRCLE; + } + if (socket_type_always_single(decl)) { + return SOCK_DISPLAY_SHAPE_LINE; + } + switch (structure_type) { + case StructureType::Single: + return SOCK_DISPLAY_SHAPE_LINE; + case StructureType::Dynamic: + return SOCK_DISPLAY_SHAPE_CIRCLE; + case StructureType::Field: + return SOCK_DISPLAY_SHAPE_DIAMOND; + case StructureType::Grid: + return SOCK_DISPLAY_SHAPE_VOLUME_GRID; + } + BLI_assert_unreachable(); + return SOCK_DISPLAY_SHAPE_CIRCLE; + } + + static int get_output_socket_shape(const SocketDeclaration &decl, + const bke::FieldSocketState field_state, + const StructureType structure_type) + { + if (decl.identifier == "__extend__") { + return SOCK_DISPLAY_SHAPE_CIRCLE; + } + if (socket_type_always_single(decl)) { + return SOCK_DISPLAY_SHAPE_LINE; + } + switch (structure_type) { + case StructureType::Single: { + return SOCK_DISPLAY_SHAPE_LINE; + } + case StructureType::Dynamic: { + return SOCK_DISPLAY_SHAPE_CIRCLE; + } + case StructureType::Field: { + switch (field_state) { + case bke::FieldSocketState::RequiresSingle: + return SOCK_DISPLAY_SHAPE_LINE; + case bke::FieldSocketState::CanBeField: + return SOCK_DISPLAY_SHAPE_CIRCLE; + case bke::FieldSocketState::IsField: + return SOCK_DISPLAY_SHAPE_DIAMOND; + } + break; + } + case StructureType::Grid: { + return SOCK_DISPLAY_SHAPE_VOLUME_GRID; + } + } + BLI_assert_unreachable(); + return SOCK_DISPLAY_SHAPE_CIRCLE; + } + void update_socket_shapes(bNodeTree &ntree) { ntree.ensure_topology_cache(); - const Span field_states = ntree.runtime->field_states; - for (bNodeSocket *socket : ntree.all_sockets()) { - switch (field_states[socket->index_in_tree()]) { - case bke::FieldSocketState::RequiresSingle: - socket->display_shape = SOCK_DISPLAY_SHAPE_CIRCLE; - break; - case bke::FieldSocketState::CanBeField: - socket->display_shape = SOCK_DISPLAY_SHAPE_DIAMOND_DOT; - break; - case bke::FieldSocketState::IsField: - socket->display_shape = SOCK_DISPLAY_SHAPE_DIAMOND; - break; + if (U.experimental.use_socket_structure_type) { + const nodes::StructureTypeInterface &node_interface = + *ntree.runtime->structure_type_interface; + const Span field_states = ntree.runtime->field_states; + for (bNode *node : ntree.all_nodes()) { + if (node->is_undefined()) { + continue; + } + if (node->is_group_input()) { + const Span sockets = node->output_sockets(); + for (const int i : node_interface.inputs.index_range()) { + sockets[i]->display_shape = get_output_socket_shape( + *sockets[i]->runtime->declaration, + field_states[sockets[i]->index_in_tree()], + node_interface.inputs[i]); + } + continue; + } + if (node->is_group_output()) { + const Span sockets = node->input_sockets(); + for (const int i : node_interface.outputs.index_range()) { + sockets[i]->display_shape = get_output_socket_shape( + *sockets[i]->runtime->declaration, + field_states[sockets[i]->index_in_tree()], + node_interface.outputs[i].type); + } + continue; + } + for (bNodeSocket *socket : node->input_sockets()) { + socket->display_shape = get_input_socket_shape( + *socket->runtime->declaration, socket->runtime->declaration->structure_type); + } + for (bNodeSocket *socket : node->output_sockets()) { + socket->display_shape = get_output_socket_shape( + *socket->runtime->declaration, + field_states[socket->index_in_tree()], + socket->runtime->declaration->structure_type); + } + } + } + else { + const Span field_states = ntree.runtime->field_states; + for (bNodeSocket *socket : ntree.all_sockets()) { + switch (field_states[socket->index_in_tree()]) { + case bke::FieldSocketState::RequiresSingle: + socket->display_shape = SOCK_DISPLAY_SHAPE_CIRCLE; + break; + case bke::FieldSocketState::CanBeField: + socket->display_shape = SOCK_DISPLAY_SHAPE_DIAMOND_DOT; + break; + case bke::FieldSocketState::IsField: + socket->display_shape = SOCK_DISPLAY_SHAPE_DIAMOND; + break; + } } } } diff --git a/source/blender/blenloader/intern/versioning_450.cc b/source/blender/blenloader/intern/versioning_450.cc index c557c1aeacd..a2e72766f6f 100644 --- a/source/blender/blenloader/intern/versioning_450.cc +++ b/source/blender/blenloader/intern/versioning_450.cc @@ -5149,6 +5149,25 @@ static void version_convert_sculpt_planar_brushes(Main *bmain) } } +static void node_interface_single_value_to_structure_type(bNodeTreeInterfaceItem &item) +{ + if (item.item_type == eNodeTreeInterfaceItemType::NODE_INTERFACE_SOCKET) { + auto &socket = reinterpret_cast(item); + if (socket.flag & NODE_INTERFACE_SOCKET_SINGLE_VALUE_ONLY_LEGACY) { + socket.structure_type = NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_SINGLE; + } + else { + socket.structure_type = NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_AUTO; + } + } + else { + auto &panel = reinterpret_cast(item); + for (bNodeTreeInterfaceItem *item : blender::Span(panel.items_array, panel.items_num)) { + node_interface_single_value_to_structure_type(*item); + } + } +} + static void version_set_default_bone_drawtype(Main *bmain) { LISTBASE_FOREACH (bArmature *, arm, &bmain->armatures) { @@ -6067,6 +6086,14 @@ void blo_do_versions_450(FileData * /*fd*/, Library * /*lib*/, Main *bmain) FOREACH_NODETREE_END; } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 81)) { + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + if (ntree->type == NTREE_GEOMETRY) { + node_interface_single_value_to_structure_type(ntree->tree_interface.root_panel.item); + } + } + } + /* Always run this versioning (keep at the bottom of the function). Meshes are written with the * legacy format which always needs to be converted to the new format on file load. To be moved * to a subversion check in 5.0. */ diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index 019453a72e1..b4a57559d72 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -1476,12 +1476,8 @@ static void std_node_socket_interface_draw(ID *id, uiLayout *sub = &col->column(false); uiLayoutSetActive(sub, !is_layer_selection_field(*interface_socket)); sub->prop(&ptr, "hide_in_modifier", DEFAULT_FLAGS, std::nullopt, ICON_NONE); - if (nodes::socket_type_supports_fields(type)) { - uiLayout *sub_sub = &col->column(false); - uiLayoutSetActive(sub_sub, - (interface_socket->default_input == NODE_DEFAULT_INPUT_VALUE) && - !is_layer_selection_field(*interface_socket)); - sub_sub->prop(&ptr, "force_non_field", DEFAULT_FLAGS, std::nullopt, ICON_NONE); + if (nodes::socket_type_supports_fields(type) || nodes::socket_type_supports_grids(type)) { + sub->prop(&ptr, "structure_type", DEFAULT_FLAGS, std::nullopt, ICON_NONE); } } } diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 7cc5e86aca4..0d584f35e2e 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -2167,6 +2167,24 @@ static std::string node_socket_get_tooltip(const SpaceNode *snode, if (std::optional info = create_declaration_inspection_string(socket)) { inspection_strings.append(std::move(*info)); } + if (U.experimental.use_socket_structure_type) { + if (socket.runtime->declaration) { + switch (socket.runtime->declaration->structure_type) { + case nodes::StructureType::Single: + inspection_strings.append("(Single Value)"); + break; + case nodes::StructureType::Dynamic: + inspection_strings.append("(Dynamic Structure Type)"); + break; + case nodes::StructureType::Field: + inspection_strings.append("(Field)"); + break; + case nodes::StructureType::Grid: + inspection_strings.append("(Volume Grid)"); + break; + } + } + } std::stringstream output; for (const std::string &info : inspection_strings) { diff --git a/source/blender/editors/space_node/node_group.cc b/source/blender/editors/space_node/node_group.cc index 46e7989db31..adc31bd11b4 100644 --- a/source/blender/editors/space_node/node_group.cc +++ b/source/blender/editors/space_node/node_group.cc @@ -1180,6 +1180,7 @@ static void node_group_make_insert_selected(const bContext &C, if (group.type == NTREE_GEOMETRY) { bke::node_field_inferencing::update_field_inferencing(group); + bke::node_structure_type_inferencing::update_structure_type_interface(group); } nodes::update_node_declaration_and_sockets(ntree, *gnode); diff --git a/source/blender/gpu/shaders/gpu_shader_2D_node_socket_frag.glsl b/source/blender/gpu/shaders/gpu_shader_2D_node_socket_frag.glsl index 2b0dae60900..b7a1e85c444 100644 --- a/source/blender/gpu/shaders/gpu_shader_2D_node_socket_frag.glsl +++ b/source/blender/gpu/shaders/gpu_shader_2D_node_socket_frag.glsl @@ -15,6 +15,8 @@ FRAGMENT_SHADER_CREATE_INFO(gpu_shader_2D_node_socket_inst) #define SOCK_DISPLAY_SHAPE_CIRCLE_DOT 3 #define SOCK_DISPLAY_SHAPE_SQUARE_DOT 4 #define SOCK_DISPLAY_SHAPE_DIAMOND_DOT 5 +#define SOCK_DISPLAY_SHAPE_LINE 6 +#define SOCK_DISPLAY_SHAPE_VOLUME_GRID 7 /* Calculates a squared distance field of a square. */ float square_sdf(float2 absCo, float2 half_size) @@ -93,6 +95,22 @@ void main() dot_threshold = finalDotRadius; break; } + case SOCK_DISPLAY_SHAPE_LINE: { + float square_radius = square_radius - corner_rounding; + distance_squared = square_sdf(co, float2(square_radius * 0.75, square_radius * 1.4)); + alpha_threshold = corner_rounding; + break; + } + case SOCK_DISPLAY_SHAPE_VOLUME_GRID: { + float size = 0.7; + float2 uv = abs(absUV - size * 0.5) - size * 0.4; + float radius_out = length(max(uv, 0.0)); + float radius_in = max(abs(uv).x, abs(uv).y) * -1.0; + float radius = mix(radius_in, radius_out, radius_out > 0); + distance_squared = max(-1.0, (radius - size * 0.15)); + alpha_threshold = -0.2; + break; + } } float2 alpha_thresholds = calculate_thresholds(alpha_threshold); diff --git a/source/blender/makesdna/DNA_node_tree_interface_types.h b/source/blender/makesdna/DNA_node_tree_interface_types.h index 73c89eb2862..39e50273604 100644 --- a/source/blender/makesdna/DNA_node_tree_interface_types.h +++ b/source/blender/makesdna/DNA_node_tree_interface_types.h @@ -61,7 +61,8 @@ typedef enum NodeTreeInterfaceSocketFlag { NODE_INTERFACE_SOCKET_HIDE_VALUE = 1 << 2, NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER = 1 << 3, NODE_INTERFACE_SOCKET_COMPACT = 1 << 4, - NODE_INTERFACE_SOCKET_SINGLE_VALUE_ONLY = 1 << 5, + /* To be deprecated when structure types are moved out of experimental. */ + NODE_INTERFACE_SOCKET_SINGLE_VALUE_ONLY_LEGACY = 1 << 5, NODE_INTERFACE_SOCKET_LAYER_SELECTION = 1 << 6, /* INSPECT is used by Connect to Output operator to ensure socket that exits from node group. */ NODE_INTERFACE_SOCKET_INSPECT = 1 << 7, @@ -72,6 +73,26 @@ typedef enum NodeTreeInterfaceSocketFlag { } NodeTreeInterfaceSocketFlag; ENUM_OPERATORS(NodeTreeInterfaceSocketFlag, NODE_INTERFACE_SOCKET_PANEL_TOGGLE); +typedef enum NodeSocketInterfaceStructureType { + NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_AUTO = 0, + NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_SINGLE = 1, + NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_DYNAMIC = 2, + NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_FIELD = 3, + NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_GRID = 4, +} NodeSocketInterfaceStructureType; + +// TODO: Move out of DNA. +#ifdef __cplusplus +namespace blender::nodes { +enum class StructureType : int8_t { + Single = NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_SINGLE, + Dynamic = NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_DYNAMIC, + Field = NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_FIELD, + Grid = NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_GRID, +}; +} +#endif + typedef struct bNodeTreeInterfaceSocket { bNodeTreeInterfaceItem item; @@ -96,6 +117,10 @@ typedef struct bNodeTreeInterfaceSocket { struct IDProperty *properties; + /** #NodeSocketInterfaceStructureType. */ + int8_t structure_type; + char _pad[7]; + #ifdef __cplusplus bNodeSocketTypeHandle *socket_typeinfo() const; blender::ColorGeometry4f socket_color() const; diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 1fa46177e98..af327c518cc 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -321,6 +321,8 @@ typedef enum eNodeSocketDisplayShape { SOCK_DISPLAY_SHAPE_CIRCLE_DOT = 3, SOCK_DISPLAY_SHAPE_SQUARE_DOT = 4, SOCK_DISPLAY_SHAPE_DIAMOND_DOT = 5, + SOCK_DISPLAY_SHAPE_LINE = 6, + SOCK_DISPLAY_SHAPE_VOLUME_GRID = 7, } eNodeSocketDisplayShape; /** Socket side (input/output). */ diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 532239f11eb..154b3446b4b 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -227,7 +227,8 @@ typedef struct UserDef_Experimental { char use_new_volume_nodes; char use_shader_node_previews; char use_bundle_and_closure_nodes; - char _pad[5]; + char use_socket_structure_type; + char _pad[4]; } UserDef_Experimental; #define USER_EXPERIMENTAL_TEST(userdef, member) \ diff --git a/source/blender/makesrna/intern/rna_node_tree_interface.cc b/source/blender/makesrna/intern/rna_node_tree_interface.cc index 5c92d4bdf10..f0ef91c6091 100644 --- a/source/blender/makesrna/intern/rna_node_tree_interface.cc +++ b/source/blender/makesrna/intern/rna_node_tree_interface.cc @@ -26,6 +26,26 @@ static const EnumPropertyItem node_tree_interface_socket_in_out_items[] = { {NODE_INTERFACE_SOCKET_OUTPUT, "OUTPUT", 0, "Output", "Generate a output node socket"}, {0, nullptr, 0, nullptr, nullptr}}; +static const EnumPropertyItem node_tree_interface_socket_structure_type_items[] = { + {NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_AUTO, + "AUTO", + 0, + "Auto", + "Automatically detect a good structure type based on how the socket is used"}, + {NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_SINGLE, + "SINGLE", + 0, + "Single", + "Socket expects a single value"}, + {NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_DYNAMIC, + "DYNAMIC", + 0, + "Dynamic", + "Socket can work with different kinds of structures"}, + {NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_FIELD, "FIELD", 0, "Field", "Socket expects a field"}, + {NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_GRID, "GRID", 0, "Grid", "Socket expects a grid"}, + {0, nullptr, 0, nullptr, nullptr}}; + static const EnumPropertyItem node_default_input_items[] = { {NODE_DEFAULT_INPUT_VALUE, "VALUE", 0, "Default Value", "The node socket's default value"}, {NODE_DEFAULT_INPUT_INDEX_FIELD, "INDEX", 0, "Index", "The index from the context"}, @@ -76,6 +96,7 @@ static const EnumPropertyItem node_default_input_items[] = { # include "BLT_translation.hh" # include "NOD_node_declaration.hh" +# include "NOD_socket.hh" # include "DNA_material_types.h" # include "ED_node.hh" @@ -444,6 +465,80 @@ static const EnumPropertyItem *rna_NodeTreeInterfaceSocket_socket_type_itemf( ntree->typeinfo, rna_NodeTreeInterfaceSocket_socket_type_poll, r_free); } +/** + * Also control the structure type when setting the "Is Single" status. To be removed when the + * structure type feature is moved out of experimental. + */ +static void rna_NodeTreeInterfaceSocket_force_non_field_set(PointerRNA *ptr, const bool value) +{ + bNodeTreeInterfaceSocket *socket = static_cast(ptr->data); + SET_FLAG_FROM_TEST(socket->flag, value, NODE_INTERFACE_SOCKET_SINGLE_VALUE_ONLY_LEGACY); + socket->structure_type = value ? NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_SINGLE : + NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_AUTO; +} + +static const EnumPropertyItem *rna_NodeTreeInterfaceSocket_structure_type_itemf( + bContext * /*C*/, PointerRNA *ptr, PropertyRNA * /*prop*/, bool *r_free) +{ + const bNodeTree *ntree = reinterpret_cast(ptr->owner_id); + const bNodeTreeInterfaceSocket *socket = static_cast( + ptr->data); + if (!ntree) { + return rna_enum_dummy_NULL_items; + } + + const bool is_geometry_nodes = ntree->type == NTREE_GEOMETRY; + + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(socket->socket_typeinfo()->type); + const bool supports_fields = is_geometry_nodes && + blender::nodes::socket_type_supports_fields(socket_type); + const bool supports_grids = is_geometry_nodes && + blender::nodes::socket_type_supports_grids(socket_type); + + *r_free = true; + EnumPropertyItem *items = nullptr; + int items_count = 0; + + for (const EnumPropertyItem *item = node_tree_interface_socket_structure_type_items; + item->identifier; + item++) + { + switch (NodeSocketInterfaceStructureType(item->value)) { + case NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_SINGLE: + case NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_AUTO: { + RNA_enum_item_add(&items, &items_count, item); + break; + } + case NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_DYNAMIC: { + if (U.experimental.use_socket_structure_type) { + if (supports_fields || supports_grids) { + RNA_enum_item_add(&items, &items_count, item); + } + } + break; + } + case NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_FIELD: { + if (U.experimental.use_socket_structure_type) { + if (supports_fields) { + RNA_enum_item_add(&items, &items_count, item); + } + } + break; + } + case NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_GRID: { + if (U.experimental.use_socket_structure_type) { + if (supports_grids) { + RNA_enum_item_add(&items, &items_count, item); + } + } + break; + } + } + } + RNA_enum_item_end(&items, &items_count); + return items; +} + static const EnumPropertyItem *rna_NodeTreeInterfaceSocket_default_input_itemf( bContext * /*C*/, PointerRNA *ptr, PropertyRNA * /*prop*/, bool *r_free) { @@ -1055,10 +1150,14 @@ static void rna_def_node_interface_socket(BlenderRNA *brna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update"); prop = RNA_def_property(srna, "force_non_field", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, nullptr, "flag", NODE_INTERFACE_SOCKET_SINGLE_VALUE_ONLY); + RNA_def_property_boolean_sdna( + prop, nullptr, "flag", NODE_INTERFACE_SOCKET_SINGLE_VALUE_ONLY_LEGACY); + RNA_def_property_boolean_funcs(prop, nullptr, "rna_NodeTreeInterfaceSocket_force_non_field_set"); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); RNA_def_property_ui_text( - prop, "Single Value", "Only allow single value inputs rather than fields"); + prop, + "Single Value", + "Only allow single value inputs rather than field.\nDeprecated. Will be remove in 5.0."); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update"); prop = RNA_def_property(srna, "is_inspect_output", PROP_BOOLEAN, PROP_NONE); @@ -1109,6 +1208,16 @@ static void rna_def_node_interface_socket(BlenderRNA *brna) "geometry nodes modifier"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update"); + prop = RNA_def_property(srna, "structure_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, node_tree_interface_socket_structure_type_items); + RNA_def_property_ui_text( + prop, + "Structure Type", + "What kind of higher order types are expected to flow through this socket"); + RNA_def_property_enum_funcs( + prop, nullptr, nullptr, "rna_NodeTreeInterfaceSocket_structure_type_itemf"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update"); + prop = RNA_def_property(srna, "default_input", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, node_default_input_items); RNA_def_property_ui_text( diff --git a/source/blender/makesrna/intern/rna_userdef.cc b/source/blender/makesrna/intern/rna_userdef.cc index 48c75d8cdba..bc10661d0b1 100644 --- a/source/blender/makesrna/intern/rna_userdef.cc +++ b/source/blender/makesrna/intern/rna_userdef.cc @@ -7574,6 +7574,12 @@ static void rna_def_userdef_experimental(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Bundle and Closure Nodes", "Enables bundle and closure nodes in Geometry Nodes"); + prop = RNA_def_property(srna, "use_socket_structure_type", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_ui_text( + prop, + "Node Structure Types", + "Enables new visualization of socket data compatibility in Geometry Nodes"); + prop = RNA_def_property(srna, "use_extensions_debug", PROP_BOOLEAN, PROP_NONE); RNA_def_property_ui_text( prop, diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 4eb8dd3b90b..c52dba96687 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -909,6 +909,8 @@ static void check_property_socket_sync(const Object *ob, int geometry_socket_count = 0; nmd->node_group->ensure_interface_cache(); + const Span input_structure_types = + nmd->node_group->runtime->structure_type_interface->inputs; for (const int i : nmd->node_group->interface_inputs().index_range()) { const bNodeTreeInterfaceSocket *socket = nmd->node_group->interface_inputs()[i]; const bke::bNodeSocketType *typeinfo = socket->socket_typeinfo(); @@ -920,6 +922,9 @@ static void check_property_socket_sync(const Object *ob, if (i == 0 && type == SOCK_GEOMETRY) { continue; } + if (input_structure_types[i] == nodes::StructureType::Grid) { + continue; + } IDProperty *property = properties.lookup_key_default_as(socket->identifier, nullptr); if (property == nullptr) { diff --git a/source/blender/nodes/NOD_geometry_nodes_execute.hh b/source/blender/nodes/NOD_geometry_nodes_execute.hh index c70d1946288..db9ed8eaac1 100644 --- a/source/blender/nodes/NOD_geometry_nodes_execute.hh +++ b/source/blender/nodes/NOD_geometry_nodes_execute.hh @@ -64,7 +64,9 @@ bool id_property_type_matches_socket(const bNodeTreeInterfaceSocket &socket, bool use_name_for_ids = false); std::unique_ptr id_property_create_from_socket( - const bNodeTreeInterfaceSocket &socket, bool use_name_for_ids); + const bNodeTreeInterfaceSocket &socket, + nodes::StructureType structure_type, + bool use_name_for_ids); bke::GeometrySet execute_geometry_nodes_on_geometry(const bNodeTree &btree, const PropertiesVectorSet &properties_set, diff --git a/source/blender/nodes/NOD_node_declaration.hh b/source/blender/nodes/NOD_node_declaration.hh index 6db0d136f30..cdcc7443b57 100644 --- a/source/blender/nodes/NOD_node_declaration.hh +++ b/source/blender/nodes/NOD_node_declaration.hh @@ -88,6 +88,20 @@ struct FieldInferencingInterface { BLI_STRUCT_EQUALITY_OPERATORS_2(FieldInferencingInterface, inputs, outputs) }; +struct StructureTypeInterface { + struct OutputDependency { + StructureType type; + Array linked_inputs; + + BLI_STRUCT_EQUALITY_OPERATORS_2(OutputDependency, type, linked_inputs) + }; + + Array inputs; + Array outputs; + + BLI_STRUCT_EQUALITY_OPERATORS_2(StructureTypeInterface, inputs, outputs) +}; + namespace anonymous_attribute_lifetime { /** @@ -212,6 +226,8 @@ class SocketDeclaration : public ItemDeclaration { InputSocketFieldType input_field_type = InputSocketFieldType::None; OutputFieldDependency output_field_dependency; + StructureType structure_type = StructureType::Single; + private: CompositorInputRealizationMode compositor_realization_mode_ = CompositorInputRealizationMode::OperationDomain; @@ -422,6 +438,8 @@ class BaseSocketDeclarationBuilder { */ BaseSocketDeclarationBuilder &panel_toggle(bool value = true); + BaseSocketDeclarationBuilder &structure_type(StructureType structure_type); + BaseSocketDeclarationBuilder &is_layer_name(bool value = true); /** Index in the list of inputs or outputs. */ diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc index c023d5e3f7d..809a6866e8f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc @@ -51,8 +51,10 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output(data_type, item.name, output_identifier).field_on_all().align_with_previous(); } } - b.add_input("", "__extend__"); - b.add_output("", "__extend__").align_with_previous(); + b.add_input("", "__extend__").structure_type(StructureType::Field); + b.add_output("", "__extend__") + .structure_type(StructureType::Field) + .align_with_previous(); } static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) diff --git a/source/blender/nodes/geometry/nodes/node_geo_bake.cc b/source/blender/nodes/geometry/nodes/node_geo_bake.cc index 3a27d7abb12..29a2b402718 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_bake.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_bake.cc @@ -80,8 +80,10 @@ static void node_declare(NodeDeclarationBuilder &b) } } } - b.add_input("", "__extend__"); - b.add_output("", "__extend__").align_with_previous(); + b.add_input("", "__extend__").structure_type(StructureType::Dynamic); + b.add_output("", "__extend__") + .structure_type(StructureType::Dynamic) + .align_with_previous(); } static void node_init(bNodeTree * /*tree*/, bNode *node) diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc index 8c100507765..a30aca80c5d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc @@ -36,19 +36,19 @@ static void node_declare(NodeDeclarationBuilder &b) .min(0.0f) .max(1.0f) .subtype(PROP_FACTOR) - .field_on_all() + .supports_field() .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_FACTOR; }); auto &length = b.add_input("Length") .min(0.0f) .subtype(PROP_DISTANCE) - .field_on_all() + .supports_field() .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_LENGTH; }); auto &index = - b.add_input("Curve Index").field_on_all().make_available([](bNode &node) { + b.add_input("Curve Index").supports_field().make_available([](bNode &node) { node_storage(node).use_all_curves = false; }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_in_grid.cc b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_in_grid.cc index ddf0ee6f41b..6b730a0d606 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_in_grid.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_in_grid.cc @@ -34,7 +34,7 @@ enum class DistributeMode { static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Grid").hide_value(); + b.add_input("Grid").hide_value().structure_type(StructureType::Grid); auto &density = b.add_input("Density") .default_value(1.0f) .min(0.0f) diff --git a/source/blender/nodes/geometry/nodes/node_geo_foreach_geometry_element.cc b/source/blender/nodes/geometry/nodes/node_geo_foreach_geometry_element.cc index b570087f661..b63f2e479b4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_foreach_geometry_element.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_foreach_geometry_element.cc @@ -158,8 +158,10 @@ static void node_declare(NodeDeclarationBuilder &b) } } - b.add_input("", "__extend__"); - b.add_output("", "__extend__").align_with_previous(); + b.add_input("", "__extend__").structure_type(StructureType::Dynamic); + b.add_output("", "__extend__") + .structure_type(StructureType::Dynamic) + .align_with_previous(); } static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) diff --git a/source/blender/nodes/geometry/nodes/node_geo_get_named_grid.cc b/source/blender/nodes/geometry/nodes/node_geo_get_named_grid.cc index b45156985b0..b69ba4764ea 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_get_named_grid.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_get_named_grid.cc @@ -33,7 +33,7 @@ static void node_declare(NodeDeclarationBuilder &b) return; } - b.add_output(eNodeSocketDatatype(node->custom1), "Grid"); + b.add_output(eNodeSocketDatatype(node->custom1), "Grid").structure_type(StructureType::Grid); } static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) diff --git a/source/blender/nodes/geometry/nodes/node_geo_grid_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_grid_to_mesh.cc index 02a2e588e97..e6fb649ebe4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_grid_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_grid_to_mesh.cc @@ -16,7 +16,7 @@ namespace blender::nodes::node_geo_grid_to_mesh_cc { static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Grid").hide_value(); + b.add_input("Grid").hide_value().structure_type(StructureType::Grid); b.add_input("Threshold") .default_value(0.1f) .description("Values larger than the threshold are inside the generated mesh"); diff --git a/source/blender/nodes/geometry/nodes/node_geo_menu_switch.cc b/source/blender/nodes/geometry/nodes/node_geo_menu_switch.cc index b083ba73889..f4c63d268dd 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_menu_switch.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_menu_switch.cc @@ -95,7 +95,7 @@ static void node_declare(blender::nodes::NodeDeclarationBuilder &b) output.propagate_all(); } - b.add_input("", "__extend__"); + b.add_input("", "__extend__").structure_type(StructureType::Dynamic); } static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_density_grid.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_density_grid.cc index 2bbdeecc71d..461bb638753 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_density_grid.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_density_grid.cc @@ -29,7 +29,7 @@ static void node_declare(NodeDeclarationBuilder &b) .max(FLT_MAX) .subtype(PROP_DISTANCE) .description("Width of the gradient inside of the mesh"); - b.add_output("Density Grid"); + b.add_output("Density Grid").structure_type(StructureType::Grid); } static void node_geo_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_sdf_grid.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_sdf_grid.cc index 4ce9642bbdd..bae02703c73 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_sdf_grid.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_sdf_grid.cc @@ -25,7 +25,7 @@ static void node_declare(NodeDeclarationBuilder &b) .min(1) .max(100) .description("Width of the active voxel surface, in voxels"); - b.add_output("SDF Grid"); + b.add_output("SDF Grid").structure_type(StructureType::Grid); } static void node_geo_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_sdf_grid.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_sdf_grid.cc index 4c16608457c..03ee5761315 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_sdf_grid.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_sdf_grid.cc @@ -20,7 +20,7 @@ static void node_declare(NodeDeclarationBuilder &b) .subtype(PROP_DISTANCE) .field_on_all(); b.add_input("Voxel Size").default_value(0.3f).min(0.01f).subtype(PROP_DISTANCE); - b.add_output("SDF Grid"); + b.add_output("SDF Grid").structure_type(StructureType::Grid); } #ifdef WITH_OPENVDB diff --git a/source/blender/nodes/geometry/nodes/node_geo_repeat.cc b/source/blender/nodes/geometry/nodes/node_geo_repeat.cc index 00bd046350e..0021e8e143b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_repeat.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_repeat.cc @@ -78,8 +78,7 @@ static void node_declare(NodeDeclarationBuilder &b) const bNodeTree *tree = b.tree_or_null(); if (node && tree) { const NodeGeometryRepeatInput &storage = node_storage(*node); - const bNode *output_node = tree->node_by_id(storage.output_node_id); - if (output_node) { + if (const bNode *output_node = tree->node_by_id(storage.output_node_id)) { const auto &output_storage = *static_cast( output_node->storage); for (const int i : IndexRange(output_storage.items_num)) { @@ -98,8 +97,10 @@ static void node_declare(NodeDeclarationBuilder &b) } } } - b.add_input("", "__extend__"); - b.add_output("", "__extend__").align_with_previous(); + b.add_input("", "__extend__").structure_type(StructureType::Dynamic); + b.add_output("", "__extend__") + .structure_type(StructureType::Dynamic) + .align_with_previous(); } static void node_init(bNodeTree * /*tree*/, bNode *node) @@ -177,8 +178,10 @@ static void node_declare(NodeDeclarationBuilder &b) } } } - b.add_input("", "__extend__"); - b.add_output("", "__extend__").align_with_previous(); + b.add_input("", "__extend__").structure_type(StructureType::Dynamic); + b.add_output("", "__extend__") + .structure_type(StructureType::Dynamic) + .align_with_previous(); } static void node_init(bNodeTree * /*tree*/, bNode *node) diff --git a/source/blender/nodes/geometry/nodes/node_geo_sample_grid.cc b/source/blender/nodes/geometry/nodes/node_geo_sample_grid.cc index a98cd4a231a..e01adf49673 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_sample_grid.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_sample_grid.cc @@ -37,7 +37,7 @@ static void node_declare(NodeDeclarationBuilder &b) } const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1); - b.add_input(data_type, "Grid").hide_value(); + b.add_input(data_type, "Grid").hide_value().structure_type(StructureType::Grid); b.add_input("Position").implicit_field(NODE_DEFAULT_INPUT_POSITION_FIELD); b.add_output(data_type, "Value").dependent_field({1}); diff --git a/source/blender/nodes/geometry/nodes/node_geo_sample_grid_index.cc b/source/blender/nodes/geometry/nodes/node_geo_sample_grid_index.cc index 9460a661b8d..b67a68eea01 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_sample_grid_index.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_sample_grid_index.cc @@ -28,7 +28,7 @@ static void node_declare(NodeDeclarationBuilder &b) } const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1); - b.add_input(data_type, "Grid").hide_value(); + b.add_input(data_type, "Grid").hide_value().structure_type(StructureType::Grid); b.add_input("X").supports_field(); b.add_input("Y").supports_field(); b.add_input("Z").supports_field(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_sdf_grid_boolean.cc b/source/blender/nodes/geometry/nodes/node_geo_sdf_grid_boolean.cc index 1907a31f82b..516313ba116 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_sdf_grid_boolean.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_sdf_grid_boolean.cc @@ -30,7 +30,8 @@ static void node_declare(NodeDeclarationBuilder &b) { const bNode *node = b.node_or_null(); - auto &first_grid = b.add_input("Grid 1").hide_value(); + auto &first_grid = b.add_input("Grid 1").hide_value().structure_type( + StructureType::Grid); if (node) { static const auto make_available = [](bNode &node) { @@ -42,16 +43,20 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_input("Grid", "Grid 2") .hide_value() .multi_input() - .make_available(make_available); + .make_available(make_available) + .structure_type(StructureType::Grid); break; case Operation::Difference: - b.add_input("Grid 2").hide_value().multi_input().make_available( - make_available); + b.add_input("Grid 2") + .hide_value() + .multi_input() + .make_available(make_available) + .structure_type(StructureType::Grid); break; } } - b.add_output("Grid").hide_value(); + b.add_output("Grid").hide_value().structure_type(StructureType::Grid); if (node) { switch (Operation(node->custom1)) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_simulation.cc b/source/blender/nodes/geometry/nodes/node_geo_simulation.cc index 2d9e198efee..fabe7bd8a61 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_simulation.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation.cc @@ -436,12 +436,15 @@ static void node_declare(NodeDeclarationBuilder &b) &node_tree->id, SimulationItemsAccessor::item_srna, &item, "name"); auto &output_decl = b.add_output(socket_type, name, identifier).align_with_previous(); if (socket_type_supports_fields(socket_type)) { - input_decl.supports_field(); + /* If it's below a geometry input it may be a field evaluated on that geometry. */ + input_decl.supports_field().structure_type(StructureType::Dynamic); output_decl.dependent_field({input_decl.index()}); } } - b.add_input("", "__extend__"); - b.add_output("", "__extend__").align_with_previous(); + b.add_input("", "__extend__").structure_type(StructureType::Dynamic); + b.add_output("", "__extend__") + .structure_type(StructureType::Dynamic) + .align_with_previous(); } static void node_init(bNodeTree * /*tree*/, bNode *node) @@ -796,12 +799,15 @@ static void node_declare(NodeDeclarationBuilder &b) &tree->id, SimulationItemsAccessor::item_srna, &item, "name"); auto &output_decl = b.add_output(socket_type, name, identifier).align_with_previous(); if (socket_type_supports_fields(socket_type)) { - input_decl.supports_field(); + /* If it's below a geometry input it may be a field evaluated on that geometry. */ + input_decl.supports_field().structure_type(StructureType::Dynamic); output_decl.dependent_field({input_decl.index()}); } } - b.add_input("", "__extend__"); - b.add_output("", "__extend__").align_with_previous(); + b.add_input("", "__extend__").structure_type(StructureType::Dynamic); + b.add_output("", "__extend__") + .structure_type(StructureType::Dynamic) + .align_with_previous(); } static void node_init(bNodeTree * /*tree*/, bNode *node) diff --git a/source/blender/nodes/geometry/nodes/node_geo_store_named_grid.cc b/source/blender/nodes/geometry/nodes/node_geo_store_named_grid.cc index d10fb8ad4b8..97fd9c306e0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_store_named_grid.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_store_named_grid.cc @@ -32,7 +32,9 @@ static void node_declare(NodeDeclarationBuilder &b) return; } - b.add_input(*bke::grid_type_to_socket_type(VolumeGridType(node->custom1)), "Grid").hide_value(); + b.add_input(*bke::grid_type_to_socket_type(VolumeGridType(node->custom1)), "Grid") + .hide_value() + .structure_type(StructureType::Grid); } static void search_link_ops(GatherLinkSearchOpParams ¶ms) diff --git a/source/blender/nodes/intern/geometry_nodes_execute.cc b/source/blender/nodes/intern/geometry_nodes_execute.cc index 1c6af7e0d88..81350e92b21 100644 --- a/source/blender/nodes/intern/geometry_nodes_execute.cc +++ b/source/blender/nodes/intern/geometry_nodes_execute.cc @@ -109,8 +109,14 @@ static std::unique_ptr id_name_or_va } std::unique_ptr id_property_create_from_socket( - const bNodeTreeInterfaceSocket &socket, const bool use_name_for_ids) + const bNodeTreeInterfaceSocket &socket, + const nodes::StructureType structure_type, + const bool use_name_for_ids) { + if (structure_type == StructureType::Grid) { + /* Grids currently aren't exposed as properties. */ + return nullptr; + } const StringRefNull identifier = socket.identifier; const bke::bNodeSocketType *typeinfo = socket.socket_typeinfo(); const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM; @@ -970,16 +976,18 @@ void update_input_properties_from_node_tree(const bNodeTree &tree, { tree.ensure_interface_cache(); const Span tree_inputs = tree.interface_inputs(); + const Span input_structure_types = + tree.runtime->structure_type_interface->inputs; for (const int i : tree_inputs.index_range()) { const bNodeTreeInterfaceSocket &socket = *tree_inputs[i]; const StringRefNull socket_identifier = socket.identifier; const bke::bNodeSocketType *typeinfo = socket.socket_typeinfo(); const eNodeSocketDatatype socket_type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM; - IDProperty *new_prop = id_property_create_from_socket(socket, use_name_for_ids).release(); + IDProperty *new_prop = id_property_create_from_socket( + socket, input_structure_types[i], use_name_for_ids) + .release(); if (new_prop == nullptr) { - /* Out of the set of supported input sockets, these sockets aren't added to the modifier. */ - BLI_assert(ELEM(socket_type, SOCK_GEOMETRY, SOCK_MATRIX, SOCK_BUNDLE, SOCK_CLOSURE)); continue; } diff --git a/source/blender/nodes/intern/node_common.cc b/source/blender/nodes/intern/node_common.cc index f6875b1433d..8265c698fa1 100644 --- a/source/blender/nodes/intern/node_common.cc +++ b/source/blender/nodes/intern/node_common.cc @@ -254,6 +254,7 @@ get_init_socket_fn(const bNodeTreeInterface &interface, const bNodeTreeInterface static BaseSocketDeclarationBuilder &build_interface_socket_declaration( const bNodeTree &tree, const bNodeTreeInterfaceSocket &io_socket, + const std::optional structure_type, const eNodeSocketInOut in_out, DeclarationListBuilder &b) { @@ -389,16 +390,21 @@ static BaseSocketDeclarationBuilder &build_interface_socket_declaration( decl->compact(io_socket.flag & NODE_INTERFACE_SOCKET_COMPACT); decl->panel_toggle(io_socket.flag & NODE_INTERFACE_SOCKET_PANEL_TOGGLE); decl->default_input_type(NodeDefaultInputType(io_socket.default_input)); + if (structure_type) { + decl->structure_type(*structure_type); + } if (io_socket.default_input != NODE_DEFAULT_INPUT_VALUE) { decl->hide_value(); } return *decl; } -static void node_group_declare_panel_recursive(DeclarationListBuilder &b, - const bNodeTree &group, - const bNodeTreeInterfacePanel &io_parent_panel, - const bool is_root) +static void node_group_declare_panel_recursive( + DeclarationListBuilder &b, + const bNodeTree &group, + const Map &structure_type_by_socket, + const bNodeTreeInterfacePanel &io_parent_panel, + const bool is_root) { bool layout_added = false; auto add_layout_if_needed = [&]() { @@ -417,7 +423,8 @@ static void node_group_declare_panel_recursive(DeclarationListBuilder &b, if (in_out == SOCK_IN) { add_layout_if_needed(); } - build_interface_socket_declaration(group, io_socket, in_out, b); + build_interface_socket_declaration( + group, io_socket, structure_type_by_socket.lookup_try(&io_socket), in_out, b); break; } case NODE_INTERFACE_PANEL: { @@ -426,7 +433,8 @@ static void node_group_declare_panel_recursive(DeclarationListBuilder &b, auto &panel_b = b.add_panel(StringRef(io_panel.name), io_panel.identifier) .description(StringRef(io_panel.description)) .default_closed(io_panel.flag & NODE_INTERFACE_PANEL_DEFAULT_CLOSED); - node_group_declare_panel_recursive(panel_b, group, io_panel, false); + node_group_declare_panel_recursive( + panel_b, group, structure_type_by_socket, io_panel, false); break; } } @@ -455,7 +463,29 @@ void node_group_declare(NodeDeclarationBuilder &b) /* Allow the node group interface to define the socket order. */ r_declaration.use_custom_socket_order = true; - node_group_declare_panel_recursive(b, *group, group->tree_interface.root_panel, true); + group->ensure_interface_cache(); + + Map structure_type_by_socket; + if (group->type == NTREE_GEOMETRY) { + structure_type_by_socket.reserve(group->interface_items().size()); + + const Span inputs = group->interface_inputs(); + const Span input_structure_types = + group->runtime->structure_type_interface->inputs; + for (const int i : inputs.index_range()) { + structure_type_by_socket.add(inputs[i], input_structure_types[i]); + } + + const Span outputs = group->interface_outputs(); + const Span output_structure_types = + group->runtime->structure_type_interface->outputs; + for (const int i : outputs.index_range()) { + structure_type_by_socket.add(outputs[i], output_structure_types[i].type); + } + } + + node_group_declare_panel_recursive( + b, *group, structure_type_by_socket, group->tree_interface.root_panel, true); if (group->type == NTREE_GEOMETRY) { group->ensure_interface_cache(); @@ -528,8 +558,12 @@ static void node_reroute_declare(blender::nodes::NodeDeclarationBuilder &b) const blender::StringRefNull socket_idname( static_cast(node->storage)->type_idname); - b.add_input("Input").idname(socket_idname.c_str()); - b.add_output("Output").idname(socket_idname.c_str()); + b.add_input("Input") + .idname(socket_idname.c_str()) + .structure_type(blender::nodes::StructureType::Dynamic); + b.add_output("Output") + .idname(socket_idname.c_str()) + .structure_type(blender::nodes::StructureType::Dynamic); } static void node_reroute_init(bNodeTree * /*ntree*/, bNode *node) @@ -745,7 +779,12 @@ static void group_input_declare(NodeDeclarationBuilder &b) const bNodeTreeInterfaceSocket &socket = node_interface::get_item_as(item); if (socket.flag & NODE_INTERFACE_SOCKET_INPUT) { - build_interface_socket_declaration(*node_tree, socket, SOCK_OUT, b); + /* Trying to use the evaluated structure type for the group output node introduces a + * "dependency cycle" between this and the structure type inferencing which uses node + * declarations. The compromise is to not use the proper structure type in the group + * input/output declarations and instead use a special case for the choice of socket + * shapes.*/ + build_interface_socket_declaration(*node_tree, socket, std::nullopt, SOCK_OUT, b); } break; } @@ -770,7 +809,7 @@ static void group_output_declare(NodeDeclarationBuilder &b) const bNodeTreeInterfaceSocket &socket = node_interface::get_item_as(item); if (socket.flag & NODE_INTERFACE_SOCKET_OUTPUT) { - build_interface_socket_declaration(*node_tree, socket, SOCK_IN, b); + build_interface_socket_declaration(*node_tree, socket, std::nullopt, SOCK_IN, b); } break; } diff --git a/source/blender/nodes/intern/node_declaration.cc b/source/blender/nodes/intern/node_declaration.cc index 9704c5b9b0a..58bb8ce508a 100644 --- a/source/blender/nodes/intern/node_declaration.cc +++ b/source/blender/nodes/intern/node_declaration.cc @@ -94,6 +94,14 @@ void NodeDeclarationBuilder::build_remaining_anonymous_attribute_relations() void NodeDeclarationBuilder::finalize() { this->build_remaining_anonymous_attribute_relations(); + if (is_function_node_) { + for (SocketDeclaration *socket_decl : declaration_.inputs) { + socket_decl->structure_type = StructureType::Dynamic; + } + for (SocketDeclaration *socket_decl : declaration_.outputs) { + socket_decl->structure_type = StructureType::Dynamic; + } + } #ifndef NDEBUG declaration_.assert_valid(); #endif @@ -522,6 +530,7 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::supports_field() { BLI_assert(this->is_input()); decl_base_->input_field_type = InputSocketFieldType::IsSupported; + this->structure_type(StructureType::Field); return *this; } @@ -532,6 +541,7 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::dependent_field( this->reference_pass(input_dependencies); decl_base_->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField( std::move(input_dependencies)); + this->structure_type(StructureType::Dynamic); return *this; } @@ -595,6 +605,7 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::field_on(const Span< relations.available_relations.append(relation); } } + this->structure_type(StructureType::Field); return *this; } @@ -657,6 +668,7 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::field_on_all() this->field_source(); } field_on_all_ = true; + this->structure_type(StructureType::Field); return *this; } @@ -664,6 +676,7 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::field_source() { BLI_assert(this->is_output()); decl_base_->output_field_dependency = OutputFieldDependency::ForFieldSource(); + this->structure_type(StructureType::Field); return *this; } @@ -672,6 +685,7 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::implicit_field( { BLI_assert(this->is_input()); this->hide_value(); + this->structure_type(StructureType::Dynamic); decl_base_->input_field_type = InputSocketFieldType::Implicit; decl_base_->default_input_type = default_input_type; return *this; @@ -682,6 +696,7 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::implicit_field_on_al { this->implicit_field(default_input_type); field_on_all_ = true; + this->structure_type(StructureType::Field); return *this; } @@ -690,6 +705,7 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::implicit_field_on( { this->field_on(input_indices); this->implicit_field(default_input_type); + this->structure_type(StructureType::Field); return *this; } @@ -697,6 +713,7 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::dependent_field() { BLI_assert(this->is_output()); decl_base_->output_field_dependency = OutputFieldDependency::ForDependentField(); + this->structure_type(StructureType::Dynamic); this->reference_pass_all(); return *this; } @@ -769,6 +786,13 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::align_with_previous( return *this; } +BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::structure_type( + const StructureType structure_type) +{ + decl_base_->structure_type = structure_type; + return *this; +} + BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder ::socket_name_ptr( const PointerRNA ptr, const StringRef property_name) {