Geometry Nodes: new Join Bundle Node

Adds a Join Bundle node with a multi-input Bundle socket. Bundles are
iterated on the top level of each input bundle and items added to the resulting
single bundle. While creating the final bundle existing items have priority and
new items with an already existing name are discarded.

There is an info message when there are duplicate keys in the input bundles.

Co-authored-by: Brady Johnston
Co-authored-by: Jacques Lucke
Pull Request: https://projects.blender.org/blender/blender/pulls/146750
This commit is contained in:
Brady Johnston
2025-10-01 09:20:11 +02:00
committed by Jacques Lucke
parent f8c4d743bc
commit 8b3be68b3d
6 changed files with 130 additions and 6 deletions

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,89 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <fmt/format.h>
#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<decl::Bundle>("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<decl::Bundle>("Bundle").align_with_previous();
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeoNodesMultiInput<BundlePtr> bundles = params.extract_input<GeoNodesMultiInput<BundlePtr>>(
"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<Bundle &>(*output_bundle);
VectorSet<StringRef> 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

View File

@@ -446,6 +446,10 @@ static Vector<SocketInContext> 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<const NodeEvaluateClosure *>(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<const NodeCombineBundle *>(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<const NodeCombineBundle *>(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;

Binary file not shown.