diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index cd845d46722..d522f1f3f21 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -790,6 +790,7 @@ class NODE_MT_category_utilities_bundle_base(node_add_menu.NodeMenu): layout = self.layout self.node_operator(layout, "NodeCombineBundle") self.node_operator(layout, "NodeSeparateBundle") + self.node_operator(layout, "NodeJoinBundle") self.draw_assets_for_catalog(layout, self.menu_path) diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 78f7b9b4571..5bc5a6b3c28 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -9743,6 +9743,7 @@ static void rna_def_nodes(BlenderRNA *brna) define("NodeInternal", "NodeCombineBundle", def_combine_bundle); define("NodeInternal", "NodeEnableOutput"); define("NodeInternal", "NodeEvaluateClosure", def_evaluate_closure); + define("NodeInternal", "NodeJoinBundle"); define("NodeInternal", "NodeSeparateBundle", def_separate_bundle); define("ShaderNode", "ShaderNodeAddShader"); diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 4b455791989..b178b6e0f37 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -146,6 +146,7 @@ set(SRC nodes/node_geo_instances_to_points.cc nodes/node_geo_interpolate_curves.cc nodes/node_geo_is_viewport.cc + nodes/node_geo_join_bundle.cc nodes/node_geo_join_geometry.cc nodes/node_geo_list.cc nodes/node_geo_list_get_item.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_bundle.cc b/source/blender/nodes/geometry/nodes/node_geo_join_bundle.cc new file mode 100644 index 00000000000..5c8e2f03b43 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_join_bundle.cc @@ -0,0 +1,89 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "NOD_geometry_nodes_bundle.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_join_bundle { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.use_custom_socket_order(); + b.allow_any_socket_order(); + b.add_input("Bundle").multi_input().description( + "Bundles to join together on the top level for each bundle. When there are duplicates, only " + "the first occurence is used"); + b.add_output("Bundle").align_with_previous(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeoNodesMultiInput bundles = params.extract_input>( + "Bundle"); + + if (bundles.values.is_empty()) { + params.set_default_remaining_outputs(); + return; + } + + BundlePtr output_bundle; + int bundle_i = 0; + for (; bundle_i < bundles.values.size(); bundle_i++) { + BundlePtr &bundle = bundles.values[bundle_i]; + if (bundle) { + output_bundle = std::move(bundle); + bundle_i++; + break; + } + } + if (!output_bundle) { + output_bundle = Bundle::create(); + } + else if (!output_bundle->is_mutable()) { + output_bundle = output_bundle->copy(); + } + else { + output_bundle->tag_ensured_mutable(); + } + Bundle &mutable_output_bundle = const_cast(*output_bundle); + + VectorSet overridden_keys; + for (; bundle_i < bundles.values.size(); bundle_i++) { + BundlePtr &bundle = bundles.values[bundle_i]; + if (!bundle) { + continue; + } + for (const Bundle::StoredItem &item : bundle->items()) { + if (!mutable_output_bundle.add(item.key, item.value)) { + overridden_keys.add(item.key); + } + } + } + + if (!overridden_keys.is_empty()) { + std::string message = fmt::format( + "{}: {}", TIP_("Duplicate keys"), fmt::join(overridden_keys, ", ")); + params.error_message_add(NodeWarningType::Info, std::move(message)); + } + + params.set_output("Bundle", output_bundle); +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + geo_node_type_base(&ntype, "NodeJoinBundle"); + ntype.ui_name = "Join Bundle"; + ntype.ui_description = "Join multiple bundles together"; + ntype.nclass = NODE_CLASS_CONVERTER; + ntype.geometry_node_execute = node_geo_exec; + ntype.declare = node_declare; + blender::bke::node_register_type(ntype); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_join_bundle diff --git a/source/blender/nodes/intern/trace_values.cc b/source/blender/nodes/intern/trace_values.cc index 2a371397543..6c297fbf6f5 100644 --- a/source/blender/nodes/intern/trace_values.cc +++ b/source/blender/nodes/intern/trace_values.cc @@ -446,6 +446,10 @@ static Vector find_origin_sockets_through_contexts( } continue; } + if (node->is_type("NodeJoinBundle")) { + add_if_new(node.input_socket(0), bundle_path); + continue; + } if (node->is_type("NodeEvaluateClosure")) { const auto &evaluate_storage = *static_cast(node->storage); const StringRef key = evaluate_storage.output_items.items[socket->index()].name; @@ -611,12 +615,37 @@ LinkedBundleSignatures gather_linked_origin_bundle_signatures( {bundle_socket_context, &bundle_socket}, compute_context_cache, [&](const SocketInContext &socket) { - const bNode &node = socket->owner_node(); - if (socket->is_output() && node.is_type("NodeCombineBundle")) { - const auto &storage = *static_cast(node.storage); - result.items.append({BundleSignature::from_combine_bundle_node(node, false), - bool(storage.flag & NODE_COMBINE_BUNDLE_FLAG_DEFINE_SIGNATURE), - socket}); + const NodeInContext node = socket.owner_node(); + if (socket->is_output()) { + if (node->is_type("NodeCombineBundle")) { + const auto &storage = *static_cast(node->storage); + result.items.append({BundleSignature::from_combine_bundle_node(*node, false), + bool(storage.flag & NODE_COMBINE_BUNDLE_FLAG_DEFINE_SIGNATURE), + socket}); + return true; + } + } + if (node->is_type("NodeJoinBundle")) { + const SocketInContext input_socket = node.input_socket(0); + BundleSignature joined_signature; + bool is_signature_definition = true; + for (const bNodeLink *link : input_socket->directly_linked_links()) { + if (!link->is_used()) { + continue; + } + const bNodeSocket *socket_from = link->fromsock; + const LinkedBundleSignatures sub_signatures = gather_linked_origin_bundle_signatures( + node.context, *socket_from, compute_context_cache); + for (const LinkedBundleSignatures::Item &sub_signature : sub_signatures.items) { + if (!sub_signature.is_signature_definition) { + is_signature_definition = false; + } + for (const BundleSignature::Item &item : sub_signature.signature.items) { + joined_signature.items.add(item); + } + } + } + result.items.append({joined_signature, is_signature_definition, socket}); return true; } return false; diff --git a/tests/files/modeling/geometry_nodes/utilities/join_bundle.blend b/tests/files/modeling/geometry_nodes/utilities/join_bundle.blend new file mode 100644 index 00000000000..92f39a60646 --- /dev/null +++ b/tests/files/modeling/geometry_nodes/utilities/join_bundle.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:344ee9e5991b7b5ea9a965e5d4382136d2295fc70f919c9d318fca6b096c3c88 +size 868815