Geometry Nodes: add Closures and Bundles behind experimental feature flag

This implements bundles and closures which are described in more detail in this
blog post: https://code.blender.org/2024/11/geometry-nodes-workshop-october-2024/

tl;dr:
* Bundles are containers that allow storing multiple socket values in a single
  value. Each value in the bundle is identified by a name. Bundles can be
  nested.
* Closures are functions that are created with the Closure Zone and can be
  evaluated with the Evaluate Closure node.

To use the patch, the `Bundle and Closure Nodes` experimental feature has to be
enabled. This is necessary, because these features are not fully done yet and
still need iterations to improve the workflow before they can be officially
released. These iterations are easier to do in `main` than in a separate branch
though. That's because this patch is quite large and somewhat prone to merge
conflicts. Also other work we want to do, depends on this.

This adds the following new nodes:
* Combine Bundle: can pack multiple values into one.
* Separate Bundle: extracts values from a bundle.
* Closure Zone: outputs a closure zone for use in the `Evaluate Closure` node.
* Evaluate Closure: evaluates the passed in closure.

Things that will be added soon after this lands:
* Fields in bundles and closures. The way this is done changes with #134811, so
  I rather implement this once both are in `main`.
* UI features for keeping sockets in sync (right now there are warnings only).

One bigger issue is the limited support for lazyness. For example, all inputs of
a Combine Bundle node will be evaluated, even if they are not all needed. The
same is true for all captured values of a closure. This is a deeper limitation
that needs to be resolved at some point. This will likely be done after an
initial version of this patch is done.

Pull Request: https://projects.blender.org/blender/blender/pulls/128340
This commit is contained in:
Jacques Lucke 2025-04-03 15:44:06 +02:00
parent 508fc4c8a8
commit 8ec9c62d3e
80 changed files with 4317 additions and 164 deletions

View file

@ -901,6 +901,7 @@ const bTheme U_theme_default = {
.node_zone_simulation = RGBA(0x66416233),
.node_zone_repeat = RGBA(0x76512f33),
.node_zone_foreach_geometry_element = RGBA(0x33527f33),
.node_zone_closure = RGBA(0x527F3333),
.movie = RGBA(0x0f0f0fcc),
.gp_vertex_size = 3,
.gp_vertex = RGBA(0x97979700),

View file

@ -233,6 +233,17 @@ class NODE_OT_add_foreach_geometry_element_zone(NodeAddZoneOperator, Operator):
add_default_geometry_link = False
class NODE_OT_add_closure_zone(NodeAddZoneOperator, Operator):
"""Add a Closure zone"""
bl_idname = "node.add_closure_zone"
bl_label = "Add Closure Zone"
bl_options = {'REGISTER', 'UNDO'}
input_node_type = "GeometryNodeClosureInput"
output_node_type = "GeometryNodeClosureOutput"
add_default_geometry_link = False
class NODE_OT_collapse_hide_unused_toggle(Operator):
"""Toggle collapsed nodes and hide unused sockets"""
bl_idname = "node.collapse_hide_unused_toggle"
@ -672,6 +683,7 @@ classes = (
NODE_OT_add_simulation_zone,
NODE_OT_add_repeat_zone,
NODE_OT_add_foreach_geometry_element_zone,
NODE_OT_add_closure_zone,
NODE_OT_collapse_hide_unused_toggle,
NODE_OT_interface_item_new,
NODE_OT_interface_item_duplicate,

View file

@ -88,6 +88,13 @@ def add_foreach_geometry_element_zone(layout, label):
return props
def add_closure_zone(layout, label):
props = layout.operator(
"node.add_closure_zone", text=label, text_ctxt=i18n_contexts.default)
props.use_transform = True
return props
class NODE_MT_category_layout(Menu):
bl_idname = "NODE_MT_category_layout"
bl_label = "Layout"

View file

@ -629,12 +629,18 @@ class NODE_MT_category_GEO_UTILITIES(Menu):
layout.menu("NODE_MT_category_GEO_UTILITIES_ROTATION")
layout.menu("NODE_MT_category_GEO_UTILITIES_DEPRECATED")
layout.separator()
if context.preferences.experimental.use_bundle_and_closure_nodes:
node_add_menu.add_closure_zone(layout, label="Closure")
node_add_menu.add_node_type(layout, "GeometryNodeEvaluateClosure")
node_add_menu.add_foreach_geometry_element_zone(layout, label="For Each Element")
node_add_menu.add_node_type(layout, "GeometryNodeIndexSwitch")
node_add_menu.add_node_type(layout, "GeometryNodeMenuSwitch")
node_add_menu.add_node_type(layout, "FunctionNodeRandomValue")
node_add_menu.add_repeat_zone(layout, label="Repeat")
node_add_menu.add_node_type(layout, "GeometryNodeSwitch")
if context.preferences.experimental.use_bundle_and_closure_nodes:
node_add_menu.add_node_type(layout, "GeometryNodeCombineBundle")
node_add_menu.add_node_type(layout, "GeometryNodeSeparateBundle")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)

View file

@ -2851,6 +2851,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel):
("blender/blender/projects/10", "Pipeline, Assets & IO Project Page")),
({"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")),
),
)

View file

@ -14,6 +14,8 @@
#include "BKE_geometry_set.hh"
#include "BKE_volume_grid_fwd.hh"
#include "NOD_socket_interface_key.hh"
namespace blender::bke::bake {
/**
@ -143,4 +145,20 @@ class StringBakeItem : public BakeItem {
void count_memory(MemoryCounter &memory) const override;
};
/**
* \note It's not possible to use #PrimitiveBakeItem for bundles in general, because the items in
* the bundle also have to be converted to their bakeable form. This is especially important when
* serializing the bake.
*/
class BundleBakeItem : public BakeItem {
public:
struct Item {
nodes::SocketInterfaceKey key;
std::string socket_idname;
std::unique_ptr<BakeItem> value;
};
Vector<Item> items;
};
} // namespace blender::bke::bake

View file

@ -156,6 +156,34 @@ class ForeachGeometryElementZoneComputeContext : public ComputeContext {
void print_current_in_line(std::ostream &stream) const override;
};
class EvaluateClosureComputeContext : public ComputeContext {
private:
static constexpr const char *s_static_type = "CLOSURE";
int32_t node_id_;
/**
* Extra information that might not always be available.
*/
const bNode *evaluate_node_ = nullptr;
public:
EvaluateClosureComputeContext(const ComputeContext *parent, int32_t node_id);
EvaluateClosureComputeContext(const ComputeContext *parent, const bNode &evaluate_node);
int32_t node_id() const
{
return node_id_;
}
const bNode *evaluate_node() const
{
return evaluate_node_;
}
private:
void print_current_in_line(std::ostream &stream) const override;
};
class OperatorComputeContext : public ComputeContext {
private:
static constexpr const char *s_static_type = "OPERATOR";

View file

@ -517,6 +517,11 @@
#define GEO_NODE_MERGE_LAYERS 2150
#define GEO_NODE_INPUT_COLLECTION 2151
#define GEO_NODE_INPUT_OBJECT 2152
#define GEO_NODE_COMBINE_BUNDLE 2153
#define GEO_NODE_SEPARATE_BUNDLE 2154
#define GEO_NODE_CLOSURE_OUTPUT 2155
#define GEO_NODE_EVALUATE_CLOSURE 2156
#define GEO_NODE_CLOSURE_INPUT 2157
/** \} */

View file

@ -179,6 +179,8 @@ static const bNodeSocketStaticTypeInfo node_socket_subtypes[] = {
{"NodeSocketTexture", "NodeTreeInterfaceSocketTexture", SOCK_TEXTURE, PROP_NONE},
{"NodeSocketMaterial", "NodeTreeInterfaceSocketMaterial", SOCK_MATERIAL, PROP_NONE},
{"NodeSocketMenu", "NodeTreeInterfaceSocketMenu", SOCK_MENU, PROP_NONE},
{"NodeSocketBundle", "NodeTreeInterfaceSocketBundle", SOCK_BUNDLE, PROP_NONE},
{"NodeSocketClosure", "NodeTreeInterfaceSocketClosure", SOCK_CLOSURE, PROP_NONE},
};
template<typename Fn> bool socket_data_to_static_type(const eNodeSocketDatatype type, const Fn &fn)
@ -228,6 +230,8 @@ template<typename Fn> bool socket_data_to_static_type(const eNodeSocketDatatype
case SOCK_SHADER:
case SOCK_MATRIX:
case SOCK_GEOMETRY:
case SOCK_BUNDLE:
case SOCK_CLOSURE:
return true;
}
return false;

View file

@ -44,11 +44,13 @@ enum class ReferenceSetType {
* input. In such cases, the caller may provide a set of attributes that should be propagated.
*/
GroupOutputData,
ClosureOutputData,
/**
* Field inputs may require attributes that need to be propagated from other geometry inputs to
* the node that evaluates the field.
*/
GroupInputReferenceSet,
ClosureInputReferenceSet,
/**
* Locally created anonymous attributes (like with the Capture Attribute node) need to be
* propagated to the nodes that use them or even to the group output.
@ -61,7 +63,7 @@ struct ReferenceSetInfo {
union {
/** Used for group interface sockets. */
int index;
/** Used for local sockets. */
/** Used for local and closure sockets. */
const bNodeSocket *socket;
};
@ -79,7 +81,10 @@ struct ReferenceSetInfo {
ReferenceSetInfo(ReferenceSetType type, const bNodeSocket *socket) : type(type), socket(socket)
{
BLI_assert(ELEM(type, ReferenceSetType::LocalReferenceSet));
BLI_assert(ELEM(type,
ReferenceSetType::LocalReferenceSet,
ReferenceSetType::ClosureInputReferenceSet,
ReferenceSetType::ClosureOutputData));
}
friend std::ostream &operator<<(std::ostream &stream, const ReferenceSetInfo &info);
@ -102,4 +107,9 @@ struct ReferenceLifetimesInfo {
bool analyse_reference_lifetimes(bNodeTree &tree);
/** The socket type allows storing references to data stored elsewhere. */
bool can_contain_reference(eNodeSocketDatatype socket_type);
/** The socket type allows storing data that may be referenced elsewhere. */
bool can_contain_referenced_data(eNodeSocketDatatype socket_type);
} // namespace blender::bke::node_tree_reference_lifetimes

View file

@ -1507,6 +1507,20 @@ static void serialize_bake_item(const BakeItem &item,
auto io_data = serialize_primitive_value(data_type, primitive_state_item->value());
r_io_item.append("data", std::move(io_data));
}
else if (const auto *bundle_state_item = dynamic_cast<const BundleBakeItem *>(&item)) {
r_io_item.append_str("type", "BUNDLE");
ArrayValue &io_items = *r_io_item.append_array("items");
for (const BundleBakeItem::Item &item : bundle_state_item->items) {
DictionaryValue &io_bundle_item = *io_items.append_dict();
ArrayValue &io_key = *io_bundle_item.append_array("key");
for (const std::string &identifier : item.key.identifiers()) {
io_key.append_str(identifier);
}
io_bundle_item.append_str("socket_idname", item.socket_idname);
io::serialize::DictionaryValue &io_bundle_item_value = *io_bundle_item.append_dict("value");
serialize_bake_item(*item.value, blob_writer, blob_sharing, io_bundle_item_value);
}
}
}
static std::unique_ptr<BakeItem> deserialize_bake_item(const DictionaryValue &io_item,
@ -1592,6 +1606,44 @@ static std::unique_ptr<BakeItem> deserialize_bake_item(const DictionaryValue &io
return std::make_unique<StringBakeItem>(std::move(str));
}
}
if (*state_item_type == StringRef("BUNDLE")) {
const ArrayValue *io_items = io_item.lookup_array("items");
if (!io_items) {
return {};
}
auto bundle = std::make_unique<BundleBakeItem>();
for (const auto &io_item_ : io_items->elements()) {
const DictionaryValue *io_item = io_item_->as_dictionary_value();
if (!io_item) {
return {};
}
const ArrayValue *io_key = io_item->lookup_array("key");
if (!io_key) {
return {};
}
Vector<std::string> key;
for (const auto &io_key_value : io_key->elements()) {
const StringValue *io_key_string = io_key_value->as_string_value();
if (!io_key_string) {
return {};
}
key.append(io_key_string->value());
}
const std::optional<StringRefNull> socket_idname = io_item->lookup_str("socket_idname");
if (!socket_idname) {
return {};
}
const DictionaryValue *io_item_value = io_item->lookup_dict("value");
std::unique_ptr<BakeItem> value = deserialize_bake_item(
*io_item_value, blob_reader, blob_sharing);
if (!value) {
return {};
}
bundle->items.append(BundleBakeItem::Item{
nodes::SocketInterfaceKey{std::move(key)}, *socket_idname, std::move(value)});
}
return bundle;
}
const std::shared_ptr<io::serialize::Value> *io_data = io_item.lookup("data");
if (!io_data) {
return {};

View file

@ -8,6 +8,8 @@
#include "BKE_node_socket_value.hh"
#include "BKE_volume_grid.hh"
#include "NOD_geometry_nodes_bundle.hh"
namespace blender::bke::bake {
static void capture_field_on_geometry_components(GeometrySet &geometry,
@ -37,6 +39,72 @@ static void capture_field_on_geometry_components(GeometrySet &geometry,
}
}
static std::unique_ptr<BakeItem> move_common_socket_value_to_bake_item(
const bNodeSocketType &stype,
void *socket_value,
std::optional<StringRef> name,
Vector<GeometryBakeItem *> &r_geometry_bake_items)
{
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(stype.type);
switch (socket_type) {
case SOCK_GEOMETRY: {
GeometrySet &geometry = *static_cast<GeometrySet *>(socket_value);
auto item = std::make_unique<GeometryBakeItem>(std::move(geometry));
r_geometry_bake_items.append(item.get());
return item;
}
case SOCK_STRING: {
auto &value_variant = *static_cast<SocketValueVariant *>(socket_value);
return std::make_unique<StringBakeItem>(value_variant.extract<std::string>());
}
case SOCK_FLOAT:
case SOCK_VECTOR:
case SOCK_INT:
case SOCK_BOOLEAN:
case SOCK_ROTATION:
case SOCK_MATRIX:
case SOCK_RGBA: {
auto &value_variant = *static_cast<SocketValueVariant *>(socket_value);
if (value_variant.is_context_dependent_field()) {
/* Not supported here because it's not known which geometry this field belongs to. */
return {};
}
#ifdef WITH_OPENVDB
if (value_variant.is_volume_grid()) {
bke::GVolumeGrid grid = value_variant.get<bke::GVolumeGrid>();
if (name) {
grid.get_for_write().set_name(*name);
}
return std::make_unique<VolumeGridBakeItem>(
std::make_unique<bke::GVolumeGrid>(std::move(grid)));
}
#endif
value_variant.convert_to_single();
GPointer value = value_variant.get_single_ptr();
return std::make_unique<PrimitiveBakeItem>(*value.type(), value.get());
}
case SOCK_BUNDLE: {
auto &value_variant = *static_cast<SocketValueVariant *>(socket_value);
nodes::BundlePtr bundle_ptr = value_variant.extract<nodes::BundlePtr>();
auto bundle_bake_item = std::make_unique<BundleBakeItem>();
if (bundle_ptr) {
const nodes::Bundle &bundle = *bundle_ptr;
for (const nodes::Bundle::StoredItem &bundle_item : bundle.items()) {
if (std::unique_ptr<BakeItem> bake_item = move_common_socket_value_to_bake_item(
*bundle_item.type, bundle_item.value, std::nullopt, r_geometry_bake_items))
{
bundle_bake_item->items.append(BundleBakeItem::Item{
bundle_item.key, bundle_item.type->idname, std::move(bake_item)});
}
}
}
return bundle_bake_item;
}
default:
return {};
}
}
Array<std::unique_ptr<BakeItem>> move_socket_values_to_bake_items(const Span<void *> socket_values,
const BakeSocketConfig &config,
BakeDataBlockMap *data_block_map)
@ -63,6 +131,7 @@ Array<std::unique_ptr<BakeItem>> move_socket_values_to_bake_items(const Span<voi
for (const int i : socket_values.index_range()) {
const eNodeSocketDatatype socket_type = config.types[i];
const bNodeSocketType &stype = *node_socket_type_find_static(socket_type);
void *socket_value = socket_values[i];
switch (socket_type) {
case SOCK_GEOMETRY: {
@ -70,8 +139,8 @@ Array<std::unique_ptr<BakeItem>> move_socket_values_to_bake_items(const Span<voi
break;
}
case SOCK_STRING: {
auto &value_variant = *static_cast<SocketValueVariant *>(socket_value);
bake_items[i] = std::make_unique<StringBakeItem>(value_variant.extract<std::string>());
bake_items[i] = move_common_socket_value_to_bake_item(
stype, socket_value, config.names[i], geometry_bake_items);
break;
}
case SOCK_FLOAT:
@ -95,21 +164,17 @@ Array<std::unique_ptr<BakeItem>> move_socket_values_to_bake_items(const Span<voi
}
bake_items[i] = std::make_unique<AttributeBakeItem>(attribute_name);
}
#ifdef WITH_OPENVDB
else if (value_variant.is_volume_grid()) {
bke::GVolumeGrid grid = value_variant.get<bke::GVolumeGrid>();
grid.get_for_write().set_name(config.names[i]);
bake_items[i] = std::make_unique<VolumeGridBakeItem>(
std::make_unique<bke::GVolumeGrid>(std::move(grid)));
}
#endif
else {
value_variant.convert_to_single();
GPointer value = value_variant.get_single_ptr();
bake_items[i] = std::make_unique<PrimitiveBakeItem>(*value.type(), value.get());
bake_items[i] = move_common_socket_value_to_bake_item(
stype, socket_value, config.names[i], geometry_bake_items);
}
break;
}
case SOCK_BUNDLE: {
bake_items[i] = move_common_socket_value_to_bake_item(
stype, socket_value, config.names[i], geometry_bake_items);
break;
}
default:
break;
}
@ -129,6 +194,9 @@ Array<std::unique_ptr<BakeItem>> move_socket_values_to_bake_items(const Span<voi
return bake_items;
}
/**
* \return True if #r_value has been constructed.
*/
[[nodiscard]] static bool copy_bake_item_to_socket_value(
const BakeItem &bake_item,
const eNodeSocketDatatype socket_type,
@ -162,6 +230,9 @@ Array<std::unique_ptr<BakeItem>> move_socket_values_to_bake_items(const Span<voi
return false;
}
if (const auto *item = dynamic_cast<const AttributeBakeItem *>(&bake_item)) {
if (!make_attribute_field) {
return false;
}
std::shared_ptr<AttributeFieldInput> attribute_field = make_attribute_field(base_type);
r_attribute_map.add(item->name(), attribute_field->attribute_name());
fn::GField field{attribute_field};
@ -193,6 +264,32 @@ Array<std::unique_ptr<BakeItem>> move_socket_values_to_bake_items(const Span<voi
}
return false;
}
case SOCK_BUNDLE: {
if (const auto *item = dynamic_cast<const BundleBakeItem *>(&bake_item)) {
nodes::BundlePtr bundle_ptr = nodes::Bundle::create();
nodes::Bundle &bundle = const_cast<nodes::Bundle &>(*bundle_ptr);
for (const BundleBakeItem::Item &item : item->items) {
const bNodeSocketType *stype = node_socket_type_find(item.socket_idname);
if (!stype) {
return false;
}
if (!stype->geometry_nodes_cpp_type) {
return false;
}
BUFFER_FOR_CPP_TYPE_VALUE(*stype->geometry_nodes_cpp_type, buffer);
if (!copy_bake_item_to_socket_value(
*item.value, eNodeSocketDatatype(stype->type), {}, r_attribute_map, buffer))
{
return false;
}
bundle.add(item.key, *stype, buffer);
stype->geometry_nodes_cpp_type->destruct(buffer);
}
new (r_value) SocketValueVariant(std::move(bundle_ptr));
return true;
}
return false;
}
default:
return false;
}

View file

@ -144,6 +144,36 @@ void ForeachGeometryElementZoneComputeContext::print_current_in_line(std::ostrea
stream << "Foreach Geometry Element Zone ID: " << output_node_id_;
}
EvaluateClosureComputeContext::EvaluateClosureComputeContext(const ComputeContext *parent,
const int32_t node_id)
: ComputeContext(s_static_type, parent), node_id_(node_id)
{
/* Mix static type and node id into a single buffer so that only a single call to #mix_in is
* necessary. */
const int type_size = strlen(s_static_type);
const int buffer_size = type_size + 1 + sizeof(int32_t);
DynamicStackBuffer<64, 8> buffer_owner(buffer_size, 8);
char *buffer = static_cast<char *>(buffer_owner.buffer());
memcpy(buffer, s_static_type, type_size + 1);
memcpy(buffer + type_size + 1, &node_id_, sizeof(int32_t));
hash_.mix_in(buffer, buffer_size);
}
EvaluateClosureComputeContext::EvaluateClosureComputeContext(const ComputeContext *parent,
const bNode &node)
: EvaluateClosureComputeContext(parent, node.identifier)
{
evaluate_node_ = &node;
}
void EvaluateClosureComputeContext::print_current_in_line(std::ostream &stream) const
{
if (evaluate_node_ != nullptr) {
stream << "Evaluate Closure: " << evaluate_node_->name;
return;
}
}
OperatorComputeContext::OperatorComputeContext() : OperatorComputeContext(nullptr) {}
OperatorComputeContext::OperatorComputeContext(const ComputeContext *parent)

View file

@ -11,6 +11,9 @@
#include "BKE_instances.hh"
#include "BKE_node_socket_value.hh"
#include "NOD_geometry_nodes_bundle.hh"
#include "NOD_geometry_nodes_closure.hh"
#include "DNA_meshdata_types.h"
struct Tex;
@ -29,6 +32,8 @@ BLI_CPP_TYPE_MAKE(Image *, CPPTypeFlags::BasicType)
BLI_CPP_TYPE_MAKE(Material *, CPPTypeFlags::BasicType)
BLI_CPP_TYPE_MAKE(MStringProperty, CPPTypeFlags::None);
BLI_CPP_TYPE_MAKE(blender::nodes::BundlePtr, CPPTypeFlags::None);
BLI_CPP_TYPE_MAKE(blender::nodes::ClosurePtr, CPPTypeFlags::None);
BLI_CPP_TYPE_MAKE(blender::bke::GeometryNodesReferenceSet, CPPTypeFlags::None);
BLI_CPP_TYPE_MAKE(blender::bke::SocketValueVariant, CPPTypeFlags::Printable);
@ -50,6 +55,8 @@ void BKE_cpp_types_init()
BLI_CPP_TYPE_REGISTER(Material *);
BLI_CPP_TYPE_REGISTER(MStringProperty);
BLI_CPP_TYPE_REGISTER(blender::nodes::BundlePtr);
BLI_CPP_TYPE_REGISTER(blender::nodes::ClosurePtr);
BLI_CPP_TYPE_REGISTER(blender::bke::GeometryNodesReferenceSet);
BLI_CPP_TYPE_REGISTER(blender::bke::SocketValueVariant);

View file

@ -0,0 +1,146 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_type_conversions.hh"
#include "NOD_geometry_nodes_bundle.hh"
namespace blender::bke {
SocketInterfaceKey::SocketInterfaceKey(std::string identifier)
{
identifiers_.append(std::move(identifier));
}
SocketInterfaceKey::SocketInterfaceKey(Vector<std::string> identifiers)
: identifiers_(std::move(identifiers))
{
BLI_assert(!identifiers_.is_empty());
}
Span<std::string> SocketInterfaceKey::identifiers() const
{
return identifiers_;
}
bool SocketInterfaceKey::matches(const SocketInterfaceKey &other) const
{
for (const std::string &identifier : other.identifiers_) {
if (identifiers_.contains(identifier)) {
return true;
}
}
return false;
}
Bundle::Bundle() = default;
Bundle::~Bundle()
{
for (StoredItem &item : items_) {
item.type->geometry_nodes_cpp_type->destruct(item.value);
}
for (void *buffer : buffers_) {
MEM_freeN(buffer);
}
}
Bundle::Bundle(const Bundle &other)
{
for (const StoredItem &item : other.items_) {
this->add_new(item.key, *item.type, item.value);
}
}
Bundle::Bundle(Bundle &&other) noexcept
: items_(std::move(other.items_)), buffers_(std::move(other.buffers_))
{
}
Bundle &Bundle::operator=(const Bundle &other)
{
if (this == &other) {
return *this;
}
this->~Bundle();
new (this) Bundle(other);
return *this;
}
Bundle &Bundle::operator=(Bundle &&other) noexcept
{
if (this == &other) {
return *this;
}
this->~Bundle();
new (this) Bundle(std::move(other));
return *this;
}
void Bundle::add_new(SocketInterfaceKey key, const bNodeSocketType &type, const void *value)
{
BLI_assert(!this->contains(key));
BLI_assert(type.geometry_nodes_cpp_type);
const CPPType &cpp_type = *type.geometry_nodes_cpp_type;
void *buffer = MEM_mallocN_aligned(cpp_type.size(), cpp_type.alignment(), __func__);
cpp_type.copy_construct(value, buffer);
items_.append(StoredItem{std::move(key), &type, buffer});
buffers_.append(buffer);
}
bool Bundle::add(const SocketInterfaceKey &key, const bNodeSocketType &type, const void *value)
{
if (this->contains(key)) {
return false;
}
this->add_new(key, type, value);
return true;
}
bool Bundle::add(SocketInterfaceKey &&key, const bNodeSocketType &type, const void *value)
{
if (this->contains(key)) {
return false;
}
this->add_new(std::move(key), type, value);
return true;
}
std::optional<Bundle::Item> Bundle::lookup(const SocketInterfaceKey &key) const
{
for (const StoredItem &item : items_) {
if (item.key.matches(key)) {
return Item{item.type, item.value};
}
}
return std::nullopt;
}
bool Bundle::remove(const SocketInterfaceKey &key)
{
const int removed_num = items_.remove_if([&key](StoredItem &item) {
if (item.key.matches(key)) {
item.type->geometry_nodes_cpp_type->destruct(item.value);
return true;
}
return false;
});
return removed_num >= 1;
}
bool Bundle::contains(const SocketInterfaceKey &key) const
{
for (const StoredItem &item : items_) {
if (item.key.matches(key)) {
return true;
}
}
return false;
}
void Bundle::delete_self()
{
MEM_delete(this);
}
} // namespace blender::bke

View file

@ -68,6 +68,8 @@
#include "BKE_node_tree_update.hh"
#include "BKE_preview_image.hh"
#include "BKE_type_conversions.hh"
#include "NOD_geometry_nodes_bundle.hh"
#include "NOD_geometry_nodes_closure.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
@ -77,7 +79,9 @@
#include "NOD_common.hh"
#include "NOD_composite.hh"
#include "NOD_geo_bake.hh"
#include "NOD_geo_bundle.hh"
#include "NOD_geo_capture_attribute.hh"
#include "NOD_geo_closure.hh"
#include "NOD_geo_foreach_geometry_element.hh"
#include "NOD_geo_index_switch.hh"
#include "NOD_geo_menu_switch.hh"
@ -203,7 +207,11 @@ static void ntree_copy_data(Main * /*bmain*/,
dst_runtime.reference_lifetimes_info = std::make_unique<ReferenceLifetimesInfo>(
*ntree_src->runtime->reference_lifetimes_info);
for (ReferenceSetInfo &reference_set : dst_runtime.reference_lifetimes_info->reference_sets) {
if (ELEM(reference_set.type, ReferenceSetType::LocalReferenceSet)) {
if (ELEM(reference_set.type,
ReferenceSetType::LocalReferenceSet,
ReferenceSetType::ClosureInputReferenceSet,
ReferenceSetType::ClosureOutputData))
{
reference_set.socket = socket_map.lookup(reference_set.socket);
}
for (auto &socket : reference_set.potential_data_origins) {
@ -330,6 +338,8 @@ static void library_foreach_node_socket(bNodeSocket *sock, LibraryForeachIDData
case SOCK_SHADER:
case SOCK_GEOMETRY:
case SOCK_MENU:
case SOCK_BUNDLE:
case SOCK_CLOSURE:
break;
}
}
@ -712,6 +722,8 @@ static void write_node_socket_default_value(BlendWriter *writer, const bNodeSock
break;
case SOCK_SHADER:
case SOCK_GEOMETRY:
case SOCK_BUNDLE:
case SOCK_CLOSURE:
BLI_assert_unreachable();
break;
}
@ -882,6 +894,20 @@ void node_tree_blend_write(BlendWriter *writer, bNodeTree *ntree)
if (node->type_legacy == GEO_NODE_BAKE) {
nodes::socket_items::blend_write<nodes::BakeItemsAccessor>(writer, *node);
}
if (node->type_legacy == GEO_NODE_COMBINE_BUNDLE) {
nodes::socket_items::blend_write<nodes::CombineBundleItemsAccessor>(writer, *node);
}
if (node->type_legacy == GEO_NODE_SEPARATE_BUNDLE) {
nodes::socket_items::blend_write<nodes::SeparateBundleItemsAccessor>(writer, *node);
}
if (node->type_legacy == GEO_NODE_CLOSURE_OUTPUT) {
nodes::socket_items::blend_write<nodes::ClosureInputItemsAccessor>(writer, *node);
nodes::socket_items::blend_write<nodes::ClosureOutputItemsAccessor>(writer, *node);
}
if (node->type_legacy == GEO_NODE_EVALUATE_CLOSURE) {
nodes::socket_items::blend_write<nodes::EvaluateClosureInputItemsAccessor>(writer, *node);
nodes::socket_items::blend_write<nodes::EvaluateClosureOutputItemsAccessor>(writer, *node);
}
if (node->type_legacy == GEO_NODE_MENU_SWITCH) {
nodes::socket_items::blend_write<nodes::MenuSwitchItemsAccessor>(writer, *node);
}
@ -961,6 +987,8 @@ static bool is_node_socket_supported(const bNodeSocket *sock)
case SOCK_ROTATION:
case SOCK_MENU:
case SOCK_MATRIX:
case SOCK_BUNDLE:
case SOCK_CLOSURE:
return true;
}
return false;
@ -1180,6 +1208,26 @@ void node_tree_blend_read_data(BlendDataReader *reader, ID *owner_id, bNodeTree
nodes::socket_items::blend_read_data<nodes::BakeItemsAccessor>(reader, *node);
break;
}
case GEO_NODE_COMBINE_BUNDLE: {
nodes::socket_items::blend_read_data<nodes::CombineBundleItemsAccessor>(reader, *node);
break;
}
case GEO_NODE_CLOSURE_OUTPUT: {
nodes::socket_items::blend_read_data<nodes::ClosureInputItemsAccessor>(reader, *node);
nodes::socket_items::blend_read_data<nodes::ClosureOutputItemsAccessor>(reader, *node);
break;
}
case GEO_NODE_EVALUATE_CLOSURE: {
nodes::socket_items::blend_read_data<nodes::EvaluateClosureInputItemsAccessor>(reader,
*node);
nodes::socket_items::blend_read_data<nodes::EvaluateClosureOutputItemsAccessor>(reader,
*node);
break;
}
case GEO_NODE_SEPARATE_BUNDLE: {
nodes::socket_items::blend_read_data<nodes::SeparateBundleItemsAccessor>(reader, *node);
break;
}
case GEO_NODE_MENU_SWITCH: {
nodes::socket_items::blend_read_data<nodes::MenuSwitchItemsAccessor>(reader, *node);
break;
@ -1998,6 +2046,8 @@ static void socket_id_user_increment(bNodeSocket *sock)
case SOCK_CUSTOM:
case SOCK_SHADER:
case SOCK_GEOMETRY:
case SOCK_BUNDLE:
case SOCK_CLOSURE:
break;
}
}
@ -2046,6 +2096,8 @@ static bool socket_id_user_decrement(bNodeSocket *sock)
case SOCK_CUSTOM:
case SOCK_SHADER:
case SOCK_GEOMETRY:
case SOCK_BUNDLE:
case SOCK_CLOSURE:
break;
}
return false;
@ -2104,6 +2156,8 @@ void node_modify_socket_type(bNodeTree &ntree,
case SOCK_TEXTURE:
case SOCK_MATERIAL:
case SOCK_MENU:
case SOCK_BUNDLE:
case SOCK_CLOSURE:
break;
}
}
@ -2247,6 +2301,10 @@ std::optional<StringRefNull> node_static_socket_type(const int type, const int s
return "NodeSocketMaterial";
case SOCK_MENU:
return "NodeSocketMenu";
case SOCK_BUNDLE:
return "NodeSocketBundle";
case SOCK_CLOSURE:
return "NodeSocketClosure";
case SOCK_CUSTOM:
break;
}
@ -2344,6 +2402,10 @@ std::optional<StringRefNull> node_static_socket_interface_type_new(const int typ
return "NodeTreeInterfaceSocketMaterial";
case SOCK_MENU:
return "NodeTreeInterfaceSocketMenu";
case SOCK_BUNDLE:
return "NodeTreeInterfaceSocketBundle";
case SOCK_CLOSURE:
return "NodeTreeInterfaceSocketClosure";
case SOCK_CUSTOM:
break;
}
@ -2385,6 +2447,10 @@ std::optional<StringRefNull> node_static_socket_label(const int type, const int
return "Material";
case SOCK_MENU:
return "Menu";
case SOCK_BUNDLE:
return "Bundle";
case SOCK_CLOSURE:
return "Closure";
case SOCK_CUSTOM:
break;
}
@ -2863,6 +2929,8 @@ static void *socket_value_storage(bNodeSocket &socket)
case SOCK_CUSTOM:
case SOCK_SHADER:
case SOCK_GEOMETRY:
case SOCK_BUNDLE:
case SOCK_CLOSURE:
/* Unmovable types. */
break;
}
@ -4283,6 +4351,12 @@ const CPPType *socket_type_to_geo_nodes_base_cpp_type(const eNodeSocketDatatype
case SOCK_MATRIX:
cpp_type = &CPPType::get<float4x4>();
break;
case SOCK_BUNDLE:
cpp_type = &CPPType::get<nodes::BundlePtr>();
break;
case SOCK_CLOSURE:
cpp_type = &CPPType::get<nodes::ClosurePtr>();
break;
default:
cpp_type = slow_socket_type_to_geo_nodes_base_cpp_type(type);
break;
@ -4317,6 +4391,12 @@ std::optional<eNodeSocketDatatype> geo_nodes_base_cpp_type_to_socket_type(const
if (type.is<std::string>()) {
return SOCK_STRING;
}
if (type.is<nodes::BundlePtr>()) {
return SOCK_BUNDLE;
}
if (type.is<nodes::ClosurePtr>()) {
return SOCK_CLOSURE;
}
return std::nullopt;
}

View file

@ -11,6 +11,8 @@
#include "BKE_node.hh"
#include "BKE_node_socket_value.hh"
#include "BKE_volume_grid.hh"
#include "NOD_geometry_nodes_bundle.hh"
#include "NOD_geometry_nodes_closure.hh"
#include "BLI_color.hh"
#include "BLI_math_rotation_types.hh"
@ -59,6 +61,12 @@ template<typename T> static std::optional<eNodeSocketDatatype> static_type_to_so
if constexpr (is_same_any_v<T, std::string>) {
return SOCK_STRING;
}
if constexpr (is_same_any_v<T, nodes::BundlePtr>) {
return SOCK_BUNDLE;
}
if constexpr (is_same_any_v<T, nodes::ClosurePtr>) {
return SOCK_CLOSURE;
}
return std::nullopt;
}
@ -87,6 +95,10 @@ static bool static_type_is_base_socket_type(const eNodeSocketDatatype socket_typ
return std::is_same_v<T, std::string>;
case SOCK_MENU:
return std::is_same_v<T, int>;
case SOCK_BUNDLE:
return std::is_same_v<T, nodes::BundlePtr>;
case SOCK_CLOSURE:
return std::is_same_v<T, nodes::ClosurePtr>;
case SOCK_CUSTOM:
case SOCK_SHADER:
case SOCK_OBJECT:
@ -251,6 +263,14 @@ void SocketValueVariant::store_single(const eNodeSocketDatatype socket_type, con
value_.emplace<std::string>(*static_cast<const std::string *>(value));
break;
}
case SOCK_BUNDLE: {
value_.emplace<nodes::BundlePtr>(*static_cast<const nodes::BundlePtr *>(value));
break;
}
case SOCK_CLOSURE: {
value_.emplace<nodes::ClosurePtr>(*static_cast<const nodes::ClosurePtr *>(value));
break;
}
default: {
BLI_assert_unreachable();
break;
@ -347,6 +367,10 @@ void *SocketValueVariant::allocate_single(const eNodeSocketDatatype socket_type)
return value_.allocate<std::string>();
case SOCK_MENU:
return value_.allocate<int>();
case SOCK_BUNDLE:
return value_.allocate<nodes::BundlePtr>();
case SOCK_CLOSURE:
return value_.allocate<nodes::ClosurePtr>();
default: {
BLI_assert_unreachable();
return nullptr;
@ -405,6 +429,8 @@ INSTANTIATE_SINGLE_AND_FIELD_AND_GRID(blender::math::Quaternion)
INSTANTIATE(std::string)
INSTANTIATE(fn::GField)
INSTANTIATE(blender::nodes::BundlePtr)
INSTANTIATE(blender::nodes::ClosurePtr)
INSTANTIATE(float4x4)
INSTANTIATE(fn::Field<float4x4>)

View file

@ -38,6 +38,12 @@ std::ostream &operator<<(std::ostream &stream, const ReferenceSetInfo &info)
case ReferenceSetType::LocalReferenceSet:
stream << "Local: " << info.socket->name;
break;
case ReferenceSetType::ClosureInputReferenceSet:
stream << "Closure Input Reference: " << info.socket->name;
break;
case ReferenceSetType::ClosureOutputData:
stream << "Closure Output Data: " << info.socket->name;
break;
}
stream << " (";
for (const bNodeSocket *socket : info.potential_data_origins) {
@ -74,14 +80,15 @@ static bool or_into_each_other(MutableBoundedBitSpan a, MutableBoundedBitSpan b)
return true;
}
static bool can_contain_reference(const eNodeSocketDatatype socket_type)
bool can_contain_reference(const eNodeSocketDatatype socket_type)
{
return nodes::socket_type_supports_fields(socket_type);
return nodes::socket_type_supports_fields(socket_type) ||
ELEM(socket_type, SOCK_BUNDLE, SOCK_CLOSURE);
}
static bool can_contain_referenced_data(const eNodeSocketDatatype socket_type)
bool can_contain_referenced_data(const eNodeSocketDatatype socket_type)
{
return ELEM(socket_type, SOCK_GEOMETRY);
return ELEM(socket_type, SOCK_GEOMETRY, SOCK_BUNDLE, SOCK_CLOSURE);
}
static const bNodeTreeZone *get_zone_of_node_if_full(const bNodeTreeZones *zones,
@ -226,7 +233,8 @@ static Array<const aal::RelationsInNode *> prepare_relations_by_node(const bNode
static Vector<ReferenceSetInfo> find_reference_sets(
const bNodeTree &tree,
const Span<const aal::RelationsInNode *> &relations_by_node,
Vector<int> &r_group_output_reference_sets)
Vector<int> &r_group_output_reference_sets,
MultiValueMap<const bNodeTreeZone *, int> &r_output_set_sources_by_closure_zone)
{
Vector<ReferenceSetInfo> reference_sets;
const Span<const bNodeTreeInterfaceSocket *> interface_inputs = tree.interface_inputs();
@ -265,6 +273,7 @@ static Vector<ReferenceSetInfo> find_reference_sets(
}
}
}
/* Handle references created by nodes in the current tree. */
for (const bNode *node : tree.all_nodes()) {
if (node->is_muted()) {
continue;
@ -285,6 +294,45 @@ static Vector<ReferenceSetInfo> find_reference_sets(
}
}
const bNodeTreeZones *zones = tree.zones();
if (!zones) {
return reference_sets;
}
for (const bNodeTreeZone *zone : zones->zones) {
if (zone->output_node->type_legacy != GEO_NODE_CLOSURE_OUTPUT) {
continue;
}
const auto &storage = *static_cast<const NodeGeometryClosureOutput *>(
zone->output_node->storage);
const int old_reference_sets_count = reference_sets.size();
/* Handle references coming from field inputs in the closure. */
for (const int input_i : IndexRange(storage.input_items.items_num)) {
const bNodeSocket &socket = zone->input_node->output_socket(input_i);
if (can_contain_reference(eNodeSocketDatatype(socket.type))) {
reference_sets.append({ReferenceSetType::ClosureInputReferenceSet, &socket});
}
}
/* Handle references required by output geometries in the closure. */
for (const int output_i : IndexRange(storage.output_items.items_num)) {
const bNodeSocket &socket = zone->output_node->input_socket(output_i);
if (can_contain_referenced_data(eNodeSocketDatatype(socket.type))) {
r_output_set_sources_by_closure_zone.add(
zone,
reference_sets.append_and_get_index({ReferenceSetType::ClosureOutputData, &socket}));
}
}
/* All references referenced passed into this zone may exist on the geometry inputs. */
MutableSpan<ReferenceSetInfo> new_reference_sets = reference_sets.as_mutable_span().drop_front(
old_reference_sets_count);
for (const int input_i : IndexRange(storage.input_items.items_num)) {
const bNodeSocket &socket = zone->input_node->output_socket(input_i);
if (can_contain_referenced_data(eNodeSocketDatatype(socket.type))) {
for (ReferenceSetInfo &source : new_reference_sets) {
source.potential_data_origins.append(&socket);
}
}
}
}
return reference_sets;
}
@ -299,7 +347,8 @@ static void set_initial_data_and_reference_bits(const bNodeTree &tree,
r_potential_data_by_socket[socket->index_in_tree()][reference_set_i].set();
}
switch (reference_set.type) {
case ReferenceSetType::LocalReferenceSet: {
case ReferenceSetType::LocalReferenceSet:
case ReferenceSetType::ClosureInputReferenceSet: {
r_potential_reference_by_socket[reference_set.socket->index_in_tree()][reference_set_i]
.set();
break;
@ -311,7 +360,8 @@ static void set_initial_data_and_reference_bits(const bNodeTree &tree,
}
break;
}
case ReferenceSetType::GroupOutputData: {
case ReferenceSetType::GroupOutputData:
case ReferenceSetType::ClosureOutputData: {
/* Nothing to do. */
break;
}
@ -320,22 +370,22 @@ static void set_initial_data_and_reference_bits(const bNodeTree &tree,
}
static BitVector<> get_references_coming_from_outside_zone(
const bNodeTreeZone &zone,
const BitGroupVector<> &potential_data_by_socket,
const BitGroupVector<> &potential_reference_by_socket)
const bNodeTreeZone &zone, const Span<const BitGroupVector<> *> sources)
{
BitVector<> found(potential_data_by_socket.group_size(), false);
BitVector<> found(sources.first()->group_size(), false);
/* Gather references that are passed into the zone from the outside, either through the input
* node or border links. */
for (const bNodeSocket *socket : zone.input_node->input_sockets()) {
const int src = socket->index_in_tree();
found |= potential_data_by_socket[src];
found |= potential_reference_by_socket[src];
for (const BitGroupVector<> *source : sources) {
found |= (*source)[src];
}
}
for (const bNodeLink *link : zone.border_links) {
const int src = link->fromsock->index_in_tree();
found |= potential_data_by_socket[src];
found |= potential_reference_by_socket[src];
for (const BitGroupVector<> *source : sources) {
found |= (*source)[src];
}
}
return found;
}
@ -427,7 +477,7 @@ static bool pass_left_to_right(const bNodeTree &tree,
* references created in the zone stay local inside the zone and are not propagated to the
* outside. Instead, the foreach-element output node creates new references. */
const BitVector<> outside_references = get_references_coming_from_outside_zone(
*zone, r_potential_data_by_socket, r_potential_reference_by_socket);
*zone, {&r_potential_data_by_socket, &r_potential_reference_by_socket});
for (const int item_i : IndexRange(storage->generation_items.items_num)) {
const int src_index =
node->input_socket(storage->main_items.items_num + item_i).index_in_tree();
@ -442,6 +492,38 @@ static bool pass_left_to_right(const bNodeTree &tree,
}
break;
}
case GEO_NODE_CLOSURE_INPUT: {
const bNodeTreeZone *zone = get_zone_of_node_if_full(zones, *node);
if (!zone) {
break;
}
/* Data referenced by border links may also be passed into the closure as input. */
const BitVector<> outside_references = get_references_coming_from_outside_zone(
*zone, {&r_potential_data_by_socket, &r_potential_reference_by_socket});
for (const int i : node->output_sockets().index_range()) {
const int dst_index = zone->input_node->output_socket(i).index_in_tree();
r_potential_data_by_socket[dst_index] |= outside_references;
r_potential_reference_by_socket[dst_index] |= outside_references;
}
break;
}
case GEO_NODE_CLOSURE_OUTPUT: {
const bNodeTreeZone *zone = get_zone_of_node_if_full(zones, *node);
if (!zone) {
break;
}
/* References passed through border links are referenced by the closure. */
const BitVector<> passed_in_references = get_references_coming_from_outside_zone(
*zone, {&r_potential_reference_by_socket});
const int dst_index = zone->output_node->output_socket(0).index_in_tree();
for (const int i : node->input_sockets().index_range()) {
const int src_index = zone->output_node->input_socket(i).index_in_tree();
r_potential_data_by_socket[dst_index] |= r_potential_data_by_socket[src_index];
r_potential_reference_by_socket[dst_index] |= r_potential_reference_by_socket[src_index];
r_potential_reference_by_socket[dst_index] |= passed_in_references;
}
break;
}
case GEO_NODE_REPEAT_OUTPUT: {
const bNodeTreeZone *zone = get_zone_of_node_if_full(zones, *node);
if (!zone) {
@ -460,7 +542,7 @@ static bool pass_left_to_right(const bNodeTree &tree,
}
const BitVector<> outside_references = get_references_coming_from_outside_zone(
*zone, r_potential_data_by_socket, r_potential_reference_by_socket);
*zone, {&r_potential_data_by_socket, &r_potential_reference_by_socket});
/* Propagate within output node. */
for (const int i : IndexRange(items_num)) {
@ -495,7 +577,7 @@ static bool pass_left_to_right(const bNodeTree &tree,
return needs_extra_pass;
}
static void prepare_required_data_for_outputs(
static void prepare_required_data_for_group_outputs(
const bNodeTree &tree,
const Span<ReferenceSetInfo> reference_sets,
const Span<int> group_output_set_sources,
@ -503,7 +585,6 @@ static void prepare_required_data_for_outputs(
const BitGroupVector<> &potential_reference_by_socket,
BitGroupVector<> &r_required_data_by_socket)
{
/* Initialize required data for group output sockets. */
if (const bNode *group_output_node = tree.group_output_node()) {
const Span<const bNodeSocket *> sockets = group_output_node->input_sockets().drop_back(1);
for (const int reference_set_i : group_output_set_sources) {
@ -529,6 +610,72 @@ static void prepare_required_data_for_outputs(
}
}
static void prepare_required_data_for_closure_outputs(
const bNodeTree &tree,
const Span<ReferenceSetInfo> reference_sets,
MultiValueMap<const bNodeTreeZone *, int> &output_set_sources_by_closure_zone,
const BitGroupVector<> &potential_data_by_socket,
const BitGroupVector<> &potential_reference_by_socket,
BitGroupVector<> &r_required_data_by_socket)
{
const bNodeTreeZones *zones = tree.zones();
if (!zones) {
return;
}
for (const bNodeTreeZone *zone : zones->zones) {
if (!zone->input_node || !zone->output_node) {
continue;
}
if (zone->output_node->type_legacy != GEO_NODE_CLOSURE_OUTPUT) {
continue;
}
const Span<int> closure_output_set_sources = output_set_sources_by_closure_zone.lookup(zone);
for (const int reference_set_i : closure_output_set_sources) {
const ReferenceSetInfo &reference_set = reference_sets[reference_set_i];
BLI_assert(reference_set.type == ReferenceSetType::ClosureOutputData);
r_required_data_by_socket[reference_set.socket->index_in_tree()][reference_set_i].set();
}
BitVector<> potential_output_references(reference_sets.size(), false);
const Span<const bNodeSocket *> sockets = zone->output_node->input_sockets().drop_back(1);
for (const bNodeSocket *socket : sockets) {
potential_output_references |= potential_reference_by_socket[socket->index_in_tree()];
}
for (const bNodeSocket *socket : sockets) {
if (!can_contain_referenced_data(eNodeSocketDatatype(socket->type))) {
continue;
}
const int index = socket->index_in_tree();
r_required_data_by_socket[index] |= potential_output_references;
/* Make sure that only available data is also required. This is enforced in the end anyway,
* but may reduce some unnecessary work. */
r_required_data_by_socket[index] &= potential_data_by_socket[index];
}
}
}
static void prepare_required_data_for_outputs(
const bNodeTree &tree,
const Span<ReferenceSetInfo> reference_sets,
const Span<int> group_output_set_sources,
MultiValueMap<const bNodeTreeZone *, int> &output_set_sources_by_closure_zone,
const BitGroupVector<> &potential_data_by_socket,
const BitGroupVector<> &potential_reference_by_socket,
BitGroupVector<> &r_required_data_by_socket)
{
prepare_required_data_for_group_outputs(tree,
reference_sets,
group_output_set_sources,
potential_data_by_socket,
potential_reference_by_socket,
r_required_data_by_socket);
prepare_required_data_for_closure_outputs(tree,
reference_sets,
output_set_sources_by_closure_zone,
potential_data_by_socket,
potential_reference_by_socket,
r_required_data_by_socket);
}
static bool pass_right_to_left(const bNodeTree &tree,
const Span<const aal::RelationsInNode *> &relations_by_node,
const BitGroupVector<> &potential_reference_by_socket,
@ -649,6 +796,31 @@ static bool pass_right_to_left(const bNodeTree &tree,
}
break;
}
case GEO_NODE_EVALUATE_CLOSURE: {
/* Data referenced by the closure is required on all the other inputs. */
const bNodeSocket &closure_socket = node->input_socket(0);
const BoundedBitSpan required_references =
potential_reference_by_socket[closure_socket.index_in_tree()];
for (const bNodeSocket *input_socket : node->input_sockets().drop_front(1)) {
const int dst_index = input_socket->index_in_tree();
r_required_data_by_socket[dst_index] |= required_references;
}
break;
}
case GEO_NODE_CLOSURE_OUTPUT: {
const bNodeTreeZone *zone = get_zone_of_node_if_full(zones, *node);
if (!zone) {
break;
}
/* Data that's required on the closure is also required on all inputs of the closure. */
const bNodeSocket &output_socket = node->output_socket(0);
const BoundedBitSpan required_data =
r_required_data_by_socket[output_socket.index_in_tree()];
for (const bNodeSocket *input_socket : node->input_sockets()) {
r_required_data_by_socket[input_socket->index_in_tree()] |= required_data;
}
break;
}
}
}
return needs_extra_pass;
@ -788,8 +960,9 @@ static std::unique_ptr<ReferenceLifetimesInfo> make_reference_lifetimes_info(con
Array<const aal::RelationsInNode *> relations_by_node = prepare_relations_by_node(tree, scope);
Vector<int> group_output_set_sources;
MultiValueMap<const bNodeTreeZone *, int> output_set_sources_by_closure_zone;
reference_lifetimes_info->reference_sets = find_reference_sets(
tree, relations_by_node, group_output_set_sources);
tree, relations_by_node, group_output_set_sources, output_set_sources_by_closure_zone);
const Span<ReferenceSetInfo> reference_sets = reference_lifetimes_info->reference_sets;
const int sockets_num = tree.all_sockets().size();
@ -811,6 +984,7 @@ static std::unique_ptr<ReferenceLifetimesInfo> make_reference_lifetimes_info(con
prepare_required_data_for_outputs(tree,
reference_sets,
group_output_set_sources,
output_set_sources_by_closure_zone,
potential_data_by_socket,
potential_reference_by_socket,
required_data_by_socket);
@ -828,7 +1002,7 @@ static std::unique_ptr<ReferenceLifetimesInfo> make_reference_lifetimes_info(con
std::cout << "\n\n"
<< node_tree_to_dot(tree,
bNodeTreeBitGroupVectorOptions(
{required_data_by_socket, potential_reference_by_socket}))
{potential_reference_by_socket, required_data_by_socket}))
<< "\n\n";
#endif

View file

@ -231,6 +231,10 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme)
FROM_DEFAULT_V4_UCHAR(tui.wcol_state.success);
}
if (!USER_VERSION_ATLEAST(405, 14)) {
FROM_DEFAULT_V4_UCHAR(space_node.node_zone_closure);
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a USER_VERSION_ATLEAST check.

View file

@ -7,6 +7,7 @@ set(INC
../animrig
../makesrna
../modifiers
../nodes
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna
)

View file

@ -381,6 +381,8 @@ static std::optional<ID_Type> socket_type_to_id_type(const eNodeSocketDatatype s
case SOCK_ROTATION:
case SOCK_MENU:
case SOCK_MATRIX:
case SOCK_BUNDLE:
case SOCK_CLOSURE:
return std::nullopt;
case SOCK_OBJECT:
return ID_OB;

View file

@ -1124,6 +1124,8 @@ DEF_ICON_VECTOR(NODE_SOCKET_MATERIAL)
DEF_ICON_VECTOR(NODE_SOCKET_ROTATION)
DEF_ICON_VECTOR(NODE_SOCKET_MENU)
DEF_ICON_VECTOR(NODE_SOCKET_MATRIX)
DEF_ICON_VECTOR(NODE_SOCKET_BUNDLE)
DEF_ICON_VECTOR(NODE_SOCKET_CLOSURE)
/* add as needed. */

View file

@ -202,6 +202,7 @@ enum ThemeColorID {
TH_NODE_ZONE_SIMULATION,
TH_NODE_ZONE_REPEAT,
TH_NODE_ZONE_FOREACH_GEOMETRY_ELEMENT,
TH_NODE_ZONE_CLOSURE,
TH_SIMULATED_FRAMES,
TH_CONSOLE_OUTPUT,

View file

@ -633,6 +633,8 @@ DEF_ICON_NODE_SOCKET_DRAW(material, eNodeSocketDatatype::SOCK_MATERIAL)
DEF_ICON_NODE_SOCKET_DRAW(rotation, eNodeSocketDatatype::SOCK_ROTATION)
DEF_ICON_NODE_SOCKET_DRAW(menu, eNodeSocketDatatype::SOCK_MENU)
DEF_ICON_NODE_SOCKET_DRAW(matrix, eNodeSocketDatatype::SOCK_MATRIX)
DEF_ICON_NODE_SOCKET_DRAW(bundle, eNodeSocketDatatype::SOCK_BUNDLE)
DEF_ICON_NODE_SOCKET_DRAW(closure, eNodeSocketDatatype::SOCK_CLOSURE)
/* Dynamically render icon instead of rendering a plain color to a texture/buffer
* This is not strictly a "vicon", as it needs access to icon->obj to get the color info,
@ -1025,6 +1027,8 @@ static void init_internal_icons()
def_internal_vicon(ICON_NODE_SOCKET_ROTATION, icon_node_socket_draw_rotation);
def_internal_vicon(ICON_NODE_SOCKET_MENU, icon_node_socket_draw_menu);
def_internal_vicon(ICON_NODE_SOCKET_MATRIX, icon_node_socket_draw_matrix);
def_internal_vicon(ICON_NODE_SOCKET_BUNDLE, icon_node_socket_draw_bundle);
def_internal_vicon(ICON_NODE_SOCKET_CLOSURE, icon_node_socket_draw_closure);
}
#else

View file

@ -694,6 +694,9 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid)
case TH_NODE_ZONE_FOREACH_GEOMETRY_ELEMENT:
cp = ts->node_zone_foreach_geometry_element;
break;
case TH_NODE_ZONE_CLOSURE:
cp = ts->node_zone_closure;
break;
case TH_SIMULATED_FRAMES:
cp = ts->simulated_frames;
break;

View file

@ -56,7 +56,7 @@ static void draw_node_input(bContext *C,
if (socket.typeinfo->draw == nullptr) {
return;
}
if (ELEM(socket.type, SOCK_GEOMETRY, SOCK_MATRIX, SOCK_SHADER)) {
if (ELEM(socket.type, SOCK_GEOMETRY, SOCK_MATRIX, SOCK_SHADER, SOCK_BUNDLE, SOCK_CLOSURE)) {
return;
}
const bNode &node = *static_cast<bNode *>(node_ptr->data);

View file

@ -7,6 +7,7 @@ set(INC
../../ikplugin
../../makesrna
../../modifiers
../../nodes
../../python
../../shader_fx

View file

@ -6,6 +6,7 @@ set(INC
../include
../../makesrna
../../modifiers
../../nodes
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna

View file

@ -1145,6 +1145,8 @@ static const float std_node_socket_colors[][4] = {
{0.65, 0.39, 0.78, 1.0}, /* SOCK_ROTATION */
{0.40, 0.40, 0.40, 1.0}, /* SOCK_MENU */
{0.72, 0.20, 0.52, 1.0}, /* SOCK_MATRIX */
{0.30, 0.50, 0.50, 1.0}, /* SOCK_BUNDLE */
{0.50, 0.60, 0.40, 1.0}, /* SOCK_CLOSURE */
};
void std_node_socket_colors_get(int socket_type, float *r_color)
@ -1172,23 +1174,16 @@ static void std_node_socket_color_simple_fn(const bke::bNodeSocketType *type, fl
using SocketColorFn = void (*)(bContext *C, PointerRNA *ptr, PointerRNA *node_ptr, float *r_color);
/* Callbacks for all built-in socket types. */
static const SocketColorFn std_node_socket_color_funcs[] = {
std_node_socket_color_fn<SOCK_FLOAT>,
std_node_socket_color_fn<SOCK_VECTOR>,
std_node_socket_color_fn<SOCK_RGBA>,
std_node_socket_color_fn<SOCK_SHADER>,
std_node_socket_color_fn<SOCK_BOOLEAN>,
nullptr /* UNUSED. */,
std_node_socket_color_fn<SOCK_INT>,
std_node_socket_color_fn<SOCK_STRING>,
std_node_socket_color_fn<SOCK_OBJECT>,
std_node_socket_color_fn<SOCK_IMAGE>,
std_node_socket_color_fn<SOCK_GEOMETRY>,
std_node_socket_color_fn<SOCK_COLLECTION>,
std_node_socket_color_fn<SOCK_TEXTURE>,
std_node_socket_color_fn<SOCK_MATERIAL>,
std_node_socket_color_fn<SOCK_ROTATION>,
std_node_socket_color_fn<SOCK_MENU>,
std_node_socket_color_fn<SOCK_MATRIX>,
std_node_socket_color_fn<SOCK_FLOAT>, std_node_socket_color_fn<SOCK_VECTOR>,
std_node_socket_color_fn<SOCK_RGBA>, std_node_socket_color_fn<SOCK_SHADER>,
std_node_socket_color_fn<SOCK_BOOLEAN>, nullptr /* UNUSED. */,
std_node_socket_color_fn<SOCK_INT>, std_node_socket_color_fn<SOCK_STRING>,
std_node_socket_color_fn<SOCK_OBJECT>, std_node_socket_color_fn<SOCK_IMAGE>,
std_node_socket_color_fn<SOCK_GEOMETRY>, std_node_socket_color_fn<SOCK_COLLECTION>,
std_node_socket_color_fn<SOCK_TEXTURE>, std_node_socket_color_fn<SOCK_MATERIAL>,
std_node_socket_color_fn<SOCK_ROTATION>, std_node_socket_color_fn<SOCK_MENU>,
std_node_socket_color_fn<SOCK_MATRIX>, std_node_socket_color_fn<SOCK_BUNDLE>,
std_node_socket_color_fn<SOCK_CLOSURE>,
};
/* draw function for file output node sockets,
@ -1580,6 +1575,8 @@ static void std_node_socket_interface_draw(ID *id,
case SOCK_SHADER:
case SOCK_GEOMETRY:
case SOCK_MATRIX:
case SOCK_BUNDLE:
case SOCK_CLOSURE:
break;
case SOCK_CUSTOM:

View file

@ -1818,6 +1818,48 @@ static void create_inspection_string_for_geometry_socket(fmt::memory_buffer &buf
}
}
static void create_inspection_string_for_bundle(const geo_log::BundleValueLog &value_log,
fmt::memory_buffer &buf)
{
if (value_log.items.is_empty()) {
fmt::format_to(fmt::appender(buf), "{}", TIP_("Empty Bundle"));
return;
}
fmt::format_to(fmt::appender(buf), "{}", TIP_("Bundle values:\n"));
for (const geo_log::BundleValueLog::Item &item : value_log.items) {
fmt::format_to(fmt::appender(buf),
fmt::runtime("\u2022 \"{}\" ({})\n"),
item.key.identifiers().first(),
TIP_(item.type->label));
}
}
static void create_inspection_string_for_closure(const geo_log::ClosureValueLog &value_log,
fmt::memory_buffer &buf)
{
if (value_log.inputs.is_empty() && value_log.outputs.is_empty()) {
fmt::format_to(fmt::appender(buf), "{}", TIP_("Empty Closure"));
}
if (!value_log.inputs.is_empty()) {
fmt::format_to(fmt::appender(buf), "{}:\n", TIP_("Inputs"));
for (const geo_log::ClosureValueLog::Item &item : value_log.inputs) {
fmt::format_to(fmt::appender(buf),
fmt::runtime("\u2022 {} ({})\n"),
item.key.identifiers().first(),
IFACE_(item.type->label));
}
}
if (!value_log.outputs.is_empty()) {
fmt::format_to(fmt::appender(buf), "{}:\n", TIP_("Outputs"));
for (const geo_log::ClosureValueLog::Item &item : value_log.outputs) {
fmt::format_to(fmt::appender(buf),
fmt::runtime("\u2022 {} ({})\n"),
item.key.identifiers().first(),
IFACE_(item.type->label));
}
}
}
static void create_inspection_string_for_default_socket_value(const bNodeSocket &socket,
fmt::memory_buffer &buf)
{
@ -1905,6 +1947,16 @@ static std::optional<std::string> create_log_inspection_string(geo_log::GeoTreeL
{
create_inspection_string_for_geometry_info(*geo_value_log, buf);
}
else if (const geo_log::BundleValueLog *bundle_value_log =
dynamic_cast<const geo_log::BundleValueLog *>(value_log))
{
create_inspection_string_for_bundle(*bundle_value_log, buf);
}
else if (const geo_log::ClosureValueLog *closure_value_log =
dynamic_cast<const geo_log::ClosureValueLog *>(value_log))
{
create_inspection_string_for_closure(*closure_value_log, buf);
}
std::string str = fmt::to_string(buf);
if (str.empty()) {
@ -3117,7 +3169,8 @@ static Vector<NodeExtraInfoRow> node_get_extra_info(const bContext &C,
NODE_GROUP_OUTPUT,
GEO_NODE_SIMULATION_OUTPUT,
GEO_NODE_REPEAT_OUTPUT,
GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT)))
GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT,
GEO_NODE_EVALUATE_CLOSURE)))
{
std::optional<NodeExtraInfoRow> row = node_get_execution_time_label_row(
tree_draw_ctx, snode, node);

View file

@ -2525,6 +2525,8 @@ static int get_main_socket_priority(const bNodeSocket *socket)
case SOCK_TEXTURE:
case SOCK_MATERIAL:
case SOCK_MENU:
case SOCK_BUNDLE:
case SOCK_CLOSURE:
return 6;
}
return -1;

View file

@ -55,6 +55,9 @@ static ViewerPathElem *viewer_path_elem_for_zone(const bNodeTreeZone &zone)
node_elem->index = storage.inspection_index;
return &node_elem->base;
}
case GEO_NODE_CLOSURE_OUTPUT: {
return nullptr;
}
}
BLI_assert_unreachable();
return nullptr;
@ -125,6 +128,9 @@ static void viewer_path_for_geometry_node(const SpaceNode &snode,
node->identifier);
for (const bNodeTreeZone *zone : zone_stack) {
ViewerPathElem *zone_elem = viewer_path_elem_for_zone(*zone);
if (!zone_elem) {
return;
}
BLI_addtail(&r_dst.path, zone_elem);
}
@ -143,6 +149,9 @@ static void viewer_path_for_geometry_node(const SpaceNode &snode,
node.identifier);
for (const bNodeTreeZone *zone : zone_stack) {
ViewerPathElem *zone_elem = viewer_path_elem_for_zone(*zone);
if (!zone_elem) {
return;
}
BLI_addtail(&r_dst.path, zone_elem);
}

View file

@ -121,6 +121,11 @@ class GraphExecutor : public LazyFunction {
friend class Executor;
public:
GraphExecutor(const Graph &graph,
const Logger *logger,
const SideEffectProvider *side_effect_provider,
const NodeExecuteWrapper *node_execute_wrapper);
GraphExecutor(const Graph &graph,
Vector<const GraphInputSocket *> graph_inputs,
Vector<const GraphOutputSocket *> graph_outputs,

View file

@ -1459,6 +1459,19 @@ inline void Executor::execute_node(const FunctionNode &node,
}
}
GraphExecutor::GraphExecutor(const Graph &graph,
const Logger *logger,
const SideEffectProvider *side_effect_provider,
const NodeExecuteWrapper *node_execute_wrapper)
: GraphExecutor(graph,
Vector<const GraphInputSocket *>(graph.graph_inputs()),
Vector<const GraphOutputSocket *>(graph.graph_outputs()),
logger,
side_effect_provider,
node_execute_wrapper)
{
}
GraphExecutor::GraphExecutor(const Graph &graph,
Vector<const GraphInputSocket *> graph_inputs,
Vector<const GraphOutputSocket *> graph_outputs,

View file

@ -273,6 +273,8 @@ typedef enum eNodeSocketDatatype {
SOCK_ROTATION = 14,
SOCK_MENU = 15,
SOCK_MATRIX = 16,
SOCK_BUNDLE = 17,
SOCK_CLOSURE = 18,
} eNodeSocketDatatype;
/** Socket shape. */
@ -2107,6 +2109,85 @@ typedef struct NodeGeometryForeachGeometryElementOutput {
char _pad[3];
} NodeGeometryForeachGeometryElementOutput;
typedef struct NodeGeometryClosureInput {
/** bNode.identifier of the corresponding output node. */
int32_t output_node_id;
} NodeGeometryClosureInput;
typedef struct NodeGeometryClosureInputItem {
char *name;
/** #eNodeSocketDatatype. */
short socket_type;
char _pad[2];
int identifier;
} NodeGeometryClosureInputItem;
typedef struct NodeGeometryClosureOutputItem {
char *name;
/** #eNodeSocketDatatype. */
short socket_type;
char _pad[2];
int identifier;
} NodeGeometryClosureOutputItem;
typedef struct NodeGeometryClosureInputItems {
NodeGeometryClosureInputItem *items;
int items_num;
int active_index;
int next_identifier;
char _pad[4];
} NodeGeometryClosureInputItems;
typedef struct NodeGeometryClosureOutputItems {
NodeGeometryClosureOutputItem *items;
int items_num;
int active_index;
int next_identifier;
char _pad[4];
} NodeGeometryClosureOutputItems;
typedef struct NodeGeometryClosureOutput {
NodeGeometryClosureInputItems input_items;
NodeGeometryClosureOutputItems output_items;
} NodeGeometryClosureOutput;
typedef struct NodeGeometryEvaluateClosureInputItem {
char *name;
/** #eNodeSocketDatatype */
short socket_type;
char _pad[2];
int identifier;
} NodeGeometryEvaluateClosureInputItem;
typedef struct NodeGeometryEvaluateClosureOutputItem {
char *name;
/** #eNodeSocketDatatype */
short socket_type;
char _pad[2];
int identifier;
} NodeGeometryEvaluateClosureOutputItem;
typedef struct NodeGeometryEvaluateClosureInputItems {
NodeGeometryEvaluateClosureInputItem *items;
int items_num;
int active_index;
int next_identifier;
char _pad[4];
} NodeGeometryEvaluateClosureInputItems;
typedef struct NodeGeometryEvaluateClosureOutputItems {
NodeGeometryEvaluateClosureOutputItem *items;
int items_num;
int active_index;
int next_identifier;
char _pad[4];
} NodeGeometryEvaluateClosureOutputItems;
typedef struct NodeGeometryEvaluateClosure {
NodeGeometryEvaluateClosureInputItems input_items;
NodeGeometryEvaluateClosureOutputItems output_items;
} NodeGeometryEvaluateClosure;
typedef struct IndexSwitchItem {
/** Generated unique identifier which stays the same even when the item order or names change. */
int identifier;
@ -2221,6 +2302,36 @@ typedef struct NodeGeometryBake {
char _pad[4];
} NodeGeometryBake;
typedef struct NodeGeometryCombineBundleItem {
char *name;
int identifier;
int16_t socket_type;
char _pad[2];
} NodeGeometryCombineBundleItem;
typedef struct NodeGeometryCombineBundle {
NodeGeometryCombineBundleItem *items;
int items_num;
int next_identifier;
int active_index;
char _pad[4];
} NodeGeometryCombineBundle;
typedef struct NodeGeometrySeparateBundleItem {
char *name;
int identifier;
int16_t socket_type;
char _pad[2];
} NodeGeometrySeparateBundleItem;
typedef struct NodeGeometrySeparateBundle {
NodeGeometrySeparateBundleItem *items;
int items_num;
int next_identifier;
int active_index;
char _pad[4];
} NodeGeometrySeparateBundle;
/* script node mode */
enum {
NODE_SCRIPT_INTERNAL = 0,

View file

@ -359,7 +359,9 @@ typedef struct ThemeSpace {
unsigned char node_zone_simulation[4];
unsigned char node_zone_repeat[4];
unsigned char node_zone_foreach_geometry_element[4];
unsigned char node_zone_closure[4];
unsigned char simulated_frames[4];
char _pad7[4];
/** For sequence editor. */
unsigned char movie[4], movieclip[4], mask[4], image[4], scene[4], audio[4];
@ -766,7 +768,8 @@ typedef struct UserDef_Experimental {
char use_sculpt_texture_paint;
char use_new_volume_nodes;
char use_shader_node_previews;
char _pad[6];
char use_bundle_and_closure_nodes;
char _pad[5];
} UserDef_Experimental;
#define USER_EXPERIMENTAL_TEST(userdef, member) \

View file

@ -34,6 +34,8 @@ const EnumPropertyItem rna_enum_node_socket_type_items[] = {
{SOCK_TEXTURE, "TEXTURE", ICON_NODE_SOCKET_TEXTURE, "Texture", ""},
{SOCK_MATERIAL, "MATERIAL", ICON_NODE_SOCKET_MATERIAL, "Material", ""},
{SOCK_MENU, "MENU", ICON_NODE_SOCKET_MENU, "Menu", ""},
{SOCK_BUNDLE, "BUNDLE", ICON_NODE_SOCKET_BUNDLE, "Bundle", ""},
{SOCK_CLOSURE, "CLOSURE", ICON_NODE_SOCKET_CLOSURE, "Closure", ""},
{0, nullptr, 0, nullptr, nullptr},
};
@ -1565,6 +1567,48 @@ static void rna_def_node_socket_interface_geometry(BlenderRNA *brna, const char
rna_def_node_tree_interface_socket_builtin(srna);
}
static void rna_def_node_socket_bundle(BlenderRNA *brna, const char *identifier)
{
StructRNA *srna;
srna = RNA_def_struct(brna, identifier, "NodeSocketStandard");
RNA_def_struct_ui_text(srna, "Bundle Node Socket", "Bundle socket of a node");
RNA_def_struct_ui_icon(srna, ICON_NODE_SOCKET_BUNDLE);
RNA_def_struct_sdna(srna, "bNodeSocket");
}
static void rna_def_node_socket_interface_bundle(BlenderRNA *brna, const char *identifier)
{
StructRNA *srna;
srna = RNA_def_struct(brna, identifier, "NodeTreeInterfaceSocket");
RNA_def_struct_ui_text(srna, "Bundle Node Socket Interface", "Bundle socket of a node");
RNA_def_struct_sdna(srna, "bNodeTreeInterfaceSocket");
rna_def_node_tree_interface_socket_builtin(srna);
}
static void rna_def_node_socket_closure(BlenderRNA *brna, const char *identifier)
{
StructRNA *srna;
srna = RNA_def_struct(brna, identifier, "NodeSocketStandard");
RNA_def_struct_ui_text(srna, "Closure Node Socket", "Closure socket of a node");
RNA_def_struct_ui_icon(srna, ICON_NODE_SOCKET_CLOSURE);
RNA_def_struct_sdna(srna, "bNodeSocket");
}
static void rna_def_node_socket_interface_closure(BlenderRNA *brna, const char *identifier)
{
StructRNA *srna;
srna = RNA_def_struct(brna, identifier, "NodeTreeInterfaceSocket");
RNA_def_struct_ui_text(srna, "Closure Node Socket Interface", "Closure socket of a node");
RNA_def_struct_sdna(srna, "bNodeTreeInterfaceSocket");
rna_def_node_tree_interface_socket_builtin(srna);
}
static void rna_def_node_socket_collection(BlenderRNA *brna, const char *identifier)
{
StructRNA *srna;
@ -1787,6 +1831,8 @@ static const bNodeSocketStaticTypeInfo node_socket_subtypes[] = {
{"NodeSocketTexture", "NodeTreeInterfaceSocketTexture", SOCK_TEXTURE, PROP_NONE},
{"NodeSocketMaterial", "NodeTreeInterfaceSocketMaterial", SOCK_MATERIAL, PROP_NONE},
{"NodeSocketMenu", "NodeTreeInterfaceSocketMenu", SOCK_MENU, PROP_NONE},
{"NodeSocketBundle", "NodeTreeInterfaceSocketBundle", SOCK_BUNDLE, PROP_NONE},
{"NodeSocketClosure", "NodeTreeInterfaceSocketClosure", SOCK_CLOSURE, PROP_NONE},
};
static void rna_def_node_socket_subtypes(BlenderRNA *brna)
@ -1843,6 +1889,12 @@ static void rna_def_node_socket_subtypes(BlenderRNA *brna)
case SOCK_MENU:
rna_def_node_socket_menu(brna, identifier);
break;
case SOCK_BUNDLE:
rna_def_node_socket_bundle(brna, identifier);
break;
case SOCK_CLOSURE:
rna_def_node_socket_closure(brna, identifier);
break;
case SOCK_CUSTOM:
break;
@ -1909,6 +1961,12 @@ void rna_def_node_socket_interface_subtypes(BlenderRNA *brna)
case SOCK_MATERIAL:
rna_def_node_socket_interface_material(brna, identifier);
break;
case SOCK_BUNDLE:
rna_def_node_socket_interface_bundle(brna, identifier);
break;
case SOCK_CLOSURE:
rna_def_node_socket_interface_closure(brna, identifier);
break;
case SOCK_CUSTOM:
break;

View file

@ -57,6 +57,8 @@ const EnumPropertyItem rna_enum_node_socket_data_type_items[] = {
{SOCK_COLLECTION, "COLLECTION", ICON_NODE_SOCKET_COLLECTION, "Collection", ""},
{SOCK_TEXTURE, "TEXTURE", ICON_NODE_SOCKET_TEXTURE, "Texture", ""},
{SOCK_MATERIAL, "MATERIAL", ICON_NODE_SOCKET_MATERIAL, "Material", ""},
{SOCK_BUNDLE, "BUNDLE", ICON_NODE_SOCKET_BUNDLE, "Bundle", ""},
{SOCK_CLOSURE, "CLOSURE", ICON_NODE_SOCKET_CLOSURE, "Closure", ""},
{0, nullptr, 0, nullptr, nullptr},
};
@ -627,7 +629,9 @@ static const EnumPropertyItem node_cryptomatte_layer_name_items[] = {
# include "NOD_common.hh"
# include "NOD_composite.hh"
# include "NOD_geo_bake.hh"
# include "NOD_geo_bundle.hh"
# include "NOD_geo_capture_attribute.hh"
# include "NOD_geo_closure.hh"
# include "NOD_geo_foreach_geometry_element.hh"
# include "NOD_geo_index_switch.hh"
# include "NOD_geo_menu_switch.hh"
@ -654,12 +658,18 @@ static const EnumPropertyItem node_cryptomatte_layer_name_items[] = {
using blender::float2;
using blender::nodes::BakeItemsAccessor;
using blender::nodes::CaptureAttributeItemsAccessor;
using blender::nodes::ClosureInputItemsAccessor;
using blender::nodes::ClosureOutputItemsAccessor;
using blender::nodes::CombineBundleItemsAccessor;
using blender::nodes::EvaluateClosureInputItemsAccessor;
using blender::nodes::EvaluateClosureOutputItemsAccessor;
using blender::nodes::ForeachGeometryElementGenerationItemsAccessor;
using blender::nodes::ForeachGeometryElementInputItemsAccessor;
using blender::nodes::ForeachGeometryElementMainItemsAccessor;
using blender::nodes::IndexSwitchItemsAccessor;
using blender::nodes::MenuSwitchItemsAccessor;
using blender::nodes::RepeatItemsAccessor;
using blender::nodes::SeparateBundleItemsAccessor;
using blender::nodes::SimulationItemsAccessor;
extern FunctionRNA rna_NodeTree_poll_func;
@ -10022,6 +10032,13 @@ static void def_geo_foreach_geometry_element_input(BlenderRNA *brna, StructRNA *
def_common_zone_input(brna, srna);
}
static void def_geo_closure_input(BlenderRNA *brna, StructRNA *srna)
{
RNA_def_struct_sdna_from(srna, "NodeGeometryClosureInput", "storage");
def_common_zone_input(brna, srna);
}
static void rna_def_node_item_array_socket_item_common(StructRNA *srna,
const char *accessor,
const bool add_socket_type)
@ -10404,6 +10421,79 @@ static void def_geo_foreach_geometry_element_output(BlenderRNA * /*brna*/, Struc
RNA_def_property_update(prop, NC_NODE, "rna_Node_update");
}
static void rna_def_geo_closure_input_item(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "NodeGeometryClosureInputItem", nullptr);
RNA_def_struct_ui_text(srna, "Closure Input Item", "");
RNA_def_struct_sdna(srna, "NodeGeometryClosureInputItem");
rna_def_node_item_array_socket_item_common(srna, "ClosureInputItemsAccessor", true);
}
static void rna_def_geo_closure_input_items(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "NodeGeometryClosureInputItems", nullptr);
RNA_def_struct_ui_text(srna, "Closure Input Items", "");
RNA_def_struct_sdna(srna, "bNode");
rna_def_node_item_array_new_with_socket_and_name(
srna, "NodeGeometryClosureInputItem", "ClosureInputItemsAccessor");
rna_def_node_item_array_common_functions(
srna, "NodeGeometryClosureInputItem", "ClosureInputItemsAccessor");
}
static void rna_def_geo_closure_output_item(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "NodeGeometryClosureOutputItem", nullptr);
RNA_def_struct_ui_text(srna, "Closure Output Item", "");
RNA_def_struct_sdna(srna, "NodeGeometryClosureOutputItem");
rna_def_node_item_array_socket_item_common(srna, "ClosureOutputItemsAccessor", true);
}
static void rna_def_geo_closure_output_items(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "NodeGeometryClosureOutputItems", nullptr);
RNA_def_struct_ui_text(srna, "Closure Output Items", "");
RNA_def_struct_sdna(srna, "bNode");
rna_def_node_item_array_new_with_socket_and_name(
srna, "NodeGeometryClosureOutputItem", "ClosureOutputItemsAccessor");
rna_def_node_item_array_common_functions(
srna, "NodeGeometryClosureOutputItem", "ClosureOutputItemsAccessor");
}
static void def_geo_closure_output(BlenderRNA * /*brna*/, StructRNA *srna)
{
PropertyRNA *prop;
RNA_def_struct_sdna_from(srna, "NodeGeometryClosureOutput", "storage");
prop = RNA_def_property(srna, "input_items", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, nullptr, "input_items.items", "input_items.items_num");
RNA_def_property_struct_type(prop, "NodeGeometryClosureInputItem");
RNA_def_property_srna(prop, "NodeGeometryClosureInputItems");
prop = RNA_def_property(srna, "output_items", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, nullptr, "output_items.items", "output_items.items_num");
RNA_def_property_struct_type(prop, "NodeGeometryClosureOutputItem");
RNA_def_property_srna(prop, "NodeGeometryClosureOutputItems");
prop = RNA_def_property(srna, "active_input_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, nullptr, "input_items.active_index");
RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_update(prop, NC_NODE, nullptr);
prop = RNA_def_property(srna, "active_output_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, nullptr, "output_items.active_index");
RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_update(prop, NC_NODE, nullptr);
}
static void rna_def_geo_capture_attribute_item(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "NodeGeometryCaptureAttributeItem", nullptr);
@ -10471,6 +10561,87 @@ static void rna_def_geo_capture_attribute(BlenderRNA * /*brna*/, StructRNA *srna
RNA_def_property_update(prop, NC_NODE, "rna_Node_update");
}
static void rna_def_geo_evaluate_closure_input_item(BlenderRNA *brna)
{
StructRNA *srna;
srna = RNA_def_struct(brna, "NodeGeometryEvaluateClosureInputItem", nullptr);
RNA_def_struct_ui_text(srna, "Input Item", "");
rna_def_node_item_array_socket_item_common(srna, "EvaluateClosureInputItemsAccessor", true);
}
static void rna_def_geo_evaluate_closure_input_items(BlenderRNA *brna)
{
StructRNA *srna;
srna = RNA_def_struct(brna, "NodeGeometryEvaluateClosureInputItems", nullptr);
RNA_def_struct_ui_text(srna, "Input Items", "");
RNA_def_struct_sdna(srna, "bNode");
rna_def_node_item_array_new_with_socket_and_name(
srna, "NodeGeometryEvaluateClosureInputItem", "EvaluateClosureInputItemsAccessor");
rna_def_node_item_array_common_functions(
srna, "NodeGeometryEvaluateClosureInputItem", "EvaluateClosureInputItemsAccessor");
}
static void rna_def_geo_evaluate_closure_output_item(BlenderRNA *brna)
{
StructRNA *srna;
srna = RNA_def_struct(brna, "NodeGeometryEvaluateClosureOutputItem", nullptr);
RNA_def_struct_ui_text(srna, "Output Item", "");
rna_def_node_item_array_socket_item_common(srna, "EvaluateClosureOutputItemsAccessor", true);
}
static void rna_def_geo_evaluate_closure_output_items(BlenderRNA *brna)
{
StructRNA *srna;
srna = RNA_def_struct(brna, "NodeGeometryEvaluateClosureOutputItems", nullptr);
RNA_def_struct_ui_text(srna, "Output Items", "");
RNA_def_struct_sdna(srna, "bNode");
rna_def_node_item_array_new_with_socket_and_name(
srna, "NodeGeometryEvaluateClosureOutputItem", "EvaluateClosureOutputItemsAccessor");
rna_def_node_item_array_common_functions(
srna, "NodeGeometryEvaluateClosureOutputItem", "EvaluateClosureOutputItemsAccessor");
}
static void def_geo_evaluate_closure(BlenderRNA * /*brna*/, StructRNA *srna)
{
PropertyRNA *prop;
RNA_def_struct_sdna_from(srna, "NodeGeometryEvaluateClosure", "storage");
prop = RNA_def_property(srna, "input_items", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, nullptr, "input_items.items", "input_items.items_num");
RNA_def_property_struct_type(prop, "NodeGeometryEvaluateClosureInputItem");
RNA_def_property_ui_text(prop, "Input Items", "");
RNA_def_property_srna(prop, "NodeGeometryEvaluateClosureInputItems");
prop = RNA_def_property(srna, "output_items", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, nullptr, "output_items.items", "output_items.items_num");
RNA_def_property_struct_type(prop, "NodeGeometryEvaluateClosureOutputItem");
RNA_def_property_ui_text(prop, "Output Items", "");
RNA_def_property_srna(prop, "NodeGeometryEvaluateClosureOutputItems");
prop = RNA_def_property(srna, "active_input_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, nullptr, "input_items.active_index");
RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_update(prop, NC_NODE, nullptr);
prop = RNA_def_property(srna, "active_output_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, nullptr, "output_items.active_index");
RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_update(prop, NC_NODE, nullptr);
}
static void rna_def_geo_bake_item(BlenderRNA *brna)
{
PropertyRNA *prop;
@ -10543,6 +10714,86 @@ static void rna_def_geo_bake(BlenderRNA * /*brna*/, StructRNA *srna)
RNA_def_property_update(prop, NC_NODE, nullptr);
}
static void rna_def_geo_combine_bundle_item(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "NodeGeometryCombineBundleItem", nullptr);
RNA_def_struct_ui_text(srna, "Combine Bundle Item", "");
rna_def_node_item_array_socket_item_common(srna, "CombineBundleItemsAccessor", true);
}
static void rna_def_geo_combine_bundle_items(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "NodeGeometryCombineBundleItems", nullptr);
RNA_def_struct_sdna(srna, "bNode");
RNA_def_struct_ui_text(srna, "Items", "Collection of combine bundle items");
rna_def_node_item_array_new_with_socket_and_name(
srna, "NodeGeometryCombineBundleItem", "CombineBundleItemsAccessor");
rna_def_node_item_array_common_functions(
srna, "NodeGeometryCombineBundleItem", "CombineBundleItemsAccessor");
}
static void rna_def_geo_combine_bundle(BlenderRNA * /*brna*/, StructRNA *srna)
{
PropertyRNA *prop;
RNA_def_struct_sdna_from(srna, "NodeGeometryCombineBundle", "storage");
prop = RNA_def_property(srna, "bundle_items", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, nullptr, "items", "items_num");
RNA_def_property_struct_type(prop, "NodeGeometryCombineBundleItem");
RNA_def_property_ui_text(prop, "Items", "");
RNA_def_property_srna(prop, "NodeGeometryCombineBundleItems");
prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, nullptr, "active_index");
RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_update(prop, NC_NODE, nullptr);
}
static void rna_def_geo_separate_bundle_item(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "NodeGeometrySeparateBundleItem", nullptr);
RNA_def_struct_ui_text(srna, "Separate Bundle Item", "");
rna_def_node_item_array_socket_item_common(srna, "SeparateBundleItemsAccessor", true);
}
static void rna_def_geo_separate_bundle_items(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "NodeGeometrySeparateBundleItems", nullptr);
RNA_def_struct_sdna(srna, "bNode");
RNA_def_struct_ui_text(srna, "Items", "Collection of separate bundle items");
rna_def_node_item_array_new_with_socket_and_name(
srna, "NodeGeometrySeparateBundleItem", "SeparateBundleItemsAccessor");
rna_def_node_item_array_common_functions(
srna, "NodeGeometrySeparateBundleItem", "SeparateBundleItemsAccessor");
}
static void rna_def_geo_separate_bundle(BlenderRNA * /*brna*/, StructRNA *srna)
{
PropertyRNA *prop;
RNA_def_struct_sdna_from(srna, "NodeGeometrySeparateBundle", "storage");
prop = RNA_def_property(srna, "bundle_items", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, nullptr, "items", "items_num");
RNA_def_property_struct_type(prop, "NodeGeometrySeparateBundleItem");
RNA_def_property_ui_text(prop, "Items", "");
RNA_def_property_srna(prop, "NodeGeometrySeparateBundleItems");
prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, nullptr, "active_index");
RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_update(prop, NC_NODE, nullptr);
}
static void rna_def_index_switch_item(BlenderRNA *brna)
{
PropertyRNA *prop;
@ -12650,6 +12901,12 @@ static void rna_def_nodes(BlenderRNA *brna)
define("GeometryNode", "GeometryNodeVolumeCube");
define("GeometryNode", "GeometryNodeVolumeToMesh");
define("GeometryNode", "GeometryNodeWarning");
define("GeometryNode", "GeometryNodeSeparateBundle", rna_def_geo_separate_bundle);
define("GeometryNode", "GeometryNodeCombineBundle", rna_def_geo_combine_bundle);
define("GeometryNode", "GeometryNodeClosureInput", def_geo_closure_input);
define("GeometryNode", "GeometryNodeClosureOutput", def_geo_closure_output);
define("GeometryNode", "GeometryNodeEvaluateClosure", def_geo_evaluate_closure);
/* Node group types are currently defined for each tree type individually. */
define("ShaderNode", "ShaderNodeGroup", def_group);
@ -12687,6 +12944,12 @@ void RNA_def_nodetree(BlenderRNA *brna)
rna_def_index_switch_item(brna);
rna_def_menu_switch_item(brna);
rna_def_geo_bake_item(brna);
rna_def_geo_combine_bundle_item(brna);
rna_def_geo_separate_bundle_item(brna);
rna_def_geo_closure_input_item(brna);
rna_def_geo_closure_output_item(brna);
rna_def_geo_evaluate_closure_input_item(brna);
rna_def_geo_evaluate_closure_output_item(brna);
rna_def_geo_capture_attribute_item(brna);
rna_def_nodes(brna);
@ -12727,6 +12990,12 @@ void RNA_def_nodetree(BlenderRNA *brna)
rna_def_geo_index_switch_items(brna);
rna_def_geo_menu_switch_items(brna);
rna_def_bake_items(brna);
rna_def_geo_combine_bundle_items(brna);
rna_def_geo_separate_bundle_items(brna);
rna_def_geo_closure_input_items(brna);
rna_def_geo_closure_output_items(brna);
rna_def_geo_evaluate_closure_input_items(brna);
rna_def_geo_evaluate_closure_output_items(brna);
rna_def_geo_capture_attribute_items(brna);
rna_def_node_instance_hash(brna);

View file

@ -3500,6 +3500,12 @@ static void rna_def_userdef_theme_space_node(BlenderRNA *brna)
RNA_def_property_array(prop, 4);
RNA_def_property_ui_text(prop, "For Each Geometry Element Zone", "");
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
prop = RNA_def_property(srna, "closure_zone", PROP_FLOAT, PROP_COLOR_GAMMA);
RNA_def_property_float_sdna(prop, nullptr, "node_zone_closure");
RNA_def_property_array(prop, 4);
RNA_def_property_ui_text(prop, "Closure Zone", "");
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
}
static void rna_def_userdef_theme_space_buts(BlenderRNA *brna)
@ -7615,6 +7621,10 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
prop, "Shader Node Previews", "Enables previews in the shader node editor");
RNA_def_property_update(prop, 0, "rna_userdef_ui_update");
prop = RNA_def_property(srna, "use_bundle_and_closure_nodes", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_ui_text(
prop, "Bundle and Closure Nodes", "Enables bundle and closure nodes in Geometry Nodes");
prop = RNA_def_property(srna, "use_extensions_debug", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_ui_text(
prop,

View file

@ -862,18 +862,17 @@ static void check_property_socket_sync(const Object *ob,
const bNodeTreeInterfaceSocket *socket = nmd->node_group->interface_inputs()[i];
const bke::bNodeSocketType *typeinfo = socket->socket_typeinfo();
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
if (type == SOCK_GEOMETRY) {
geometry_socket_count++;
}
/* The first socket is the special geometry socket for the modifier object. */
if (i == 0 && type == SOCK_GEOMETRY) {
geometry_socket_count++;
continue;
}
IDProperty *property = properties.lookup_key_default_as(socket->identifier, nullptr);
if (property == nullptr) {
if (ELEM(type, SOCK_GEOMETRY, SOCK_MATRIX)) {
geometry_socket_count++;
}
else {
if (!ELEM(type, SOCK_GEOMETRY, SOCK_MATRIX, SOCK_BUNDLE, SOCK_CLOSURE)) {
BKE_modifier_set_error(
ob, md, "Missing property for input socket \"%s\"", socket->name ? socket->name : "");
}

View file

@ -68,6 +68,9 @@ set(INC_SYS
set(SRC
intern/derived_node_tree.cc
intern/geometry_nodes_bundle.cc
intern/geometry_nodes_closure_zone.cc
intern/geometry_nodes_closure.cc
intern/geometry_nodes_dependencies.cc
intern/geometry_nodes_execute.cc
intern/geometry_nodes_foreach_geometry_element_zone.cc
@ -97,6 +100,11 @@ set(SRC
NOD_derived_node_tree.hh
NOD_geometry.hh
NOD_geometry_exec.hh
NOD_geometry_nodes_bundle_fwd.hh
NOD_geometry_nodes_bundle.hh
NOD_geometry_nodes_closure.hh
NOD_geometry_nodes_closure_eval.hh
NOD_geometry_nodes_closure_fwd.hh
NOD_geometry_nodes_dependencies.hh
NOD_geometry_nodes_execute.hh
NOD_geometry_nodes_gizmos.hh

View file

@ -17,6 +17,8 @@
#include "BKE_geometry_set.hh"
#include "BKE_node_socket_value.hh"
#include "BKE_volume_grid_fwd.hh"
#include "NOD_geometry_nodes_bundle_fwd.hh"
#include "NOD_geometry_nodes_closure_fwd.hh"
#include "DNA_node_types.h"
@ -109,7 +111,7 @@ class GeoNodeExecParams {
template<typename T>
static constexpr bool stored_as_SocketValueVariant_v =
is_field_base_type_v<T> || fn::is_field_v<T> || bke::is_VolumeGrid_v<T> ||
is_same_any_v<T, GField, bke::GVolumeGrid>;
is_same_any_v<T, GField, bke::GVolumeGrid, nodes::BundlePtr, nodes::ClosurePtr>;
/**
* Get the input value for the input socket with the given identifier.
@ -168,6 +170,16 @@ class GeoNodeExecParams {
}
}
/**
* Low level access to the parameters. Usually, it's better to use #get_input, #extract_input and
* #set_output instead because they are easier to use and more safe. Sometimes it can be
* beneficial to have more direct access to the raw values though and avoid the indirection.
*/
lf::Params &low_level_lazy_function_params()
{
return params_;
}
/**
* Store the output value for the given socket identifier.
*/

View file

@ -0,0 +1,66 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BKE_node.hh"
#include "NOD_geometry_nodes_bundle_fwd.hh"
#include "NOD_socket_interface_key.hh"
#include "DNA_node_types.h"
namespace blender::nodes {
/**
* A bundle is a map containing keys and their corresponding values. Values are stored as the type
* they have in Geometry Nodes (#bNodeSocketType::geometry_nodes_cpp_type).
*/
class Bundle : public ImplicitSharingMixin {
public:
struct StoredItem {
SocketInterfaceKey key;
const bke::bNodeSocketType *type;
void *value;
};
private:
Vector<StoredItem> items_;
Vector<void *> buffers_;
public:
struct Item {
const bke::bNodeSocketType *type;
const void *value;
};
Bundle();
Bundle(const Bundle &other);
Bundle(Bundle &&other) noexcept;
Bundle &operator=(const Bundle &other);
Bundle &operator=(Bundle &&other) noexcept;
~Bundle();
static BundlePtr create()
{
return BundlePtr(MEM_new<Bundle>(__func__));
}
void add_new(SocketInterfaceKey key, const bke::bNodeSocketType &type, const void *value);
bool add(const SocketInterfaceKey &key, const bke::bNodeSocketType &type, const void *value);
bool add(SocketInterfaceKey &&key, const bke::bNodeSocketType &type, const void *value);
bool remove(const SocketInterfaceKey &key);
bool contains(const SocketInterfaceKey &key) const;
std::optional<Item> lookup(const SocketInterfaceKey &key) const;
Span<StoredItem> items() const
{
return items_;
}
void delete_self() override;
};
} // namespace blender::nodes

View file

@ -0,0 +1,14 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_implicit_sharing_ptr.hh"
namespace blender::nodes {
class Bundle;
using BundlePtr = ImplicitSharingPtr<Bundle>;
} // namespace blender::nodes

View file

@ -0,0 +1,119 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BKE_node.hh"
#include "NOD_geometry_nodes_closure_fwd.hh"
#include "NOD_socket_interface_key.hh"
#include "BLI_resource_scope.hh"
#include "FN_lazy_function.hh"
namespace blender::nodes {
/** Describes the names and types of the inputs and outputs of a closure. */
class ClosureSignature {
public:
struct Item {
SocketInterfaceKey key;
const bke::bNodeSocketType *type = nullptr;
};
Vector<Item> inputs;
Vector<Item> outputs;
std::optional<int> find_input_index(const SocketInterfaceKey &key) const;
std::optional<int> find_output_index(const SocketInterfaceKey &key) const;
};
/**
* Describes the meaning of the various inputs and outputs of the lazy-function that's contained
* in the closure.
*/
struct ClosureFunctionIndices {
struct {
IndexRange main;
/** A boolean input for each output indicating whether that output is used. */
IndexRange output_usages;
/**
* A #GeometryNodesReferenceSet input for a subset of the outputs. This is used to tell the
* closure which attributes it has to propagate to the outputs.
*
* Main output index -> input lf socket index.
*/
Map<int, int> output_data_reference_sets;
} inputs;
struct {
IndexRange main;
/** A boolean output for each input indicating whether that input is used. */
IndexRange input_usages;
} outputs;
};
/**
* A closure is like a node group that is passed around as a value. It's typically evaluated using
* the Evaluate Closure node.
*
* Internally, a closure is a lazy-function. So the inputs that are passed to the closure are
* requested lazily. It's *not* yet supported to request the potentially captured values from the
* Closure Zone lazily.
*/
class Closure : public ImplicitSharingMixin {
private:
std::shared_ptr<ClosureSignature> signature_;
/**
* When building complex lazy-functions, e.g. from Geometry Nodes, one often has to allocate
* various additional resources (e.g. the lazy-functions for the individual nodes). Using
* #ResourceScope provides a simple way to pass ownership of all these additional resources to
* the Closure.
*/
std::unique_ptr<ResourceScope> scope_;
const fn::lazy_function::LazyFunction &function_;
ClosureFunctionIndices indices_;
Vector<const void *> default_input_values_;
public:
Closure(std::shared_ptr<ClosureSignature> signature,
std::unique_ptr<ResourceScope> scope,
const fn::lazy_function::LazyFunction &function,
ClosureFunctionIndices indices,
Vector<const void *> default_input_values)
: signature_(signature),
scope_(std::move(scope)),
function_(function),
indices_(indices),
default_input_values_(std::move(default_input_values))
{
}
const ClosureSignature &signature() const
{
return *signature_;
}
const ClosureFunctionIndices &indices() const
{
return indices_;
}
const fn::lazy_function::LazyFunction &function() const
{
return function_;
}
const void *default_input_value(const int index) const
{
return default_input_values_[index];
}
void delete_self() override
{
MEM_delete(this);
}
};
} // namespace blender::nodes

View file

@ -0,0 +1,38 @@
/* SPDX-FileCopyrightText: 2025 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "NOD_geometry_nodes_closure.hh"
#include "NOD_geometry_nodes_lazy_function.hh"
namespace blender::nodes {
struct ClosureEagerEvalParams {
struct InputItem {
SocketInterfaceKey key;
const bke::bNodeSocketType *type = nullptr;
/**
* The actual socket value of type bNodeSocketType::geometry_nodes_cpp_type.
* This is not const, because it may be moved from.
*/
void *value = nullptr;
};
struct OutputItem {
SocketInterfaceKey key;
const bke::bNodeSocketType *type = nullptr;
/** Where the output value should be stored. */
void *value = nullptr;
};
Vector<InputItem> inputs;
Vector<OutputItem> outputs;
GeoNodesLFUserData *user_data = nullptr;
};
void evaluate_closure_eagerly(const Closure &closure, ClosureEagerEvalParams &params);
} // namespace blender::nodes

View file

@ -0,0 +1,14 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_implicit_sharing_ptr.hh"
namespace blender::nodes {
class Closure;
using ClosurePtr = ImplicitSharingPtr<Closure>;
} // namespace blender::nodes

View file

@ -454,6 +454,7 @@ void set_default_remaining_node_outputs(lf::Params &params, const bNode &node);
void set_default_value_for_output_socket(lf::Params &params,
const int lf_index,
const bNodeSocket &bsocket);
void construct_socket_default_value(const bke::bNodeSocketType &stype, void *r_value);
std::string make_anonymous_attribute_socket_inspection_string(const bNodeSocket &socket);
std::string make_anonymous_attribute_socket_inspection_string(StringRef node_name,
@ -463,6 +464,7 @@ struct FoundNestedNodeID {
int id;
bool is_in_simulation = false;
bool is_in_loop = false;
bool is_in_closure = false;
};
std::optional<FoundNestedNodeID> find_nested_node_id(const GeoNodesLFUserData &user_data,
@ -589,9 +591,36 @@ LazyFunction &build_foreach_geometry_element_zone_lazy_function(ResourceScope &s
ZoneBuildInfo &zone_info,
const ZoneBodyFunction &body_fn);
LazyFunction &build_closure_zone_lazy_function(ResourceScope &scope,
const bNodeTree &btree,
const bke::bNodeTreeZone &zone,
ZoneBuildInfo &zone_info,
const ZoneBodyFunction &body_fn);
struct EvaluateClosureFunctionIndices {
struct {
Vector<int> main;
Vector<int> output_usages;
Map<int, int> reference_set_by_output;
} inputs;
struct {
Vector<int> main;
Vector<int> input_usages;
} outputs;
};
struct EvaluateClosureFunction {
const LazyFunction *lazy_function = nullptr;
EvaluateClosureFunctionIndices indices;
};
EvaluateClosureFunction build_evaluate_closure_node_lazy_function(ResourceScope &scope,
const bNode &bnode);
void initialize_zone_wrapper(const bke::bNodeTreeZone &zone,
ZoneBuildInfo &zone_info,
const ZoneBodyFunction &body_fn,
bool expose_all_reference_sets,
Vector<lf::Input> &r_inputs,
Vector<lf::Output> &r_outputs);
@ -605,4 +634,21 @@ std::string zone_wrapper_output_name(const ZoneBuildInfo &zone_info,
const Span<lf::Output> outputs,
const int lf_socket_i);
/**
* Performs implicit conversion between socket types. Returns false if the conversion is not
* possible. In that case, r_to_value is left uninitialized.
*/
[[nodiscard]] bool implicitly_convert_socket_value(const bke::bNodeSocketType &from_type,
const void *from_value,
const bke::bNodeSocketType &to_type,
void *r_to_value);
/**
* Builds a lazy-function that can convert between socket types. Returns null if the conversion is
* never possible.
*/
const LazyFunction *build_implicit_conversion_lazy_function(const bke::bNodeSocketType &from_type,
const bke::bNodeSocketType &to_type,
ResourceScope &scope);
} // namespace blender::nodes

View file

@ -40,6 +40,8 @@
#include "BKE_node_tree_zones.hh"
#include "BKE_volume_grid_fwd.hh"
#include "NOD_geometry_nodes_bundle.hh"
#include "FN_field.hh"
#include "DNA_node_types.h"
@ -184,6 +186,31 @@ class GeometryInfoLog : public ValueLog {
GeometryInfoLog(const bke::GVolumeGrid &grid);
};
class BundleValueLog : public ValueLog {
public:
struct Item {
SocketInterfaceKey key;
const bke::bNodeSocketType *type;
};
Vector<Item> items;
BundleValueLog(Vector<Item> items);
};
class ClosureValueLog : public ValueLog {
public:
struct Item {
SocketInterfaceKey key;
const bke::bNodeSocketType *type;
};
Vector<Item> inputs;
Vector<Item> outputs;
ClosureValueLog(Vector<Item> inputs, Vector<Item> outputs);
};
/**
* Data logged by a viewer node when it is executed. In this case, we do want to log the entire
* geometry.

View file

@ -238,6 +238,42 @@ class MenuBuilder : public SocketDeclarationBuilder<Menu> {
MenuBuilder &default_value(int32_t value);
};
class BundleBuilder;
class Bundle : public SocketDeclaration {
public:
static constexpr eNodeSocketDatatype static_socket_type = SOCK_BUNDLE;
friend BundleBuilder;
using Builder = BundleBuilder;
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
bool matches(const bNodeSocket &socket) const override;
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
bool can_connect(const bNodeSocket &socket) const override;
};
class BundleBuilder : public SocketDeclarationBuilder<Bundle> {};
class ClosureBuilder;
class Closure : public SocketDeclaration {
public:
static constexpr eNodeSocketDatatype static_socket_type = SOCK_CLOSURE;
friend ClosureBuilder;
using Builder = ClosureBuilder;
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
bool matches(const bNodeSocket &socket) const override;
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
bool can_connect(const bNodeSocket &socket) const override;
};
class ClosureBuilder : public SocketDeclarationBuilder<Closure> {};
class IDSocketDeclaration : public SocketDeclaration {
public:
const char *idname;

View file

@ -0,0 +1,32 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <string>
#include "BLI_vector.hh"
namespace blender::nodes {
/**
* A key that identifies values in a bundle or inputs/outputs of a closure.
* Note that this key does not have a hash and thus can't be used in a hash table. This wouldn't
* work well if these items have multiple identifiers for compatibility reasons. While that's not
* used currently, it's good to keep it possible.
*/
class SocketInterfaceKey {
private:
/** May have multiple keys to improve compatibility between systems that use different keys. */
Vector<std::string> identifiers_;
public:
explicit SocketInterfaceKey(Vector<std::string> identifiers);
explicit SocketInterfaceKey(std::string identifier);
bool matches(const SocketInterfaceKey &other) const;
Span<std::string> identifiers() const;
};
} // namespace blender::nodes

View file

@ -13,6 +13,8 @@
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
#include "BLI_function_ref.hh"
#include "BKE_screen.hh"
#include "NOD_socket_items.hh"

View file

@ -32,7 +32,9 @@ set(SRC
nodes/node_geo_boolean.cc
nodes/node_geo_bounding_box.cc
nodes/node_geo_camera_info.cc
nodes/node_geo_closure.cc
nodes/node_geo_collection_info.cc
nodes/node_geo_combine_bundle.cc
nodes/node_geo_common.cc
nodes/node_geo_convex_hull.cc
nodes/node_geo_curve_endpoint_selection.cc
@ -73,6 +75,7 @@ set(SRC
nodes/node_geo_edge_split.cc
nodes/node_geo_edges_to_face_groups.cc
nodes/node_geo_evaluate_at_index.cc
nodes/node_geo_evaluate_closure.cc
nodes/node_geo_evaluate_on_domain.cc
nodes/node_geo_extrude_mesh.cc
nodes/node_geo_flip_faces.cc
@ -184,6 +187,7 @@ set(SRC
nodes/node_geo_scale_instances.cc
nodes/node_geo_sdf_grid_boolean.cc
nodes/node_geo_self_object.cc
nodes/node_geo_separate_bundle.cc
nodes/node_geo_separate_components.cc
nodes/node_geo_separate_geometry.cc
nodes/node_geo_set_curve_handles.cc

View file

@ -0,0 +1,181 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "DNA_node_types.h"
#include "NOD_socket_items.hh"
namespace blender::nodes {
inline bool socket_type_supported_in_bundle(const eNodeSocketDatatype socket_type)
{
return ELEM(socket_type,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_MATRIX,
SOCK_INT,
SOCK_STRING,
SOCK_GEOMETRY,
SOCK_OBJECT,
SOCK_MATERIAL,
SOCK_IMAGE,
SOCK_COLLECTION,
SOCK_BUNDLE,
SOCK_CLOSURE);
}
struct CombineBundleItemsAccessor {
using ItemT = NodeGeometryCombineBundleItem;
static StructRNA *item_srna;
static int node_type;
static int item_dna_type;
static constexpr const char *node_idname = "GeometryNodeCombineBundle";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
static constexpr bool has_single_identifier_str = true;
struct operator_idnames {
static constexpr const char *add_item = "NODE_OT_combine_bundle_item_add";
static constexpr const char *remove_item = "NODE_OT_combine_bundle_item_remove";
static constexpr const char *move_item = "NODE_OT_combine_bundle_item_move";
};
struct ui_idnames {
static constexpr const char *list = "DATA_UL_combine_bundle_items";
};
struct rna_names {
static constexpr const char *items = "bundle_items";
static constexpr const char *active_index = "active_index";
};
static socket_items::SocketItemsRef<ItemT> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeGeometryCombineBundle *>(node.storage);
return {&storage->items, &storage->items_num, &storage->active_index};
}
static void copy_item(const ItemT &src, ItemT &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(ItemT *item)
{
MEM_SAFE_FREE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return eNodeSocketDatatype(item.socket_type);
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type)
{
return socket_type_supported_in_bundle(socket_type);
}
static void init_with_socket_type_and_name(bNode &node,
ItemT &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometryCombineBundle *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->next_identifier++;
socket_items::set_item_name_and_make_unique<CombineBundleItemsAccessor>(node, item, name);
}
static std::string socket_identifier_for_item(const ItemT &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
struct SeparateBundleItemsAccessor {
using ItemT = NodeGeometrySeparateBundleItem;
static StructRNA *item_srna;
static int node_type;
static int item_dna_type;
static constexpr const char *node_idname = "GeometryNodeSeparateBundle";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
static constexpr bool has_single_identifier_str = true;
struct operator_idnames {
static constexpr const char *add_item = "NODE_OT_separate_bundle_item_add";
static constexpr const char *remove_item = "NODE_OT_separate_bundle_item_remove";
static constexpr const char *move_item = "NODE_OT_separate_bundle_item_move";
};
struct ui_idnames {
static constexpr const char *list = "DATA_UL_separate_bundle_items";
};
struct rna_names {
static constexpr const char *items = "bundle_items";
static constexpr const char *active_index = "active_index";
};
static socket_items::SocketItemsRef<ItemT> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeGeometrySeparateBundle *>(node.storage);
return {&storage->items, &storage->items_num, &storage->active_index};
}
static void copy_item(const ItemT &src, ItemT &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(ItemT *item)
{
MEM_SAFE_FREE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return eNodeSocketDatatype(item.socket_type);
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type)
{
return socket_type_supported_in_bundle(socket_type);
}
static void init_with_socket_type_and_name(bNode &node,
ItemT &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometrySeparateBundle *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->next_identifier++;
socket_items::set_item_name_and_make_unique<SeparateBundleItemsAccessor>(node, item, name);
}
static std::string socket_identifier_for_item(const ItemT &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
} // namespace blender::nodes

View file

@ -0,0 +1,339 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "DNA_node_types.h"
#include "NOD_socket_items.hh"
namespace blender::nodes {
inline bool socket_type_supported_in_closure(const eNodeSocketDatatype socket_type)
{
return ELEM(socket_type,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_MATRIX,
SOCK_INT,
SOCK_STRING,
SOCK_GEOMETRY,
SOCK_OBJECT,
SOCK_MATERIAL,
SOCK_IMAGE,
SOCK_COLLECTION,
SOCK_BUNDLE,
SOCK_CLOSURE);
}
struct ClosureInputItemsAccessor {
using ItemT = NodeGeometryClosureInputItem;
static StructRNA *item_srna;
static int node_type;
static int item_dna_type;
static constexpr const char *node_idname = "GeometryNodeClosureOutput";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
static constexpr bool has_single_identifier_str = true;
struct operator_idnames {
static constexpr const char *add_item = "NODE_OT_closure_input_item_add";
static constexpr const char *remove_item = "NODE_OT_closure_input_item_remove";
static constexpr const char *move_item = "NODE_OT_closure_input_item_move";
};
struct ui_idnames {
static constexpr const char *list = "DATA_UL_closure_input_items";
};
struct rna_names {
static constexpr const char *items = "input_items";
static constexpr const char *active_index = "active_input_index";
};
static socket_items::SocketItemsRef<ItemT> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeGeometryClosureOutput *>(node.storage);
return {&storage->input_items.items,
&storage->input_items.items_num,
&storage->input_items.active_index};
}
static void copy_item(const ItemT &src, ItemT &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(ItemT *item)
{
MEM_SAFE_FREE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return eNodeSocketDatatype(item.socket_type);
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type)
{
return socket_type_supported_in_closure(socket_type);
}
static void init_with_socket_type_and_name(bNode &node,
ItemT &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometryClosureOutput *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->input_items.next_identifier++;
socket_items::set_item_name_and_make_unique<ClosureInputItemsAccessor>(node, item, name);
}
static std::string socket_identifier_for_item(const ItemT &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
struct ClosureOutputItemsAccessor {
using ItemT = NodeGeometryClosureOutputItem;
static StructRNA *item_srna;
static int node_type;
static int item_dna_type;
static constexpr const char *node_idname = "GeometryNodeClosureOutput";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
static constexpr bool has_single_identifier_str = true;
struct operator_idnames {
static constexpr const char *add_item = "NODE_OT_closure_output_item_add";
static constexpr const char *remove_item = "NODE_OT_closure_output_item_remove";
static constexpr const char *move_item = "NODE_OT_closure_output_item_move";
};
struct ui_idnames {
static constexpr const char *list = "DATA_UL_closure_output_items";
};
struct rna_names {
static constexpr const char *items = "output_items";
static constexpr const char *active_index = "active_output_index";
};
static socket_items::SocketItemsRef<ItemT> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeGeometryClosureOutput *>(node.storage);
return {&storage->output_items.items,
&storage->output_items.items_num,
&storage->output_items.active_index};
}
static void copy_item(const ItemT &src, ItemT &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(ItemT *item)
{
MEM_SAFE_FREE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return eNodeSocketDatatype(item.socket_type);
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type)
{
return socket_type_supported_in_closure(socket_type);
}
static void init_with_socket_type_and_name(bNode &node,
ItemT &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometryClosureOutput *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->output_items.next_identifier++;
socket_items::set_item_name_and_make_unique<ClosureOutputItemsAccessor>(node, item, name);
}
static std::string socket_identifier_for_item(const ItemT &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
struct EvaluateClosureInputItemsAccessor {
using ItemT = NodeGeometryEvaluateClosureInputItem;
static StructRNA *item_srna;
static int node_type;
static int item_dna_type;
static constexpr const char *node_idname = "GeometryNodeEvaluateClosure";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
static constexpr bool has_single_identifier_str = true;
struct operator_idnames {
static constexpr const char *add_item = "NODE_OT_evaluate_closure_input_item_add";
static constexpr const char *remove_item = "NODE_OT_evaluate_closure_input_item_remove";
static constexpr const char *move_item = "NODE_OT_evaluate_closure_input_item_move";
};
struct ui_idnames {
static constexpr const char *list = "DATA_UL_evaluate_closure_input_items";
};
struct rna_names {
static constexpr const char *items = "input_items";
static constexpr const char *active_index = "active_input_index";
};
static socket_items::SocketItemsRef<ItemT> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeGeometryEvaluateClosure *>(node.storage);
return {&storage->input_items.items,
&storage->input_items.items_num,
&storage->input_items.active_index};
}
static void copy_item(const ItemT &src, ItemT &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(ItemT *item)
{
MEM_SAFE_FREE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return eNodeSocketDatatype(item.socket_type);
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type)
{
return socket_type_supported_in_closure(socket_type);
}
static void init_with_socket_type_and_name(bNode &node,
ItemT &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometryEvaluateClosure *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->input_items.next_identifier++;
socket_items::set_item_name_and_make_unique<EvaluateClosureInputItemsAccessor>(
node, item, name);
}
static std::string socket_identifier_for_item(const ItemT &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
struct EvaluateClosureOutputItemsAccessor {
using ItemT = NodeGeometryEvaluateClosureOutputItem;
static StructRNA *item_srna;
static int node_type;
static int item_dna_type;
static constexpr const char *node_idname = "GeometryNodeEvaluateClosure";
static constexpr bool has_type = true;
static constexpr bool has_name = true;
static constexpr bool has_single_identifier_str = true;
struct operator_idnames {
static constexpr const char *add_item = "NODE_OT_evaluate_closure_output_item_add";
static constexpr const char *remove_item = "NODE_OT_evaluate_closure_output_item_remove";
static constexpr const char *move_item = "NODE_OT_evaluate_closure_output_item_move";
};
struct ui_idnames {
static constexpr const char *list = "DATA_UL_evaluate_closure_output_items";
};
struct rna_names {
static constexpr const char *items = "output_items";
static constexpr const char *active_index = "active_output_index";
};
static socket_items::SocketItemsRef<ItemT> get_items_from_node(bNode &node)
{
auto *storage = static_cast<NodeGeometryEvaluateClosure *>(node.storage);
return {&storage->output_items.items,
&storage->output_items.items_num,
&storage->output_items.active_index};
}
static void copy_item(const ItemT &src, ItemT &dst)
{
dst = src;
dst.name = BLI_strdup_null(dst.name);
}
static void destruct_item(ItemT *item)
{
MEM_SAFE_FREE(item->name);
}
static void blend_write_item(BlendWriter *writer, const ItemT &item);
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
static eNodeSocketDatatype get_socket_type(const ItemT &item)
{
return eNodeSocketDatatype(item.socket_type);
}
static char **get_name(ItemT &item)
{
return &item.name;
}
static bool supports_socket_type(const eNodeSocketDatatype socket_type)
{
return socket_type_supported_in_closure(socket_type);
}
static void init_with_socket_type_and_name(bNode &node,
ItemT &item,
const eNodeSocketDatatype socket_type,
const char *name)
{
auto *storage = static_cast<NodeGeometryEvaluateClosure *>(node.storage);
item.socket_type = socket_type;
item.identifier = storage->output_items.next_identifier++;
socket_items::set_item_name_and_make_unique<EvaluateClosureOutputItemsAccessor>(
node, item, name);
}
static std::string socket_identifier_for_item(const ItemT &item)
{
return "Item_" + std::to_string(item.identifier);
}
};
} // namespace blender::nodes

View file

@ -81,7 +81,9 @@ struct RepeatItemsAccessor {
SOCK_OBJECT,
SOCK_MATERIAL,
SOCK_IMAGE,
SOCK_COLLECTION);
SOCK_COLLECTION,
SOCK_BUNDLE,
SOCK_CLOSURE);
}
static void init_with_socket_type_and_name(bNode &node,

View file

@ -68,6 +68,7 @@ struct SimulationItemsAccessor {
static bool supports_socket_type(const eNodeSocketDatatype socket_type)
{
/* Data-block types and closures are not supported. */
return ELEM(socket_type,
SOCK_FLOAT,
SOCK_VECTOR,
@ -77,7 +78,8 @@ struct SimulationItemsAccessor {
SOCK_MATRIX,
SOCK_INT,
SOCK_STRING,
SOCK_GEOMETRY);
SOCK_GEOMETRY,
SOCK_BUNDLE);
}
static void init_with_socket_type_and_name(bNode &node,

View file

@ -116,22 +116,24 @@ static bool geometry_node_tree_validate_link(eNodeSocketDatatype type_a,
static bool geometry_node_tree_socket_type_valid(blender::bke::bNodeTreeType * /*treetype*/,
blender::bke::bNodeSocketType *socket_type)
{
return blender::bke::node_is_static_socket_type(*socket_type) && ELEM(socket_type->type,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_MATRIX,
SOCK_INT,
SOCK_STRING,
SOCK_OBJECT,
SOCK_GEOMETRY,
SOCK_COLLECTION,
SOCK_TEXTURE,
SOCK_IMAGE,
SOCK_MATERIAL,
SOCK_MENU);
return blender::bke::node_is_static_socket_type(*socket_type) &&
(ELEM(socket_type->type,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_MATRIX,
SOCK_INT,
SOCK_STRING,
SOCK_OBJECT,
SOCK_GEOMETRY,
SOCK_COLLECTION,
SOCK_TEXTURE,
SOCK_IMAGE,
SOCK_MATERIAL,
SOCK_MENU) ||
ELEM(socket_type->type, SOCK_BUNDLE, SOCK_CLOSURE));
}
void register_node_tree_type_geo()

View file

@ -251,7 +251,7 @@ class LazyFunctionForBakeNode final : public LazyFunction {
this->set_default_outputs(params);
return;
}
if (found_id->is_in_loop) {
if (found_id->is_in_loop || found_id->is_in_closure) {
DummyDataBlockMap data_block_map;
this->pass_through(params, user_data, &data_block_map);
return;

View file

@ -0,0 +1,237 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
#include "BLI_string_utf8.h"
#include "NOD_geo_closure.hh"
#include "NOD_socket_items_ops.hh"
#include "NOD_socket_items_ui.hh"
#include "BLO_read_write.hh"
namespace blender::nodes::node_geo_closure_cc {
/** Shared between closure input and output node. */
static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *current_node_ptr)
{
bNodeTree &ntree = *reinterpret_cast<bNodeTree *>(current_node_ptr->owner_id);
bNode *current_node = static_cast<bNode *>(current_node_ptr->data);
const bke::bNodeTreeZones *zones = ntree.zones();
if (!zones) {
return;
}
const bke::bNodeTreeZone *zone = zones->get_zone_by_node(current_node->identifier);
if (!zone) {
return;
}
if (!zone->output_node) {
return;
}
bNode &output_node = const_cast<bNode &>(*zone->output_node);
if (current_node->type_legacy == GEO_NODE_CLOSURE_INPUT) {
if (uiLayout *panel = uiLayoutPanel(C, layout, "input_items", false, TIP_("Input Items"))) {
socket_items::ui::draw_items_list_with_operators<ClosureInputItemsAccessor>(
C, panel, ntree, output_node);
socket_items::ui::draw_active_item_props<ClosureInputItemsAccessor>(
ntree, output_node, [&](PointerRNA *item_ptr) {
uiItemR(panel, item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
});
}
}
else {
if (uiLayout *panel = uiLayoutPanel(C, layout, "output_items", false, TIP_("Output Items"))) {
socket_items::ui::draw_items_list_with_operators<ClosureOutputItemsAccessor>(
C, panel, ntree, output_node);
socket_items::ui::draw_active_item_props<ClosureOutputItemsAccessor>(
ntree, output_node, [&](PointerRNA *item_ptr) {
uiItemR(panel, item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
});
}
}
}
namespace input_node {
NODE_STORAGE_FUNCS(NodeGeometryClosureInput);
static void node_declare(NodeDeclarationBuilder &b)
{
const bNode *node = b.node_or_null();
const bNodeTree *tree = b.tree_or_null();
if (node && tree) {
const NodeGeometryClosureInput &storage = node_storage(*node);
const bNode *output_node = tree->node_by_id(storage.output_node_id);
if (output_node) {
const auto &output_storage = *static_cast<const NodeGeometryClosureOutput *>(
output_node->storage);
for (const int i : IndexRange(output_storage.input_items.items_num)) {
const NodeGeometryClosureInputItem &item = output_storage.input_items.items[i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
const std::string identifier = ClosureInputItemsAccessor::socket_identifier_for_item(item);
b.add_output(socket_type, item.name, identifier);
}
}
}
b.add_output<decl::Extend>("", "__extend__");
}
static void node_label(const bNodeTree * /*ntree*/,
const bNode * /*node*/,
char *label,
const int label_maxncpy)
{
BLI_strncpy_utf8(label, IFACE_("Closure"), label_maxncpy);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryClosureInput *data = MEM_callocN<NodeGeometryClosureInput>(__func__);
node->storage = data;
}
static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
{
bNode *output_node = ntree->node_by_id(node_storage(*node).output_node_id);
if (!output_node) {
return true;
}
return socket_items::try_add_item_via_any_extend_socket<ClosureInputItemsAccessor>(
*ntree, *node, *output_node, *link);
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeClosureInput", GEO_NODE_CLOSURE_INPUT);
ntype.ui_name = "Closure Input";
ntype.nclass = NODE_CLASS_INTERFACE;
ntype.declare = node_declare;
ntype.gather_link_search_ops = nullptr;
ntype.initfunc = node_init;
ntype.labelfunc = node_label;
ntype.no_muting = true;
ntype.insert_link = node_insert_link;
ntype.draw_buttons_ex = node_layout_ex;
blender::bke::node_type_storage(
ntype, "NodeGeometryClosureInput", node_free_standard_storage, node_copy_standard_storage);
blender::bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace input_node
namespace output_node {
NODE_STORAGE_FUNCS(NodeGeometryClosureOutput);
static void node_declare(NodeDeclarationBuilder &b)
{
const bNodeTree *tree = b.tree_or_null();
const bNode *node = b.node_or_null();
if (node && tree) {
const NodeGeometryClosureOutput &storage = node_storage(*node);
for (const int i : IndexRange(storage.output_items.items_num)) {
const NodeGeometryClosureOutputItem &item = storage.output_items.items[i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
const std::string identifier = ClosureOutputItemsAccessor::socket_identifier_for_item(item);
b.add_input(socket_type, item.name, identifier);
}
}
b.add_input<decl::Extend>("", "__extend__");
b.add_output<decl::Closure>("Closure");
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryClosureOutput *data = MEM_callocN<NodeGeometryClosureOutput>(__func__);
node->storage = data;
}
static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
{
const NodeGeometryClosureOutput &src_storage = node_storage(*src_node);
auto *dst_storage = MEM_dupallocN<NodeGeometryClosureOutput>(__func__, src_storage);
dst_node->storage = dst_storage;
socket_items::copy_array<ClosureInputItemsAccessor>(*src_node, *dst_node);
socket_items::copy_array<ClosureOutputItemsAccessor>(*src_node, *dst_node);
}
static void node_free_storage(bNode *node)
{
socket_items::destruct_array<ClosureInputItemsAccessor>(*node);
socket_items::destruct_array<ClosureOutputItemsAccessor>(*node);
MEM_freeN(node->storage);
}
static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
{
return socket_items::try_add_item_via_any_extend_socket<ClosureOutputItemsAccessor>(
*ntree, *node, *node, *link);
}
static void node_operators()
{
socket_items::ops::make_common_operators<ClosureInputItemsAccessor>();
socket_items::ops::make_common_operators<ClosureOutputItemsAccessor>();
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeClosureOutput", GEO_NODE_CLOSURE_OUTPUT);
ntype.ui_name = "Closure Output";
ntype.nclass = NODE_CLASS_INTERFACE;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.labelfunc = input_node::node_label;
ntype.no_muting = true;
ntype.register_operators = node_operators;
ntype.insert_link = node_insert_link;
ntype.draw_buttons_ex = node_layout_ex;
bke::node_type_storage(ntype, "NodeGeometryClosureOutput", node_free_storage, node_copy_storage);
blender::bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace output_node
} // namespace blender::nodes::node_geo_closure_cc
namespace blender::nodes {
StructRNA *ClosureInputItemsAccessor::item_srna = &RNA_NodeGeometryClosureInputItem;
int ClosureInputItemsAccessor::node_type = GEO_NODE_CLOSURE_OUTPUT;
int ClosureInputItemsAccessor::item_dna_type = SDNA_TYPE_FROM_STRUCT(NodeGeometryClosureInputItem);
void ClosureInputItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
BLO_write_string(writer, item.name);
}
void ClosureInputItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
StructRNA *ClosureOutputItemsAccessor::item_srna = &RNA_NodeGeometryClosureOutputItem;
int ClosureOutputItemsAccessor::node_type = GEO_NODE_CLOSURE_OUTPUT;
int ClosureOutputItemsAccessor::item_dna_type = SDNA_TYPE_FROM_STRUCT(
NodeGeometryClosureOutputItem);
void ClosureOutputItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
BLO_write_string(writer, item.name);
}
void ClosureOutputItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
} // namespace blender::nodes

View file

@ -0,0 +1,157 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
#include "NOD_geo_bundle.hh"
#include "NOD_socket_items_ops.hh"
#include "NOD_socket_items_ui.hh"
#include "BLO_read_write.hh"
#include "NOD_geometry_nodes_bundle.hh"
#include "UI_interface.hh"
namespace blender::nodes::node_geo_combine_bundle_cc {
NODE_STORAGE_FUNCS(NodeGeometryCombineBundle);
static void node_declare(NodeDeclarationBuilder &b)
{
const bNodeTree *tree = b.tree_or_null();
const bNode *node = b.node_or_null();
if (tree && node) {
const NodeGeometryCombineBundle &storage = node_storage(*node);
for (const int i : IndexRange(storage.items_num)) {
const NodeGeometryCombineBundleItem &item = storage.items[i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
const StringRef name = item.name ? item.name : "";
const std::string identifier = CombineBundleItemsAccessor::socket_identifier_for_item(item);
b.add_input(socket_type, name, identifier)
.socket_name_ptr(&tree->id, CombineBundleItemsAccessor::item_srna, &item, "name");
}
}
b.add_input<decl::Extend>("", "__extend__");
b.add_output<decl::Bundle>("Bundle").propagate_all().reference_pass_all();
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
auto *storage = MEM_callocN<NodeGeometryCombineBundle>(__func__);
node->storage = storage;
}
static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
{
const NodeGeometryCombineBundle &src_storage = node_storage(*src_node);
auto *dst_storage = MEM_dupallocN<NodeGeometryCombineBundle>(__func__, src_storage);
dst_node->storage = dst_storage;
socket_items::copy_array<CombineBundleItemsAccessor>(*src_node, *dst_node);
}
static void node_free_storage(bNode *node)
{
socket_items::destruct_array<CombineBundleItemsAccessor>(*node);
MEM_freeN(node->storage);
}
static bool node_insert_link(bNodeTree *tree, bNode *node, bNodeLink *link)
{
return socket_items::try_add_item_via_any_extend_socket<CombineBundleItemsAccessor>(
*tree, *node, *node, *link);
}
static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *node_ptr)
{
bNodeTree &ntree = *reinterpret_cast<bNodeTree *>(node_ptr->owner_id);
bNode &node = *static_cast<bNode *>(node_ptr->data);
if (uiLayout *panel = uiLayoutPanel(C, layout, "bundle_items", false, TIP_("Bundle Items"))) {
socket_items::ui::draw_items_list_with_operators<CombineBundleItemsAccessor>(
C, panel, ntree, node);
socket_items::ui::draw_active_item_props<CombineBundleItemsAccessor>(
ntree, node, [&](PointerRNA *item_ptr) {
uiItemR(panel, item_ptr, "socket_type", UI_ITEM_NONE, "Type", ICON_NONE);
});
}
}
static void node_operators()
{
socket_items::ops::make_common_operators<CombineBundleItemsAccessor>();
}
static void node_geo_exec(GeoNodeExecParams params)
{
if (!U.experimental.use_bundle_and_closure_nodes) {
params.set_default_remaining_outputs();
return;
}
const bNode &node = params.node();
const NodeGeometryCombineBundle &storage = node_storage(node);
BundlePtr bundle_ptr = Bundle::create();
BLI_assert(bundle_ptr->is_mutable());
Bundle &bundle = const_cast<Bundle &>(*bundle_ptr);
for (const int i : IndexRange(storage.items_num)) {
const NodeGeometryCombineBundleItem &item = storage.items[i];
const bke::bNodeSocketType *stype = bke::node_socket_type_find_static(item.socket_type);
if (!stype || !stype->geometry_nodes_cpp_type) {
continue;
}
const StringRef name = item.name;
if (name.is_empty()) {
continue;
}
void *input_ptr = params.low_level_lazy_function_params().try_get_input_data_ptr(i);
BLI_assert(input_ptr);
bundle.add(SocketInterfaceKey(name), *stype, input_ptr);
}
params.set_output("Bundle", std::move(bundle_ptr));
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeCombineBundle", GEO_NODE_COMBINE_BUNDLE);
ntype.ui_name = "Combine Bundle";
ntype.ui_description = "Combine multiple socket values into one.";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.geometry_node_execute = node_geo_exec;
ntype.insert_link = node_insert_link;
ntype.draw_buttons_ex = node_layout_ex;
ntype.register_operators = node_operators;
bke::node_type_storage(ntype, "NodeGeometryCombineBundle", node_free_storage, node_copy_storage);
blender::bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_combine_bundle_cc
namespace blender::nodes {
StructRNA *CombineBundleItemsAccessor::item_srna = &RNA_NodeGeometryCombineBundleItem;
int CombineBundleItemsAccessor::node_type = GEO_NODE_COMBINE_BUNDLE;
int CombineBundleItemsAccessor::item_dna_type = SDNA_TYPE_FROM_STRUCT(
NodeGeometryCombineBundleItem);
void CombineBundleItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
BLO_write_string(writer, item.name);
}
void CombineBundleItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
} // namespace blender::nodes

View file

@ -0,0 +1,163 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "NOD_geo_closure.hh"
#include "NOD_socket_items_ops.hh"
#include "NOD_socket_items_ui.hh"
#include "BLO_read_write.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_evaluate_closure_cc {
NODE_STORAGE_FUNCS(NodeGeometryEvaluateClosure)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Closure>("Closure");
const bNode *node = b.node_or_null();
if (node) {
const auto &storage = node_storage(*node);
for (const int i : IndexRange(storage.input_items.items_num)) {
const NodeGeometryEvaluateClosureInputItem &item = storage.input_items.items[i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
const std::string identifier = EvaluateClosureInputItemsAccessor::socket_identifier_for_item(
item);
b.add_input(socket_type, item.name, identifier);
}
for (const int i : IndexRange(storage.output_items.items_num)) {
const NodeGeometryEvaluateClosureOutputItem &item = storage.output_items.items[i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
const std::string identifier =
EvaluateClosureOutputItemsAccessor::socket_identifier_for_item(item);
b.add_output(socket_type, item.name, identifier).propagate_all().reference_pass_all();
}
}
b.add_input<decl::Extend>("", "__extend__");
b.add_output<decl::Extend>("", "__extend__");
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
auto *storage = MEM_callocN<NodeGeometryEvaluateClosure>(__func__);
node->storage = storage;
}
static void node_copy_storage(bNodeTree * /*tree*/, bNode *dst_node, const bNode *src_node)
{
const NodeGeometryEvaluateClosure &src_storage = node_storage(*src_node);
auto *dst_storage = MEM_dupallocN<NodeGeometryEvaluateClosure>(__func__, src_storage);
dst_node->storage = dst_storage;
socket_items::copy_array<EvaluateClosureInputItemsAccessor>(*src_node, *dst_node);
socket_items::copy_array<EvaluateClosureOutputItemsAccessor>(*src_node, *dst_node);
}
static void node_free_storage(bNode *node)
{
socket_items::destruct_array<EvaluateClosureInputItemsAccessor>(*node);
socket_items::destruct_array<EvaluateClosureOutputItemsAccessor>(*node);
MEM_freeN(node->storage);
}
static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link)
{
if (link->tonode == node) {
return socket_items::try_add_item_via_any_extend_socket<EvaluateClosureInputItemsAccessor>(
*ntree, *node, *node, *link);
}
return socket_items::try_add_item_via_any_extend_socket<EvaluateClosureOutputItemsAccessor>(
*ntree, *node, *node, *link);
}
static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr)
{
bNodeTree &tree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
bNode &node = *static_cast<bNode *>(ptr->data);
if (uiLayout *panel = uiLayoutPanel(C, layout, "input_items", false, IFACE_("Input Items"))) {
socket_items::ui::draw_items_list_with_operators<EvaluateClosureInputItemsAccessor>(
C, panel, tree, node);
socket_items::ui::draw_active_item_props<EvaluateClosureInputItemsAccessor>(
tree, node, [&](PointerRNA *item_ptr) {
uiItemR(panel, item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
});
}
if (uiLayout *panel = uiLayoutPanel(C, layout, "output_items", false, IFACE_("Output Items"))) {
socket_items::ui::draw_items_list_with_operators<EvaluateClosureOutputItemsAccessor>(
C, panel, tree, node);
socket_items::ui::draw_active_item_props<EvaluateClosureOutputItemsAccessor>(
tree, node, [&](PointerRNA *item_ptr) {
uiItemR(panel, item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
});
}
}
static void node_operators()
{
socket_items::ops::make_common_operators<EvaluateClosureInputItemsAccessor>();
socket_items::ops::make_common_operators<EvaluateClosureOutputItemsAccessor>();
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeEvaluateClosure", GEO_NODE_EVALUATE_CLOSURE);
ntype.ui_name = "Evaluate Closure";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.insert_link = node_insert_link;
ntype.draw_buttons_ex = node_layout_ex;
ntype.register_operators = node_operators;
bke::node_type_storage(
ntype, "NodeGeometryEvaluateClosure", node_free_storage, node_copy_storage);
blender::bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_evaluate_closure_cc
namespace blender::nodes {
StructRNA *EvaluateClosureInputItemsAccessor::item_srna =
&RNA_NodeGeometryEvaluateClosureInputItem;
int EvaluateClosureInputItemsAccessor::node_type = GEO_NODE_EVALUATE_CLOSURE;
int EvaluateClosureInputItemsAccessor::item_dna_type = SDNA_TYPE_FROM_STRUCT(
NodeGeometryEvaluateClosureInputItem);
void EvaluateClosureInputItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
BLO_write_string(writer, item.name);
}
void EvaluateClosureInputItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
StructRNA *EvaluateClosureOutputItemsAccessor::item_srna =
&RNA_NodeGeometryEvaluateClosureOutputItem;
int EvaluateClosureOutputItemsAccessor::node_type = GEO_NODE_EVALUATE_CLOSURE;
int EvaluateClosureOutputItemsAccessor::item_dna_type = SDNA_TYPE_FROM_STRUCT(
NodeGeometryEvaluateClosureOutputItem);
void EvaluateClosureOutputItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
BLO_write_string(writer, item.name);
}
void EvaluateClosureOutputItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
} // namespace blender::nodes

View file

@ -340,7 +340,9 @@ static void node_rna(StructRNA *srna)
SOCK_COLLECTION,
SOCK_MATERIAL,
SOCK_IMAGE,
SOCK_MENU);
SOCK_MENU,
SOCK_BUNDLE,
SOCK_CLOSURE);
});
});
}

View file

@ -46,7 +46,9 @@ static bool is_supported_socket_type(const eNodeSocketDatatype data_type)
SOCK_COLLECTION,
SOCK_MATERIAL,
SOCK_IMAGE,
SOCK_MATRIX);
SOCK_MATRIX,
SOCK_BUNDLE,
SOCK_CLOSURE);
}
static void node_declare(blender::nodes::NodeDeclarationBuilder &b)

View file

@ -0,0 +1,174 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
#include "NOD_geo_bundle.hh"
#include "NOD_socket_items_ops.hh"
#include "NOD_socket_items_ui.hh"
#include "BLO_read_write.hh"
#include "NOD_geometry_nodes_bundle.hh"
#include "UI_interface.hh"
#include <fmt/format.h>
namespace blender::nodes::node_geo_separate_bundle_cc {
NODE_STORAGE_FUNCS(NodeGeometrySeparateBundle);
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Bundle>("Bundle");
const bNodeTree *tree = b.tree_or_null();
const bNode *node = b.node_or_null();
if (tree && node) {
const NodeGeometrySeparateBundle &storage = node_storage(*node);
for (const int i : IndexRange(storage.items_num)) {
const NodeGeometrySeparateBundleItem &item = storage.items[i];
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type);
const StringRef name = item.name ? item.name : "";
const std::string identifier = SeparateBundleItemsAccessor::socket_identifier_for_item(item);
b.add_output(socket_type, name, identifier)
.socket_name_ptr(&tree->id, SeparateBundleItemsAccessor::item_srna, &item, "name")
.propagate_all()
.reference_pass_all();
}
}
b.add_output<decl::Extend>("", "__extend__");
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
auto *storage = MEM_callocN<NodeGeometrySeparateBundle>(__func__);
node->storage = storage;
}
static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node)
{
const NodeGeometrySeparateBundle &src_storage = node_storage(*src_node);
auto *dst_storage = MEM_dupallocN<NodeGeometrySeparateBundle>(__func__, src_storage);
dst_node->storage = dst_storage;
socket_items::copy_array<SeparateBundleItemsAccessor>(*src_node, *dst_node);
}
static void node_free_storage(bNode *node)
{
socket_items::destruct_array<SeparateBundleItemsAccessor>(*node);
MEM_freeN(node->storage);
}
static bool node_insert_link(bNodeTree *tree, bNode *node, bNodeLink *link)
{
return socket_items::try_add_item_via_any_extend_socket<SeparateBundleItemsAccessor>(
*tree, *node, *node, *link);
}
static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *node_ptr)
{
bNodeTree &ntree = *reinterpret_cast<bNodeTree *>(node_ptr->owner_id);
bNode &node = *static_cast<bNode *>(node_ptr->data);
if (uiLayout *panel = uiLayoutPanel(C, layout, "bundle_items", false, TIP_("Bundle Items"))) {
socket_items::ui::draw_items_list_with_operators<SeparateBundleItemsAccessor>(
C, panel, ntree, node);
socket_items::ui::draw_active_item_props<SeparateBundleItemsAccessor>(
ntree, node, [&](PointerRNA *item_ptr) {
uiItemR(panel, item_ptr, "socket_type", UI_ITEM_NONE, "Type", ICON_NONE);
});
}
}
static void node_operators()
{
socket_items::ops::make_common_operators<SeparateBundleItemsAccessor>();
}
static void node_geo_exec(GeoNodeExecParams params)
{
if (!U.experimental.use_bundle_and_closure_nodes) {
params.set_default_remaining_outputs();
return;
}
nodes::BundlePtr bundle = params.extract_input<nodes::BundlePtr>("Bundle");
if (!bundle) {
params.set_default_remaining_outputs();
return;
}
const bNode &node = params.node();
const NodeGeometrySeparateBundle &storage = node_storage(node);
lf::Params &lf_params = params.low_level_lazy_function_params();
for (const int i : IndexRange(storage.items_num)) {
const NodeGeometrySeparateBundleItem &item = storage.items[i];
const StringRef name = item.name;
if (name.is_empty()) {
continue;
}
const bke::bNodeSocketType *stype = bke::node_socket_type_find_static(item.socket_type);
if (!stype || !stype->geometry_nodes_cpp_type) {
continue;
}
const std::optional<Bundle::Item> value = bundle->lookup(SocketInterfaceKey(name));
if (!value) {
params.error_message_add(NodeWarningType::Error,
fmt::format(fmt::runtime(TIP_("Value not found: \"{}\"")), name));
continue;
}
void *output_ptr = lf_params.get_output_data_ptr(i);
if (!implicitly_convert_socket_value(*value->type, value->value, *stype, output_ptr)) {
construct_socket_default_value(*stype, output_ptr);
}
lf_params.output_set(i);
}
params.set_default_remaining_outputs();
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype, "GeometryNodeSeparateBundle", GEO_NODE_SEPARATE_BUNDLE);
ntype.ui_name = "Separate Bundle";
ntype.ui_description = "Split a bundle into multiple sockets.";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.insert_link = node_insert_link;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons_ex = node_layout_ex;
ntype.register_operators = node_operators;
bke::node_type_storage(
ntype, "NodeGeometrySeparateBundle", node_free_storage, node_copy_storage);
blender::bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_separate_bundle_cc
namespace blender::nodes {
StructRNA *SeparateBundleItemsAccessor::item_srna = &RNA_NodeGeometrySeparateBundleItem;
int SeparateBundleItemsAccessor::node_type = GEO_NODE_SEPARATE_BUNDLE;
int SeparateBundleItemsAccessor::item_dna_type = SDNA_TYPE_FROM_STRUCT(
NodeGeometrySeparateBundleItem);
void SeparateBundleItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
{
BLO_write_string(writer, item.name);
}
void SeparateBundleItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
{
BLO_read_string(reader, &item.name);
}
} // namespace blender::nodes

View file

@ -301,7 +301,7 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction {
this->set_default_outputs(params);
return;
}
if (found_id->is_in_loop) {
if (found_id->is_in_loop || found_id->is_in_closure) {
this->set_default_outputs(params);
return;
}
@ -573,7 +573,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction {
this->set_default_outputs(params);
return;
}
if (found_id->is_in_loop) {
if (found_id->is_in_loop || found_id->is_in_closure) {
this->set_default_outputs(params);
return;
}

View file

@ -239,7 +239,9 @@ static void node_rna(StructRNA *srna)
SOCK_COLLECTION,
SOCK_MATERIAL,
SOCK_IMAGE,
SOCK_MENU);
SOCK_MENU,
SOCK_BUNDLE,
SOCK_CLOSURE);
});
});
}

View file

@ -0,0 +1,149 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_cpp_type.hh"
#include "NOD_geometry_nodes_bundle.hh"
namespace blender::nodes {
SocketInterfaceKey::SocketInterfaceKey(std::string identifier)
{
identifiers_.append(std::move(identifier));
}
SocketInterfaceKey::SocketInterfaceKey(Vector<std::string> identifiers)
: identifiers_(std::move(identifiers))
{
BLI_assert(!identifiers_.is_empty());
}
Span<std::string> SocketInterfaceKey::identifiers() const
{
return identifiers_;
}
bool SocketInterfaceKey::matches(const SocketInterfaceKey &other) const
{
for (const std::string &identifier : other.identifiers_) {
if (identifiers_.contains(identifier)) {
return true;
}
}
return false;
}
Bundle::Bundle() = default;
Bundle::~Bundle()
{
for (StoredItem &item : items_) {
item.type->geometry_nodes_cpp_type->destruct(item.value);
}
for (void *buffer : buffers_) {
MEM_freeN(buffer);
}
}
Bundle::Bundle(const Bundle &other)
{
for (const StoredItem &item : other.items_) {
this->add_new(item.key, *item.type, item.value);
}
}
Bundle::Bundle(Bundle &&other) noexcept
: items_(std::move(other.items_)), buffers_(std::move(other.buffers_))
{
}
Bundle &Bundle::operator=(const Bundle &other)
{
if (this == &other) {
return *this;
}
this->~Bundle();
new (this) Bundle(other);
return *this;
}
Bundle &Bundle::operator=(Bundle &&other) noexcept
{
if (this == &other) {
return *this;
}
this->~Bundle();
new (this) Bundle(std::move(other));
return *this;
}
void Bundle::add_new(SocketInterfaceKey key, const bke::bNodeSocketType &type, const void *value)
{
BLI_assert(!this->contains(key));
BLI_assert(type.geometry_nodes_cpp_type);
const CPPType &cpp_type = *type.geometry_nodes_cpp_type;
void *buffer = MEM_mallocN_aligned(cpp_type.size(), cpp_type.alignment(), __func__);
cpp_type.copy_construct(value, buffer);
items_.append(StoredItem{std::move(key), &type, buffer});
buffers_.append(buffer);
}
bool Bundle::add(const SocketInterfaceKey &key,
const bke::bNodeSocketType &type,
const void *value)
{
if (this->contains(key)) {
return false;
}
this->add_new(key, type, value);
return true;
}
bool Bundle::add(SocketInterfaceKey &&key, const bke::bNodeSocketType &type, const void *value)
{
if (this->contains(key)) {
return false;
}
this->add_new(std::move(key), type, value);
return true;
}
std::optional<Bundle::Item> Bundle::lookup(const SocketInterfaceKey &key) const
{
for (const StoredItem &item : items_) {
if (item.key.matches(key)) {
return Item{item.type, item.value};
}
}
return std::nullopt;
}
bool Bundle::remove(const SocketInterfaceKey &key)
{
const int removed_num = items_.remove_if([&key](StoredItem &item) {
if (item.key.matches(key)) {
item.type->geometry_nodes_cpp_type->destruct(item.value);
return true;
}
return false;
});
return removed_num >= 1;
}
bool Bundle::contains(const SocketInterfaceKey &key) const
{
for (const StoredItem &item : items_) {
if (item.key.matches(key)) {
return true;
}
}
return false;
}
void Bundle::delete_self()
{
MEM_delete(this);
}
} // namespace blender::nodes

View file

@ -0,0 +1,31 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_geometry_nodes_closure.hh"
namespace blender::nodes {
std::optional<int> ClosureSignature::find_input_index(const SocketInterfaceKey &key) const
{
for (const int i : this->inputs.index_range()) {
const Item &item = this->inputs[i];
if (item.key.matches(key)) {
return i;
}
}
return std::nullopt;
}
std::optional<int> ClosureSignature::find_output_index(const SocketInterfaceKey &key) const
{
for (const int i : this->outputs.index_range()) {
const Item &item = this->outputs[i];
if (item.key.matches(key)) {
return i;
}
}
return std::nullopt;
}
} // namespace blender::nodes

View file

@ -0,0 +1,717 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <fmt/format.h>
#include "NOD_geometry_nodes_closure_eval.hh"
#include "NOD_geometry_nodes_lazy_function.hh"
#include "BKE_compute_contexts.hh"
#include "BKE_geometry_nodes_reference_set.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_socket_value.hh"
#include "BKE_node_tree_reference_lifetimes.hh"
#include "NOD_geometry_nodes_closure.hh"
#include "DEG_depsgraph_query.hh"
#include "FN_lazy_function_execute.hh"
namespace blender::nodes {
using bke::node_tree_reference_lifetimes::ReferenceSetInfo;
using bke::node_tree_reference_lifetimes::ReferenceSetType;
/**
* A lazy function that internally has a lazy-function graph that mimics the "body" of the closure
* zone.
*/
class LazyFunctionForClosureZone : public LazyFunction {
private:
const bNodeTree &btree_;
const bke::bNodeTreeZone &zone_;
const bNode &output_bnode_;
const ZoneBuildInfo &zone_info_;
const ZoneBodyFunction &body_fn_;
std::shared_ptr<ClosureSignature> closure_signature_;
public:
LazyFunctionForClosureZone(const bNodeTree &btree,
const bke::bNodeTreeZone &zone,
ZoneBuildInfo &zone_info,
const ZoneBodyFunction &body_fn)
: btree_(btree),
zone_(zone),
output_bnode_(*zone.output_node),
zone_info_(zone_info),
body_fn_(body_fn)
{
debug_name_ = "Closure Zone";
initialize_zone_wrapper(zone, zone_info, body_fn, false, inputs_, outputs_);
for (const auto item : body_fn.indices.inputs.reference_sets.items()) {
const ReferenceSetInfo &reference_set =
btree.runtime->reference_lifetimes_info->reference_sets[item.key];
if (reference_set.type == ReferenceSetType::ClosureInputReferenceSet) {
BLI_assert(&reference_set.socket->owner_node() != zone_.input_node);
}
if (reference_set.type == ReferenceSetType::ClosureOutputData) {
if (&reference_set.socket->owner_node() == zone_.output_node) {
/* This reference set comes from the caller of the closure and is not captured at the
* place where the closure is created. */
continue;
}
}
zone_info.indices.inputs.reference_sets.add_new(
item.key,
inputs_.append_and_get_index_as("Reference Set",
CPPType::get<bke::GeometryNodesReferenceSet>()));
}
/* All border links are used. */
for (const int i : zone_.border_links.index_range()) {
inputs_[zone_info.indices.inputs.border_links[i]].usage = lf::ValueUsage::Used;
}
const auto &storage = *static_cast<const NodeGeometryClosureOutput *>(output_bnode_.storage);
closure_signature_ = std::make_shared<ClosureSignature>();
for (const int i : IndexRange(storage.input_items.items_num)) {
const bNodeSocket &bsocket = zone_.input_node->output_socket(i);
closure_signature_->inputs.append({SocketInterfaceKey(bsocket.name), bsocket.typeinfo});
}
for (const int i : IndexRange(storage.output_items.items_num)) {
const bNodeSocket &bsocket = zone_.output_node->input_socket(i);
closure_signature_->outputs.append({SocketInterfaceKey(bsocket.name), bsocket.typeinfo});
}
}
void execute_impl(lf::Params &params, const lf::Context & /*context*/) const override
{
/* All border links are captured currently. */
for (const int i : zone_.border_links.index_range()) {
params.set_output(zone_info_.indices.outputs.border_link_usages[i], true);
}
if (!U.experimental.use_bundle_and_closure_nodes) {
params.set_output(zone_info_.indices.outputs.main[0], bke::SocketValueVariant(ClosurePtr()));
return;
}
const auto &storage = *static_cast<const NodeGeometryClosureOutput *>(output_bnode_.storage);
std::unique_ptr<ResourceScope> closure_scope = std::make_unique<ResourceScope>();
LinearAllocator<> &closure_allocator = closure_scope->linear_allocator();
lf::Graph &lf_graph = closure_scope->construct<lf::Graph>("Closure Graph");
lf::FunctionNode &lf_body_node = lf_graph.add_function(*body_fn_.function);
ClosureFunctionIndices closure_indices;
Vector<const void *> default_input_values;
for (const int i : IndexRange(storage.input_items.items_num)) {
const NodeGeometryClosureInputItem &item = storage.input_items.items[i];
const bNodeSocket &bsocket = zone_.input_node->output_socket(i);
const CPPType &cpp_type = *bsocket.typeinfo->geometry_nodes_cpp_type;
lf::GraphInputSocket &lf_graph_input = lf_graph.add_input(cpp_type, item.name);
lf_graph.add_link(lf_graph_input, lf_body_node.input(body_fn_.indices.inputs.main[i]));
lf::GraphOutputSocket &lf_graph_input_usage = lf_graph.add_output(
CPPType::get<bool>(), "Usage: " + StringRef(item.name));
lf_graph.add_link(lf_body_node.output(body_fn_.indices.outputs.input_usages[i]),
lf_graph_input_usage);
void *default_value = closure_allocator.allocate(cpp_type.size(), cpp_type.alignment());
construct_socket_default_value(*bsocket.typeinfo, default_value);
default_input_values.append(default_value);
}
closure_indices.inputs.main = lf_graph.graph_inputs().index_range().take_back(
storage.input_items.items_num);
closure_indices.outputs.input_usages = lf_graph.graph_outputs().index_range().take_back(
storage.input_items.items_num);
for (const int i : IndexRange(storage.output_items.items_num)) {
const NodeGeometryClosureOutputItem &item = storage.output_items.items[i];
const bNodeSocket &bsocket = zone_.output_node->input_socket(i);
const CPPType &cpp_type = *bsocket.typeinfo->geometry_nodes_cpp_type;
lf::GraphOutputSocket &lf_graph_output = lf_graph.add_output(cpp_type, item.name);
lf_graph.add_link(lf_body_node.output(body_fn_.indices.outputs.main[i]), lf_graph_output);
lf::GraphInputSocket &lf_graph_output_usage = lf_graph.add_input(
CPPType::get<bool>(), "Usage: " + StringRef(item.name));
lf_graph.add_link(lf_graph_output_usage,
lf_body_node.input(body_fn_.indices.inputs.output_usages[i]));
}
closure_indices.outputs.main = lf_graph.graph_outputs().index_range().take_back(
storage.output_items.items_num);
closure_indices.inputs.output_usages = lf_graph.graph_inputs().index_range().take_back(
storage.output_items.items_num);
for (const int i : zone_.border_links.index_range()) {
const CPPType &cpp_type = *zone_.border_links[i]->tosock->typeinfo->geometry_nodes_cpp_type;
void *input_ptr = params.try_get_input_data_ptr(zone_info_.indices.inputs.border_links[i]);
void *stored_ptr = closure_allocator.allocate(cpp_type.size(), cpp_type.alignment());
cpp_type.move_construct(input_ptr, stored_ptr);
if (!cpp_type.is_trivially_destructible()) {
closure_scope->add_destruct_call(
[&cpp_type, stored_ptr]() { cpp_type.destruct(stored_ptr); });
}
lf_body_node.input(body_fn_.indices.inputs.border_links[i]).set_default_value(stored_ptr);
}
for (const auto &item : body_fn_.indices.inputs.reference_sets.items()) {
const ReferenceSetInfo &reference_set =
btree_.runtime->reference_lifetimes_info->reference_sets[item.key];
if (reference_set.type == ReferenceSetType::ClosureOutputData) {
const bNodeSocket &socket = *reference_set.socket;
const bNode &node = socket.owner_node();
if (&node == zone_.output_node) {
/* This reference set is passed in by the code that invokes the closure. */
lf::GraphInputSocket &lf_graph_input = lf_graph.add_input(
CPPType::get<bke::GeometryNodesReferenceSet>(),
StringRef("Reference Set: ") + reference_set.socket->name);
lf_graph.add_link(
lf_graph_input,
lf_body_node.input(body_fn_.indices.inputs.reference_sets.lookup(item.key)));
closure_indices.inputs.output_data_reference_sets.add_new(reference_set.socket->index(),
lf_graph_input.index());
continue;
}
}
auto &input_reference_set = *params.try_get_input_data_ptr<bke::GeometryNodesReferenceSet>(
zone_info_.indices.inputs.reference_sets.lookup(item.key));
auto &stored = closure_scope->construct<bke::GeometryNodesReferenceSet>(
std::move(input_reference_set));
lf_body_node.input(body_fn_.indices.inputs.reference_sets.lookup(item.key))
.set_default_value(&stored);
}
bNodeTree &btree_orig = *reinterpret_cast<bNodeTree *>(
DEG_get_original_id(const_cast<ID *>(&btree_.id)));
if (btree_orig.runtime->logged_zone_graphs) {
std::lock_guard lock{btree_orig.runtime->logged_zone_graphs->mutex};
btree_orig.runtime->logged_zone_graphs->graph_by_zone_id.lookup_or_add_cb(
output_bnode_.identifier, [&]() { return lf_graph.to_dot(); });
}
lf_graph.update_node_indices();
lf::GraphExecutor &lf_graph_executor = closure_scope->construct<lf::GraphExecutor>(
lf_graph, nullptr, nullptr, nullptr);
ClosurePtr closure{MEM_new<Closure>(__func__,
closure_signature_,
std::move(closure_scope),
lf_graph_executor,
closure_indices,
std::move(default_input_values))};
params.set_output(zone_info_.indices.outputs.main[0],
bke::SocketValueVariant(std::move(closure)));
}
};
struct EvaluateClosureEvalStorage {
ResourceScope scope;
ClosurePtr closure;
lf::Graph graph;
std::optional<lf::GraphExecutor> graph_executor;
void *graph_executor_storage = nullptr;
};
/**
* A lazy function that is used to evaluate a passed in closure. Internally that has to build
* another lazy-function graph, which "fixes" different orderings of inputs/outputs, handles
* missing sockets and type conversions.
*/
class LazyFunctionForEvaluateClosureNode : public LazyFunction {
private:
const bNodeTree &btree_;
const bNode &bnode_;
EvaluateClosureFunctionIndices indices_;
public:
LazyFunctionForEvaluateClosureNode(const bNode &bnode)
: btree_(bnode.owner_tree()), bnode_(bnode)
{
debug_name_ = bnode.name;
for (const int i : bnode.input_sockets().index_range().drop_back(1)) {
const bNodeSocket &bsocket = bnode.input_socket(i);
indices_.inputs.main.append(inputs_.append_and_get_index_as(
bsocket.name, *bsocket.typeinfo->geometry_nodes_cpp_type, lf::ValueUsage::Maybe));
indices_.outputs.input_usages.append(
outputs_.append_and_get_index_as("Usage", CPPType::get<bool>()));
}
/* The closure input is always used. */
inputs_[indices_.inputs.main[0]].usage = lf::ValueUsage::Used;
for (const int i : bnode.output_sockets().index_range().drop_back(1)) {
const bNodeSocket &bsocket = bnode.output_socket(i);
indices_.outputs.main.append(outputs_.append_and_get_index_as(
bsocket.name, *bsocket.typeinfo->geometry_nodes_cpp_type));
indices_.inputs.output_usages.append(
inputs_.append_and_get_index_as("Usage", CPPType::get<bool>()));
if (bke::node_tree_reference_lifetimes::can_contain_referenced_data(
eNodeSocketDatatype(bsocket.type)))
{
const int input_i = inputs_.append_and_get_index_as(
"Reference Set", CPPType::get<bke::GeometryNodesReferenceSet>());
indices_.inputs.reference_set_by_output.add(i, input_i);
}
}
}
EvaluateClosureFunctionIndices indices() const
{
return indices_;
}
void *init_storage(LinearAllocator<> &allocator) const override
{
return allocator.construct<EvaluateClosureEvalStorage>().release();
}
void destruct_storage(void *storage) const override
{
auto *s = static_cast<EvaluateClosureEvalStorage *>(storage);
if (s->graph_executor_storage) {
s->graph_executor->destruct_storage(s->graph_executor_storage);
}
std::destroy_at(s);
}
void execute_impl(lf::Params &params, const lf::Context &context) const override
{
const ScopedNodeTimer node_timer{context, bnode_};
auto &user_data = *static_cast<GeoNodesLFUserData *>(context.user_data);
auto &eval_storage = *static_cast<EvaluateClosureEvalStorage *>(context.storage);
if (!eval_storage.graph_executor) {
eval_storage.closure = params.extract_input<bke::SocketValueVariant>(indices_.inputs.main[0])
.extract<ClosurePtr>();
if (!eval_storage.closure) {
for (const bNodeSocket *bsocket : bnode_.output_sockets().drop_back(1)) {
const int index = bsocket->index();
set_default_value_for_output_socket(params, indices_.outputs.main[index], *bsocket);
params.set_output(indices_.outputs.input_usages[index], false);
}
return;
}
this->generate_closure_compatibility_warnings(*eval_storage.closure, context);
this->initialize_execution_graph(eval_storage);
}
bke::EvaluateClosureComputeContext closure_compute_context{user_data.compute_context, bnode_};
GeoNodesLFUserData closure_user_data = user_data;
closure_user_data.compute_context = &closure_compute_context;
GeoNodesLFLocalUserData closure_local_user_data{closure_user_data};
lf::Context eval_graph_context{
eval_storage.graph_executor_storage, &closure_user_data, &closure_local_user_data};
eval_storage.graph_executor->execute(params, eval_graph_context);
}
void generate_closure_compatibility_warnings(const Closure &closure,
const lf::Context &context) const
{
const auto &node_storage = *static_cast<const NodeGeometryEvaluateClosure *>(bnode_.storage);
const auto &user_data = *static_cast<GeoNodesLFUserData *>(context.user_data);
const auto &local_user_data = *static_cast<GeoNodesLFLocalUserData *>(context.local_user_data);
geo_eval_log::GeoTreeLogger *tree_logger = local_user_data.try_get_tree_logger(user_data);
if (tree_logger == nullptr) {
return;
}
const ClosureSignature &signature = closure.signature();
for (const NodeGeometryEvaluateClosureInputItem &item :
Span{node_storage.input_items.items, node_storage.input_items.items_num})
{
if (const std::optional<int> i = signature.find_input_index(SocketInterfaceKey{item.name})) {
const ClosureSignature::Item &closure_item = signature.inputs[*i];
if (!btree_.typeinfo->validate_link(eNodeSocketDatatype(item.socket_type),
eNodeSocketDatatype(closure_item.type->type)))
{
tree_logger->node_warnings.append(
*tree_logger->allocator,
{bnode_.identifier,
{geo_eval_log::NodeWarningType::Error,
fmt::format(fmt::runtime(TIP_("Closure input has incompatible type: \"{}\"")),
item.name)}});
}
}
else {
tree_logger->node_warnings.append(
*tree_logger->allocator,
{bnode_.identifier,
{
geo_eval_log::NodeWarningType::Error,
fmt::format(fmt::runtime(TIP_("Closure does not have input: \"{}\"")), item.name),
}});
}
}
for (const NodeGeometryEvaluateClosureOutputItem &item :
Span{node_storage.output_items.items, node_storage.output_items.items_num})
{
if (const std::optional<int> i = signature.find_output_index(SocketInterfaceKey{item.name}))
{
const ClosureSignature::Item &closure_item = signature.outputs[*i];
if (!btree_.typeinfo->validate_link(eNodeSocketDatatype(closure_item.type->type),
eNodeSocketDatatype(item.socket_type)))
{
tree_logger->node_warnings.append(
*tree_logger->allocator,
{bnode_.identifier,
{geo_eval_log::NodeWarningType::Error,
fmt::format(fmt::runtime(TIP_("Closure output has incompatible type: \"{}\"")),
item.name)}});
}
}
else {
tree_logger->node_warnings.append(
*tree_logger->allocator,
{bnode_.identifier,
{geo_eval_log::NodeWarningType::Error,
fmt::format(fmt::runtime(TIP_("Closure does not have output: \"{}\"")),
item.name)}});
}
}
}
void initialize_execution_graph(EvaluateClosureEvalStorage &eval_storage) const
{
const auto &node_storage = *static_cast<const NodeGeometryEvaluateClosure *>(bnode_.storage);
lf::Graph &lf_graph = eval_storage.graph;
for (const lf::Input &input : inputs_) {
lf_graph.add_input(*input.type, input.debug_name);
}
for (const lf::Output &output : outputs_) {
lf_graph.add_output(*output.type, output.debug_name);
}
const Span<lf::GraphInputSocket *> lf_graph_inputs = lf_graph.graph_inputs();
const Span<lf::GraphOutputSocket *> lf_graph_outputs = lf_graph.graph_outputs();
const Closure &closure = *eval_storage.closure;
const ClosureSignature &closure_signature = closure.signature();
const ClosureFunctionIndices &closure_indices = closure.indices();
Array<std::optional<int>> inputs_map(node_storage.input_items.items_num);
for (const int i : inputs_map.index_range()) {
inputs_map[i] = closure_signature.find_input_index(
SocketInterfaceKey(node_storage.input_items.items[i].name));
}
Array<std::optional<int>> outputs_map(node_storage.output_items.items_num);
for (const int i : outputs_map.index_range()) {
outputs_map[i] = closure_signature.find_output_index(
SocketInterfaceKey(node_storage.output_items.items[i].name));
}
lf::FunctionNode &lf_closure_node = lf_graph.add_function(closure.function());
static constexpr bool static_true = true;
static constexpr bool static_false = false;
/* The closure input is always used. */
lf_graph_outputs[indices_.outputs.input_usages[0]]->set_default_value(&static_true);
for (const int input_item_i : IndexRange(node_storage.input_items.items_num)) {
lf::GraphOutputSocket &lf_usage_output =
*lf_graph_outputs[indices_.outputs.input_usages[input_item_i + 1]];
if (const std::optional<int> mapped_i = inputs_map[input_item_i]) {
const bke::bNodeSocketType &from_type = *bnode_.input_socket(input_item_i + 1).typeinfo;
const bke::bNodeSocketType &to_type = *closure_signature.inputs[*mapped_i].type;
lf::OutputSocket *lf_from = lf_graph_inputs[indices_.inputs.main[input_item_i + 1]];
lf::InputSocket &lf_to = lf_closure_node.input(closure_indices.inputs.main[*mapped_i]);
if (&from_type != &to_type) {
if (const LazyFunction *conversion_fn = build_implicit_conversion_lazy_function(
from_type, to_type, eval_storage.scope))
{
/* The provided type when evaluating the closure may be different from what the closure
* expects exactly, so do an implicit conversion. */
lf::Node &conversion_node = lf_graph.add_function(*conversion_fn);
lf_graph.add_link(*lf_from, conversion_node.input(0));
lf_from = &conversion_node.output(0);
}
else {
/* Use the default value if the provided input value is not compatible with what the
* closure expects. */
const void *default_value = closure.default_input_value(*mapped_i);
BLI_assert(default_value);
lf_to.set_default_value(default_value);
lf_usage_output.set_default_value(&static_false);
continue;
}
}
lf_graph.add_link(*lf_from, lf_to);
lf_graph.add_link(lf_closure_node.output(closure_indices.outputs.input_usages[*mapped_i]),
lf_usage_output);
}
else {
lf_usage_output.set_default_value(&static_false);
}
}
auto get_output_default_value = [&](const bke::bNodeSocketType &type) {
const CPPType &cpp_type = *type.geometry_nodes_cpp_type;
void *fallback_value = eval_storage.scope.linear_allocator().allocate(cpp_type.size(),
cpp_type.alignment());
construct_socket_default_value(type, fallback_value);
if (!cpp_type.is_trivially_destructible()) {
eval_storage.scope.add_destruct_call(
[fallback_value, type = &cpp_type]() { type->destruct(fallback_value); });
}
return fallback_value;
};
for (const int output_item_i : IndexRange(node_storage.output_items.items_num)) {
lf::GraphOutputSocket &lf_main_output =
*lf_graph_outputs[indices_.outputs.main[output_item_i]];
const bke::bNodeSocketType &main_output_type = *bnode_.output_socket(output_item_i).typeinfo;
if (const std::optional<int> mapped_i = outputs_map[output_item_i]) {
const bke::bNodeSocketType &closure_output_type =
*closure_signature.outputs[*mapped_i].type;
lf::OutputSocket *lf_from = &lf_closure_node.output(
closure_indices.outputs.main[*mapped_i]);
if (&closure_output_type != &main_output_type) {
if (const LazyFunction *conversion_fn = build_implicit_conversion_lazy_function(
closure_output_type, main_output_type, eval_storage.scope))
{
/* Convert the type of the value coming out of the closure to the output socket type of
* the evaluation. */
lf::Node &conversion_node = lf_graph.add_function(*conversion_fn);
lf_graph.add_link(*lf_from, conversion_node.input(0));
lf_from = &conversion_node.output(0);
}
else {
/* The socket types are not compatible, so use the default value. */
void *fallback_value = get_output_default_value(main_output_type);
lf_main_output.set_default_value(fallback_value);
continue;
}
}
/* Link the output of the closure to the output of the entire evaluation. */
lf_graph.add_link(*lf_from, lf_main_output);
lf_graph.add_link(*lf_graph_inputs[indices_.inputs.output_usages[output_item_i]],
lf_closure_node.input(closure_indices.inputs.output_usages[*mapped_i]));
}
else {
void *fallback_value = get_output_default_value(main_output_type);
lf_main_output.set_default_value(fallback_value);
}
}
for (const int i : closure_indices.inputs.main.index_range()) {
lf::InputSocket &lf_closure_input = lf_closure_node.input(closure_indices.inputs.main[i]);
if (lf_closure_input.origin()) {
/* Handled already. */
continue;
}
const void *default_value = closure.default_input_value(i);
lf_closure_input.set_default_value(default_value);
}
static const bke::GeometryNodesReferenceSet static_empty_reference_set;
for (const int i : closure_indices.outputs.main.index_range()) {
lf::OutputSocket &lf_closure_output = lf_closure_node.output(
closure_indices.outputs.main[i]);
if (const std::optional<int> lf_reference_set_input_i =
closure_indices.inputs.output_data_reference_sets.lookup_try(i))
{
lf::InputSocket &lf_reference_set_input = lf_closure_node.input(*lf_reference_set_input_i);
const int node_output_i = outputs_map.as_span().first_index_try(i);
if (node_output_i == -1) {
lf_reference_set_input.set_default_value(&static_empty_reference_set);
}
else {
if (const std::optional<int> lf_evaluate_node_reference_set_input_i =
indices_.inputs.reference_set_by_output.lookup_try(node_output_i))
{
lf_graph.add_link(*lf_graph_inputs[*lf_evaluate_node_reference_set_input_i],
lf_reference_set_input);
}
else {
lf_reference_set_input.set_default_value(&static_empty_reference_set);
}
}
}
if (!lf_closure_output.targets().is_empty()) {
/* Handled already. */
continue;
}
lf_closure_node.input(closure_indices.inputs.output_usages[i])
.set_default_value(&static_false);
}
lf_graph.update_node_indices();
eval_storage.graph_executor.emplace(lf_graph, nullptr, nullptr, nullptr);
eval_storage.graph_executor_storage = eval_storage.graph_executor->init_storage(
eval_storage.scope.linear_allocator());
/* Log graph for debugging purposes. */
bNodeTree &btree_orig = *reinterpret_cast<bNodeTree *>(
DEG_get_original_id(const_cast<ID *>(&btree_.id)));
if (btree_orig.runtime->logged_zone_graphs) {
std::lock_guard lock{btree_orig.runtime->logged_zone_graphs->mutex};
btree_orig.runtime->logged_zone_graphs->graph_by_zone_id.lookup_or_add_cb(
bnode_.identifier, [&]() { return lf_graph.to_dot(); });
}
}
};
void evaluate_closure_eagerly(const Closure &closure, ClosureEagerEvalParams &params)
{
const LazyFunction &fn = closure.function();
const ClosureFunctionIndices &indices = closure.indices();
const ClosureSignature &signature = closure.signature();
const int fn_inputs_num = fn.inputs().size();
const int fn_outputs_num = fn.outputs().size();
ResourceScope scope;
LinearAllocator<> &allocator = scope.linear_allocator();
GeoNodesLFLocalUserData local_user_data(*params.user_data);
void *storage = fn.init_storage(allocator);
lf::Context lf_context{storage, params.user_data, &local_user_data};
Array<GMutablePointer> lf_input_values(fn_inputs_num);
Array<GMutablePointer> lf_output_values(fn_outputs_num);
Array<std::optional<lf::ValueUsage>> lf_input_usages(fn_inputs_num);
Array<lf::ValueUsage> lf_output_usages(fn_outputs_num, lf::ValueUsage::Unused);
Array<bool> lf_set_outputs(fn_outputs_num, false);
Array<std::optional<int>> inputs_map(params.inputs.size());
for (const int i : inputs_map.index_range()) {
inputs_map[i] = signature.find_input_index(params.inputs[i].key);
}
Array<std::optional<int>> outputs_map(params.outputs.size());
for (const int i : outputs_map.index_range()) {
outputs_map[i] = signature.find_output_index(params.outputs[i].key);
}
for (const int input_item_i : params.inputs.index_range()) {
ClosureEagerEvalParams::InputItem &item = params.inputs[input_item_i];
if (const std::optional<int> mapped_i = inputs_map[input_item_i]) {
const bke::bNodeSocketType &from_type = *item.type;
const bke::bNodeSocketType &to_type = *signature.inputs[*mapped_i].type;
const CPPType &to_cpp_type = *to_type.geometry_nodes_cpp_type;
void *value = allocator.allocate(to_cpp_type.size(), to_cpp_type.alignment());
if (&from_type == &to_type) {
to_cpp_type.copy_construct(item.value, value);
}
else {
if (!implicitly_convert_socket_value(from_type, item.value, to_type, value)) {
const void *default_value = closure.default_input_value(*mapped_i);
to_cpp_type.copy_construct(default_value, value);
}
}
lf_input_values[indices.inputs.main[*mapped_i]] = {to_cpp_type, value};
}
else {
/* Provided input value is ignored. */
}
}
for (const int output_item_i : params.outputs.index_range()) {
if (const std::optional<int> mapped_i = outputs_map[output_item_i]) {
/* Tell the closure that this output is used. */
lf_input_values[indices.inputs.output_usages[*mapped_i]] = {
CPPType::get<bool>(), allocator.construct<bool>(true).release()};
lf_output_usages[indices.outputs.main[*mapped_i]] = lf::ValueUsage::Used;
}
}
/* Set remaining main inputs to their default values. */
for (const int main_input_i : indices.inputs.main.index_range()) {
const int lf_input_i = indices.inputs.main[main_input_i];
if (!lf_input_values[lf_input_i]) {
const bke::bNodeSocketType &type = *signature.inputs[main_input_i].type;
const CPPType &cpp_type = *type.geometry_nodes_cpp_type;
const void *default_value = closure.default_input_value(main_input_i);
void *value = allocator.allocate(cpp_type.size(), cpp_type.alignment());
cpp_type.copy_construct(default_value, value);
lf_input_values[lf_input_i] = {cpp_type, value};
}
lf_output_values[indices.outputs.input_usages[main_input_i]] = allocator.allocate<bool>();
}
/* Set remaining output usages to false.*/
for (const int output_usage_i : indices.inputs.output_usages.index_range()) {
const int lf_input_i = indices.inputs.output_usages[output_usage_i];
if (!lf_input_values[lf_input_i]) {
lf_input_values[lf_input_i] = {CPPType::get<bool>(),
allocator.construct<bool>(false).release()};
}
}
/** Set output data reference sets. */
for (auto &&[main_output_i, lf_input_i] : indices.inputs.output_data_reference_sets.items()) {
/* TODO: Propagate all attributes or let the caller decide. */
auto *value = &scope.construct<bke::GeometryNodesReferenceSet>();
lf_input_values[lf_input_i] = {value};
}
/** Set main outputs. */
for (const int main_output_i : indices.outputs.main.index_range()) {
const bke::bNodeSocketType &type = *signature.outputs[main_output_i].type;
const CPPType &cpp_type = *type.geometry_nodes_cpp_type;
lf_output_values[indices.outputs.main[main_output_i]] = {
cpp_type, allocator.allocate(cpp_type.size(), cpp_type.alignment())};
}
lf::BasicParams lf_params{
fn, lf_input_values, lf_output_values, lf_input_usages, lf_output_usages, lf_set_outputs};
fn.execute(lf_params, lf_context);
fn.destruct_storage(storage);
for (const int output_item_i : params.outputs.index_range()) {
ClosureEagerEvalParams::OutputItem &item = params.outputs[output_item_i];
if (const std::optional<int> mapped_i = outputs_map[output_item_i]) {
const bke::bNodeSocketType &from_type = *signature.outputs[*mapped_i].type;
const bke::bNodeSocketType &to_type = *item.type;
const CPPType &to_cpp_type = *to_type.geometry_nodes_cpp_type;
void *computed_value = lf_output_values[indices.outputs.main[*mapped_i]].get();
if (&from_type == &to_type) {
to_cpp_type.move_construct(computed_value, item.value);
}
else {
if (!implicitly_convert_socket_value(from_type, computed_value, to_type, item.value)) {
construct_socket_default_value(to_type, item.value);
}
}
}
else {
/* This output item is not computed by the closure, so set it to the default value. */
construct_socket_default_value(*item.type, item.value);
}
}
for (GMutablePointer value : lf_input_values) {
if (value) {
value.destruct();
}
}
for (const int i : lf_output_values.index_range()) {
if (lf_set_outputs[i]) {
lf_output_values[i].destruct();
}
}
}
LazyFunction &build_closure_zone_lazy_function(ResourceScope &scope,
const bNodeTree &btree,
const bke::bNodeTreeZone &zone,
ZoneBuildInfo &zone_info,
const ZoneBodyFunction &body_fn)
{
return scope.construct<LazyFunctionForClosureZone>(btree, zone, zone_info, body_fn);
}
EvaluateClosureFunction build_evaluate_closure_node_lazy_function(ResourceScope &scope,
const bNode &bnode)
{
EvaluateClosureFunction info;
auto &fn = scope.construct<LazyFunctionForEvaluateClosureNode>(bnode);
info.lazy_function = &fn;
info.indices = fn.indices();
return info;
}
} // namespace blender::nodes

View file

@ -249,6 +249,8 @@ std::unique_ptr<IDProperty, bke::idprop::IDPropertyDeleter> id_property_create_f
case SOCK_CUSTOM:
case SOCK_GEOMETRY:
case SOCK_SHADER:
case SOCK_BUNDLE:
case SOCK_CLOSURE:
return nullptr;
}
return nullptr;
@ -414,6 +416,8 @@ static bool old_id_property_type_matches_socket_convert_to_new(
case SOCK_MATRIX:
case SOCK_GEOMETRY:
case SOCK_SHADER:
case SOCK_BUNDLE:
case SOCK_CLOSURE:
return false;
}
BLI_assert_unreachable();
@ -939,9 +943,8 @@ void update_input_properties_from_node_tree(const bNodeTree &tree,
SOCK_CUSTOM;
IDProperty *new_prop = id_property_create_from_socket(socket, use_name_for_ids).release();
if (new_prop == nullptr) {
/* Out of the set of supported input sockets, only
* geometry sockets aren't added to the modifier. */
BLI_assert(ELEM(socket_type, SOCK_GEOMETRY, SOCK_MATRIX));
/* 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;
}

View file

@ -269,7 +269,7 @@ class LazyFunctionForForeachGeometryElementZone : public LazyFunction {
{
debug_name_ = "Foreach Geometry Element";
initialize_zone_wrapper(zone, zone_info, body_fn, inputs_, outputs_);
initialize_zone_wrapper(zone, zone_info, body_fn, true, inputs_, outputs_);
/* All main inputs are always used for now. */
for (const int i : zone_info.indices.inputs.main) {
inputs_[i].usage = lf::ValueUsage::Used;

View file

@ -413,18 +413,24 @@ class LazyFunctionForUndefinedNode : public LazyFunction {
}
};
void construct_socket_default_value(const bke::bNodeSocketType &stype, void *r_value)
{
const CPPType *cpp_type = stype.geometry_nodes_cpp_type;
BLI_assert(cpp_type);
if (stype.geometry_nodes_default_cpp_value) {
cpp_type->copy_construct(stype.geometry_nodes_default_cpp_value, r_value);
}
else {
cpp_type->value_initialize(r_value);
}
}
void set_default_value_for_output_socket(lf::Params &params,
const int lf_index,
const bNodeSocket &bsocket)
{
const CPPType &cpp_type = *bsocket.typeinfo->geometry_nodes_cpp_type;
void *output_value = params.get_output_data_ptr(lf_index);
if (bsocket.typeinfo->geometry_nodes_default_cpp_value) {
cpp_type.copy_construct(bsocket.typeinfo->geometry_nodes_default_cpp_value, output_value);
}
else {
cpp_type.value_initialize(output_value);
}
construct_socket_default_value(*bsocket.typeinfo, output_value);
params.output_set(lf_index);
}
@ -546,6 +552,77 @@ static void execute_multi_function_on_value_variant(const MultiFunction &fn,
}
}
bool implicitly_convert_socket_value(const bke::bNodeSocketType &from_type,
const void *from_value,
const bke::bNodeSocketType &to_type,
void *r_to_value)
{
BLI_assert(from_value != r_to_value);
if (&from_type == &to_type) {
from_type.geometry_nodes_cpp_type->copy_construct(from_value, r_to_value);
return true;
}
const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions();
const CPPType *from_cpp_type = from_type.base_cpp_type;
const CPPType *to_cpp_type = to_type.base_cpp_type;
if (!from_cpp_type || !to_cpp_type) {
return false;
}
if (conversions.is_convertible(*from_cpp_type, *to_cpp_type)) {
const MultiFunction &multi_fn = *conversions.get_conversion_multi_function(
mf::DataType::ForSingle(*from_cpp_type), mf::DataType::ForSingle(*to_cpp_type));
SocketValueVariant input_variant = *static_cast<const SocketValueVariant *>(from_value);
SocketValueVariant *output_variant = new (r_to_value) SocketValueVariant();
execute_multi_function_on_value_variant(multi_fn, {}, {&input_variant}, {output_variant});
return true;
}
return false;
}
class LazyFunctionForImplicitConversion : public LazyFunction {
private:
const MultiFunction &fn_;
public:
LazyFunctionForImplicitConversion(const MultiFunction &fn) : fn_(fn)
{
debug_name_ = "Convert";
inputs_.append_as("From", CPPType::get<SocketValueVariant>());
outputs_.append_as("To", CPPType::get<SocketValueVariant>());
}
void execute_impl(lf::Params &params, const lf::Context & /*context*/) const override
{
SocketValueVariant *from_value = params.try_get_input_data_ptr<SocketValueVariant>(0);
SocketValueVariant *to_value = new (params.get_output_data_ptr(0)) SocketValueVariant();
BLI_assert(from_value != nullptr);
BLI_assert(to_value != nullptr);
execute_multi_function_on_value_variant(fn_, {}, {from_value}, {to_value});
params.output_set(0);
}
};
const LazyFunction *build_implicit_conversion_lazy_function(const bke::bNodeSocketType &from_type,
const bke::bNodeSocketType &to_type,
ResourceScope &scope)
{
if (!from_type.geometry_nodes_cpp_type || !to_type.geometry_nodes_cpp_type) {
return nullptr;
}
if (&from_type == &to_type) {
return &scope.construct<LazyFunctionForRerouteNode>(*from_type.geometry_nodes_cpp_type);
}
const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions();
const CPPType &from_base_type = *from_type.base_cpp_type;
const CPPType &to_base_type = *to_type.base_cpp_type;
if (conversions.is_convertible(from_base_type, to_base_type)) {
const MultiFunction &multi_fn = *conversions.get_conversion_multi_function(
mf::DataType::ForSingle(from_base_type), mf::DataType::ForSingle(to_base_type));
return &scope.construct<LazyFunctionForImplicitConversion>(multi_fn);
}
return nullptr;
}
/**
* Behavior of muted nodes:
* - Some inputs are forwarded to outputs without changes.
@ -610,21 +687,9 @@ class LazyFunctionForMutedNode : public LazyFunction {
continue;
}
void *output_value = params.get_output_data_ptr(lf_output_index);
if (input_bsocket->type == output_bsocket->type) {
inputs_[lf_input_index].type->copy_construct(input_value, output_value);
params.output_set(lf_output_index);
continue;
}
const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions();
if (conversions.is_convertible(*input_bsocket->typeinfo->base_cpp_type,
*output_bsocket->typeinfo->base_cpp_type))
if (implicitly_convert_socket_value(
*input_bsocket->typeinfo, input_value, *output_bsocket->typeinfo, output_value))
{
const MultiFunction &multi_fn = *conversions.get_conversion_multi_function(
mf::DataType::ForSingle(*input_bsocket->typeinfo->base_cpp_type),
mf::DataType::ForSingle(*output_bsocket->typeinfo->base_cpp_type));
SocketValueVariant input_variant = *static_cast<const SocketValueVariant *>(input_value);
SocketValueVariant *output_variant = new (output_value) SocketValueVariant();
execute_multi_function_on_value_variant(multi_fn, {}, {&input_variant}, {output_variant});
params.output_set(lf_output_index);
continue;
}
@ -633,35 +698,6 @@ class LazyFunctionForMutedNode : public LazyFunction {
}
};
/**
* Type conversions are generally implemented as multi-functions. This node checks if the input is
* a field or single value and outputs a field or single value respectively.
*/
class LazyFunctionForMultiFunctionConversion : public LazyFunction {
private:
const MultiFunction &fn_;
public:
LazyFunctionForMultiFunctionConversion(const MultiFunction &fn) : fn_(fn)
{
debug_name_ = "Convert";
inputs_.append_as("From", CPPType::get<SocketValueVariant>());
outputs_.append_as("To", CPPType::get<SocketValueVariant>());
}
void execute_impl(lf::Params &params, const lf::Context & /*context*/) const override
{
SocketValueVariant *from_value = params.try_get_input_data_ptr<SocketValueVariant>(0);
SocketValueVariant *to_value = new (params.get_output_data_ptr(0)) SocketValueVariant();
BLI_assert(from_value != nullptr);
BLI_assert(to_value != nullptr);
execute_multi_function_on_value_variant(fn_, {}, {from_value}, {to_value});
params.output_set(0);
}
};
/**
* This lazy-function wraps nodes that are implemented as multi-function (mostly math nodes).
*/
@ -1556,6 +1592,7 @@ static bool ignore_zone_bsocket(const bNodeSocket &bsocket)
void initialize_zone_wrapper(const bNodeTreeZone &zone,
ZoneBuildInfo &zone_info,
const ZoneBodyFunction &body_fn,
const bool expose_all_reference_sets,
Vector<lf::Input> &r_inputs,
Vector<lf::Output> &r_outputs)
{
@ -1598,11 +1635,14 @@ void initialize_zone_wrapper(const bNodeTreeZone &zone,
r_outputs.append_and_get_index_as("Border Link Usage", CPPType::get<bool>()));
}
for (const auto item : body_fn.indices.inputs.reference_sets.items()) {
zone_info.indices.inputs.reference_sets.add_new(
item.key,
r_inputs.append_and_get_index_as(
"Reference Set", CPPType::get<GeometryNodesReferenceSet>(), lf::ValueUsage::Maybe));
/* Some zone types (e.g. the closure zone) do not expose all reference sets. */
if (expose_all_reference_sets) {
for (const auto item : body_fn.indices.inputs.reference_sets.items()) {
zone_info.indices.inputs.reference_sets.add_new(
item.key,
r_inputs.append_and_get_index_as(
"Reference Set", CPPType::get<GeometryNodesReferenceSet>(), lf::ValueUsage::Maybe));
}
}
}
@ -1922,6 +1962,9 @@ struct GeometryNodesLazyFunctionBuilder {
case GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT:
this->build_foreach_geometry_element_zone_function(zone);
break;
case GEO_NODE_CLOSURE_OUTPUT:
this->build_closure_zone_function(zone);
break;
default: {
BLI_assert_unreachable();
break;
@ -2081,7 +2124,8 @@ struct GeometryNodesLazyFunctionBuilder {
{
ZoneBuildInfo &zone_info = zone_build_infos_[zone.index];
/* Build a function for the loop body. */
ZoneBodyFunction &body_fn = this->build_zone_body_function(zone, "Repeat Body");
ZoneBodyFunction &body_fn = this->build_zone_body_function(
zone, "Repeat Body", &scope_.construct<GeometryNodesLazyFunctionSideEffectProvider>());
/* Wrap the loop body by another function that implements the repeat behavior. */
auto &zone_fn = build_repeat_zone_lazy_function(scope_, btree_, zone, zone_info, body_fn);
zone_info.lazy_function = &zone_fn;
@ -2091,17 +2135,30 @@ struct GeometryNodesLazyFunctionBuilder {
{
ZoneBuildInfo &zone_info = zone_build_infos_[zone.index];
/* Build a function for the loop body. */
ZoneBodyFunction &body_fn = this->build_zone_body_function(zone, "Foreach Body");
ZoneBodyFunction &body_fn = this->build_zone_body_function(
zone, "Foreach Body", &scope_.construct<GeometryNodesLazyFunctionSideEffectProvider>());
/* Wrap the loop body in another function that implements the foreach behavior. */
auto &zone_fn = build_foreach_geometry_element_zone_lazy_function(
scope_, btree_, zone, zone_info, body_fn);
zone_info.lazy_function = &zone_fn;
}
void build_closure_zone_function(const bNodeTreeZone &zone)
{
ZoneBuildInfo &zone_info = zone_build_infos_[zone.index];
/* Build a function for the closure body. */
ZoneBodyFunction &body_fn = this->build_zone_body_function(zone, "Closure Body", nullptr);
auto &zone_fn = build_closure_zone_lazy_function(scope_, btree_, zone, zone_info, body_fn);
zone_info.lazy_function = &zone_fn;
}
/**
* Build a lazy-function for the "body" of a zone, i.e. for all the nodes within the zone.
*/
ZoneBodyFunction &build_zone_body_function(const bNodeTreeZone &zone, const StringRef name)
ZoneBodyFunction &build_zone_body_function(
const bNodeTreeZone &zone,
const StringRef name,
const lf::GraphExecutorSideEffectProvider *side_effect_provider)
{
lf::Graph &lf_body_graph = scope_.construct<lf::Graph>(name);
@ -2198,13 +2255,12 @@ struct GeometryNodesLazyFunctionBuilder {
lf_body_graph.update_node_indices();
auto &logger = scope_.construct<GeometryNodesLazyFunctionLogger>(*lf_graph_info_);
auto &side_effect_provider = scope_.construct<GeometryNodesLazyFunctionSideEffectProvider>();
body_fn.function = &scope_.construct<lf::GraphExecutor>(lf_body_graph,
lf_body_inputs.as_span(),
lf_body_outputs.as_span(),
&logger,
&side_effect_provider,
side_effect_provider,
nullptr);
lf_graph_info_->debug_zone_body_graphs.add(zone.output_node->identifier, &lf_body_graph);
@ -2259,7 +2315,9 @@ struct GeometryNodesLazyFunctionBuilder {
add_reference_set_zone_input(reference_set_i);
break;
}
case ReferenceSetType::LocalReferenceSet: {
case ReferenceSetType::LocalReferenceSet:
case ReferenceSetType::ClosureOutputData:
case ReferenceSetType::ClosureInputReferenceSet: {
const bNodeSocket &bsocket = *reference_set.socket;
if (lf::OutputSocket *lf_socket = graph_params.lf_output_by_bsocket.lookup_default(
&bsocket, nullptr))
@ -2438,6 +2496,12 @@ struct GeometryNodesLazyFunctionBuilder {
lf_reference_sets.add_new(reference_set_i, lf_reference_set_socket);
break;
}
case ReferenceSetType::ClosureOutputData:
case ReferenceSetType::ClosureInputReferenceSet: {
/* These reference sets are not used outside of zones. */
BLI_assert_unreachable();
break;
}
}
}
}
@ -2788,6 +2852,10 @@ struct GeometryNodesLazyFunctionBuilder {
this->build_menu_switch_node(bnode, graph_params);
break;
}
case GEO_NODE_EVALUATE_CLOSURE: {
this->build_evaluate_closure_node(bnode, graph_params);
break;
}
default: {
if (node_type->geometry_node_execute) {
this->build_geometry_node(bnode, graph_params);
@ -3541,6 +3609,50 @@ struct GeometryNodesLazyFunctionBuilder {
}
}
void build_evaluate_closure_node(const bNode &bnode, BuildGraphParams &graph_params)
{
const EvaluateClosureFunction function = build_evaluate_closure_node_lazy_function(scope_,
bnode);
lf::FunctionNode &lf_node = graph_params.lf_graph.add_function(*function.lazy_function);
const int inputs_num = bnode.input_sockets().size() - 1;
const int outputs_num = bnode.output_sockets().size() - 1;
BLI_assert(inputs_num == function.indices.inputs.main.size());
BLI_assert(inputs_num == function.indices.outputs.input_usages.size());
BLI_assert(outputs_num == function.indices.outputs.main.size());
BLI_assert(outputs_num == function.indices.inputs.output_usages.size());
for (const int i : IndexRange(inputs_num)) {
const bNodeSocket &bsocket = bnode.input_socket(i);
lf::InputSocket &lf_socket = lf_node.input(function.indices.inputs.main[i]);
graph_params.lf_inputs_by_bsocket.add(&bsocket, &lf_socket);
mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket);
graph_params.usage_by_bsocket.add(&bsocket,
&lf_node.output(function.indices.outputs.input_usages[i]));
}
for (const int i : IndexRange(outputs_num)) {
const bNodeSocket &bsocket = bnode.output_socket(i);
lf::OutputSocket &lf_socket = lf_node.output(function.indices.outputs.main[i]);
graph_params.lf_output_by_bsocket.add(&bsocket, &lf_socket);
mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket);
lf::InputSocket &lf_usage_socket = lf_node.input(function.indices.inputs.output_usages[i]);
graph_params.socket_usage_inputs.add(&lf_usage_socket);
if (lf::OutputSocket *output_is_used = graph_params.usage_by_bsocket.lookup(&bsocket)) {
graph_params.lf_graph.add_link(*output_is_used, lf_usage_socket);
}
else {
static const bool static_false = false;
lf_usage_socket.set_default_value(&static_false);
}
}
for (const auto item : function.indices.inputs.reference_set_by_output.items()) {
const bNodeSocket &bsocket = bnode.output_socket(item.key);
lf_graph_info_->mapping
.lf_input_index_for_reference_set_for_output[bsocket.index_in_all_outputs()] =
item.value;
graph_params.lf_reference_set_input_by_output.add(&bsocket, &lf_node.input(item.value));
}
}
void build_undefined_node(const bNode &bnode, BuildGraphParams &graph_params)
{
auto &lazy_function = scope_.construct<LazyFunctionForUndefinedNode>(
@ -3688,16 +3800,12 @@ struct GeometryNodesLazyFunctionBuilder {
if (from_typeinfo.type == to_typeinfo.type) {
return &from_socket;
}
if (from_typeinfo.base_cpp_type && to_typeinfo.base_cpp_type) {
if (conversions_->is_convertible(*from_typeinfo.base_cpp_type, *to_typeinfo.base_cpp_type)) {
const MultiFunction &multi_fn = *conversions_->get_conversion_multi_function(
mf::DataType::ForSingle(*from_typeinfo.base_cpp_type),
mf::DataType::ForSingle(*to_typeinfo.base_cpp_type));
auto &fn = scope_.construct<LazyFunctionForMultiFunctionConversion>(multi_fn);
lf::Node &conversion_node = lf_graph.add_function(fn);
lf_graph.add_link(from_socket, conversion_node.input(0));
return &conversion_node.output(0);
}
if (const LazyFunction *conversion_fn = build_implicit_conversion_lazy_function(
from_typeinfo, to_typeinfo, scope_))
{
lf::Node &conversion_node = lf_graph.add_function(*conversion_fn);
lf_graph.add_link(from_socket, conversion_node.input(0));
return &conversion_node.output(0);
}
return nullptr;
}
@ -4109,6 +4217,9 @@ std::optional<FoundNestedNodeID> find_nested_node_id(const GeoNodesLFUserData &u
{
found.is_in_loop = true;
}
else if (dynamic_cast<const bke::EvaluateClosureComputeContext *>(context) != nullptr) {
found.is_in_closure = true;
}
}
std::reverse(node_ids.begin(), node_ids.end());
node_ids.append(node_id);

View file

@ -2,6 +2,8 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_geometry_nodes_bundle.hh"
#include "NOD_geometry_nodes_closure.hh"
#include "NOD_geometry_nodes_log.hh"
#include "BLI_listbase.h"
@ -213,6 +215,13 @@ GeometryInfoLog::GeometryInfoLog(const bke::GVolumeGrid &grid)
#endif
}
BundleValueLog::BundleValueLog(Vector<Item> items) : items(std::move(items)) {}
ClosureValueLog::ClosureValueLog(Vector<Item> inputs, Vector<Item> outputs)
: inputs(std::move(inputs)), outputs(std::move(outputs))
{
}
/* Avoid generating these in every translation unit. */
GeoModifierLog::GeoModifierLog() = default;
GeoModifierLog::~GeoModifierLog() = default;
@ -268,6 +277,30 @@ void GeoTreeLogger::log_value(const bNode &node, const bNodeSocket &socket, cons
store_logged_value(this->allocator->construct<GeometryInfoLog>(grid));
}
#endif
else if (value_variant.valid_for_socket(SOCK_BUNDLE)) {
Vector<BundleValueLog::Item> items;
if (const BundlePtr bundle = value_variant.extract<BundlePtr>()) {
for (const Bundle::StoredItem &item : bundle->items()) {
items.append({item.key, item.type});
}
}
store_logged_value(this->allocator->construct<BundleValueLog>(std::move(items)));
}
else if (value_variant.valid_for_socket(SOCK_CLOSURE)) {
Vector<ClosureValueLog::Item> inputs;
Vector<ClosureValueLog::Item> outputs;
if (const ClosurePtr closure = value_variant.extract<ClosurePtr>()) {
const ClosureSignature &signature = closure->signature();
for (const ClosureSignature::Item &item : signature.inputs) {
inputs.append({item.key, item.type});
}
for (const ClosureSignature::Item &item : signature.outputs) {
outputs.append({item.key, item.type});
}
}
store_logged_value(
this->allocator->construct<ClosureValueLog>(std::move(inputs), std::move(outputs)));
}
else {
value_variant.convert_to_single();
const GPointer value = value_variant.get_single_ptr();
@ -676,6 +709,11 @@ static void find_tree_zone_hash_recursive(
*zone.output_node, storage.inspection_index);
break;
}
case GEO_NODE_CLOSURE_OUTPUT: {
/* Can't find hashes for closure zones. Nodes in these zones may be evaluated in different
* contexts based on where the closures are called. */
return;
}
}
r_hash_by_zone.add_new(&zone, compute_context_builder.hash());
for (const bNodeTreeZone *child_zone : zone.child_zones) {

View file

@ -127,7 +127,7 @@ class LazyFunctionForRepeatZone : public LazyFunction {
{
debug_name_ = "Repeat Zone";
initialize_zone_wrapper(zone, zone_info, body_fn, inputs_, outputs_);
initialize_zone_wrapper(zone, zone_info, body_fn, true, inputs_, outputs_);
/* Iterations input is always used. */
inputs_[zone_info.indices.inputs.main[0]].usage = lf::ValueUsage::Used;
}

View file

@ -356,6 +356,14 @@ static BaseSocketDeclarationBuilder &build_interface_socket_declaration(
.default_value_fn(get_default_id_getter(tree.tree_interface, io_socket));
break;
}
case SOCK_BUNDLE: {
decl = &b.add_socket<decl::Bundle>(name, identifier, in_out);
break;
}
case SOCK_CLOSURE: {
decl = &b.add_socket<decl::Closure>(name, identifier, in_out);
break;
}
case SOCK_CUSTOM: {
decl = &b.add_socket<decl::Custom>(name, identifier, in_out)
.idname(io_socket.socket_type)

View file

@ -38,7 +38,7 @@ void build_node_declaration(const bke::bNodeType &typeinfo,
void NodeDeclarationBuilder::build_remaining_anonymous_attribute_relations()
{
auto is_data_socket_decl = [](const SocketDeclaration *socket_decl) {
return dynamic_cast<const decl::Geometry *>(socket_decl);
return ELEM(socket_decl->socket_type, SOCK_GEOMETRY, SOCK_BUNDLE, SOCK_CLOSURE);
};
Vector<int> geometry_inputs;
@ -354,6 +354,12 @@ static bool socket_type_to_static_decl_type(const eNodeSocketDatatype socket_typ
case SOCK_MENU:
fn(TypeTag<decl::Menu>());
return true;
case SOCK_BUNDLE:
fn(TypeTag<decl::Bundle>());
return true;
case SOCK_CLOSURE:
fn(TypeTag<decl::Closure>());
return true;
default:
return false;
}

View file

@ -102,14 +102,34 @@ class ForeachGeometryElementZoneType : public blender::bke::bNodeZoneType {
}
};
class ClosureZoneType : public blender::bke::bNodeZoneType {
public:
ClosureZoneType()
{
this->input_idname = "GeometryNodeClosureInput";
this->output_idname = "GeometryNodeClosureOutput";
this->input_type = GEO_NODE_CLOSURE_INPUT;
this->output_type = GEO_NODE_CLOSURE_OUTPUT;
this->theme_id = TH_NODE_ZONE_CLOSURE;
}
const int &get_corresponding_output_id(const bNode &input_bnode) const override
{
BLI_assert(input_bnode.type_legacy == this->input_type);
return static_cast<NodeGeometryClosureInput *>(input_bnode.storage)->output_node_id;
}
};
static void register_zone_types()
{
static SimulationZoneType simulation_zone_type;
static RepeatZoneType repeat_zone_type;
static ForeachGeometryElementZoneType foreach_geometry_element_zone_type;
static ClosureZoneType closure_zone_type;
blender::bke::register_node_zone_type(simulation_zone_type);
blender::bke::register_node_zone_type(repeat_zone_type);
blender::bke::register_node_zone_type(foreach_geometry_element_zone_type);
blender::bke::register_node_zone_type(closure_zone_type);
}
void register_nodes()

View file

@ -34,6 +34,8 @@
#include "MEM_guardedalloc.h"
#include "NOD_geometry_nodes_bundle.hh"
#include "NOD_geometry_nodes_closure.hh"
#include "NOD_node_declaration.hh"
#include "NOD_socket.hh"
@ -683,6 +685,8 @@ void node_socket_init_default_value_data(eNodeSocketDatatype datatype, int subty
case SOCK_GEOMETRY:
case SOCK_MATRIX:
case SOCK_SHADER:
case SOCK_BUNDLE:
case SOCK_CLOSURE:
break;
}
}
@ -782,6 +786,8 @@ void node_socket_copy_default_value_data(eNodeSocketDatatype datatype, void *to,
case SOCK_GEOMETRY:
case SOCK_MATRIX:
case SOCK_SHADER:
case SOCK_BUNDLE:
case SOCK_CLOSURE:
break;
}
}
@ -979,6 +985,38 @@ static bke::bNodeSocketType *make_socket_type_matrix()
return socktype;
}
static bke::bNodeSocketType *make_socket_type_bundle()
{
bke::bNodeSocketType *socktype = make_standard_socket_type(SOCK_BUNDLE, PROP_NONE);
socktype->base_cpp_type = &blender::CPPType::get<nodes::BundlePtr>();
socktype->get_base_cpp_value = [](const void * /*socket_value*/, void *r_value) {
new (r_value) nodes::BundlePtr();
};
socktype->geometry_nodes_cpp_type = &blender::CPPType::get<SocketValueVariant>();
socktype->get_geometry_nodes_cpp_value = [](const void * /*socket_value*/, void *r_value) {
new (r_value) SocketValueVariant(nodes::BundlePtr());
};
static SocketValueVariant default_value{nodes::BundlePtr()};
socktype->geometry_nodes_default_cpp_value = &default_value;
return socktype;
}
static bke::bNodeSocketType *make_socket_type_closure()
{
bke::bNodeSocketType *socktype = make_standard_socket_type(SOCK_CLOSURE, PROP_NONE);
socktype->base_cpp_type = &blender::CPPType::get<nodes::ClosurePtr>();
socktype->get_base_cpp_value = [](const void * /*socket_value*/, void *r_value) {
new (r_value) nodes::ClosurePtr();
};
socktype->geometry_nodes_cpp_type = &blender::CPPType::get<SocketValueVariant>();
socktype->get_geometry_nodes_cpp_value = [](const void * /*socket_value*/, void *r_value) {
new (r_value) SocketValueVariant(nodes::ClosurePtr());
};
static SocketValueVariant default_value{nodes::ClosurePtr()};
socktype->geometry_nodes_default_cpp_value = &default_value;
return socktype;
}
static bke::bNodeSocketType *make_socket_type_float(PropertySubType subtype)
{
bke::bNodeSocketType *socktype = make_standard_socket_type(SOCK_FLOAT, subtype);
@ -1207,5 +1245,8 @@ void register_standard_node_socket_types()
bke::node_register_socket_type(*make_socket_type_material());
bke::node_register_socket_type(*make_socket_type_bundle());
bke::node_register_socket_type(*make_socket_type_closure());
bke::node_register_socket_type(*make_socket_type_virtual());
}

View file

@ -593,6 +593,102 @@ bNodeSocket &Menu::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &s
/** \} */
/* -------------------------------------------------------------------- */
/** \name #Bundle
* \{ */
bNodeSocket &Bundle::build(bNodeTree &ntree, bNode &node) const
{
bNodeSocket &socket = *bke::node_add_static_socket(ntree,
node,
this->in_out,
SOCK_BUNDLE,
PROP_NONE,
this->identifier.c_str(),
this->name.c_str());
this->set_common_flags(socket);
return socket;
}
bool Bundle::matches(const bNodeSocket &socket) const
{
if (!this->matches_common_data(socket)) {
return false;
}
if (socket.type != SOCK_BUNDLE) {
return false;
}
return true;
}
bool Bundle::can_connect(const bNodeSocket &socket) const
{
if (!sockets_can_connect(*this, socket)) {
return false;
}
return ELEM(socket.type, SOCK_BUNDLE);
}
bNodeSocket &Bundle::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const
{
if (socket.type != SOCK_BUNDLE) {
BLI_assert(socket.in_out == this->in_out);
return this->build(ntree, node);
}
this->set_common_flags(socket);
return socket;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #Closure
* \{ */
bNodeSocket &Closure::build(bNodeTree &ntree, bNode &node) const
{
bNodeSocket &socket = *bke::node_add_static_socket(ntree,
node,
this->in_out,
SOCK_CLOSURE,
PROP_NONE,
this->identifier.c_str(),
this->name.c_str());
this->set_common_flags(socket);
return socket;
}
bool Closure::matches(const bNodeSocket &socket) const
{
if (!this->matches_common_data(socket)) {
return false;
}
if (socket.type != SOCK_CLOSURE) {
return false;
}
return true;
}
bool Closure::can_connect(const bNodeSocket &socket) const
{
if (!sockets_can_connect(*this, socket)) {
return false;
}
return ELEM(socket.type, SOCK_CLOSURE);
}
bNodeSocket &Closure::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const
{
if (socket.type != SOCK_CLOSURE) {
BLI_assert(socket.in_out == this->in_out);
return this->build(ntree, node);
}
this->set_common_flags(socket);
return socket;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #IDSocketDeclaration
* \{ */