Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_bundle_type.hh"
#include "NOD_geometry_nodes_bundle.hh"
namespace blender::nodes {
FlatBundleType::FlatBundleType(std::string name, Vector<std::unique_ptr<SocketDeclaration>> decls)
: name_(std::move(name))
{
for (std::unique_ptr<SocketDeclaration> &decl : decls) {
items_.add_new(Item{std::move(decl)});
}
}
const SocketDeclaration *FlatBundleType::find_decl(const UString name) const
{
const Item *item = items_.lookup_key_ptr_as(name);
if (!item) {
return nullptr;
}
return item->decl.get();
}
FlatBundleTypeBuilder::FlatBundleTypeBuilder(std::string name) : name_(std::move(name)) {}
FlatBundleTypePtr FlatBundleTypeBuilder::build()
{
return std::make_shared<const FlatBundleType>(std::move(name_), std::move(decls_));
}
BundleSignature FlatBundleType::to_bundle_signature() const
{
BundleSignature signature;
signature.add(Bundle::type_item_name.ustr().string(), SOCK_STRING);
for (const Item &item : items_) {
signature.add(item.name().ref(), item.decl->socket_type);
}
return signature;
}
NestedBundleType::NestedBundleType(std::string name, Vector<FlatBundleTypePtr> bundle_types)
: name_(std::move(name))
{
for (FlatBundleTypePtr &bundle_type : bundle_types) {
items_.add_new(std::move(bundle_type));
}
}
static BundleTypeRegistry &get_bundle_type_registry()
{
static BundleTypeRegistry singleton;
return singleton;
}
FlatBundleTypePtr BundleTypeRegistry::try_find_single_flat(const StringRef name)
{
const BundleTypeRegistry &registry = get_bundle_type_registry();
const Set<BundleType> *types_with_name = registry.types_.lookup_ptr(name);
if (!types_with_name) {
return nullptr;
}
if (types_with_name->size() != 1) {
return nullptr;
}
const BundleType &bundle_type = *types_with_name->begin();
if (const auto *flat_bundle_type = std::get_if<FlatBundleTypePtr>(&bundle_type.type)) {
return *flat_bundle_type;
}
return nullptr;
}
Vector<std::string> BundleTypeRegistry::get_all_flat_type_names()
{
const BundleTypeRegistry &registry = get_bundle_type_registry();
Vector<std::string> names;
for (const auto &[name, types] : registry.types_.items()) {
if (std::any_of(types.begin(), types.end(), [](const BundleType &type) {
return std::holds_alternative<FlatBundleTypePtr>(type.type);
}))
{
names.append(name);
}
}
return names;
}
void BundleTypeRegistry::register_type(BundleType bundle_type)
{
BundleTypeRegistry &registry = get_bundle_type_registry();
registry.types_.lookup_or_add_default(bundle_type.name()).add(bundle_type);
}
} // namespace blender::nodes

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,324 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_context.hh"
#include "BLI_array.hh"
#include "BLI_listbase.h"
#include "BLI_listbase_iterator.hh"
#include "BLI_span.hh"
#include "BLI_string_utf8.h"
#include "BLT_translation.hh"
#include "DNA_node_tree_interface_types.h"
#include "DNA_node_types.h"
#include "DNA_sequence_types.h"
#include "NOD_caller_ui.hh"
#include "NOD_composite.hh"
#include "NOD_compositor_nodes_caller_ui.hh"
#include "NOD_compositor_nodes_srna.hh"
#include "NOD_socket_usage_inference.hh"
#include "SEQ_iterator.hh"
#include "SEQ_modifier.hh"
#include "SEQ_modifiertypes.hh"
#include "SEQ_sequencer.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_prototypes.hh"
#include "UI_interface.hh"
#include "UI_interface_c.hh"
#include "UI_interface_layout.hh"
namespace blender::nodes {
namespace {
struct DrawGroupInputsContext {
const bContext &C;
bNodeTree *tree;
PointerRNA *properties_ptr;
PointerRNA *bmain_ptr;
Array<nodes::socket_usage_inference::SocketUsage> input_usages;
Array<nodes::socket_usage_inference::SocketUsage> output_usages;
bool input_is_visible(const bNodeTreeInterfaceSocket &socket) const
{
return this->input_usages[this->tree->interface_input_index(socket)].is_visible;
}
bool input_is_active(const bNodeTreeInterfaceSocket &socket) const
{
return this->input_usages[this->tree->interface_input_index(socket)].is_used;
}
};
}; // namespace
/* Drawing the properties manually with #ui::Layout::prop instead of #uiDefAutoButsRNA allows using
* the node socket identifier for the property names, since they are unique, but also having
* the correct label displayed in the UI. */
static void draw_property_for_socket(DrawGroupInputsContext &ctx,
ui::Layout &layout,
const bNodeTreeInterfaceSocket &socket,
PointerRNA *socket_props_ptr,
const std::optional<StringRef> parent_name = std::nullopt)
{
if (!ctx.input_is_visible(socket)) {
/* The input is not used currently, but it would be used if any menu input is changed.
* By convention, the input is hidden in this case instead of just grayed out. */
return;
}
ui::Layout &row = layout.row(true);
row.use_property_decorate_set(true);
row.active_set(ctx.input_is_active(socket));
/* Use #ui::Layout::prop_search to draw pointer properties because #ui::Layout::prop would not
* have enough information about what type of ID to select for editing the values. This is
* because pointer IDProperties contain no information about their type. */
const bke::bNodeSocketType *typeinfo = socket.socket_typeinfo();
const eNodeSocketDatatype type = typeinfo ? typeinfo->type : SOCK_CUSTOM;
/* Check #composite_node_tree_socket_type_valid for which socket types are valid and should be
* drawn. */
if (!ELEM(type,
SOCK_FLOAT,
SOCK_INT,
SOCK_BOOLEAN,
SOCK_VECTOR,
SOCK_INT_VECTOR,
SOCK_RGBA,
SOCK_ROTATION,
SOCK_MATRIX,
SOCK_MENU,
SOCK_STRING,
SOCK_FONT,
SOCK_OBJECT))
{
return;
}
std::string name = socket.name ? IFACE_(socket.name) : "";
/* If the property has a prefix that's the same string as the name of the panel it's in, remove
* the prefix so it appears less verbose. */
if (parent_name.has_value()) {
const StringRef prefix_to_remove = *parent_name;
const int prefix_size = prefix_to_remove.size();
const int pos = name.find(prefix_to_remove);
if (pos == 0 && name.size() > prefix_size && name[prefix_size] == ' ') {
name = name.substr(prefix_size + 1);
}
}
switch (type) {
case SOCK_OBJECT: {
row.prop_search(socket_props_ptr, "value", ctx.bmain_ptr, "objects", name, ICON_OBJECT_DATA);
break;
}
case SOCK_MENU: {
if (socket.flag & NODE_INTERFACE_SOCKET_MENU_EXPANDED) {
/* Use a single space when the name is empty to work around a bug with expanded enums. Also
* see #ui_item_enum_expand_exec. */
row.prop(socket_props_ptr,
"value",
ui::ITEM_R_EXPAND,
StringRef(name).is_empty() ? " " : name,
ICON_NONE);
}
else {
row.prop(socket_props_ptr, "value", UI_ITEM_NONE, name, ICON_NONE);
}
break;
}
case SOCK_FONT: {
template_id(&row,
&ctx.C,
socket_props_ptr,
"value",
nullptr,
"FONT_OT_open",
"FONT_OT_unlink",
ui::TEMPLATE_ID_FILTER_ALL,
false,
name);
break;
}
default: {
row.prop(socket_props_ptr, "value", UI_ITEM_NONE, name, ICON_NONE);
break;
}
}
}
static void draw_interface_root_panel_content(DrawGroupInputsContext &ctx,
ui::Layout &layout,
const bNodeTreeInterfacePanel &interface_panel,
const bool is_mask_input_used)
{
bool found_image_input = false;
bool found_mask_input = false;
for (const bNodeTreeInterfaceItem *item : interface_panel.items()) {
switch (item->item_type) {
case NodeTreeInterfaceItemType::Panel: {
const auto &sub_interface_panel = *reinterpret_cast<const bNodeTreeInterfacePanel *>(item);
draw_interface_panel_as_panel(
ctx.C,
layout,
ctx.properties_ptr,
sub_interface_panel,
[&](const bNodeTreeInterfaceSocket &socket) { return ctx.input_is_visible(socket); },
[&](const bNodeTreeInterfaceSocket &socket) { return ctx.input_is_active(socket); },
[&](ui::Layout &layout,
const bNodeTreeInterfaceSocket &socket,
PointerRNA *socket_props_ptr,
const std::optional<StringRef> parent_name) {
draw_property_for_socket(ctx, layout, socket, socket_props_ptr, parent_name);
});
break;
}
case NodeTreeInterfaceItemType::Socket: {
const auto &interface_socket = *reinterpret_cast<const bNodeTreeInterfaceSocket *>(item);
const bke::bNodeSocketType *typeinfo = interface_socket.socket_typeinfo();
const eNodeSocketDatatype socket_type = typeinfo ? typeinfo->type : SOCK_CUSTOM;
if (interface_socket.flag & NODE_INTERFACE_SOCKET_INPUT) {
/* Don't draw the first color input. It's the strip input. */
if (!found_image_input && socket_type == SOCK_RGBA) {
found_image_input = true;
}
/* Don't draw the second color input if the mask input is used. */
else if (is_mask_input_used && !found_mask_input && socket_type == SOCK_RGBA) {
found_mask_input = true;
}
else if (!(interface_socket.flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER)) {
PointerRNA inputs_ptr = RNA_pointer_get(ctx.properties_ptr, "inputs");
PointerRNA socket_props_ptr = RNA_pointer_get(&inputs_ptr,
interface_socket.identifier);
draw_property_for_socket(
ctx, layout, interface_socket, &socket_props_ptr, std::nullopt);
}
}
break;
}
}
}
}
static void draw_mask_input_type_settings(const bContext &C, ui::Layout &layout, PointerRNA *ptr)
{
Scene *sequencer_scene = CTX_data_sequencer_scene(&C);
Editing *ed = seq::editing_get(sequencer_scene);
const int input_mask_type = RNA_enum_get(ptr, "input_mask_type");
layout.use_property_split_set(true);
ui::Layout &col = layout.column(false);
ui::Layout *row = &col.row(true);
row->prop(ptr, "input_mask_type", ui::ITEM_R_EXPAND, IFACE_("Type"), ICON_NONE);
if (input_mask_type == STRIP_MASK_INPUT_STRIP) {
PointerRNA sequences_object = RNA_pointer_create_discrete(
&sequencer_scene->id, RNA_SequenceEditor, ed);
col.prop_search(
ptr, "input_mask_strip", &sequences_object, "strips_all", IFACE_("Mask"), ICON_NONE);
}
else {
col.prop(ptr, "input_mask_id", UI_ITEM_NONE, std::nullopt, ICON_NONE);
row = &col.row(true);
row->prop(ptr, "mask_time", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
}
}
static void draw_error_message(const bNodeTree &tree, ui::Layout &layout, const bool is_mask_used)
{
const Span<const bNodeTreeInterfaceSocket *> interface_inputs = tree.interface_inputs();
const Span<const bNodeTreeInterfaceSocket *> interface_ouputs = tree.interface_outputs();
if (interface_inputs.size() > 0) {
const bke::bNodeSocketType *typeinfo = interface_inputs[0]->socket_typeinfo();
const eNodeSocketDatatype socket_type = typeinfo ? typeinfo->type : SOCK_CUSTOM;
if (socket_type != SOCK_RGBA) {
ui::Layout &row = layout.row(false);
row.label(RPT_("The first node group input must have the Color type"), ICON_ERROR);
}
}
if (is_mask_used) {
if (interface_inputs.size() < 1) {
ui::Layout &row = layout.row(false);
row.label(RPT_("Node group must have at least two inputs to use the mask input"),
ICON_ERROR);
}
const bke::bNodeSocketType *typeinfo = interface_inputs[1]->socket_typeinfo();
const eNodeSocketDatatype socket_type = typeinfo ? typeinfo->type : SOCK_CUSTOM;
if (socket_type != SOCK_RGBA) {
ui::Layout &row = layout.row(false);
row.label(RPT_("The second node group input must have the Color type"), ICON_ERROR);
}
}
if (interface_ouputs.is_empty()) {
ui::Layout &row = layout.row(false);
row.label(RPT_("Node group must have an output"), ICON_ERROR);
}
else {
const bke::bNodeSocketType *typeinfo = interface_ouputs[0]->socket_typeinfo();
const eNodeSocketDatatype socket_type = typeinfo ? typeinfo->type : SOCK_CUSTOM;
if (socket_type != SOCK_RGBA) {
ui::Layout &row = layout.row(false);
row.label(RPT_("The first node group output must have the Color type"), ICON_ERROR);
}
}
}
void draw_compositor_nodes_modifier_ui(const bContext &C,
PointerRNA *modifier_ptr,
ui::Layout &layout)
{
Main *bmain = CTX_data_main(&C);
PointerRNA bmain_ptr = RNA_main_pointer_create(bmain);
SequencerCompositorModifierData &cmd = *modifier_ptr->data_as<SequencerCompositorModifierData>();
PointerRNA properties_ptr = RNA_pointer_get(modifier_ptr, "properties");
DrawGroupInputsContext ctx{C, cmd.node_group, &properties_ptr, &bmain_ptr};
layout.use_property_split_set(true);
if ((cmd.flag & SEQ_COMP_MOD_HIDE_DATABLOCK_SELECTOR) == 0) {
const char *newop = (cmd.node_group == nullptr) ?
"node.new_compositor_sequencer_node_group" :
"node.duplicate_compositing_modifier_node_group";
template_id(&layout, &C, modifier_ptr, "node_group", newop, nullptr, nullptr);
}
const StripModifierData &smd = cmd.modifier;
const bool is_mask_used = smd.mask_input_type == STRIP_MASK_INPUT_STRIP ?
smd.mask_strip != nullptr :
smd.mask_id != nullptr;
if (cmd.node_group != nullptr && !(ID_MISSING(cmd.node_group))) {
bNodeTree &tree = *cmd.node_group;
tree.ensure_interface_cache();
draw_error_message(tree, layout, is_mask_used);
ctx.input_usages.reinitialize(tree.interface_inputs().size());
ctx.output_usages.reinitialize(tree.interface_outputs().size());
nodes::socket_usage_inference::infer_group_interface_inputs_usage(
tree, *ctx.properties_ptr, ctx.input_usages, ctx.output_usages);
draw_interface_root_panel_content(ctx, layout, tree.tree_interface.root_panel, is_mask_used);
}
if (ui::Layout *mask_input_layout = layout.panel_prop(
&C, modifier_ptr, "open_mask_input_panel", IFACE_("Mask Input")))
{
draw_mask_input_type_settings(C, *mask_input_layout, modifier_ptr);
}
}
}; // namespace blender::nodes

View File

@@ -0,0 +1,182 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <fmt/format.h>
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "NOD_compositor_nodes_srna.hh"
#include "NOD_socket.hh"
#include "DNA_node_types.h"
#include "DNA_sequence_types.h"
#include "BKE_idprop.hh"
#include "BKE_node_runtime.hh"
#include "SEQ_iterator.hh"
#include "SEQ_sequencer.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_prototypes.hh"
namespace blender::nodes {
static constexpr EnumPropertyItem input_type_item_fallback = {
int(CompositorNodesInputType::Fallback), "FALLBACK", 0, "Fallback", "Fallback"};
static constexpr EnumPropertyItem input_type_item_value = {
int(CompositorNodesInputType::Value), "VALUE", 0, "Value", "Pass a single value"};
const EnumPropertyItem compositor_nodes_input_type_items_fallback[] = {
input_type_item_fallback,
{0},
};
const EnumPropertyItem compositor_nodes_input_type_items_value[] = {
input_type_item_value,
{0},
};
static std::pair<const Strip *, const StripModifierData *>
find_strip_and_modifier_data_from_system_property(const PointerRNA *ptr)
{
if (const auto modifier = RNA_struct_search_closest_ancestor_by_type(ptr, RNA_StripModifier)) {
if (const auto strip = RNA_struct_search_closest_ancestor_by_type(ptr, RNA_Strip)) {
return {static_cast<const Strip *>(strip->data),
static_cast<const StripModifierData *>(modifier->data)};
}
}
const Scene *sequencer_scene = id_cast<const Scene *>(ptr->owner_id);
const Editing *ed = seq::editing_get(sequencer_scene);
BLI_assert(ed);
for (Strip *strip : seq::query_all_strips_recursive(&ed->seqbase)) {
for (StripModifierData &md : strip->modifiers) {
bool found = false;
IDP_foreach_property(md.system_properties, 0, [&](IDProperty *id_prop) {
if (id_prop == ptr->data) {
found = true;
}
});
if (found) {
return {strip, &md};
}
}
}
return {};
}
static std::optional<std::string> rna_CompositorNodesModifierProperty_path(
const PointerRNA *ptr, const StringRef properties_path)
{
StructRNA *srna = ptr->type;
const char *identifier = RNA_struct_identifier(srna);
const auto [strip, smd] = find_strip_and_modifier_data_from_system_property(ptr);
BLI_assert(strip && smd);
std::string strip_name_esc = BLI_str_escape(strip->name + 2);
std::string modifier_name_esc = BLI_str_escape(smd->name);
return fmt::format("sequence_editor.strips_all[\"{}\"].modifiers[\"{}\"].properties.{}.{}",
strip_name_esc,
modifier_name_esc,
properties_path,
identifier);
}
static std::optional<std::string> rna_CompositorNodesModifierPropertyInput_path(
const PointerRNA *ptr)
{
return rna_CompositorNodesModifierProperty_path(ptr, "inputs");
}
static StructRNA *get_input_socket_struct_rna(const bNodeTree &tree,
const bNodeTreeInterfaceSocket &socket,
GeneratedTreeSrnaData &r_generated)
{
const bke::bNodeSocketType *stype = socket.socket_typeinfo();
if (!stype) {
return nullptr;
}
const StringRefNull srna_identifier = r_generated.scope.allocator().copy_string(
socket.identifier);
StructRNA *srna = RNA_def_struct_ptr(
r_generated.generated_rna, srna_identifier.c_str(), RNA_PropertyGroup);
RNA_def_struct_path_func_runtime(srna, rna_CompositorNodesModifierPropertyInput_path);
if (stype->make_compositor_nodes_input_srna) {
stype->make_compositor_nodes_input_srna(tree, *srna, socket, r_generated);
}
return srna;
}
static StructRNA *create_inputs_srna(const bNodeTree &tree, GeneratedTreeSrnaData &r_generated)
{
StructRNA *srna = RNA_def_struct_ptr(
r_generated.generated_rna, "CompositorNodesInterfaceInputs", RNA_PropertyGroup);
for (const bNodeTreeInterfaceSocket *socket : tree.interface_inputs()) {
StructRNA *socket_srna = get_input_socket_struct_rna(tree, *socket, r_generated);
if (!socket_srna) {
continue;
}
const StringRefNull identifier = r_generated.scope.allocator().copy_string(socket->identifier);
RNA_def_pointer_runtime(srna, identifier.c_str(), socket_srna, socket->name, "");
}
return srna;
}
static StructRNA *create_panels_srna(const bNodeTree &tree, GeneratedTreeSrnaData &r_generated)
{
StructRNA *srna = RNA_def_struct_ptr(
r_generated.generated_rna, "CompositorNodesInterfacePanels", RNA_PropertyGroup);
LinearAllocator<> &allocator = r_generated.scope.allocator();
tree.ensure_interface_cache();
for (const bNodeTreeInterfaceItem *item : tree.interface_items()) {
if (item->item_type != NodeTreeInterfaceItemType::Panel) {
continue;
}
const auto &panel = *reinterpret_cast<const bNodeTreeInterfacePanel *>(item);
const StringRefNull identifier = allocator.copy_string(
fmt::format("open_{}", panel.identifier));
PropertyRNA *prop = RNA_def_boolean(srna,
identifier.c_str(),
!(panel.flag & NODE_INTERFACE_PANEL_DEFAULT_CLOSED),
"Is Open",
"");
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
}
return srna;
}
std::shared_ptr<GeneratedTreeSrnaData> create_compositor_nodes_rna_for_strip_modifier(
const bNodeTree &tree)
{
auto generated = std::make_unique<GeneratedTreeSrnaData>();
tree.ensure_interface_cache();
StructRNA *srna = RNA_def_struct_ptr(generated->generated_rna,
"CompositorNodesModifierInterface",
RNA_SequencerCompositorModifierProperties);
generated->properties_struct = srna;
StructRNA *inputs_srna = create_inputs_srna(tree, *generated);
/* Note: We don't generate any srna for the outputs because they are unused by the compositor. */
StructRNA *panels_srna = create_panels_srna(tree, *generated);
PropertyRNA *prop;
prop = RNA_def_pointer_runtime(
srna, "inputs", inputs_srna, "Inputs", "Settings for input sockets");
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
prop = RNA_def_pointer_runtime(srna, "panels", panels_srna, "Panels", "Settings for panels");
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
return generated;
}
} // namespace blender::nodes

View File

@@ -0,0 +1,324 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_dependencies.hh"
#include "DNA_ID.h"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
#include "BKE_image.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_node_runtime.hh"
#include "NOD_node_declaration.hh"
namespace blender::nodes {
void EvalDependencies::add_generic_id(ID *id)
{
if (!id) {
return;
}
this->ids.add(id->session_uid, id);
}
void EvalDependencies::add_generic_id_full(ID *id)
{
if (!id) {
return;
}
if (GS(id->name) == ID_OB) {
this->add_object(reinterpret_cast<Object *>(id));
}
else {
this->add_generic_id(id);
}
}
void EvalDependencies::add_object(Object *object, const ObjectDependencyInfo &object_deps)
{
if (!object) {
return;
}
this->add_generic_id(&object->id);
ObjectDependencyInfo &deps = this->objects_info.lookup_or_add(object->id.session_uid,
object_deps);
deps.geometry |= object_deps.geometry;
deps.transform |= object_deps.transform;
deps.camera_parameters |= object_deps.camera_parameters;
deps.pose |= object_deps.pose;
}
void EvalDependencies::merge(const EvalDependencies &other)
{
for (ID *id : other.ids.values()) {
this->add_generic_id(id);
}
for (const auto &&item : other.objects_info.items()) {
ID *id = other.ids.lookup(item.key);
BLI_assert(GS(id->name) == ID_OB);
this->add_object(reinterpret_cast<Object *>(id), item.value);
}
this->needs_own_transform |= other.needs_own_transform;
this->needs_active_camera |= other.needs_active_camera;
this->needs_scene_render_params |= other.needs_scene_render_params;
this->time_dependent |= other.time_dependent;
}
static bool is_used_default_input(const bNodeSocket &socket, const NodeDefaultInputType type)
{
if (!socket.is_input()) {
return false;
}
if (socket.is_logically_linked()) {
return false;
}
if (!socket.runtime->declaration) {
return false;
}
return socket.runtime->declaration->default_input_type == type;
}
static void add_eval_dependencies_from_socket(const bNodeSocket &socket, EvalDependencies &deps)
{
if (socket.is_input()) {
if (socket.is_logically_linked()) {
/* The input value is unused. */
return;
}
}
switch (socket.type) {
case SOCK_OBJECT: {
if (is_used_default_input(socket, NODE_DEFAULT_INPUT_SELF_OBJECT)) {
deps.needs_own_transform |= true;
}
else if (Object *object = static_cast<bNodeSocketValueObject *>(socket.default_value)->value)
{
deps.add_object(object);
}
break;
}
case SOCK_COLLECTION: {
if (Collection *collection =
static_cast<bNodeSocketValueCollection *>(socket.default_value)->value)
{
deps.add_generic_id(reinterpret_cast<ID *>(collection));
}
break;
}
case SOCK_MATERIAL: {
if (Material *material =
static_cast<bNodeSocketValueMaterial *>(socket.default_value)->value)
{
deps.add_generic_id(reinterpret_cast<ID *>(material));
}
break;
}
case SOCK_TEXTURE: {
if (Tex *texture = static_cast<bNodeSocketValueTexture *>(socket.default_value)->value) {
deps.add_generic_id(reinterpret_cast<ID *>(texture));
}
break;
}
case SOCK_IMAGE: {
if (Image *image = static_cast<bNodeSocketValueImage *>(socket.default_value)->value) {
deps.add_generic_id(reinterpret_cast<ID *>(image));
}
break;
}
case SOCK_FONT: {
if (VFont *font = static_cast<bNodeSocketValueFont *>(socket.default_value)->value) {
deps.add_generic_id(reinterpret_cast<ID *>(font));
}
break;
}
case SOCK_SCENE: {
if (Scene *scene = static_cast<bNodeSocketValueScene *>(socket.default_value)->value) {
deps.add_generic_id(reinterpret_cast<ID *>(scene));
}
break;
}
case SOCK_TEXT_ID: {
if (Text *text = static_cast<bNodeSocketValueText *>(socket.default_value)->value) {
deps.add_generic_id(reinterpret_cast<ID *>(text));
}
break;
}
case SOCK_MASK: {
if (Mask *mask = static_cast<bNodeSocketValueMask *>(socket.default_value)->value) {
deps.add_generic_id(reinterpret_cast<ID *>(mask));
}
break;
}
case SOCK_SOUND: {
if (bSound *sound = static_cast<bNodeSocketValueSound *>(socket.default_value)->value) {
deps.add_generic_id(reinterpret_cast<ID *>(sound));
}
break;
}
case SOCK_INT:
case SOCK_FLOAT: {
if (is_used_default_input(socket, NODE_DEFAULT_INPUT_SCENE_FRAME)) {
deps.time_dependent = true;
}
break;
}
default:
break;
}
}
static void add_eval_dependencies_from_node_data(const bNodeTree &tree, EvalDependencies &deps)
{
for (const bNode *node : tree.all_nodes()) {
if (node->is_muted()) {
continue;
}
/* Group nodes are handles separately. */
if (node->is_group()) {
continue;
}
if (node->id == nullptr) {
continue;
}
ID_Type id_type = GS(node->id->name);
if (id_type == ID_OB) {
deps.add_object(reinterpret_cast<Object *>(node->id));
}
else if (id_type == ID_IM) {
if (BKE_image_is_animated(reinterpret_cast<Image *>(node->id))) {
deps.time_dependent = true;
}
deps.add_generic_id(node->id);
}
else {
deps.add_generic_id(node->id);
}
}
}
static bool has_enabled_nodes_of_type(const bNodeTree &tree, const UString type_idname)
{
for (const bNode *node : tree.nodes_by_type(type_idname)) {
if (!node->is_muted()) {
return true;
}
}
return false;
}
static void add_own_transform_dependencies(const bNodeTree &tree, EvalDependencies &deps)
{
bool needs_own_transform = false;
needs_own_transform |= has_enabled_nodes_of_type(tree, "GeometryNodeSelfObject"_ustr);
needs_own_transform |= has_enabled_nodes_of_type(tree, "GeometryNodeDeformCurvesOnSurface"_ustr);
for (const bNode *node : tree.nodes_by_type("GeometryNodeCollectionInfo"_ustr)) {
if (node->is_muted()) {
continue;
}
const NodeGeometryCollectionInfo &storage = *static_cast<const NodeGeometryCollectionInfo *>(
node->storage);
needs_own_transform |= storage.transform_space == GEO_NODE_TRANSFORM_SPACE_RELATIVE;
}
for (const bNode *node : tree.nodes_by_type("GeometryNodeObjectInfo"_ustr)) {
if (node->is_muted()) {
continue;
}
const NodeGeometryObjectInfo &storage = *static_cast<const NodeGeometryObjectInfo *>(
node->storage);
needs_own_transform |= storage.transform_space == GEO_NODE_TRANSFORM_SPACE_RELATIVE;
}
deps.needs_own_transform |= needs_own_transform;
}
static bool needs_scene_render_params(const bNodeTree &ntree)
{
for (const bNode *node : ntree.nodes_by_type("GeometryNodeCameraInfo"_ustr)) {
if (node->is_muted()) {
continue;
}
const bNodeSocket &projection_matrix_socket = *node->output_by_identifier(
"Projection Matrix"_ustr);
if (projection_matrix_socket.is_logically_linked()) {
return true;
}
}
return false;
}
static void gather_geometry_nodes_eval_dependencies(
const bNodeTree &ntree,
EvalDependencies &deps,
FunctionRef<const EvalDependencies *(const bNodeTree &group)> get_group_deps)
{
ntree.ensure_topology_cache();
for (const bNodeSocket *socket : ntree.all_sockets()) {
add_eval_dependencies_from_socket(*socket, deps);
}
deps.needs_active_camera |= has_enabled_nodes_of_type(ntree,
"GeometryNodeInputActiveCamera"_ustr);
deps.needs_scene_render_params |= needs_scene_render_params(ntree);
deps.time_dependent |= has_enabled_nodes_of_type(ntree, "GeometryNodeSimulationInput"_ustr) ||
has_enabled_nodes_of_type(ntree, "GeometryNodeInputSceneTime"_ustr) ||
has_enabled_nodes_of_type(ntree, "CompositorNodeSceneTime"_ustr) ||
has_enabled_nodes_of_type(ntree, "CompositorNodeTime"_ustr) ||
has_enabled_nodes_of_type(ntree, "CompositorNodeTrackPos"_ustr) ||
has_enabled_nodes_of_type(ntree, "CompositorNodeStabilize"_ustr) ||
has_enabled_nodes_of_type(ntree, "CompositorNodePlaneTrackDeform"_ustr) ||
has_enabled_nodes_of_type(ntree, "CompositorNodeMovieDistortion"_ustr) ||
has_enabled_nodes_of_type(ntree, "CompositorNodeMovieClip"_ustr) ||
has_enabled_nodes_of_type(ntree, "CompositorNodeKeyingScreen"_ustr);
add_eval_dependencies_from_node_data(ntree, deps);
add_own_transform_dependencies(ntree, deps);
for (const bNode *node : ntree.group_nodes()) {
if (!node->id) {
continue;
}
const bNodeTree &group = *reinterpret_cast<const bNodeTree *>(node->id);
if (const EvalDependencies *group_deps = get_group_deps(group)) {
deps.merge(*group_deps);
}
}
}
EvalDependencies gather_eval_dependencies_with_cache(const bNodeTree &ntree)
{
EvalDependencies deps;
gather_geometry_nodes_eval_dependencies(
ntree, deps, [](const bNodeTree &group) { return group.runtime->eval_dependencies.get(); });
return deps;
}
static void gather_geometry_nodes_eval_dependencies_recursive_impl(
const bNodeTree &ntree, Map<const bNodeTree *, EvalDependencies> &deps_by_tree)
{
if (deps_by_tree.contains(&ntree)) {
return;
}
EvalDependencies new_deps;
gather_geometry_nodes_eval_dependencies(ntree, new_deps, [&](const bNodeTree &group) {
gather_geometry_nodes_eval_dependencies_recursive_impl(group, deps_by_tree);
return &deps_by_tree.lookup(&group);
});
deps_by_tree.add(&ntree, std::move(new_deps));
}
EvalDependencies gather_eval_dependencies_recursive(const bNodeTree &ntree)
{
Map<const bNodeTree *, EvalDependencies> deps_by_tree;
gather_geometry_nodes_eval_dependencies_recursive_impl(ntree, deps_by_tree);
return deps_by_tree.lookup(&ntree);
}
} // namespace blender::nodes

View File

@@ -0,0 +1,132 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
'''
Usage:
python discover_nodes.py
<sources/root>
<path/to/output.cc>
<generated_function_name>
<source>...
The goal is to make it easy for nodes to register themselves without having to have
a central place that registers all nodes manually. A node can use this mechanism by
invoking `NOD_REGISTER_NODE(register_function_name)`.
This scripts finds all those macro invocations generates code that calls the functions.
'''
__all__ = (
"main",
)
import os
import re
import sys
def filepath_is_older(filepath_test: str, filepath_compare: tuple[str, ...]) -> bool:
import stat
mtime = os.stat(filepath_test)[stat.ST_MTIME]
for filepath_other in filepath_compare:
if mtime < os.stat(filepath_other)[stat.ST_MTIME]:
return True
return False
def main() -> int:
# The build system requires the generated file to be touched if any files used to generate it are newer.
try:
sys.argv.remove("--use-makefile-workaround")
use_makefile_workaround = True
except ValueError:
use_makefile_workaround = False
# NOTE: avoid `pathlib`, pulls in many modules indirectly, path handling is simple enough.
source_root = sys.argv[1]
output_cc_file = sys.argv[2]
function_to_generate = sys.argv[3]
source_cc_files = [
os.path.join(source_root, path)
for path in sys.argv[4:]
if path.endswith(".cc")
]
macro_name = "NOD_REGISTER_NODE"
discover_suffix = "_discover"
include_lines: list[str] = []
decl_lines: list[str] = []
func_lines: list[str] = []
# Add forward declaration to avoid warning.
func_lines.append("namespace blender {")
func_lines.append(f"void {function_to_generate}();")
func_lines.append(f"void {function_to_generate}()")
func_lines.append("{")
# Use a single regular expression to search for opening name-spaces, closing name-spaces
# and macro invocations. This makes it easy to iterate over the matches in order.
re_namespace_begin = r"^namespace ([\w:]+) \{"
re_namespace_end = r"^\} // namespace ([\w:]+)"
re_macro = r"MACRO\((\w+)\)".replace("MACRO", macro_name)
re_all = f"({re_namespace_begin})|({re_namespace_end})|({re_macro})"
re_all_compiled = re.compile(re_all, flags=re.MULTILINE)
for path in source_cc_files:
# Read the source code.
with open(path, "r", encoding="utf-8") as fh:
code = fh.read()
# Keeps track of the current name-space we're in.
namespace_parts: list[str] = []
for match in re_all_compiled.finditer(code):
if entered_namespace := match.group(2):
# Enter a (nested) name-space.
namespace_parts += entered_namespace.split("::")
elif exited_namespace := match.group(4):
# Exit a (nested) name-space.
del namespace_parts[-len(exited_namespace.split("::")):]
elif function_name := match.group(6):
# Macro invocation in the current name-space.
namespace_str = "::".join(namespace_parts)
# Add suffix so that this refers to the function created by the macro.
auto_run_name = function_name + discover_suffix
# Declare either outside of any named name-space or in a (nested) name-space.
# Can't declare it in an anonymous name-space because that would make the
# declared function static.
if namespace_str:
decl_lines.append(f"namespace {namespace_str} {{")
decl_lines.append(f"void {auto_run_name}();")
if namespace_str:
decl_lines.append("}")
# Call the function.
func_lines.append(f" {namespace_str}::{auto_run_name}();")
func_lines.append("}")
func_lines.append("} // namespace blender")
# Write the generated code if it changed. If the newly generated code is the same as before,
# don't overwrite the existing file to avoid unnecessary rebuilds.
try:
with open(output_cc_file, "r", encoding="utf-8") as fh:
old_generated_code = fh.read()
except Exception:
old_generated_code = ""
new_generated_code = "\n".join(include_lines + decl_lines + [""] + func_lines)
if old_generated_code != new_generated_code:
with open(output_cc_file, "w", encoding="utf-8") as fh:
fh.write(new_generated_code)
elif use_makefile_workaround and filepath_is_older(output_cc_file, (__file__, *source_cc_files)):
# If the generated file is older than this command, this file would be generated every time.
os.utime(output_cc_file)
return 0
if __name__ == "__main__":
sys.exit(main())

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,540 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <fmt/format.h>
#include <fmt/ranges.h>
#include "BKE_node_socket_value.hh"
#include "BKE_node_runtime.hh"
#include "NOD_geometry_nodes_bundle.hh"
#include "NOD_geometry_nodes_bundle_signature.hh"
namespace blender::nodes {
bool operator==(const BundleSignature &a, const BundleSignature &b)
{
return a.items.as_span() == b.items.as_span();
}
bool operator!=(const BundleSignature &a, const BundleSignature &b)
{
return !(a == b);
}
void BundleSignature::set_auto_structure_types()
{
for (const BundleSignature::Item &item : this->items) {
const_cast<BundleSignature::Item &>(item).structure_type =
NodeSocketInterfaceStructureType::Auto;
}
}
bool BundleKey::is_valid_key(const StringRef key)
{
if (key.is_empty()) {
return false;
}
if (key != key.trim()) {
/* Keys must not have leading or trailing white-space. This simplifies potentially using
these
* keys in expressions later on (or even just have a comma separated list of keys). */
return false;
}
return key.find_first_of(BundleKey::forbidden_key_chars) == StringRef::not_found;
}
bool Bundle::is_valid_path(const StringRef path)
{
return split_path(path).has_value();
}
std::optional<Vector<BundleKey>> Bundle::split_path(const StringRef path)
{
if (path.is_empty()) {
return std::nullopt;
}
Vector<BundleKey> path_elems;
StringRef remaining = path;
while (!remaining.is_empty()) {
const int sep = remaining.find_first_of('/');
if (sep == StringRef::not_found) {
const StringRef key_str = remaining;
if (const std::optional<BundleKey> key = BundleKey::from_str(key_str)) {
path_elems.append(*key);
}
else {
return std::nullopt;
}
break;
}
const StringRef key_str = remaining.substr(0, sep);
if (const std::optional<BundleKey> key = BundleKey::from_str(key_str)) {
path_elems.append(*key);
remaining = remaining.substr(sep + 1);
}
else {
return std::nullopt;
}
}
return path_elems;
}
BundlePtr Bundle::create()
{
return BundlePtr(MEM_new<Bundle>(__func__));
}
void Bundle::add_new(const BundleKey key, const BundleItemValue &value)
{
items_.add_new_as(key, value);
}
void Bundle::add_new(BundleKey key, BundleItemValue &&value)
{
items_.add_new_as(key, std::move(value));
}
void Bundle::add_override(const BundleKey key, const BundleItemValue &value)
{
this->remove(key);
this->add_new(key, value);
}
bool Bundle::add(const BundleKey key, const BundleItemValue &value)
{
if (this->contains(key)) {
return false;
}
this->add_new(key, value);
return true;
}
bool Bundle::add(const BundleKey key, BundleItemValue &&value)
{
if (this->contains(key)) {
return false;
}
this->add_new(key, std::move(value));
return true;
}
static BundleItemValue create_nested_bundle_item()
{
static const bke::bNodeSocketType *bundle_socket_type = bke::node_socket_type_find_static(
SOCK_BUNDLE);
return {
BundleItemSocketValue{bundle_socket_type, bke::SocketValueVariant::From(Bundle::create())}};
}
void Bundle::add_path_override(const Span<BundleKey> path, const BundleItemValue &value)
{
Bundle *current = this;
for (const BundleKey path_elem : path.drop_back(1)) {
BundleItemValue &item = current->items_.lookup_or_add_cb_as(
path_elem, [&]() { return create_nested_bundle_item(); });
BundlePtr *child_bundle_ptr = item.as_pointer<BundlePtr>();
if (!child_bundle_ptr || !*child_bundle_ptr) {
/* Override the items content with a new bundle. */
item = create_nested_bundle_item();
child_bundle_ptr = item.as_pointer<BundlePtr>();
}
current = &child_bundle_ptr->ensure_mutable_inplace();
}
current->items_.add_overwrite_as(path.last(), value);
}
void Bundle::add_path_override(const StringRef path, const BundleItemValue &value)
{
BLI_assert(is_valid_path(path));
const Vector<BundleKey> path_elems = *split_path(path);
this->add_path_override(path_elems, value);
}
bool Bundle::add_path(StringRef path, const BundleItemValue &value)
{
if (this->contains_path(path)) {
return false;
}
this->add_path_new(path, value);
return true;
}
void Bundle::add_path_new(StringRef path, const BundleItemValue &value)
{
BLI_assert(!this->contains_path(path));
this->add_path_override(path, value);
}
Bundle &Bundle::ensure_nested_bundle(const StringRef path)
{
BundlePtr *bundle_ptr = this->lookup_path_for_write_ptr<BundlePtr>(path);
if (bundle_ptr && *bundle_ptr) {
return bundle_ptr->ensure_mutable_inplace();
}
BundlePtr new_bundle = Bundle::create();
Bundle &new_bundle_ref = new_bundle.ensure_mutable_inplace();
this->add_path_override(path, std::move(new_bundle));
return new_bundle_ref;
}
const BundleItemValue *Bundle::lookup(const BundleKey key) const
{
return items_.lookup_ptr_as(key);
}
BundleItemValue *Bundle::lookup(const BundleKey key)
{
return items_.lookup_ptr_as(key);
}
const BundleItemValue *Bundle::lookup_path(const Span<BundleKey> path) const
{
BLI_assert(!path.is_empty());
const BundleKey first_elem = path[0];
const BundleItemValue *item = this->lookup(first_elem);
if (!item) {
return nullptr;
}
if (path.size() == 1) {
return item;
}
const BundlePtr child_bundle = item->as<BundlePtr>().value_or(nullptr);
if (!child_bundle) {
return nullptr;
}
return child_bundle->lookup_path(path.drop_front(1));
}
const BundleItemValue *Bundle::lookup_path(const StringRef path) const
{
BLI_assert(is_valid_path(path));
const Vector<BundleKey> path_elems = *split_path(path);
return this->lookup_path(path_elems);
}
BundleItemValue *Bundle::lookup_path_for_write(Span<BundleKey> path)
{
BLI_assert(!path.is_empty());
const BundleKey first_elem = path[0];
BundleItemValue *item = this->lookup(first_elem);
if (!item) {
return nullptr;
}
if (path.size() == 1) {
return item;
}
BundlePtr *child_bundle_ptr = item->as_pointer<BundlePtr>();
if (!child_bundle_ptr) {
return nullptr;
}
if (!*child_bundle_ptr) {
return nullptr;
}
Bundle &child_bundle = child_bundle_ptr->ensure_mutable_inplace();
return child_bundle.lookup_path_for_write(path.drop_front(1));
}
BundleItemValue *Bundle::lookup_path_for_write(StringRef path)
{
BLI_assert(is_valid_path(path));
const Vector<BundleKey> path_elems = *split_path(path);
return this->lookup_path_for_write(path_elems);
}
void Bundle::merge(const Bundle &other)
{
for (const auto &item : other.items_.items()) {
this->add(item.key, item.value);
}
}
void Bundle::merge_override(const Bundle &other)
{
for (const auto &item : other.items_.items()) {
this->add_override(item.key, item.value);
}
}
void Bundle::ensure_owns_direct_data()
{
for (const auto &item : items_.items()) {
if (auto *socket_value = std::get_if<BundleItemSocketValue>(&item.value.value)) {
socket_value->value.ensure_owns_direct_data();
}
}
}
bool Bundle::owns_direct_data() const
{
for (const auto &item : items_.items()) {
if (const auto *socket_value = std::get_if<BundleItemSocketValue>(&item.value.value)) {
if (!socket_value->value.owns_direct_data()) {
return false;
}
}
}
return true;
}
BundlePtr Bundle::copy() const
{
BundlePtr copy_ptr = Bundle::create();
Bundle &copy = const_cast<Bundle &>(*copy_ptr);
copy.items_ = items_;
return copy_ptr;
}
bool Bundle::remove(const BundleKey key)
{
return items_.remove_as(key);
}
bool Bundle::remove_path(const StringRef path)
{
BLI_assert(is_valid_path(path));
const Vector<BundleKey> path_elems = *split_path(path);
return this->remove_path(path_elems);
}
bool Bundle::remove_path(const Span<BundleKey> path)
{
BLI_assert(this->is_mutable());
BLI_assert(!path.is_empty());
if (!this->contains_path(path)) {
return false;
}
Bundle *current = this;
for (const BundleKey path_elem : path.drop_back(1)) {
BundleItemValue &item = current->items_.lookup_as(path_elem);
BundlePtr *child_bundle_ptr = item.as_pointer<BundlePtr>();
current = &child_bundle_ptr->ensure_mutable_inplace();
}
current->items_.remove_contained_as(path.last());
return true;
}
bool Bundle::contains(const BundleKey key) const
{
return items_.contains_as(key);
}
bool Bundle::contains_path(const StringRef path) const
{
return this->lookup_path(path) != nullptr;
}
bool Bundle::contains_path(const Span<BundleKey> path) const
{
return this->lookup_path(path) != nullptr;
}
std::string Bundle::combine_path(const Span<StringRef> path)
{
return fmt::format("{}", fmt::join(path, "/"));
}
std::string Bundle::combine_path(const Span<BundleKey> path)
{
return fmt::format("{}", fmt::join(path, "/"));
}
void Bundle::delete_self()
{
MEM_delete(this);
}
void Bundle::clear()
{
items_.clear();
}
std::optional<StringRef> Bundle::type() const
{
const std::string *type = this->lookup_ptr<std::string>(Bundle::type_item_name);
return type ? std::optional<StringRef>(*type) : std::nullopt;
}
void Bundle::count_memory(MemoryCounter &memory) const
{
for (const auto &item : items_.items()) {
if (const auto *socket_value = std::get_if<BundleItemSocketValue>(&item.value.value)) {
socket_value->value.count_memory(memory);
}
}
}
NodeSocketInterfaceStructureType get_structure_type_for_bundle_signature(
const bNodeSocket &socket,
const NodeSocketInterfaceStructureType stored_structure_type,
const bool allow_auto_structure_type)
{
if (stored_structure_type != NodeSocketInterfaceStructureType::Auto) {
return stored_structure_type;
}
if (allow_auto_structure_type) {
return NodeSocketInterfaceStructureType::Auto;
}
return NodeSocketInterfaceStructureType(socket.runtime->inferred_structure_type);
}
void BundleSignature::add(std::string key, const eNodeSocketDatatype socket_type)
{
const bke::bNodeSocketType *stype = bke::node_socket_type_find_static(socket_type);
BLI_assert(stype);
items.add({std::move(key), stype});
}
BundleSignature BundleSignature::from_combine_bundle_node(const bNode &node,
const bool allow_auto_structure_type)
{
BLI_assert(node.is_type("NodeCombineBundle"_ustr));
const auto &storage = *static_cast<const NodeCombineBundle *>(node.storage);
BundleSignature signature;
for (const int i : IndexRange(storage.items_num)) {
const NodeCombineBundleItem &item = storage.items[i];
const bNodeSocket &socket = node.input_socket(i);
if (const bke::bNodeSocketType *stype = bke::node_socket_type_find_static(item.socket_type)) {
const NodeSocketInterfaceStructureType structure_type =
get_structure_type_for_bundle_signature(
socket, item.structure_type, allow_auto_structure_type);
signature.items.add({item.name, stype, structure_type});
}
}
return signature;
}
BundleSignature BundleSignature::from_separate_bundle_node(const bNode &node,
const bool allow_auto_structure_type)
{
BLI_assert(node.is_type("NodeSeparateBundle"_ustr));
const auto &storage = *static_cast<const NodeSeparateBundle *>(node.storage);
BundleSignature signature;
for (const int i : IndexRange(storage.items_num)) {
const NodeSeparateBundleItem &item = storage.items[i];
const bNodeSocket &socket = node.output_socket(i);
if (const bke::bNodeSocketType *stype = bke::node_socket_type_find_static(item.socket_type)) {
const NodeSocketInterfaceStructureType structure_type =
get_structure_type_for_bundle_signature(
socket, item.structure_type, allow_auto_structure_type);
signature.items.add({item.name, stype, structure_type});
}
}
return signature;
}
bool LinkedBundleSignatures::has_type_definition() const
{
for (const Item &item : this->items) {
if (item.is_signature_definition) {
return true;
}
}
return false;
}
std::optional<BundleSignature> LinkedBundleSignatures::get_merged_signature() const
{
BundleSignature signature;
for (const Item &src_signature : this->items) {
for (const BundleSignature::Item &item : src_signature.signature.items) {
if (!signature.items.add(item)) {
const BundleSignature::Item &existing_item = *signature.items.lookup_key_ptr_as(item.key);
if (item.type->type != existing_item.type->type) {
return std::nullopt;
}
if (existing_item.structure_type != item.structure_type) {
const_cast<BundleSignature::Item &>(existing_item).structure_type =
NodeSocketInterfaceStructureType::Dynamic;
}
}
}
}
return signature;
}
static void foreach_nested_bundle_item_recursive(
const Bundle &bundle,
const FunctionRef<void(Span<BundleKey>, const BundleItemValue &value)> fn,
Vector<BundleKey> &r_path)
{
for (const auto &child_item : bundle.items()) {
r_path.append(child_item.key);
BLI_SCOPED_DEFER([&]() { r_path.pop_last(); });
if (const BundlePtr *child_bundle_ptr = child_item.value.as_pointer<BundlePtr>()) {
if (*child_bundle_ptr) {
const Bundle &child_bundle = **child_bundle_ptr;
if (!child_bundle.type().has_value()) {
foreach_nested_bundle_item_recursive(child_bundle, fn, r_path);
continue;
}
}
}
fn(r_path, child_item.value);
}
}
void foreach_nested_bundle_item(
const Bundle &bundle,
const FunctionRef<void(Span<BundleKey>, const BundleItemValue &value)> fn)
{
Vector<BundleKey> path;
foreach_nested_bundle_item_recursive(bundle, fn, path);
}
Vector<std::string> gather_bundle_paths_by_bundle_type(
const Bundle &bundle, const FunctionRef<bool(StringRef type)> type_filter_fn)
{
Vector<std::string> paths;
foreach_nested_bundle_item(
bundle, [&](const Span<BundleKey> path, const BundleItemValue &value) {
if (const BundlePtr *child_bundle_ptr = value.as_pointer<BundlePtr>()) {
if (*child_bundle_ptr) {
if (const std::optional<StringRef> type = (*child_bundle_ptr)->type()) {
if (type_filter_fn(*type)) {
paths.append(Bundle::combine_path(path));
}
}
}
}
});
return paths;
}
Vector<std::string> gather_bundle_paths_by_data_type(const Bundle &bundle,
const eNodeSocketDatatype data_type)
{
Vector<std::string> paths;
foreach_nested_bundle_item(
bundle, [&](const Span<BundleKey> path, const BundleItemValue &value) {
if (const auto *socket_value = std::get_if<BundleItemSocketValue>(&value.value)) {
if (socket_value->type->type == data_type) {
paths.append(Bundle::combine_path(path));
}
}
});
return paths;
}
std::optional<bke::SocketValueVariant> BundleItemValue::as_socket_value(
const bke::bNodeSocketType &dst_socket_type) const
{
const BundleItemSocketValue *socket_value = std::get_if<BundleItemSocketValue>(&this->value);
if (!socket_value) {
return std::nullopt;
}
if (socket_value->type->type == dst_socket_type.type) {
return socket_value->value;
}
if (std::optional<bke::SocketValueVariant> converted_value = implicitly_convert_socket_value(
*socket_value->type, socket_value->value, dst_socket_type))
{
return converted_value;
}
return std::nullopt;
}
} // namespace blender::nodes

View File

@@ -0,0 +1,121 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "testing/testing.h"
#include "BKE_gtest_base.hh"
#include "NOD_geometry_nodes_bundle.hh"
namespace blender::nodes::tests {
class BundleTest : public bke::BlenderGTestBase {};
TEST_F(BundleTest, DefaultBundle)
{
BundlePtr bundle = Bundle::create();
EXPECT_TRUE(bundle);
EXPECT_TRUE(bundle->is_empty());
}
TEST_F(BundleTest, AddItems)
{
BundlePtr bundle_ptr = Bundle::create();
Bundle &bundle = const_cast<Bundle &>(*bundle_ptr);
bundle.add(*BundleKey::from_str("a"), 3);
EXPECT_EQ(bundle.size(), 1);
EXPECT_TRUE(bundle.contains(*BundleKey::from_str("a")));
EXPECT_EQ(bundle.lookup<int>(*BundleKey::from_str("a")), 3);
}
TEST_F(BundleTest, AddLookupPath)
{
BundlePtr bundle_ptr = Bundle::create();
Bundle &bundle = const_cast<Bundle &>(*bundle_ptr);
bundle.add_path("a/b/c", 3);
bundle.add_path("a/b/d", 4);
EXPECT_EQ(bundle.size(), 1);
EXPECT_EQ((*bundle.lookup_path<BundlePtr>("a"))->size(), 1);
EXPECT_EQ((*bundle.lookup_path<BundlePtr>("a/b"))->size(), 2);
EXPECT_EQ(bundle.lookup_path<int>("a/b/c"), 3);
EXPECT_EQ(bundle.lookup_path<int>("a/b/d"), 4);
EXPECT_EQ(bundle.lookup_path<BundlePtr>("a/b/c"), std::nullopt);
EXPECT_EQ(bundle.lookup_path<BundlePtr>("a/b/x"), std::nullopt);
bundle.add_path_override("a/b/c/d", 5);
EXPECT_EQ(bundle.lookup_path<int>("a/b/c/d"), 5);
}
TEST_F(BundleTest, RemovePath)
{
BundlePtr bundle_ptr = Bundle::create();
Bundle &bundle = const_cast<Bundle &>(*bundle_ptr);
bundle.add_path("a/b/c", 3);
bundle.add_path("a/b/d", 4);
EXPECT_FALSE(bundle.remove_path("a/b/x"));
EXPECT_EQ(bundle.lookup_path<int>("a/b/c"), 3);
EXPECT_TRUE(bundle.remove_path("a/b/c"));
EXPECT_EQ(bundle.lookup_path<int>("a/b/c"), std::nullopt);
EXPECT_TRUE((*bundle.lookup_path<BundlePtr>("a/b"))->size() == 1);
bundle.remove_path("a/b");
EXPECT_EQ(bundle.lookup_path<BundlePtr>("a/b"), std::nullopt);
EXPECT_TRUE((*bundle.lookup_path<BundlePtr>("a"))->is_empty());
EXPECT_TRUE(bundle.remove_path("a"));
}
TEST_F(BundleTest, LookupConversion)
{
BundlePtr bundle_ptr = Bundle::create();
Bundle &bundle = const_cast<Bundle &>(*bundle_ptr);
bundle.add_path("a/b", -3.4f);
EXPECT_EQ(bundle.lookup_path<float>("a/b"), -3.4f);
EXPECT_EQ(bundle.lookup_path<int>("a/b"), -3);
EXPECT_EQ(bundle.lookup_path<bool>("a/b"), false);
EXPECT_EQ(bundle.lookup_path<float3>("a/b"), float3(-3.4f));
EXPECT_EQ(bundle.lookup_path<std::string>("a/b"), std::nullopt);
}
TEST_F(BundleTest, AddOverride)
{
BundlePtr bundle_ptr = Bundle::create();
Bundle &bundle = const_cast<Bundle &>(*bundle_ptr);
bundle.add_path("a/b", 4);
EXPECT_EQ(bundle.lookup_path<int>("a/b"), 4);
bundle.add_path_override("a/b", 10);
EXPECT_EQ(bundle.lookup_path<int>("a/b"), 10);
bundle.add_path("a/b", 15);
EXPECT_EQ(bundle.lookup_path<int>("a/b"), 10);
}
TEST_F(BundleTest, EnsureNestedBundle)
{
BundlePtr bundle_ptr = Bundle::create();
Bundle &bundle = bundle_ptr.ensure_mutable_inplace();
Bundle &nested_bundle = bundle.ensure_nested_bundle("a/b/c");
nested_bundle.add(*BundleKey::from_str("test"), 4);
const std::optional<int> value = bundle.lookup_path<int>("a/b/c/test");
EXPECT_EQ(value, 4);
}
TEST_F(BundleTest, LookupPtr)
{
BundlePtr bundle_ptr = Bundle::create();
Bundle &bundle = bundle_ptr.ensure_mutable_inplace();
bundle.add_path("a/b", 3);
EXPECT_EQ(bundle.lookup_path_ptr<int>("a/a"), nullptr);
EXPECT_EQ(*bundle.lookup_path<int>("a/b"), 3);
int *value = bundle.lookup_path_for_write_ptr<int>("a/b");
*value = 10;
EXPECT_EQ(bundle.lookup_path<int>("a/b"), 10);
}
TEST_F(BundleTest, Clear)
{
BundlePtr bundle_ptr = Bundle::create();
Bundle &bundle = bundle_ptr.ensure_mutable_inplace();
bundle.add_path("a/b", 3);
EXPECT_FALSE(bundle.is_empty());
bundle.clear();
EXPECT_TRUE(bundle.is_empty());
}
} // namespace blender::nodes::tests

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,181 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_node_runtime.hh"
#include "NOD_geometry_nodes_bundle_signature.hh"
#include "NOD_geometry_nodes_closure.hh"
namespace blender::nodes {
std::optional<int> ClosureSignature::find_input_index(const StringRef key) const
{
for (const int i : this->inputs.index_range()) {
const Item &item = this->inputs[i];
if (item.key == key) {
return i;
}
}
return std::nullopt;
}
std::optional<int> ClosureSignature::find_output_index(const StringRef key) const
{
for (const int i : this->outputs.index_range()) {
const Item &item = this->outputs[i];
if (item.key == key) {
return i;
}
}
return std::nullopt;
}
void ClosureSignature::set_auto_structure_types()
{
for (const Item &item : this->inputs) {
const_cast<Item &>(item).structure_type = NodeSocketInterfaceStructureType::Auto;
}
for (const Item &item : this->outputs) {
const_cast<Item &>(item).structure_type = NodeSocketInterfaceStructureType::Auto;
}
}
bool operator==(const ClosureSignature &a, const ClosureSignature &b)
{
return a.inputs.as_span() == b.inputs.as_span() && a.outputs.as_span() == b.outputs.as_span();
}
bool operator!=(const ClosureSignature &a, const ClosureSignature &b)
{
return !(a == b);
}
ClosureSignature ClosureSignature::from_closure_output_node(const bNode &node,
const bool allow_auto_structure_type)
{
BLI_assert(node.is_type("NodeClosureOutput"_ustr));
const bNodeTree &tree = node.owner_tree();
const bNode *input_node =
bke::zone_type_by_node_type(node.type_legacy)->get_corresponding_input(tree, node);
const auto &storage = *static_cast<const NodeClosureOutput *>(node.storage);
nodes::ClosureSignature signature;
if (input_node) {
for (const int i : IndexRange(storage.input_items.items_num)) {
const NodeClosureInputItem &item = storage.input_items.items[i];
const bNodeSocket &socket = input_node->output_socket(i);
if (const bke::bNodeSocketType *stype = bke::node_socket_type_find_static(item.socket_type))
{
const NodeSocketInterfaceStructureType structure_type =
get_structure_type_for_bundle_signature(
socket, item.structure_type, allow_auto_structure_type);
signature.inputs.add({item.name, stype, structure_type});
}
}
}
for (const int i : IndexRange(storage.output_items.items_num)) {
const NodeClosureOutputItem &item = storage.output_items.items[i];
const bNodeSocket &socket = node.input_socket(i);
if (const bke::bNodeSocketType *stype = bke::node_socket_type_find_static(item.socket_type)) {
const NodeSocketInterfaceStructureType structure_type =
get_structure_type_for_bundle_signature(
socket, item.structure_type, allow_auto_structure_type);
signature.outputs.add({item.name, stype, structure_type});
}
}
return signature;
}
ClosureSignature ClosureSignature::from_evaluate_closure_node(const bNode &node,
const bool allow_auto_structure_type)
{
BLI_assert(node.is_type("NodeEvaluateClosure"_ustr));
const auto &storage = *static_cast<const NodeEvaluateClosure *>(node.storage);
nodes::ClosureSignature signature;
for (const int i : IndexRange(storage.input_items.items_num)) {
const NodeEvaluateClosureInputItem &item = storage.input_items.items[i];
const bNodeSocket &socket = node.input_socket(i + 1);
if (const bke::bNodeSocketType *stype = bke::node_socket_type_find_static(item.socket_type)) {
const NodeSocketInterfaceStructureType structure_type =
get_structure_type_for_bundle_signature(
socket, item.structure_type, allow_auto_structure_type);
signature.inputs.add({item.name, stype, structure_type});
}
}
for (const int i : IndexRange(storage.output_items.items_num)) {
const NodeEvaluateClosureOutputItem &item = storage.output_items.items[i];
const bNodeSocket &socket = node.output_socket(i);
if (const bke::bNodeSocketType *stype = bke::node_socket_type_find_static(item.socket_type)) {
const NodeSocketInterfaceStructureType structure_type =
get_structure_type_for_bundle_signature(
socket, item.structure_type, allow_auto_structure_type);
signature.outputs.add({item.name, stype, structure_type});
}
}
return signature;
}
ClosureSignature ClosureSignature::from_closure_to_list_node(const bNode &node)
{
BLI_assert(node.is_type("GeometryNodeClosureToList"_ustr));
const auto &storage = *static_cast<const GeometryNodeClosureToList *>(node.storage);
ClosureSignature signature;
signature.inputs.add({.key = "Index",
.type = bke::node_socket_type_find("NodeSocketInt"),
.structure_type = NodeSocketInterfaceStructureType::Single});
for (const int i : IndexRange(storage.items_num)) {
const GeometryNodeClosureToListItem &item = storage.items[i];
const auto type = eNodeSocketDatatype(item.socket_type);
signature.outputs.add(
{.key = item.name,
.type = bke::node_socket_type_find_static(type),
.structure_type = NodeSocketInterfaceStructureType(item.structure_type)});
}
return signature;
}
bool LinkedClosureSignatures::has_type_definition() const
{
for (const Item &item : this->items) {
if (item.define_signature) {
return true;
}
}
return false;
}
std::optional<ClosureSignature> LinkedClosureSignatures::get_merged_signature() const
{
ClosureSignature signature;
for (const Item &src_signature : this->items) {
for (const ClosureSignature::Item &item : src_signature.signature.inputs) {
if (!signature.inputs.add(item)) {
const ClosureSignature::Item &existing_item = *signature.inputs.lookup_key_ptr_as(
item.key);
if (existing_item.type->type != item.type->type) {
return std::nullopt;
}
if (existing_item.structure_type != item.structure_type) {
const_cast<ClosureSignature::Item &>(existing_item).structure_type =
NodeSocketInterfaceStructureType::Dynamic;
}
}
}
for (const ClosureSignature::Item &item : src_signature.signature.outputs) {
if (!signature.outputs.add(item)) {
const ClosureSignature::Item &existing_item = *signature.outputs.lookup_key_ptr_as(
item.key);
if (existing_item.type->type != item.type->type) {
return std::nullopt;
}
if (existing_item.structure_type != item.structure_type) {
const_cast<ClosureSignature::Item &>(existing_item).structure_type =
NodeSocketInterfaceStructureType::Dynamic;
}
}
}
}
return signature;
}
} // namespace blender::nodes

View File

@@ -0,0 +1,907 @@
/* 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_geo_closure.hh"
#include "NOD_geometry_nodes_closure.hh"
#include "NOD_geometry_nodes_values.hh"
#include "DEG_depsgraph_query.hh"
#include "FN_lazy_function_execute.hh"
#include "BLI_string_utf8_symbols.h"
namespace blender::nodes {
using bke::node_tree_reference_lifetimes::ReferenceSetInfo;
using bke::node_tree_reference_lifetimes::ReferenceSetType;
/**
* Evaluating a closure lazy function creates a wrapper lazy function graph around it which handles
* things like type conversion and missing inputs. This side effect provider is used to make sure
* that if the closure itself contains a side-effect node (e.g. a viewer), the wrapper graph will
* also have a side-effect node. Otherwise, the inner side-effect node will not be executed in some
* cases.
*/
class ClosureIntermediateGraphSideEffectProvider : public lf::GraphExecutorSideEffectProvider {
private:
/**
* The node that is wrapped and should be marked as having side effects if the closure
* itself has side effects.
*/
const lf::FunctionNode *body_node_;
public:
ClosureIntermediateGraphSideEffectProvider(const lf::FunctionNode &body_node)
: body_node_(&body_node)
{
}
Vector<const lf::FunctionNode *> get_nodes_with_side_effects(
const lf::Context &context) const override
{
const GeoNodesUserData &user_data = *dynamic_cast<GeoNodesUserData *>(context.user_data);
const ComputeContextHash &context_hash = user_data.compute_context->hash();
if (!user_data.call_data->side_effect_nodes) {
/* There are no requested side effect nodes at all. */
return {};
}
const Span<const lf::FunctionNode *> side_effect_nodes_in_closure =
user_data.call_data->side_effect_nodes->nodes_by_context.lookup(context_hash);
if (side_effect_nodes_in_closure.is_empty()) {
/* The closure does not have any side effect nodes, so the wrapper also does not have any. */
return {};
}
return {body_node_};
}
};
/**
* 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_;
/**
* This is a weak_ptr because otherwise there is a cyclic dependency between the zone and the
* node tree that contains it. The actual reference count is increased when the zone creates a
* closure to be evaluated elsewhere.
*/
std::weak_ptr<const GeometryNodesLazyFunctionGraphInfo> lf_graph_info_;
public:
LazyFunctionForClosureZone(const bNodeTree &btree,
const bke::bNodeTreeZone &zone,
ZoneBuildInfo &zone_info,
const ZoneBodyFunction &body_fn,
std::shared_ptr<GeometryNodesLazyFunctionGraphInfo> &lf_graph_info)
: btree_(btree),
zone_(zone),
output_bnode_(*zone.output_node()),
zone_info_(zone_info),
body_fn_(body_fn),
lf_graph_info_(lf_graph_info)
{
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 NodeClosureOutput *>(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.add({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.add({bsocket.name, bsocket.typeinfo});
}
}
void execute_impl(lf::Params &params, const lf::Context &context) const override
{
auto &user_data = *static_cast<GeoNodesUserData *>(context.user_data);
/* 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);
}
const auto &storage = *static_cast<const NodeClosureOutput *>(output_bnode_.storage);
std::unique_ptr<ResourceScope> closure_scope = std::make_unique<ResourceScope>();
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<bke::SocketValueVariant> default_input_values;
for (const int i : IndexRange(storage.input_items.items_num)) {
const NodeClosureInputItem &item = storage.input_items.items[i];
const bNodeSocket &bsocket = zone_.input_node()->output_socket(i);
lf::GraphInputSocket &lf_graph_input = lf_graph.add_input(
CPPType::get<bke::SocketValueVariant>(), 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);
default_input_values.append(*bsocket.typeinfo->geometry_nodes_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 NodeClosureOutputItem &item = storage.output_items.items[i];
lf::GraphOutputSocket &lf_graph_output = lf_graph.add_output(
CPPType::get<bke::SocketValueVariant>(), 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);
Vector<const bke::SocketValueVariant *> captured_values;
for (const int i : zone_.border_links.index_range()) {
bke::SocketValueVariant *input_ptr = params.try_get_input_data_ptr<bke::SocketValueVariant>(
zone_info_.indices.inputs.border_links[i]);
bke::SocketValueVariant &stored_ptr = closure_scope->construct<bke::SocketValueVariant>(
std::move(*input_ptr));
/* The value is captured here and we need to make sure that it doesn't reference data which
* may become dangling. */
stored_ptr.ensure_owns_direct_data();
captured_values.append(&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);
}
const bNodeTree &btree_orig = *DEG_get_original(&btree_);
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();
/* This is expected to work when the closure is created. */
std::shared_ptr<const GeometryNodesLazyFunctionGraphInfo> lf_graph_info =
lf_graph_info_.lock();
BLI_assert(lf_graph_info);
/* The closure has to take ownership of its execution information. */
closure_scope->add(std::move(lf_graph_info));
const auto &side_effect_provider =
closure_scope->construct<ClosureIntermediateGraphSideEffectProvider>(lf_body_node);
lf::GraphExecutor &lf_graph_executor = closure_scope->construct<lf::GraphExecutor>(
lf_graph, nullptr, &side_effect_provider, nullptr);
ClosureSourceLocation source_location{
&btree_,
output_bnode_.identifier,
user_data.compute_context->hash(),
};
ClosurePtr closure{MEM_new<Closure>(__func__,
closure_signature_,
std::move(closure_scope),
lf_graph_executor,
closure_indices,
std::move(default_input_values),
source_location,
std::make_shared<ClosureEvalLog>(),
std::move(captured_values))};
params.set_output(zone_info_.indices.outputs.main[0],
bke::SocketValueVariant::From(std::move(closure)));
}
};
struct EvaluateClosureEvalStorage {
ResourceScope scope;
ClosurePtr closure;
lf::Graph graph;
std::optional<lf::GraphExecutor> graph_executor;
std::optional<ClosureIntermediateGraphSideEffectProvider> side_effect_provider;
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, CPPType::get<bke::SocketValueVariant>(), 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, CPPType::get<bke::SocketValueVariant>()));
indices_.inputs.output_usages.append(
inputs_.append_and_get_index_as("Usage", CPPType::get<bool>(), lf::ValueUsage::Maybe));
if (bke::node_tree_reference_lifetimes::can_contain_referenced_data(bsocket.type)) {
const int input_i = inputs_.append_and_get_index_as(
"Reference Set",
CPPType::get<bke::GeometryNodesReferenceSet>(),
lf::ValueUsage::Maybe);
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<GeoNodesUserData *>(context.user_data);
auto &eval_storage = *static_cast<EvaluateClosureEvalStorage *>(context.storage);
auto local_user_data = *static_cast<GeoNodesLocalUserData *>(context.local_user_data);
if (!eval_storage.graph_executor) {
eval_storage.closure = params.extract_input<bke::SocketValueVariant>(indices_.inputs.main[0])
.extract<ClosurePtr>();
if (eval_storage.closure) {
if (user_data.is_stack_limit_reached()) {
this->initialize_pass_through_graph(eval_storage);
if (eval_log::NodeTreeLogger *tree_logger = local_user_data.try_get_tree_logger(
user_data))
{
tree_logger->node_warnings.append(
*tree_logger->allocator,
{bnode_.identifier,
{NodeWarningType::Error,
TIP_("Stack limit reached. Closure becomes pass-through.")}});
}
}
else {
this->generate_closure_compatibility_warnings(*eval_storage.closure, context);
this->initialize_execution_graph(eval_storage);
const bNodeTree &btree_orig = *DEG_get_original(&btree_);
ClosureEvalLocation eval_location{
btree_orig.id.session_uid, bnode_.identifier, user_data.compute_context->hash()};
eval_storage.closure->log_evaluation(eval_location);
}
}
else {
/* If no closure is provided, the Evaluate Closure node behaves as if it was muted. So some
* values may be passed through if there are internal links. */
this->initialize_pass_through_graph(eval_storage);
}
}
const std::optional<ClosureSourceLocation> closure_source_location =
eval_storage.closure ? eval_storage.closure->source_location() : std::nullopt;
bke::EvaluateClosureComputeContext closure_compute_context{
user_data.compute_context, bnode_.identifier, &btree_, closure_source_location};
GeoNodesUserData closure_user_data = user_data;
closure_user_data.compute_context = &closure_compute_context;
closure_user_data.verbose_log = should_log_verbose_in_context(user_data,
closure_compute_context.hash());
GeoNodesLocalUserData 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);
}
bool is_recursive_call(const GeoNodesUserData &user_data) const
{
for (const ComputeContext *context = user_data.compute_context; context;
context = context->parent())
{
if (const auto *closure_context = dynamic_cast<const bke::EvaluateClosureComputeContext *>(
context))
{
if (closure_context->node() == &bnode_) {
return true;
}
}
}
return false;
}
void set_default_outputs(lf::Params &params) const
{
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);
}
for (const bNodeSocket *bsocket : bnode_.input_sockets().drop_back(1)) {
params.set_output(indices_.outputs.input_usages[bsocket->index()], false);
}
}
void generate_closure_compatibility_warnings(const Closure &closure,
const lf::Context &context) const
{
const auto &node_storage = *static_cast<const NodeEvaluateClosure *>(bnode_.storage);
const auto &user_data = *static_cast<GeoNodesUserData *>(context.user_data);
const auto &local_user_data = *static_cast<GeoNodesLocalUserData *>(context.local_user_data);
eval_log::NodeTreeLogger *tree_logger = local_user_data.try_get_tree_logger(user_data);
if (tree_logger == nullptr) {
return;
}
const ClosureSignature &signature = closure.signature();
for (const NodeEvaluateClosureInputItem &item :
Span{node_storage.input_items.items, node_storage.input_items.items_num})
{
const bke::bNodeSocketType *item_type = bke::node_socket_type_find_static(item.socket_type);
if (const std::optional<int> i = signature.find_input_index(item.name)) {
const ClosureSignature::Item &closure_item = signature.inputs[*i];
if (!btree_.typeinfo->validate_link(item.socket_type, closure_item.type->type)) {
tree_logger->node_warnings.append(
*tree_logger->allocator,
{bnode_.identifier,
{NodeWarningType::Error,
fmt::format("{}: {} \"{}\" ({} " BLI_STR_UTF8_BLACK_RIGHT_POINTING_SMALL_TRIANGLE
" {})",
TIP_("Conversion not supported when evaluating closure"),
TIP_("Input"),
item.name,
TIP_(item_type->label),
TIP_(closure_item.type->label))}});
}
else if (item.socket_type != closure_item.type->type) {
tree_logger->node_warnings.append(
*tree_logger->allocator,
{bnode_.identifier,
{NodeWarningType::Info,
fmt::format("{}: {} \"{}\" ({} " BLI_STR_UTF8_BLACK_RIGHT_POINTING_SMALL_TRIANGLE
" {})",
TIP_("Implicit type conversion when evaluating closure"),
TIP_("Input"),
item.name,
TIP_(item_type->label),
TIP_(closure_item.type->label))}});
}
}
else {
tree_logger->node_warnings.append(
*tree_logger->allocator,
{bnode_.identifier,
{
NodeWarningType::Error,
fmt::format(fmt::runtime(TIP_("Closure does not have input: \"{}\"")), item.name),
}});
}
}
for (const NodeEvaluateClosureOutputItem &item :
Span{node_storage.output_items.items, node_storage.output_items.items_num})
{
const bke::bNodeSocketType *item_type = bke::node_socket_type_find_static(item.socket_type);
if (const std::optional<int> i = signature.find_output_index(item.name)) {
const ClosureSignature::Item &closure_item = signature.outputs[*i];
if (!btree_.typeinfo->validate_link(closure_item.type->type, item.socket_type)) {
tree_logger->node_warnings.append(
*tree_logger->allocator,
{bnode_.identifier,
{NodeWarningType::Error,
fmt::format("{}: {} \"{}\" ({} " BLI_STR_UTF8_BLACK_RIGHT_POINTING_SMALL_TRIANGLE
" {})",
TIP_("Conversion not supported when evaluating closure"),
TIP_("Output"),
item.name,
TIP_(closure_item.type->label),
TIP_(item_type->label))}});
}
else if (item.socket_type != closure_item.type->type) {
tree_logger->node_warnings.append(
*tree_logger->allocator,
{bnode_.identifier,
{NodeWarningType::Info,
fmt::format("{}: {} \"{}\" ({} " BLI_STR_UTF8_BLACK_RIGHT_POINTING_SMALL_TRIANGLE
" {})",
TIP_("Implicit type conversion when evaluating closure"),
TIP_("Output"),
item.name,
TIP_(closure_item.type->label),
TIP_(item_type->label))}});
}
}
else {
tree_logger->node_warnings.append(
*tree_logger->allocator,
{bnode_.identifier,
{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 NodeEvaluateClosure *>(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(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(
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. */
lf_to.set_default_value(&closure.default_input_value(*mapped_i));
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);
}
}
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. */
lf_main_output.set_default_value(main_output_type.geometry_nodes_default_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 {
lf_main_output.set_default_value(main_output_type.geometry_nodes_default_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;
}
lf_closure_input.set_default_value(&closure.default_input_value(i));
}
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.side_effect_provider.emplace(lf_closure_node);
eval_storage.graph_executor.emplace(
lf_graph, nullptr, &*eval_storage.side_effect_provider, nullptr);
eval_storage.graph_executor_storage = eval_storage.graph_executor->init_storage(
eval_storage.scope.allocator());
/* Log graph for debugging purposes. */
const bNodeTree &btree_orig = *DEG_get_original(&btree_);
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 initialize_pass_through_graph(EvaluateClosureEvalStorage &eval_storage) const
{
const auto &node_storage = *static_cast<const NodeEvaluateClosure *>(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();
for (const int output_item_i : IndexRange(node_storage.output_items.items_num)) {
const bNodeSocket &output_bsocket = bnode_.output_socket(output_item_i);
const bNodeSocket *input_bsocket = evaluate_closure_node_internally_linked_input(
output_bsocket);
lf::GraphOutputSocket &lf_main_output =
*lf_graph_outputs[indices_.outputs.main[output_item_i]];
lf::GraphInputSocket &lf_usage_input =
*lf_graph_inputs[indices_.inputs.output_usages[output_item_i]];
const bke::bNodeSocketType &output_type = *output_bsocket.typeinfo;
if (input_bsocket) {
lf::OutputSocket &lf_main_input =
*lf_graph_inputs[indices_.inputs.main[input_bsocket->index()]];
lf::GraphOutputSocket &lf_usage_output =
*lf_graph_outputs[indices_.outputs.input_usages[input_bsocket->index()]];
const bke::bNodeSocketType &input_type = *input_bsocket->typeinfo;
if (&input_type == &output_type) {
lf_graph.add_link(lf_main_input, lf_main_output);
lf_graph.add_link(lf_usage_input, lf_usage_output);
continue;
}
if (const LazyFunction *conversion_fn = build_implicit_conversion_lazy_function(
input_type, output_type, eval_storage.scope))
{
lf::Node &conversion_node = lf_graph.add_function(*conversion_fn);
lf_graph.add_link(lf_main_input, conversion_node.input(0));
lf_graph.add_link(conversion_node.output(0), lf_main_output);
lf_graph.add_link(lf_usage_input, lf_usage_output);
continue;
}
}
lf_main_output.set_default_value(output_type.geometry_nodes_default_value);
}
static constexpr bool static_false = false;
for (const int usage_i : indices_.outputs.input_usages) {
lf::GraphOutputSocket &lf_usage_output = *lf_graph_outputs[usage_i];
if (!lf_usage_output.origin()) {
lf_usage_output.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.allocator());
}
};
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.allocator();
GeoNodesLocalUserData 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;
bke::SocketValueVariant input_value;
if (std::optional<bke::SocketValueVariant> value = implicitly_convert_socket_value(
from_type, item.value, to_type))
{
input_value = *value;
}
else {
input_value = *to_type.geometry_nodes_default_value;
}
lf_input_values[indices.inputs.main[*mapped_i]] = {
CPPType::get<bke::SocketValueVariant>(),
allocator.construct<bke::SocketValueVariant>(std::move(input_value)).release()};
}
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]) {
bke::SocketValueVariant &value = scope.construct<bke::SocketValueVariant>(
closure.default_input_value(main_input_i));
lf_input_values[lf_input_i] = &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()) {
lf_output_values[indices.outputs.main[main_output_i]] =
allocator.allocate<bke::SocketValueVariant>();
}
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;
if (std::optional<bke::SocketValueVariant> value = implicitly_convert_socket_value(
from_type,
*lf_output_values[indices.outputs.main[*mapped_i]].get<bke::SocketValueVariant>(),
to_type))
{
new (item.value) bke::SocketValueVariant(std::move(*value));
}
else {
new (item.value) bke::SocketValueVariant(*to_type.geometry_nodes_default_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,
std::shared_ptr<GeometryNodesLazyFunctionGraphInfo> &lf_graph_info)
{
return scope.construct<LazyFunctionForClosureZone>(
btree, zone, zone_info, body_fn, lf_graph_info);
}
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

@@ -0,0 +1,713 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup nodes
*/
#include <cfloat>
#include "BLI_listbase.h"
#include "BLI_math_euler.hh"
#include "BLI_string.h"
#include "PRF_profile.hh"
#include "NOD_geometry.hh"
#include "NOD_geometry_nodes_bundle.hh"
#include "NOD_geometry_nodes_execute.hh"
#include "NOD_geometry_nodes_lazy_function.hh"
#include "NOD_geometry_nodes_srna.hh"
#include "NOD_menu_value.hh"
#include "NOD_node_declaration.hh"
#include "NOD_socket.hh"
#include "GEO_foreach_geometry.hh"
#include "BKE_geometry_fields.hh"
#include "BKE_geometry_nodes_reference_set.hh"
#include "BKE_geometry_set.hh"
#include "BKE_idprop.hh"
#include "BKE_lib_id.hh"
#include "BKE_node_enum.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_socket_value.hh"
#include "BKE_node_socket_value_iter.hh"
#include "FN_lazy_function_execute.hh"
#include "DNA_collection_types.h"
#include "DNA_mask_types.h"
#include "DNA_material_types.h"
#include "DNA_scene_types.h"
#include "DNA_sound_types.h"
#include "DNA_text_types.h"
#include "DNA_vfont_types.h"
#include "RNA_access.hh"
#include "UI_resources.hh"
namespace blender {
namespace lf = fn::lazy_function;
namespace nodes {
bool socket_type_has_attribute_toggle(const eNodeSocketDatatype type)
{
return socket_type_supports_attributes(type);
}
bool input_has_attribute_toggle(const bNodeTree &node_tree, const int socket_index)
{
node_tree.ensure_interface_cache();
const bke::bNodeSocketType *typeinfo =
node_tree.interface_inputs()[socket_index]->socket_typeinfo();
if (!typeinfo || !socket_type_has_attribute_toggle(typeinfo->type)) {
return false;
}
BLI_assert(node_tree.runtime->structure_type_interface);
const StructureType structure_type =
node_tree.runtime->structure_type_interface->inputs[socket_index];
return ELEM(structure_type, StructureType::Field, StructureType::Dynamic);
}
template<typename T>
[[nodiscard]] static std::optional<bke::SocketValueVariant> load_attribute_field_input(
PointerRNA &input_props_ptr)
{
const std::string attribute_name = RNA_string_get(&input_props_ptr, "attribute_name");
if (!bke::allow_procedural_attribute_access(attribute_name)) {
return std::nullopt;
}
return bke::SocketValueVariant::From(bke::AttributeFieldInput::from<T>(attribute_name));
}
template<typename T>
static bke::SocketValueVariant load_data_block_input(const GeoNodesCallData *call_data,
PointerRNA &input_props_ptr)
{
PropertyRNA &prop = *RNA_struct_find_property(&input_props_ptr, "value");
if (RNA_property_type(&prop) == PROP_STRING) {
if (!call_data) {
return bke::SocketValueVariant::From(static_cast<T *>(nullptr));
}
BLI_assert(call_data->operator_data);
const std::string name = RNA_string_get(&input_props_ptr, "value");
const ID *id_orig = call_data->operator_data->input_ids->lookup_default(name, nullptr);
if (!id_orig) {
return bke::SocketValueVariant::From(static_cast<T *>(nullptr));
}
const ID *id_eval = call_data->operator_data->depsgraphs->get_evaluated_id(*id_orig);
return bke::SocketValueVariant::From(id_cast<T *>(const_cast<ID *>(id_eval)));
}
BLI_assert(RNA_property_type(&prop) == PROP_POINTER);
T *data_block = id_cast<T *>(RNA_pointer_get(&input_props_ptr, "value").owner_id);
return bke::SocketValueVariant::From(data_block);
}
static GeometryNodesInputType get_effective_input_type(PointerRNA *input_props_ptr,
const bNodeTree &ntree,
const bNodeTreeInterfaceSocket &io_socket)
{
const int input_index = ntree.interface_input_index(io_socket);
if (PropertyRNA *prop = RNA_struct_find_property(input_props_ptr, "type")) {
if (nodes::input_has_attribute_toggle(ntree, input_index)) {
return GeometryNodesInputType(RNA_property_enum_get(input_props_ptr, prop));
}
return GeometryNodesInputType::Value;
}
return GeometryNodesInputType::Fallback;
}
static bke::SocketValueVariant init_socket_cpp_value(const GeoNodesCallData *call_data,
PointerRNA *input_props_ptr,
const bNodeTree &ntree,
const bNodeTreeInterfaceSocket &io_socket)
{
const bke::bNodeSocketType *stype = io_socket.socket_typeinfo();
const eNodeSocketDatatype socket_type = stype->type;
switch (socket_type) {
case SOCK_FLOAT: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
const float value = RNA_float_get(input_props_ptr, "value");
return bke::SocketValueVariant(value);
}
if (type == GeometryNodesInputType::Attribute) {
if (std::optional<bke::SocketValueVariant> value = load_attribute_field_input<float>(
*input_props_ptr))
{
return std::move(*value);
}
}
break;
}
case SOCK_VECTOR: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
/* Vector can have variable length. Use a large enough value to read all components.
* Zero initialize to in case length is below 3. */
float4 value = float4(0.0f);
RNA_float_get_array(input_props_ptr, "value", value);
return bke::SocketValueVariant(float3(value));
}
if (type == GeometryNodesInputType::Attribute) {
if (std::optional<bke::SocketValueVariant> value = load_attribute_field_input<float3>(
*input_props_ptr))
{
return std::move(*value);
}
}
break;
}
case SOCK_RGBA: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
ColorGeometry4f value;
RNA_float_get_array(input_props_ptr, "value", value);
return bke::SocketValueVariant(value);
}
if (type == GeometryNodesInputType::Attribute) {
if (std::optional<bke::SocketValueVariant> value =
load_attribute_field_input<ColorGeometry4f>(*input_props_ptr))
{
return std::move(*value);
}
}
break;
}
case SOCK_BOOLEAN: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
const bool value = RNA_boolean_get(input_props_ptr, "value");
return bke::SocketValueVariant(value);
}
if (type == GeometryNodesInputType::Attribute) {
if (std::optional<bke::SocketValueVariant> value = load_attribute_field_input<bool>(
*input_props_ptr))
{
return std::move(*value);
}
}
if (type == GeometryNodesInputType::Layer) {
const std::string layer_name = RNA_string_get(input_props_ptr, "layer_name");
return bke::SocketValueVariant::From(
fn::GField::from_input<bke::NamedLayerSelectionFieldInput>(layer_name));
}
break;
}
case SOCK_INT: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
const int value = RNA_int_get(input_props_ptr, "value");
return bke::SocketValueVariant(value);
}
if (type == GeometryNodesInputType::Attribute) {
if (std::optional<bke::SocketValueVariant> value = load_attribute_field_input<int>(
*input_props_ptr))
{
return std::move(*value);
}
}
break;
}
case SOCK_ROTATION: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
float3 value_euler;
RNA_float_get_array(input_props_ptr, "value", value_euler);
math::Quaternion value_rotation = math::to_quaternion(math::EulerXYZ(value_euler));
return bke::SocketValueVariant(value_rotation);
}
if (type == GeometryNodesInputType::Attribute) {
if (std::optional<bke::SocketValueVariant> value =
load_attribute_field_input<math::Quaternion>(*input_props_ptr))
{
return std::move(*value);
}
}
break;
}
case SOCK_MENU: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
const int value = RNA_enum_get(input_props_ptr, "value");
return bke::SocketValueVariant::From(MenuValue(value));
}
break;
}
case SOCK_STRING: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
const std::string value = RNA_string_get(input_props_ptr, "value");
return bke::SocketValueVariant(value);
}
break;
}
case SOCK_OBJECT: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
return load_data_block_input<Object>(call_data, *input_props_ptr);
}
break;
}
case SOCK_IMAGE: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
return load_data_block_input<Image>(call_data, *input_props_ptr);
}
break;
}
case SOCK_COLLECTION: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
return load_data_block_input<Collection>(call_data, *input_props_ptr);
}
break;
}
case SOCK_TEXTURE: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
return load_data_block_input<Tex>(call_data, *input_props_ptr);
}
break;
}
case SOCK_MATERIAL: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
return load_data_block_input<Material>(call_data, *input_props_ptr);
}
break;
}
case SOCK_FONT: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
return load_data_block_input<VFont>(call_data, *input_props_ptr);
}
break;
}
case SOCK_SCENE: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
return load_data_block_input<Scene>(call_data, *input_props_ptr);
}
break;
}
case SOCK_TEXT_ID: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
return load_data_block_input<Text>(call_data, *input_props_ptr);
}
break;
}
case SOCK_MASK: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
return load_data_block_input<Mask>(call_data, *input_props_ptr);
}
break;
}
case SOCK_SOUND: {
const GeometryNodesInputType type = get_effective_input_type(
input_props_ptr, ntree, io_socket);
if (type == GeometryNodesInputType::Value) {
return load_data_block_input<bSound>(call_data, *input_props_ptr);
}
break;
}
case SOCK_GEOMETRY:
case SOCK_MATRIX:
case SOCK_BUNDLE:
case SOCK_CLOSURE:
case SOCK_SHADER:
case SOCK_CUSTOM:
case SOCK_INT_VECTOR:
break;
}
return *stype->geometry_nodes_default_value;
}
struct OutputAttributeInfo {
fn::GField field;
std::string name;
};
struct OutputAttributeToStore {
bke::GeometryComponent::Type component_type;
bke::AttrDomain domain;
std::string name;
GMutableSpan data;
};
/**
* The output attributes are organized based on their domain, because attributes on the same domain
* can be evaluated together.
*/
static MultiValueMap<bke::AttrDomain, OutputAttributeInfo> find_output_attributes_to_store(
const bNodeTree &tree, const PointerRNA &properties_ptr, Span<GMutablePointer> output_values)
{
PropertyRNA *outputs_prop = RNA_struct_find_property(const_cast<PointerRNA *>(&properties_ptr),
"outputs");
if (!outputs_prop) {
return {};
}
PointerRNA outputs_ptr = RNA_property_pointer_get(const_cast<PointerRNA *>(&properties_ptr),
outputs_prop);
const bNode &output_node = *tree.group_output_node();
MultiValueMap<bke::AttrDomain, OutputAttributeInfo> outputs_by_domain;
for (const bNodeSocket *socket : output_node.input_sockets().drop_front(1).drop_back(1)) {
if (!socket_type_has_attribute_toggle(socket->type)) {
continue;
}
PointerRNA output_props_ptr = RNA_pointer_get(&outputs_ptr, socket->identifier);
const std::string attribute_name = RNA_string_get(&output_props_ptr, "attribute_name");
if (attribute_name.empty()) {
continue;
}
if (!bke::allow_procedural_attribute_access(attribute_name)) {
continue;
}
const int index = socket->index();
bke::SocketValueVariant &value_variant = *output_values[index].get<bke::SocketValueVariant>();
const fn::GField field = value_variant.get<fn::GField>();
const bNodeTreeInterfaceSocket *interface_socket = tree.interface_outputs()[index];
const bke::AttrDomain domain = bke::AttrDomain(interface_socket->attribute_domain);
OutputAttributeInfo output_info{.field = std::move(field), .name = attribute_name};
outputs_by_domain.add(domain, std::move(output_info));
}
return outputs_by_domain;
}
/**
* The computed values are stored in newly allocated arrays. They still have to be moved to the
* actual geometry.
*/
static Vector<OutputAttributeToStore> compute_attributes_to_store(
const bke::GeometrySet &geometry,
const MultiValueMap<bke::AttrDomain, OutputAttributeInfo> &outputs_by_domain,
const Span<const bke::GeometryComponent::Type> component_types)
{
Vector<OutputAttributeToStore> attributes_to_store;
for (const auto component_type : component_types) {
if (!geometry.has(component_type)) {
continue;
}
const bke::GeometryComponent &component = *geometry.get_component(component_type);
const bke::AttributeAccessor attributes = *component.attributes();
for (const auto item : outputs_by_domain.items()) {
const bke::AttrDomain domain = item.key;
const Span<OutputAttributeInfo> outputs_info = item.value;
if (!attributes.domain_supported(domain)) {
continue;
}
const int domain_size = attributes.domain_size(domain);
bke::GeometryFieldContext field_context{component, domain};
fn::FieldEvaluator field_evaluator{field_context, domain_size};
for (const OutputAttributeInfo &output_info : outputs_info) {
const CPPType &type = output_info.field.cpp_type();
const bke::AttributeValidator validator = attributes.lookup_validator(output_info.name);
OutputAttributeToStore store{
component_type,
domain,
output_info.name,
GMutableSpan{
type,
MEM_new_uninitialized_aligned(type.size * domain_size, type.alignment, __func__),
domain_size}};
fn::GField field = validator.validate_field_if_necessary(output_info.field);
field_evaluator.add_with_destination(std::move(field), store.data);
attributes_to_store.append(store);
}
field_evaluator.evaluate();
}
}
return attributes_to_store;
}
static void remove_anonymous_attributes(bke::GeometrySet &geometry)
{
using namespace bke::socket_value_visitor;
auto has_anonymous_attributes = [&](const bke::AttributeAccessor &attributes) {
return attributes.has_anonymous();
};
auto remove_anonymous_attributes = [&](bke::MutableAttributeAccessor &attributes) {
attributes.remove_anonymous();
};
VisitParams params;
params.check_AttributeAccessor = has_anonymous_attributes;
params.edit_AttributeAccessor = remove_anonymous_attributes;
edit_recursive(geometry, params);
}
static void store_computed_output_attributes(
bke::GeometrySet &geometry, const Span<OutputAttributeToStore> attributes_to_store)
{
for (const OutputAttributeToStore &store : attributes_to_store) {
bke::GeometryComponent &component = geometry.get_component_for_write(store.component_type);
bke::MutableAttributeAccessor attributes = *component.attributes_for_write();
const bke::AttrType data_type = bke::cpp_type_to_attribute_type(store.data.type());
const std::optional<bke::AttributeMetaData> meta_data = attributes.lookup_meta_data(
store.name);
/* Attempt to remove the attribute if it already exists but the domain and type don't match.
* Removing the attribute won't succeed if it is built in and non-removable. */
if (meta_data.has_value() &&
(meta_data->domain != store.domain || meta_data->data_type != data_type))
{
attributes.remove(store.name);
}
/* Try to create the attribute reusing the stored buffer. This will only succeed if the
* attribute didn't exist before, or if it existed but was removed above. */
if (attributes.add(store.name,
store.domain,
bke::cpp_type_to_attribute_type(store.data.type()),
bke::AttributeInitMoveArray(store.data.data())))
{
continue;
}
bke::GAttributeWriter attribute = attributes.lookup_or_add_for_write(
store.name, store.domain, data_type);
if (attribute) {
attribute.varray.set_all(store.data.data());
attribute.finish();
}
/* We were unable to reuse the data, so it must be destructed and freed. */
store.data.type().destruct_n(store.data.data(), store.data.size());
MEM_delete_void(store.data.data());
}
}
static void store_output_attributes(bke::GeometrySet &geometry,
const bNodeTree &tree,
const PointerRNA &properties_ptr,
Span<GMutablePointer> output_values)
{
/* All new attribute values have to be computed before the geometry is actually changed. This is
* necessary because some fields might depend on attributes that are overwritten. */
MultiValueMap<bke::AttrDomain, OutputAttributeInfo> outputs_by_domain =
find_output_attributes_to_store(tree, properties_ptr, output_values);
if (outputs_by_domain.size() == 0) {
return;
}
{
/* Handle top level instances separately first. */
Vector<OutputAttributeToStore> attributes_to_store = compute_attributes_to_store(
geometry, outputs_by_domain, {bke::GeometryComponent::Type::Instance});
store_computed_output_attributes(geometry, attributes_to_store);
}
const bool only_instance_attributes = outputs_by_domain.size() == 1 &&
*outputs_by_domain.keys().begin() ==
bke::AttrDomain::Instance;
if (only_instance_attributes) {
/* No need to call #foreach_real_geometry when only adding attributes to top-level instances.
* This avoids some unnecessary data copies currently if some sub-geometries are not yet owned
* by the geometry set, i.e. they use #GeometryOwnershipType::Editable/ReadOnly. */
return;
}
geometry::foreach_real_geometry(geometry, [&](bke::GeometrySet &instance_geometry) {
/* Instance attributes should only be created for the top-level geometry. */
Vector<OutputAttributeToStore> attributes_to_store = compute_attributes_to_store(
instance_geometry,
outputs_by_domain,
{bke::GeometryComponent::Type::Mesh,
bke::GeometryComponent::Type::PointCloud,
bke::GeometryComponent::Type::Curve});
store_computed_output_attributes(instance_geometry, attributes_to_store);
});
}
bke::GeometrySet execute_geometry_nodes_on_geometry(const bNodeTree &btree,
const PointerRNA &properties_ptr,
const ComputeContext &base_compute_context,
GeoNodesCallData &call_data,
bke::GeometrySet input_geometry)
{
PRF_scope(ProfileCategory::Default);
const GeometryNodesLazyFunctionGraphInfo &lf_graph_info =
*ensure_geometry_nodes_lazy_function_graph(btree);
const GeometryNodesGroupFunction &function = lf_graph_info.function;
const lf::LazyFunction &lazy_function = *function.function;
const int num_inputs = lazy_function.inputs().size();
const int num_outputs = lazy_function.outputs().size();
Array<GMutablePointer> param_inputs(num_inputs);
Array<GMutablePointer> param_outputs(num_outputs);
Array<std::optional<lf::ValueUsage>> param_input_usages(num_inputs);
Array<lf::ValueUsage> param_output_usages(num_outputs);
Array<bool> param_set_outputs(num_outputs, false);
/* We want to evaluate the main outputs, but don't care about which inputs are used for now. */
param_output_usages.as_mutable_span().slice(function.outputs.main).fill(lf::ValueUsage::Used);
param_output_usages.as_mutable_span()
.slice(function.outputs.input_usages)
.fill(lf::ValueUsage::Unused);
call_data.call_depth_limit = U.geometry_nodes_stack_limit;
GeoNodesUserData user_data;
user_data.call_data = &call_data;
call_data.root_ntree = &btree;
user_data.compute_context = &base_compute_context;
ResourceScope scope;
LinearAllocator<> &allocator = scope.allocator();
btree.ensure_interface_cache();
PointerRNA inputs_ptr = RNA_pointer_get(const_cast<PointerRNA *>(&properties_ptr), "inputs");
/* Prepare main inputs. */
for (const int i : btree.interface_inputs().index_range()) {
const bNodeTreeInterfaceSocket &interface_socket = *btree.interface_inputs()[i];
const bke::bNodeSocketType *typeinfo = interface_socket.socket_typeinfo();
const eNodeSocketDatatype socket_type = typeinfo ? typeinfo->type : SOCK_CUSTOM;
if (socket_type == SOCK_GEOMETRY && i == 0) {
bke::SocketValueVariant &value = scope.construct<bke::SocketValueVariant>();
value.set(std::move(input_geometry));
param_inputs[function.inputs.main[0]] = &value;
continue;
}
PointerRNA input_props_ptr = RNA_pointer_get(&inputs_ptr, interface_socket.identifier);
bke::SocketValueVariant value = init_socket_cpp_value(
&call_data, &input_props_ptr, btree, interface_socket);
param_inputs[function.inputs.main[i]] = &scope.construct<bke::SocketValueVariant>(
std::move(value));
}
/* Prepare used-outputs inputs. */
Array<bool> output_used_inputs(btree.interface_outputs().size(), true);
for (const int i : btree.interface_outputs().index_range()) {
param_inputs[function.inputs.output_usages[i]] = &output_used_inputs[i];
}
/* No anonymous attributes have to be propagated. */
Array<bke::GeometryNodesReferenceSet> references_to_propagate(
function.inputs.references_to_propagate.geometry_outputs.size());
for (const int i : references_to_propagate.index_range()) {
param_inputs[function.inputs.references_to_propagate.range[i]] = &references_to_propagate[i];
}
/* Prepare memory for output values. */
for (const int i : IndexRange(num_outputs)) {
const lf::Output &lf_output = lazy_function.outputs()[i];
const CPPType &type = *lf_output.type;
void *buffer = allocator.allocate(type);
param_outputs[i] = {type, buffer};
}
GeoNodesLocalUserData local_user_data(user_data);
lf::Context lf_context(lazy_function.init_storage(allocator), &user_data, &local_user_data);
lf::BasicParams lf_params{lazy_function,
param_inputs,
param_outputs,
param_input_usages,
param_output_usages,
param_set_outputs};
{
ScopedComputeContextTimer timer{lf_context};
lazy_function.execute(lf_params, lf_context);
}
lazy_function.destruct_storage(lf_context.storage);
bke::GeometrySet output_geometry =
param_outputs[0].get<bke::SocketValueVariant>()->extract<bke::GeometrySet>();
store_output_attributes(output_geometry, btree, properties_ptr, param_outputs);
for (const int i : IndexRange(num_outputs)) {
if (param_set_outputs[i]) {
GMutablePointer &ptr = param_outputs[i];
ptr.destruct();
}
}
if (output_geometry.has_bundle()) {
/* Ensure that the bundle data is properly owned by the geometry. Do not call this in the
* geometry itself because it may just be referenced during modifier evaluation and an
* unnecessary copy can be avoided. See #GeometryOwnershipType::Editable. */
output_geometry.bundle_for_write().ensure_owns_direct_data();
}
/* Remove anonymous attributes because their lifetimes can't be tracked reliably outside of
* Geometry Nodes. */
remove_anonymous_attributes(output_geometry);
return output_geometry;
}
Vector<InferenceValue> get_geometry_nodes_input_inference_values(const bNodeTree &btree,
const PointerRNA &properties_ptr,
ResourceScope &scope)
{
/* Assume that all inputs have unknown values by default. */
Vector<InferenceValue> inference_values(btree.interface_inputs().size(),
InferenceValue::Unknown());
PointerRNA inputs_ptr = RNA_pointer_get(const_cast<PointerRNA *>(&properties_ptr), "inputs");
btree.ensure_interface_cache();
for (const int input_i : btree.interface_inputs().index_range()) {
const bNodeTreeInterfaceSocket &io_input = *btree.interface_inputs()[input_i];
const bke::bNodeSocketType *stype = io_input.socket_typeinfo();
if (!stype) {
continue;
}
if (!stype->base_cpp_type || !stype->geometry_nodes_default_value) {
continue;
}
PointerRNA socket_props_ptr = RNA_pointer_get(&inputs_ptr, io_input.identifier);
const GeometryNodesInputType input_type = get_effective_input_type(
&socket_props_ptr, btree, io_input);
if (input_type != GeometryNodesInputType::Value) {
continue;
}
bke::SocketValueVariant &value = scope.add_value(
init_socket_cpp_value(nullptr, &socket_props_ptr, btree, io_input));
if (!value.is_single()) {
continue;
}
const GPointer single_value = value.get_single_ptr();
BLI_assert(single_value.type() == stype->base_cpp_type);
inference_values[input_i] = InferenceValue::from_primitive(single_value.get());
}
return inference_values;
}
} // namespace nodes
} // namespace blender

View File

@@ -0,0 +1,578 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_listbase.h"
#include "BKE_compute_context_cache.hh"
#include "BKE_compute_contexts.hh"
#include "BKE_context.hh"
#include "BKE_node.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_zones.hh"
#include "BKE_object.hh"
#include "BKE_workspace.hh"
#include "NOD_geometry_nodes_gizmos.hh"
#include "NOD_inverse_eval_path.hh"
#include "NOD_partial_eval.hh"
#include "NOD_socket_usage_inference.hh"
#include "DNA_layer_types.h"
#include "DNA_modifier_types.h"
#include "DNA_space_types.h"
#include "DNA_windowmanager_types.h"
#include "ED_node.hh"
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
namespace blender::nodes::gizmos {
bool is_builtin_gizmo_node(const bNode &node)
{
return ELEM(
node.type_legacy, GEO_NODE_GIZMO_LINEAR, GEO_NODE_GIZMO_DIAL, GEO_NODE_GIZMO_TRANSFORM);
}
/**
* Get the part of a socket value that may be edited with gizmos.
*/
static ie::ElemVariant get_gizmo_socket_elem(const bNode &node, const bNodeSocket &socket)
{
switch (node.type_legacy) {
case GEO_NODE_GIZMO_LINEAR: {
return {ie::FloatElem::all()};
}
case GEO_NODE_GIZMO_DIAL: {
return {ie::FloatElem::all()};
}
case GEO_NODE_GIZMO_TRANSFORM: {
const auto &storage = *static_cast<const NodeGeometryTransformGizmo *>(node.storage);
ie::MatrixElem elem;
if (storage.flag & GEO_NODE_TRANSFORM_GIZMO_USE_TRANSLATION_ALL) {
elem.translation = ie::VectorElem::all();
}
if (storage.flag &
(GEO_NODE_TRANSFORM_GIZMO_USE_ROTATION_ALL | GEO_NODE_TRANSFORM_GIZMO_USE_SCALE_ALL))
{
elem.rotation = ie::RotationElem::all();
elem.scale = ie::VectorElem::all();
}
return {elem};
}
}
const eNodeSocketDatatype socket_type = socket.type;
if (std::optional<ie::ElemVariant> elem = ie::get_elem_variant_for_socket_type(socket_type)) {
elem->set_all();
return *elem;
}
BLI_assert_unreachable();
return {};
}
static TreeGizmoPropagation build_tree_gizmo_propagation(bNodeTree &tree)
{
BLI_assert(!tree.has_available_link_cycle());
TreeGizmoPropagation gizmo_propagation;
struct GizmoInput {
const bNodeSocket *gizmo_socket;
/* For multi-input sockets we start propagation at the origin socket. */
const bNodeSocket *propagation_start_socket;
ie::ElemVariant elem;
};
/* Gather all gizmo inputs so that we can find their inverse evaluation targets afterwards. */
Vector<GizmoInput> all_gizmo_inputs;
for (const bNode *node : tree.all_nodes()) {
if (node->is_muted()) {
continue;
}
if (node->is_group()) {
if (!node->id) {
continue;
}
const bNodeTree &group = *reinterpret_cast<const bNodeTree *>(node->id);
if (!group.runtime->gizmo_propagation) {
continue;
}
const TreeGizmoPropagation &group_gizmo_propagation = *group.runtime->gizmo_propagation;
for (const ie::GroupInputElem &group_input_elem :
group_gizmo_propagation.gizmo_inputs_by_group_inputs.keys())
{
const bNodeSocket &input_socket = node->input_socket(group_input_elem.group_input_index);
all_gizmo_inputs.append({&input_socket, &input_socket, group_input_elem.elem});
}
}
if (is_builtin_gizmo_node(*node)) {
gizmo_propagation.gizmo_nodes.append(node);
const bNodeSocket &gizmo_input_socket = node->input_socket(0);
gizmo_propagation.gizmo_endpoint_sockets.add(&gizmo_input_socket);
const ie::ElemVariant elem = get_gizmo_socket_elem(*node, gizmo_input_socket);
for (const bNodeLink *link : gizmo_input_socket.directly_linked_links()) {
if (!link->is_used()) {
continue;
}
all_gizmo_inputs.append({&gizmo_input_socket, link->fromsock, elem});
}
}
}
/* Find the local gizmo targets for all gizmo inputs. */
for (const GizmoInput &gizmo_input : all_gizmo_inputs) {
gizmo_propagation.gizmo_endpoint_sockets.add(gizmo_input.gizmo_socket);
const ie::SocketElem gizmo_input_socket_elem{gizmo_input.gizmo_socket, gizmo_input.elem};
/* The conversion is necessary when e.g. connecting a Rotation directly to the matrix input of
* the Transform Gizmo node. */
const std::optional<ie::ElemVariant> converted_elem = ie::convert_socket_elem(
*gizmo_input.gizmo_socket, *gizmo_input.propagation_start_socket, gizmo_input.elem);
if (!converted_elem) {
continue;
}
const ie::LocalInverseEvalTargets targets = ie::find_local_inverse_eval_targets(
tree, {gizmo_input.propagation_start_socket, *converted_elem});
const bool has_target = !targets.input_sockets.is_empty() ||
!targets.group_inputs.is_empty() || !targets.value_nodes.is_empty();
if (!has_target) {
continue;
}
/* Remember all the gizmo targets for quick lookup later on. */
for (const ie::SocketElem &input_socket : targets.input_sockets) {
gizmo_propagation.gizmo_inputs_by_node_inputs.add(input_socket, gizmo_input_socket_elem);
gizmo_propagation.gizmo_endpoint_sockets.add(input_socket.socket);
}
for (const ie::ValueNodeElem &value_node : targets.value_nodes) {
gizmo_propagation.gizmo_inputs_by_value_nodes.add(value_node, gizmo_input_socket_elem);
gizmo_propagation.gizmo_endpoint_sockets.add(&value_node.node->output_socket(0));
}
for (const ie::GroupInputElem &group_input : targets.group_inputs) {
gizmo_propagation.gizmo_inputs_by_group_inputs.add(group_input, gizmo_input_socket_elem);
for (const bNode *group_input_node : tree.group_input_nodes()) {
gizmo_propagation.gizmo_endpoint_sockets.add(
&group_input_node->output_socket(group_input.group_input_index));
}
}
}
return gizmo_propagation;
}
bool update_tree_gizmo_propagation(bNodeTree &tree)
{
tree.ensure_topology_cache();
if (tree.has_available_link_cycle()) {
const bool changed = tree.runtime->gizmo_propagation != nullptr;
tree.runtime->gizmo_propagation.reset();
return changed;
}
TreeGizmoPropagation new_gizmo_propagation = build_tree_gizmo_propagation(tree);
const bool changed = tree.runtime->gizmo_propagation ?
*tree.runtime->gizmo_propagation != new_gizmo_propagation :
true;
tree.runtime->gizmo_propagation = std::make_unique<TreeGizmoPropagation>(
std::move(new_gizmo_propagation));
return changed;
}
static void foreach_gizmo_for_input(const ie::SocketElem &input_socket,
bke::ComputeContextCache &compute_context_cache,
const ComputeContext *compute_context,
const bNodeTree &tree,
const ForeachGizmoInModifierFn fn);
static void foreach_gizmo_for_group_input(const bNodeTree &tree,
const ie::GroupInputElem &group_input,
bke::ComputeContextCache &compute_context_cache,
const ComputeContext *compute_context,
const ForeachGizmoInModifierFn fn)
{
const TreeGizmoPropagation &gizmo_propagation = *tree.runtime->gizmo_propagation;
for (const ie::SocketElem &gizmo_input :
gizmo_propagation.gizmo_inputs_by_group_inputs.lookup(group_input))
{
foreach_gizmo_for_input(gizmo_input, compute_context_cache, compute_context, tree, fn);
}
}
static void foreach_gizmo_for_input(const ie::SocketElem &input_socket,
bke::ComputeContextCache &compute_context_cache,
const ComputeContext *compute_context,
const bNodeTree &tree,
const ForeachGizmoInModifierFn fn)
{
const bke::bNodeTreeZones *zones = tree.zones();
if (!zones) {
/* There are invalid zones. */
return;
}
const bNode &node = input_socket.socket->owner_node();
if (zones->get_zone_by_node(node.identifier) != nullptr) {
/* Gizmos in zones are not supported yet. */
return;
}
if (is_builtin_gizmo_node(node)) {
if (node.is_muted()) {
return;
}
/* Found an actual built-in gizmo node. */
fn(*compute_context, node, *input_socket.socket);
return;
}
if (node.is_group()) {
const bNodeTree &group = *reinterpret_cast<const bNodeTree *>(node.id);
group.ensure_topology_cache();
const ComputeContext &group_compute_context = compute_context_cache.for_group_node(
compute_context, node.identifier, &tree);
foreach_gizmo_for_group_input(
group,
ie::GroupInputElem{input_socket.socket->index(), input_socket.elem},
compute_context_cache,
&group_compute_context,
fn);
}
}
static void foreach_active_gizmo_in_open_node_editor(
const SpaceNode &snode,
const Object *object_filter,
const NodesModifierData *nmd_filter,
bke::ComputeContextCache &compute_context_cache,
const ForeachGizmoFn fn)
{
if (snode.nodetree == nullptr) {
return;
}
if (snode.edittree == nullptr || !snode.edittree->runtime->gizmo_propagation) {
return;
}
const std::optional<ed::space_node::ObjectAndModifier> object_and_modifier =
ed::space_node::get_modifier_for_node_editor(snode);
if (!object_and_modifier) {
return;
}
if (object_filter) {
if (object_and_modifier->object != object_filter) {
return;
}
}
if (nmd_filter) {
if (object_and_modifier->nmd != nmd_filter) {
return;
}
}
const Object &object = *object_and_modifier->object;
const NodesModifierData &nmd = *object_and_modifier->nmd;
if (!(nmd.modifier.mode & eModifierMode_Realtime)) {
/* Disabled modifiers can't have gizmos currently. */
return;
}
const ComputeContext *current_compute_context = ed::space_node::compute_context_for_edittree(
snode, compute_context_cache);
if (!current_compute_context) {
return;
}
snode.edittree->ensure_topology_cache();
const TreeGizmoPropagation &gizmo_propagation = *snode.edittree->runtime->gizmo_propagation;
Set<ie::SocketElem> used_gizmo_inputs;
/* Check gizmos on value nodes. */
for (auto &&item : gizmo_propagation.gizmo_inputs_by_value_nodes.items()) {
const bNode &node = *item.key.node;
const bNodeSocket &output_socket = node.output_socket(0);
if ((node.flag & NODE_SELECT) || (output_socket.flag & SOCK_GIZMO_PIN)) {
used_gizmo_inputs.add_multiple(item.value);
continue;
}
for (const ie::SocketElem &socket_elem : item.value) {
if (socket_elem.socket->owner_node().flag & NODE_SELECT) {
used_gizmo_inputs.add(socket_elem);
}
}
}
/* Check gizmos on input sockets. */
for (auto &&item : gizmo_propagation.gizmo_inputs_by_node_inputs.items()) {
const bNodeSocket &socket = *item.key.socket;
if (socket.is_inactive()) {
continue;
}
const bNode &node = socket.owner_node();
if ((node.flag & NODE_SELECT) || (socket.flag & SOCK_GIZMO_PIN)) {
used_gizmo_inputs.add_multiple(item.value);
continue;
}
for (const ie::SocketElem &socket_elem : item.value) {
if (socket_elem.socket->owner_node().flag & NODE_SELECT) {
used_gizmo_inputs.add(socket_elem);
}
}
}
/* Check built-in gizmo nodes. */
for (const bNode *gizmo_node : gizmo_propagation.gizmo_nodes) {
if (gizmo_node->is_muted()) {
continue;
}
const bNodeSocket &gizmo_input_socket = gizmo_node->input_socket(0);
if ((gizmo_node->flag & NODE_SELECT) || (gizmo_input_socket.flag & SOCK_GIZMO_PIN)) {
used_gizmo_inputs.add(
{&gizmo_input_socket, *ie::get_elem_variant_for_socket_type(gizmo_input_socket.type)});
}
}
for (const ie::SocketElem &gizmo_input : used_gizmo_inputs) {
foreach_gizmo_for_input(gizmo_input,
compute_context_cache,
current_compute_context,
*snode.edittree,
[&](const ComputeContext &compute_context,
const bNode &gizmo_node,
const bNodeSocket &gizmo_socket) {
fn(object, nmd, compute_context, gizmo_node, gizmo_socket);
});
}
}
static void foreach_active_gizmo_in_open_editors(const wmWindowManager &wm,
const Object *object_filter,
const NodesModifierData *nmd_filter,
bke::ComputeContextCache &compute_context_cache,
const ForeachGizmoFn fn)
{
for (const wmWindow &window : wm.windows) {
const bScreen *active_screen = BKE_workspace_active_screen_get(window.workspace_hook);
Vector<const bScreen *> screens = {active_screen};
if (ELEM(active_screen->state, SCREENMAXIMIZED, SCREENFULL)) {
const ScrArea *area = static_cast<const ScrArea *>(active_screen->areabase.first);
screens.append(area->full);
}
for (const bScreen *screen : screens) {
for (const ScrArea &area : screen->areabase) {
const SpaceLink *sl = static_cast<SpaceLink *>(area.spacedata.first);
if (sl == nullptr) {
continue;
}
if (sl->spacetype != SPACE_NODE) {
continue;
}
const SpaceNode &snode = *reinterpret_cast<const SpaceNode *>(sl);
foreach_active_gizmo_in_open_node_editor(
snode, object_filter, nmd_filter, compute_context_cache, fn);
}
}
}
}
static void foreach_active_gizmo_exposed_to_modifier(
const Object &object,
const NodesModifierData &nmd,
bke::ComputeContextCache &compute_context_cache,
const ForeachGizmoInModifierFn fn)
{
if (!nmd.node_group || ID_MISSING(nmd.node_group)) {
return;
}
const bNodeTree &tree = *nmd.node_group;
if (!tree.runtime->gizmo_propagation) {
return;
}
tree.ensure_interface_cache();
PointerRNA nmd_ptr = RNA_pointer_create_discrete(
const_cast<ID *>(&object.id), RNA_NodesModifier, const_cast<NodesModifierData *>(&nmd));
PointerRNA properties_ptr = RNA_pointer_get(&nmd_ptr, "properties");
ResourceScope scope;
const Vector<InferenceValue> input_values = get_geometry_nodes_input_inference_values(
*nmd.node_group, properties_ptr, scope);
const auto get_input_value = [&](const int group_input_i) {
return input_values[group_input_i];
};
SocketValueInferencer value_inferencer{
*nmd.node_group, scope, compute_context_cache, get_input_value};
socket_usage_inference::SocketUsageInferencer usage_inferencer(
*nmd.node_group, scope, value_inferencer, compute_context_cache);
const ComputeContext &object_context = compute_context_cache.for_data_block(nullptr, object.id);
const ComputeContext &root_compute_context = compute_context_cache.for_modifier(&object_context,
nmd);
for (auto &&item : tree.runtime->gizmo_propagation->gizmo_inputs_by_group_inputs.items()) {
const ie::GroupInputElem &group_input_elem = item.key;
if (item.value.is_empty()) {
continue;
}
if (!usage_inferencer.is_group_input_used(group_input_elem.group_input_index)) {
continue;
}
for (const ie::SocketElem &socket_elem : item.value) {
foreach_gizmo_for_input(socket_elem, compute_context_cache, &root_compute_context, tree, fn);
}
}
}
void foreach_active_gizmo_in_modifier(const Object &object,
const NodesModifierData &nmd,
const wmWindowManager &wm,
bke::ComputeContextCache &compute_context_cache,
const ForeachGizmoInModifierFn fn)
{
if (!nmd.node_group || ID_MISSING(nmd.node_group)) {
return;
}
foreach_active_gizmo_in_open_editors(wm,
&object,
&nmd,
compute_context_cache,
[&](const Object &object_with_gizmo,
const NodesModifierData &nmd_with_gizmo,
const ComputeContext &compute_context,
const bNode &gizmo_node,
const bNodeSocket &gizmo_socket) {
BLI_assert(&object == &object_with_gizmo);
BLI_assert(&nmd == &nmd_with_gizmo);
UNUSED_VARS_NDEBUG(object_with_gizmo, nmd_with_gizmo);
fn(compute_context, gizmo_node, gizmo_socket);
});
foreach_active_gizmo_exposed_to_modifier(object, nmd, compute_context_cache, fn);
}
void foreach_active_gizmo(const bContext &C,
bke::ComputeContextCache &compute_context_cache,
const ForeachGizmoFn fn)
{
const wmWindowManager *wm = CTX_wm_manager(&C);
if (!wm) {
return;
}
foreach_active_gizmo_in_open_editors(*wm, nullptr, nullptr, compute_context_cache, fn);
if (const Base *active_base = CTX_data_active_base(&C)) {
if (!(active_base->flag & BASE_SELECTED)) {
return;
}
Object *active_object = active_base->object;
if (const ModifierData *md = BKE_object_active_modifier(active_object)) {
if (!(md->mode & eModifierMode_Realtime)) {
return;
}
if (md->type == eModifierType_Nodes) {
const NodesModifierData &nmd = *reinterpret_cast<const NodesModifierData *>(md);
foreach_active_gizmo_exposed_to_modifier(
*active_object,
nmd,
compute_context_cache,
[&](const ComputeContext &compute_context,
const bNode &gizmo_node,
const bNodeSocket &gizmo_socket) {
fn(*active_object, nmd, compute_context, gizmo_node, gizmo_socket);
});
}
}
}
}
void foreach_compute_context_on_gizmo_path(const ComputeContext &gizmo_context,
const bNode &gizmo_node,
const bNodeSocket &gizmo_socket,
FunctionRef<void(const ComputeContext &context)> fn)
{
ie::foreach_element_on_inverse_eval_path(
gizmo_context, {&gizmo_socket, get_gizmo_socket_elem(gizmo_node, gizmo_socket)}, fn, {});
}
void foreach_socket_on_gizmo_path(
const ComputeContext &gizmo_context,
const bNode &gizmo_node,
const bNodeSocket &gizmo_socket,
FunctionRef<void(
const ComputeContext &context, const bNodeSocket &socket, const ie::ElemVariant &elem)> fn)
{
ie::foreach_element_on_inverse_eval_path(
gizmo_context, {&gizmo_socket, get_gizmo_socket_elem(gizmo_node, gizmo_socket)}, {}, fn);
}
ie::ElemVariant get_editable_gizmo_elem(const ComputeContext &gizmo_context,
const bNode &gizmo_node,
const bNodeSocket &gizmo_socket)
{
std::optional<ie::ElemVariant> found_elem = ie::get_elem_variant_for_socket_type(
gizmo_socket.type);
BLI_assert(found_elem.has_value());
ie::foreach_element_on_inverse_eval_path(
gizmo_context,
{&gizmo_socket, get_gizmo_socket_elem(gizmo_node, gizmo_socket)},
{},
[&](const ComputeContext &context, const bNodeSocket &socket, const ie::ElemVariant &elem) {
if (context.hash() == gizmo_context.hash() && &socket == &gizmo_socket) {
found_elem->merge(elem);
}
});
return *found_elem;
}
void apply_gizmo_change(
bContext &C,
Object &object,
NodesModifierData &nmd,
eval_log::NodesEvalLog &eval_log,
const ComputeContext &gizmo_context,
const bNodeSocket &gizmo_socket,
const FunctionRef<void(bke::SocketValueVariant &value)> apply_on_gizmo_value_fn)
{
Vector<ie::SocketToUpdate> sockets_to_update;
const bNodeTree &gizmo_node_tree = gizmo_socket.owner_tree();
eval_log::NodeTreeLog &gizmo_tree_log = eval_log.get_tree_log(gizmo_context.hash());
/* Gather all sockets to update together with their new values. */
for (const bNodeLink *link : gizmo_socket.directly_linked_links()) {
gizmo_node_tree.ensure_topology_cache();
if (!link->is_used()) {
continue;
}
if (link->fromnode->is_dangling_reroute()) {
continue;
}
const std::optional<bke::SocketValueVariant> old_value = ie::get_logged_socket_value(
gizmo_tree_log, *link->fromsock);
if (!old_value) {
continue;
}
const std::optional<bke::SocketValueVariant> old_value_converted =
ie::convert_single_socket_value(*link->fromsock, *link->tosock, *old_value);
if (!old_value_converted) {
continue;
}
bke::SocketValueVariant new_value = *old_value_converted;
apply_on_gizmo_value_fn(new_value);
sockets_to_update.append({&gizmo_context, &gizmo_socket, link, new_value});
}
/* Actually backpropagate the socket values. */
ie::backpropagate_socket_values(C, object, nmd, eval_log, sockets_to_update);
}
bool value_node_has_gizmo(const bNodeTree &tree, const bNode &node)
{
BLI_assert(partial_eval::is_supported_value_node(node));
if (!tree.runtime->gizmo_propagation) {
return false;
}
return tree.runtime->gizmo_propagation->gizmo_endpoint_sockets.contains(&node.output_socket(0));
}
} // namespace blender::nodes::gizmos

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,335 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_memory_counter.hh"
#include "NOD_geometry_nodes_bundle.hh"
#include "NOD_geometry_nodes_list.hh"
namespace blender::nodes {
class ArrayImplicitSharingData : public ImplicitSharingInfo {
public:
const CPPType &type;
void *data;
int64_t size;
ArrayImplicitSharingData(void *data, const int64_t size, const CPPType &type)
: ImplicitSharingInfo(), type(type), data(data), size(size)
{
}
private:
void delete_self_with_data() override
{
type.destruct_n(this->data, this->size);
MEM_delete_void(this->data);
MEM_delete(this);
}
};
static ImplicitSharingPtr<> sharing_ptr_for_array(void *data,
const int64_t size,
const CPPType &type)
{
if (type.is_trivially_destructible) {
/* Avoid storing size and type in sharing info if unnecessary. */
return ImplicitSharingPtr<>(implicit_sharing::info_for_mem_free(data));
}
return ImplicitSharingPtr<>(MEM_new<ArrayImplicitSharingData>(__func__, data, size, type));
}
GList::ArrayData GList::ArrayData::ForValue(const GPointer &value, const int64_t size)
{
GList::ArrayData data{};
const CPPType &type = *value.type();
const void *value_ptr = type.default_value();
void *new_data;
/* Prefer `calloc` to zeroing after allocation since it is faster. */
if (memory_is_zero(value_ptr, type.size)) {
new_data = MEM_new_array_zeroed_aligned(size, type.size, type.alignment, __func__);
}
else {
new_data = MEM_new_array_uninitialized_aligned(size, type.size, type.alignment, __func__);
type.fill_construct_n(value_ptr, new_data, size);
}
data.data = new_data;
data.sharing_info = sharing_ptr_for_array(new_data, size, type);
return data;
}
GList::ArrayData GList::ArrayData::ForDefaultValue(const CPPType &type, const int64_t size)
{
return ForValue(GPointer(type, type.default_value()), size);
}
GList::ArrayData GList::ArrayData::ForConstructed(const CPPType &type, const int64_t size)
{
GList::ArrayData data{};
void *new_data = MEM_new_array_uninitialized_aligned(size, type.size, type.alignment, __func__);
type.default_construct_n(new_data, size);
data.data = new_data;
data.sharing_info = sharing_ptr_for_array(new_data, size, type);
return data;
}
GList::ArrayData GList::ArrayData::ForUninitialized(const CPPType &type, const int64_t size)
{
GList::ArrayData data{};
void *new_data = MEM_new_array_uninitialized_aligned(size, type.size, type.alignment, __func__);
data.data = new_data;
data.sharing_info = sharing_ptr_for_array(new_data, size, type);
return data;
}
class SingleImplicitSharingData : public ImplicitSharingInfo {
public:
const CPPType &type;
void *data;
SingleImplicitSharingData(void *data, const CPPType &type)
: ImplicitSharingInfo(), type(type), data(data)
{
}
private:
void delete_self_with_data() override
{
type.destruct(this->data);
MEM_delete(this);
}
};
static ImplicitSharingPtr<> sharing_ptr_for_value(void *data, const CPPType &type)
{
if (type.is_trivially_destructible) {
/* Avoid storing size and type in sharing info if unnecessary. */
return ImplicitSharingPtr<>(implicit_sharing::info_for_mem_free(data));
}
return ImplicitSharingPtr<>(MEM_new<SingleImplicitSharingData>(__func__, data, type));
}
GList::SingleData GList::SingleData::ForValue(const GPointer &value)
{
GList::SingleData data{};
const CPPType &type = *value.type();
void *new_value = MEM_new_uninitialized_aligned(type.size, type.alignment, __func__);
type.copy_construct(value.get(), new_value);
data.value = new_value;
data.sharing_info = sharing_ptr_for_value(new_value, type);
return data;
}
GList::SingleData GList::SingleData::ForDefaultValue(const CPPType &type)
{
return ForValue(GPointer(type, type.default_value()));
}
void GList::delete_self()
{
MEM_delete(this);
}
GListPtr GList::copy() const
{
return GList::create(cpp_type_, data_, size_);
}
GVArray GList::varray() const
{
if (const auto *array_data = std::get_if<ArrayData>(&data_)) {
return GVArray::from_span(GSpan(cpp_type_, array_data->data, size_));
}
if (const auto *single_data = std::get_if<SingleData>(&data_)) {
return GVArray::from_single_ref(cpp_type_, size_, single_data->value);
}
BLI_assert_unreachable();
return {};
}
void GList::count_memory(MemoryCounter &memory) const
{
if (const auto *array_data = std::get_if<ArrayData>(&data_)) {
array_data->count_memory(memory, cpp_type_, size_);
return;
}
if (const auto *single_data = std::get_if<SingleData>(&data_)) {
single_data->count_memory(memory, cpp_type_);
return;
}
}
void GList::ensure_owns_direct_data()
{
if (cpp_type_.is<BundlePtr>()) {
this->typed<BundlePtr>().foreach_for_write([](BundlePtr &bundle_ptr) {
bundle_ptr.ensure_mutable_inplace();
const_cast<Bundle &>(*bundle_ptr).ensure_owns_direct_data();
});
}
else if (cpp_type_.is<bke::SocketValueVariant>()) {
this->typed<bke::SocketValueVariant>().foreach_for_write(
[](bke::SocketValueVariant &value) { value.ensure_owns_direct_data(); });
}
else if (cpp_type_.is<bke::GeometrySet>()) {
this->typed<bke::GeometrySet>().foreach_for_write(
[](bke::GeometrySet &geometry) { geometry.ensure_owns_direct_data(); });
}
}
bool GList::owns_direct_data() const
{
const std::variant<GSpan, GPointer> &values = this->values();
if (cpp_type_.is<BundlePtr>()) {
return std::visit(
[]<typename T>(const T &value) {
if constexpr (std::is_same_v<T, GSpan>) {
const Span span = value.template typed<BundlePtr>();
return std::all_of(span.begin(), span.end(), [](const BundlePtr &bundle_ptr) {
if (!bundle_ptr) {
return false;
}
return bundle_ptr->owns_direct_data();
});
}
else if constexpr (std::is_same_v<T, GPointer>) {
const BundlePtr *value_ptr = value.template get<BundlePtr>();
if (!value_ptr) {
return false;
}
return (*value_ptr)->owns_direct_data();
}
else {
BLI_assert_unreachable_static_t(T);
}
},
values);
}
if (cpp_type_.is<bke::SocketValueVariant>()) {
return std::visit(
[]<typename T>(const T &value) {
if constexpr (std::is_same_v<T, GSpan>) {
const Span span = value.template typed<bke::SocketValueVariant>();
return std::all_of(span.begin(), span.end(), [](const bke::SocketValueVariant &value) {
return value.owns_direct_data();
});
}
else if constexpr (std::is_same_v<T, GPointer>) {
return value.template get<bke::SocketValueVariant>()->owns_direct_data();
}
else {
BLI_assert_unreachable_static_t(T);
}
},
values);
}
if (cpp_type_.is<bke::GeometrySet>()) {
return std::visit(
[]<typename T>(const T &value) {
if constexpr (std::is_same_v<T, GSpan>) {
const Span span = value.template typed<bke::GeometrySet>();
return std::all_of(span.begin(), span.end(), [](const bke::GeometrySet &value) {
return value.owns_direct_data();
});
}
else if constexpr (std::is_same_v<T, GPointer>) {
return value.template get<bke::GeometrySet>()->owns_direct_data();
}
else {
BLI_assert_unreachable_static_t(T);
}
},
values);
}
return true;
}
void GList::ArrayData::count_memory(MemoryCounter &memory,
const CPPType &type,
const int64_t size) const
{
memory.add_shared(this->sharing_info.get(), type.size * size);
}
void GList::SingleData::count_memory(MemoryCounter &memory, const CPPType &type) const
{
memory.add(type.size);
}
GMutableSpan GList::ArrayData::span_for_write(const CPPType &type, int64_t size)
{
if (this->sharing_info && !this->sharing_info->is_mutable()) {
void *new_data = MEM_new_array_uninitialized_aligned(
size, type.size, type.alignment, __func__);
type.copy_construct_n(this->data, new_data, size);
this->data = new_data;
this->sharing_info = sharing_ptr_for_array(new_data, size, type);
}
if (this->sharing_info) {
this->sharing_info->tag_ensured_mutable();
}
return {type, const_cast<void *>(this->data), size};
}
GMutablePointer GList::SingleData::value_for_write(const CPPType &type)
{
if (this->sharing_info && !this->sharing_info->is_mutable()) {
void *new_data = MEM_new_uninitialized_aligned(type.size, type.alignment, __func__);
type.copy_construct(this->value, new_data);
this->value = new_data;
this->sharing_info = sharing_ptr_for_value(new_data, type);
}
if (this->sharing_info) {
this->sharing_info->tag_ensured_mutable();
}
return GMutablePointer{type, const_cast<void *>(this->value)};
}
std::variant<GSpan, GPointer> GList::values() const
{
if (const auto *array_data = std::get_if<ArrayData>(&data_)) {
return GSpan(cpp_type_, array_data->data, size_);
}
if (const auto *single_data = std::get_if<SingleData>(&data_)) {
return GPointer(cpp_type_, single_data->value);
}
BLI_assert_unreachable();
return {};
}
std::variant<GMutableSpan, GMutablePointer> GList::values_for_write()
{
if (auto *array_data = std::get_if<ArrayData>(&data_)) {
return array_data->span_for_write(cpp_type_, size_);
}
if (auto *single_data = std::get_if<SingleData>(&data_)) {
return single_data->value_for_write(cpp_type_);
}
BLI_assert_unreachable();
return {};
}
GList::GList(const CPPType &type, DataVariant data, const int64_t size)
: cpp_type_(type), data_(std::move(data)), size_(size)
{
}
GListPtr GList::create(const CPPType &type, DataVariant data, const int64_t size)
{
return GListPtr(MEM_new<GList>(__func__, type, std::move(data), size));
}
GListPtr GList::from_garray(GArray<> array)
{
auto *sharable_data = new ImplicitSharedValue<GArray<>>(std::move(array));
ArrayData array_data;
array_data.data = sharable_data->data.data();
array_data.sharing_info = ImplicitSharingPtr<>(sharable_data);
return GList::create(
sharable_data->data.type(), std::move(array_data), sharable_data->data.size());
}
} // namespace blender::nodes

View File

@@ -0,0 +1,213 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_bundle_type.hh"
#include "NOD_geometry_nodes_physics_bundles.hh"
#include "NOD_socket_declarations.hh"
#include "NOD_socket_declarations_geometry.hh"
namespace blender::nodes::physics_bundles {
static void add_filter(FlatBundleTypeBuilder &b)
{
b.add<decl::String>("filter"_ustr);
}
const FlatBundleTypePtr &MeshColliderBundle::get_bundle_type()
{
static const FlatBundleTypePtr bundle_type = []() {
FlatBundleTypeBuilder b(MeshColliderBundle::name);
add_filter(b);
b.add<decl::Geometry>("geometry"_ustr);
b.add<decl::Float>("margin"_ustr).min(0.0f).subtype(PROP_DISTANCE);
b.add<decl::Float>("friction"_ustr).min(0.0f);
b.add<decl::Float>("compliance"_ustr).min(0.0f);
b.add<decl::Bool>("deforming"_ustr).default_value(false);
b.add<decl::Bool>("use_edge_contacts"_ustr).default_value(false);
b.add<decl::Bool>("is_boundary"_ustr).default_value(false);
b.add<decl::Float>("error_threshold"_ustr)
.default_value(1e-3f)
.min(1e-5f)
.subtype(PROP_DISTANCE);
const FlatBundleTypePtr bundle_type = b.build();
BundleTypeRegistry::register_type(bundle_type);
return bundle_type;
}();
return bundle_type;
}
const FlatBundleTypePtr &DampingBundle::get_bundle_type()
{
static const FlatBundleTypePtr bundle_type = []() {
FlatBundleTypeBuilder b(DampingBundle::name);
add_filter(b);
b.add<decl::Float>("linear_damping"_ustr).min(0.0f);
b.add<decl::Float>("angular_damping"_ustr).min(0.0f);
const FlatBundleTypePtr bundle_type = b.build();
BundleTypeRegistry::register_type(bundle_type);
return bundle_type;
}();
return bundle_type;
}
const FlatBundleTypePtr &PinPositionBundle::get_bundle_type()
{
static const FlatBundleTypePtr bundle_type = []() {
FlatBundleTypeBuilder b(PinPositionBundle::name);
add_filter(b);
b.add<decl::Bool>("selection"_ustr).default_value(true).structure_type(StructureType::Field);
b.add<decl::Vector>("position"_ustr).structure_type(StructureType::Field);
b.add<decl::Float>("compliance"_ustr).min(0.0f).structure_type(StructureType::Field);
b.add<decl::Float>("error_threshold"_ustr)
.default_value(1e-3f)
.min(1e-5f)
.subtype(PROP_DISTANCE);
b.add<decl::String>("lambda_attribute"_ustr);
const FlatBundleTypePtr bundle_type = b.build();
BundleTypeRegistry::register_type(bundle_type);
return bundle_type;
}();
return bundle_type;
}
const FlatBundleTypePtr &PinRotationBundle::get_bundle_type()
{
static const FlatBundleTypePtr bundle_type = []() {
FlatBundleTypeBuilder b(PinRotationBundle::name);
add_filter(b);
b.add<decl::Bool>("selection"_ustr).default_value(true).structure_type(StructureType::Field);
b.add<decl::Rotation>("rotation"_ustr).structure_type(StructureType::Field);
b.add<decl::Float>("compliance"_ustr).min(0.0f).structure_type(StructureType::Field);
b.add<decl::Float>("error_threshold"_ustr).default_value(1e-2f).min(1e-5f).subtype(PROP_ANGLE);
const FlatBundleTypePtr bundle_type = b.build();
BundleTypeRegistry::register_type(bundle_type);
return bundle_type;
}();
return bundle_type;
}
const FlatBundleTypePtr &CollisionContactsBundle::get_bundle_type()
{
static const FlatBundleTypePtr bundle_type = []() {
FlatBundleTypeBuilder b(CollisionContactsBundle::name);
add_filter(b);
const FlatBundleTypePtr bundle_type = b.build();
BundleTypeRegistry::register_type(bundle_type);
return bundle_type;
}();
return bundle_type;
}
const FlatBundleTypePtr &RodStretchShearBundle::get_bundle_type()
{
static const FlatBundleTypePtr bundle_type = []() {
FlatBundleTypeBuilder b(RodStretchShearBundle::name);
add_filter(b);
b.add<decl::Float>("rest_length"_ustr).min(0.0f).structure_type(StructureType::Field);
b.add<decl::Float>("compliance"_ustr).default_value(1e-4f).min(0.0f);
b.add<decl::Float>("error_threshold"_ustr)
.default_value(1e-3f)
.min(1e-5f)
.subtype(PROP_DISTANCE);
b.add<decl::String>("lambda_position_attribute"_ustr);
b.add<decl::String>("lambda_rotation_attribute"_ustr);
const FlatBundleTypePtr bundle_type = b.build();
BundleTypeRegistry::register_type(bundle_type);
return bundle_type;
}();
return bundle_type;
}
const FlatBundleTypePtr &RodBendTwistBundle::get_bundle_type()
{
static const FlatBundleTypePtr bundle_type = []() {
FlatBundleTypeBuilder b(RodBendTwistBundle::name);
add_filter(b);
b.add<decl::Rotation>("rest_bend_rotation"_ustr).structure_type(StructureType::Field);
b.add<decl::Float>("compliance"_ustr).default_value(1e-4f).min(0.0f);
b.add<decl::Float>("error_threshold"_ustr).default_value(1e-2f).min(1e-5f).subtype(PROP_ANGLE);
const FlatBundleTypePtr bundle_type = b.build();
BundleTypeRegistry::register_type(bundle_type);
return bundle_type;
}();
return bundle_type;
}
const FlatBundleTypePtr &EdgeLengthConstraintBundle::get_bundle_type()
{
static const FlatBundleTypePtr bundle_type = []() {
FlatBundleTypeBuilder b(EdgeLengthConstraintBundle::name);
add_filter(b);
b.add<decl::Float>("rest_length"_ustr).min(0.0f).structure_type(StructureType::Field);
b.add<decl::Float>("compliance"_ustr).default_value(1e-4f).min(0.0f);
b.add<decl::Float>("error_threshold"_ustr)
.default_value(1e-3f)
.min(1e-5f)
.subtype(PROP_DISTANCE);
const FlatBundleTypePtr bundle_type = b.build();
BundleTypeRegistry::register_type(bundle_type);
return bundle_type;
}();
return bundle_type;
}
const FlatBundleTypePtr &CrossEdgeLengthConstraintBundle::get_bundle_type()
{
static const FlatBundleTypePtr bundle_type = []() {
FlatBundleTypeBuilder b(CrossEdgeLengthConstraintBundle::name);
add_filter(b);
b.add<decl::Vector>("rest_position"_ustr).structure_type(StructureType::Field);
b.add<decl::Float>("compliance"_ustr).default_value(1e-4f).min(0.0f);
b.add<decl::Float>("error_threshold"_ustr)
.default_value(1e-3f)
.min(1e-5f)
.subtype(PROP_DISTANCE);
const FlatBundleTypePtr bundle_type = b.build();
BundleTypeRegistry::register_type(bundle_type);
return bundle_type;
}();
return bundle_type;
}
const FlatBundleTypePtr &ForceBundle::get_bundle_type()
{
static const FlatBundleTypePtr bundle_type = []() {
FlatBundleTypeBuilder b(ForceBundle::name);
add_filter(b);
b.add<decl::Closure>("closure"_ustr);
const FlatBundleTypePtr bundle_type = b.build();
BundleTypeRegistry::register_type(bundle_type);
return bundle_type;
}();
return bundle_type;
}
const FlatBundleTypePtr &CustomGeometryEffector::get_bundle_type()
{
static const FlatBundleTypePtr bundle_type = []() {
FlatBundleTypeBuilder b(CustomGeometryEffector::name);
add_filter(b);
b.add<decl::String>("stage"_ustr);
b.add<decl::Closure>("closure"_ustr);
const FlatBundleTypePtr bundle_type = b.build();
BundleTypeRegistry::register_type(bundle_type);
return bundle_type;
}();
return bundle_type;
}
const FlatBundleTypePtr &CustomWorldEffector::get_bundle_type()
{
static const FlatBundleTypePtr bundle_type = []() {
FlatBundleTypeBuilder b(CustomWorldEffector::name);
b.add<decl::String>("stage"_ustr);
b.add<decl::Closure>("closure"_ustr);
const FlatBundleTypePtr bundle_type = b.build();
BundleTypeRegistry::register_type(bundle_type);
return bundle_type;
}();
return bundle_type;
}
} // namespace blender::nodes::physics_bundles

View File

@@ -0,0 +1,426 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_geometry_nodes_lazy_function.hh"
#include "BKE_compute_contexts.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_socket_value.hh"
#include "FN_lazy_function_execute.hh"
#include "BLT_translation.hh"
#include "BLI_array_utils.hh"
#include "DEG_depsgraph_query.hh"
#include "FN_lazy_function_graph_executor.hh"
namespace blender::nodes {
using bke::SocketValueVariant;
/**
* Wraps the execution of a repeat loop body. The purpose is to setup the correct #ComputeContext
* inside of the loop body. This is necessary to support correct logging inside of a repeat zone.
* An alternative would be to use a separate `LazyFunction` for every iteration, but that would
* have higher overhead.
*/
class RepeatBodyNodeExecuteWrapper : public lf::GraphExecutorNodeExecuteWrapper {
public:
const bNode *repeat_output_bnode_ = nullptr;
VectorSet<lf::FunctionNode *> *lf_body_nodes_ = nullptr;
void execute_node(const lf::FunctionNode &node,
lf::Params &params,
const lf::Context &context) const override
{
GeoNodesUserData &user_data = *static_cast<GeoNodesUserData *>(context.user_data);
const int iteration = lf_body_nodes_->index_of_try(const_cast<lf::FunctionNode *>(&node));
const LazyFunction &fn = node.function();
if (iteration == -1) {
/* The node is not a loop body node, just execute it normally. */
fn.execute(params, context);
return;
}
/* Setup context for the loop body evaluation. */
bke::RepeatZoneComputeContext body_compute_context{
user_data.compute_context, *repeat_output_bnode_, iteration};
GeoNodesUserData body_user_data = user_data;
body_user_data.compute_context = &body_compute_context;
body_user_data.verbose_log = should_log_verbose_in_context(user_data,
body_compute_context.hash());
GeoNodesLocalUserData body_local_user_data{body_user_data};
lf::Context body_context{context.storage, &body_user_data, &body_local_user_data};
fn.execute(params, body_context);
}
};
/**
* Knows which iterations of the loop evaluation have side effects.
*/
class RepeatZoneSideEffectProvider : public lf::GraphExecutorSideEffectProvider {
public:
const bNode *repeat_output_bnode_ = nullptr;
Span<lf::FunctionNode *> lf_body_nodes_;
Vector<const lf::FunctionNode *> get_nodes_with_side_effects(
const lf::Context &context) const override
{
GeoNodesUserData &user_data = *static_cast<GeoNodesUserData *>(context.user_data);
const GeoNodesCallData &call_data = *user_data.call_data;
if (!call_data.side_effect_nodes) {
return {};
}
const ComputeContextHash &context_hash = user_data.compute_context->hash();
const Span<int> iterations_with_side_effects =
call_data.side_effect_nodes->iterations_by_iteration_zone.lookup(
{context_hash, repeat_output_bnode_->identifier});
Vector<const lf::FunctionNode *> lf_nodes;
for (const int i : iterations_with_side_effects) {
if (i >= 0 && i < lf_body_nodes_.size()) {
lf_nodes.append(lf_body_nodes_[i]);
}
}
return lf_nodes;
}
};
struct RepeatEvalStorage {
LinearAllocator<> allocator;
VectorSet<lf::FunctionNode *> lf_body_nodes;
lf::Graph graph;
std::optional<LazyFunctionForLogicalOr> or_function;
std::optional<RepeatZoneSideEffectProvider> side_effect_provider;
std::optional<RepeatBodyNodeExecuteWrapper> body_execute_wrapper;
std::optional<lf::GraphExecutor> graph_executor;
Array<SocketValueVariant> index_values;
void *graph_executor_storage = nullptr;
bool multi_threading_enabled = false;
Vector<int> input_index_map;
Vector<int> output_index_map;
};
class LazyFunctionForRepeatZone : public LazyFunction {
private:
const bNodeTree &btree_;
const bke::bNodeTreeZone &zone_;
const bNode &repeat_output_bnode_;
const ZoneBuildInfo &zone_info_;
const ZoneBodyFunction &body_fn_;
public:
LazyFunctionForRepeatZone(const bNodeTree &btree,
const bke::bNodeTreeZone &zone,
ZoneBuildInfo &zone_info,
const ZoneBodyFunction &body_fn)
: btree_(btree),
zone_(zone),
repeat_output_bnode_(*zone.output_node()),
zone_info_(zone_info),
body_fn_(body_fn)
{
debug_name_ = "Repeat Zone";
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;
}
void *init_storage(LinearAllocator<> &allocator) const override
{
return allocator.construct<RepeatEvalStorage>().release();
}
void destruct_storage(void *storage) const override
{
RepeatEvalStorage *s = static_cast<RepeatEvalStorage *>(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, repeat_output_bnode_};
auto &user_data = *static_cast<GeoNodesUserData *>(context.user_data);
auto &local_user_data = *static_cast<GeoNodesLocalUserData *>(context.local_user_data);
const NodeGeometryRepeatOutput &node_storage = *static_cast<const NodeGeometryRepeatOutput *>(
repeat_output_bnode_.storage);
RepeatEvalStorage &eval_storage = *static_cast<RepeatEvalStorage *>(context.storage);
const int iterations_usage_index = zone_info_.indices.outputs.input_usages[0];
if (!params.output_was_set(iterations_usage_index)) {
/* The iterations input is always used. */
params.set_output(iterations_usage_index, true);
}
if (!eval_storage.graph_executor) {
/* Create the execution graph in the first evaluation. */
this->initialize_execution_graph(
params, eval_storage, node_storage, user_data, local_user_data);
}
/* Execute the graph for the repeat zone. */
lf::RemappedParams eval_graph_params{*eval_storage.graph_executor,
params,
eval_storage.input_index_map,
eval_storage.output_index_map,
eval_storage.multi_threading_enabled};
lf::Context eval_graph_context{
eval_storage.graph_executor_storage, context.user_data, context.local_user_data};
eval_storage.graph_executor->execute(eval_graph_params, eval_graph_context);
}
/**
* Generate a lazy-function graph that contains the loop body (`body_fn_`) as many times
* as there are iterations. Since this graph depends on the number of iterations, it can't be
* reused in general. We could consider caching a version of this graph per number of iterations,
* but right now that doesn't seem worth it. In practice, it takes much less time to create the
* graph than to execute it (for intended use cases of this generic implementation, more special
* case repeat loop evaluations could be implemented separately).
*/
void initialize_execution_graph(lf::Params &params,
RepeatEvalStorage &eval_storage,
const NodeGeometryRepeatOutput &node_storage,
GeoNodesUserData &user_data,
GeoNodesLocalUserData &local_user_data) const
{
const int num_repeat_items = node_storage.items_num;
const int num_border_links = body_fn_.indices.inputs.border_links.size();
/* Number of iterations to evaluate. */
const int iterations = std::max<int>(
0, params.get_input<SocketValueVariant>(zone_info_.indices.inputs.main[0]).get<int>());
if (iterations >= 10) {
/* Constructing and running the repeat zone has some overhead so that it's probably worth
* trying to do something else in the meantime already. */
lazy_threading::send_hint();
}
/* Show a warning when the inspection index is out of range. */
if (node_storage.inspection_index > 0) {
if (node_storage.inspection_index >= iterations) {
if (eval_log::NodeTreeLogger *tree_logger = local_user_data.try_get_tree_logger(user_data))
{
tree_logger->node_warnings.append(
*tree_logger->allocator,
{repeat_output_bnode_.identifier,
{NodeWarningType::Info, N_("Inspection index is out of range")}});
}
}
}
/* Take iterations input into account. */
const int main_inputs_offset = 1;
const int body_inputs_offset = 1;
lf::Graph &lf_graph = eval_storage.graph;
Vector<lf::GraphInputSocket *> lf_inputs;
Vector<lf::GraphOutputSocket *> lf_outputs;
for (const int i : inputs_.index_range()) {
const lf::Input &input = inputs_[i];
lf_inputs.append(&lf_graph.add_input(*input.type, this->input_name(i)));
}
for (const int i : outputs_.index_range()) {
const lf::Output &output = outputs_[i];
lf_outputs.append(&lf_graph.add_output(*output.type, this->output_name(i)));
}
/* Create body nodes. */
VectorSet<lf::FunctionNode *> &lf_body_nodes = eval_storage.lf_body_nodes;
for ([[maybe_unused]] const int i : IndexRange(iterations)) {
lf::FunctionNode &lf_node = lf_graph.add_function(*body_fn_.function);
lf_body_nodes.add_new(&lf_node);
}
/* Create nodes for combining border link usages. A border link is used when any of the loop
* bodies uses the border link, so an "or" node is necessary. */
Array<lf::FunctionNode *> lf_border_link_usage_or_nodes(num_border_links);
eval_storage.or_function.emplace(iterations);
for (const int i : IndexRange(num_border_links)) {
lf::FunctionNode &lf_node = lf_graph.add_function(*eval_storage.or_function);
lf_border_link_usage_or_nodes[i] = &lf_node;
}
const bool use_index_values = zone_.input_node()->output_socket(0).is_directly_linked();
if (use_index_values) {
eval_storage.index_values.reinitialize(iterations);
threading::parallel_for(IndexRange(iterations), 1024, [&](const IndexRange range) {
for (const int i : range) {
eval_storage.index_values[i].set(i);
}
});
}
/* Handle body nodes one by one. */
static const SocketValueVariant static_unused_index{-1};
for (const int iter_i : lf_body_nodes.index_range()) {
lf::FunctionNode &lf_node = *lf_body_nodes[iter_i];
const SocketValueVariant *index_value = use_index_values ?
&eval_storage.index_values[iter_i] :
&static_unused_index;
lf_node.input(body_fn_.indices.inputs.main[0]).set_default_value(index_value);
for (const int i : IndexRange(num_border_links)) {
lf_graph.add_link(*lf_inputs[zone_info_.indices.inputs.border_links[i]],
lf_node.input(body_fn_.indices.inputs.border_links[i]));
lf_graph.add_link(lf_node.output(body_fn_.indices.outputs.border_link_usages[i]),
lf_border_link_usage_or_nodes[i]->input(iter_i));
}
/* Handle reference sets. */
for (const auto &item : body_fn_.indices.inputs.reference_sets.items()) {
lf_graph.add_link(*lf_inputs[zone_info_.indices.inputs.reference_sets.lookup(item.key)],
lf_node.input(item.value));
}
}
static bool static_true = true;
/* Handle body nodes pair-wise. */
for (const int iter_i : lf_body_nodes.index_range().drop_back(1)) {
lf::FunctionNode &lf_node = *lf_body_nodes[iter_i];
lf::FunctionNode &lf_next_node = *lf_body_nodes[iter_i + 1];
for (const int i : IndexRange(num_repeat_items)) {
lf_graph.add_link(
lf_node.output(body_fn_.indices.outputs.main[i]),
lf_next_node.input(body_fn_.indices.inputs.main[i + body_inputs_offset]));
/* TODO: Add back-link after being able to check for cyclic dependencies. */
// lf_graph.add_link(lf_next_node.output(body_fn_.indices.outputs.input_usages[i]),
// lf_node.input(body_fn_.indices.inputs.output_usages[i]));
lf_node.input(body_fn_.indices.inputs.output_usages[i]).set_default_value(&static_true);
}
}
/* Handle border link usage outputs. */
for (const int i : IndexRange(num_border_links)) {
lf_graph.add_link(lf_border_link_usage_or_nodes[i]->output(0),
*lf_outputs[zone_info_.indices.outputs.border_link_usages[i]]);
}
if (iterations > 0) {
{
/* Link first body node to input/output nodes. */
lf::FunctionNode &lf_first_body_node = *lf_body_nodes[0];
for (const int i : IndexRange(num_repeat_items)) {
lf_graph.add_link(
*lf_inputs[zone_info_.indices.inputs.main[i + main_inputs_offset]],
lf_first_body_node.input(body_fn_.indices.inputs.main[i + body_inputs_offset]));
lf_graph.add_link(
lf_first_body_node.output(
body_fn_.indices.outputs.input_usages[i + body_inputs_offset]),
*lf_outputs[zone_info_.indices.outputs.input_usages[i + main_inputs_offset]]);
}
}
{
/* Link last body node to input/output nodes. */
lf::FunctionNode &lf_last_body_node = *lf_body_nodes.as_span().last();
for (const int i : IndexRange(num_repeat_items)) {
lf_graph.add_link(lf_last_body_node.output(body_fn_.indices.outputs.main[i]),
*lf_outputs[zone_info_.indices.outputs.main[i]]);
lf_graph.add_link(*lf_inputs[zone_info_.indices.inputs.output_usages[i]],
lf_last_body_node.input(body_fn_.indices.inputs.output_usages[i]));
}
}
}
else {
/* There are no iterations, just link the input directly to the output. */
for (const int i : IndexRange(num_repeat_items)) {
lf_graph.add_link(*lf_inputs[zone_info_.indices.inputs.main[i + main_inputs_offset]],
*lf_outputs[zone_info_.indices.outputs.main[i]]);
lf_graph.add_link(
*lf_inputs[zone_info_.indices.inputs.output_usages[i]],
*lf_outputs[zone_info_.indices.outputs.input_usages[i + main_inputs_offset]]);
}
for (const int i : IndexRange(num_border_links)) {
static bool static_false = false;
lf_outputs[zone_info_.indices.outputs.border_link_usages[i]]->set_default_value(
&static_false);
}
}
lf_outputs[zone_info_.indices.outputs.input_usages[0]]->set_default_value(&static_true);
/* The graph is ready, update the node indices which are required by the executor. */
lf_graph.update_node_indices();
// std::cout << "\n\n" << lf_graph.to_dot() << "\n\n";
/* Create a mapping from parameter indices inside of this graph to parameters of the repeat
* zone. The main complexity below stems from the fact that the iterations input is handled
* outside of this graph. */
eval_storage.output_index_map.reinitialize(outputs_.size() - 1);
eval_storage.input_index_map.resize(inputs_.size() - 1);
array_utils::fill_index_range<int>(eval_storage.input_index_map, 1);
Vector<const lf::GraphInputSocket *> lf_graph_inputs = lf_inputs.as_span().drop_front(1);
const int iteration_usage_index = zone_info_.indices.outputs.input_usages[0];
array_utils::fill_index_range<int>(
eval_storage.output_index_map.as_mutable_span().take_front(iteration_usage_index));
array_utils::fill_index_range<int>(
eval_storage.output_index_map.as_mutable_span().drop_front(iteration_usage_index),
iteration_usage_index + 1);
Vector<const lf::GraphOutputSocket *> lf_graph_outputs = lf_outputs.as_span().take_front(
iteration_usage_index);
lf_graph_outputs.extend(lf_outputs.as_span().drop_front(iteration_usage_index + 1));
eval_storage.body_execute_wrapper.emplace();
eval_storage.body_execute_wrapper->repeat_output_bnode_ = &repeat_output_bnode_;
eval_storage.body_execute_wrapper->lf_body_nodes_ = &lf_body_nodes;
eval_storage.side_effect_provider.emplace();
eval_storage.side_effect_provider->repeat_output_bnode_ = &repeat_output_bnode_;
eval_storage.side_effect_provider->lf_body_nodes_ = lf_body_nodes;
eval_storage.graph_executor.emplace(lf_graph,
std::move(lf_graph_inputs),
std::move(lf_graph_outputs),
nullptr,
&*eval_storage.side_effect_provider,
&*eval_storage.body_execute_wrapper);
eval_storage.graph_executor_storage = eval_storage.graph_executor->init_storage(
eval_storage.allocator);
/* Log graph for debugging purposes. */
const bNodeTree &btree_orig = *DEG_get_original(&btree_);
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(
repeat_output_bnode_.identifier, [&]() { return lf_graph.to_dot(); });
}
}
std::string input_name(const int i) const override
{
return zone_wrapper_input_name(zone_info_, zone_, inputs_, i);
}
std::string output_name(const int i) const override
{
return zone_wrapper_output_name(zone_info_, zone_, outputs_, i);
}
};
LazyFunction &build_repeat_zone_lazy_function(ResourceScope &scope,
const bNodeTree &btree,
const bke::bNodeTreeZone &zone,
ZoneBuildInfo &zone_info,
const ZoneBodyFunction &body_fn)
{
return scope.construct<LazyFunctionForRepeatZone>(btree, zone, zone_info, body_fn);
}
} // namespace blender::nodes

View File

@@ -0,0 +1,227 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <fmt/format.h>
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "NOD_geometry_nodes_srna.hh"
#include "NOD_socket.hh"
#include "DNA_modifier_types.h"
#include "DNA_node_types.h"
#include "BKE_idprop.hh"
#include "BKE_node_runtime.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_prototypes.hh"
namespace blender::nodes {
static constexpr EnumPropertyItem input_type_item_fallback = {
int(GeometryNodesInputType::Fallback), "FALLBACK", 0, "Fallback", "Fallback"};
static constexpr EnumPropertyItem input_type_item_value = {
int(GeometryNodesInputType::Value), "VALUE", 0, "Value", "Pass a single value"};
static constexpr EnumPropertyItem input_type_item_attribute = {
int(GeometryNodesInputType::Attribute), "ATTRIBUTE", 0, "Attribute", "Pass an attribute"};
static constexpr EnumPropertyItem input_type_item_layer = {
int(GeometryNodesInputType::Layer), "LAYER", 0, "Layer", "Pass a layer selection"};
const EnumPropertyItem geometry_nodes_input_type_items_fallback[] = {
input_type_item_fallback,
{0},
};
const EnumPropertyItem geometry_nodes_input_type_items_value[] = {
input_type_item_value,
{0},
};
const EnumPropertyItem geometry_nodes_input_type_items_value_or_attribute[] = {
input_type_item_value,
input_type_item_attribute,
{0},
};
const EnumPropertyItem geometry_nodes_input_type_items_value_or_attribute_or_layer[] = {
input_type_item_value,
input_type_item_attribute,
input_type_item_layer,
{0},
};
static const ModifierData *find_modifier_data_from_system_property(const PointerRNA *ptr)
{
for (const AncestorPointerRNA &ancestor : ptr->ancestors) {
if (RNA_struct_is_a(ancestor.type, RNA_Modifier)) {
return static_cast<const ModifierData *>(ancestor.data);
}
}
const Object *object = id_cast<const Object *>(ptr->owner_id);
for (const ModifierData &md : object->modifiers) {
bool found = false;
IDP_foreach_property(md.system_properties, 0, [&](IDProperty *id_prop) {
if (id_prop == ptr->data) {
found = true;
}
});
if (found) {
return &md;
}
}
return nullptr;
}
static std::optional<std::string> rna_NodesModifierPropertyInput_path(const PointerRNA *ptr)
{
StructRNA *srna = ptr->type;
const char *identifier = RNA_struct_identifier(srna);
const ModifierData *md = find_modifier_data_from_system_property(ptr);
std::string name_esc = BLI_str_escape(md->name);
return fmt::format("modifiers[\"{}\"].properties.inputs.{}", name_esc, identifier);
}
static StructRNA *get_input_socket_struct_rna(const bNodeTree &tree,
const bNodeTreeInterfaceSocket &socket,
GeneratedTreeSrnaData &r_generated)
{
const bke::bNodeSocketType *stype = socket.socket_typeinfo();
if (!stype) {
return nullptr;
}
// TODO: Does this actually need to copy the string?
const StringRefNull srna_identifier = r_generated.scope.allocator().copy_string(
socket.identifier);
StructRNA *srna = RNA_def_struct_ptr(
r_generated.generated_rna, srna_identifier.c_str(), RNA_PropertyGroup);
RNA_def_struct_path_func_runtime(srna, rna_NodesModifierPropertyInput_path);
if (stype->make_geometry_nodes_input_srna) {
stype->make_geometry_nodes_input_srna(tree, *srna, socket, r_generated);
}
return srna;
}
static StructRNA *create_inputs_srna(const bNodeTree &tree, GeneratedTreeSrnaData &r_generated)
{
StructRNA *srna = RNA_def_struct_ptr(
r_generated.generated_rna, "GeometryNodesInterfaceInputs", RNA_PropertyGroup);
for (const bNodeTreeInterfaceSocket *socket : tree.interface_inputs()) {
StructRNA *socket_srna = get_input_socket_struct_rna(tree, *socket, r_generated);
if (!socket_srna) {
continue;
}
const StringRefNull identifier = r_generated.scope.allocator().copy_string(socket->identifier);
PropertyRNA *prop = RNA_def_pointer_runtime(
srna, identifier.c_str(), socket_srna, socket->name, "");
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
}
return srna;
}
static std::optional<std::string> rna_NodesModifierPropertyOutput_path(const PointerRNA *ptr)
{
StructRNA *srna = ptr->type;
const char *identifier = RNA_struct_identifier(srna);
const ModifierData *md = find_modifier_data_from_system_property(ptr);
std::string name_esc = BLI_str_escape(md->name);
return fmt::format("modifiers[\"{}\"].properties.outputs.{}", name_esc, identifier);
}
static StructRNA *create_outputs_srna(const bNodeTree &tree, GeneratedTreeSrnaData &r_generated)
{
StructRNA *srna = RNA_def_struct_ptr(
r_generated.generated_rna, "GeometryNodesInterfaceOutputs", RNA_PropertyGroup);
LinearAllocator<> &allocator = r_generated.scope.allocator();
for (const bNodeTreeInterfaceSocket *output : tree.interface_outputs()) {
const bke::bNodeSocketType *socket_type = bke::node_socket_type_find(output->socket_type);
if (!nodes::socket_type_supports_attributes(socket_type->type)) {
continue;
}
const StringRefNull identifier = allocator.copy_string(output->identifier);
const StringRefNull name = allocator.copy_string(output->name);
const StringRefNull description = allocator.copy_string(output->description);
const StringRefNull default_value = allocator.copy_string(output->default_attribute_name);
StructRNA *output_srna = RNA_def_struct_ptr(
r_generated.generated_rna, identifier.c_str(), RNA_PropertyGroup);
RNA_def_struct_path_func_runtime(output_srna, rna_NodesModifierPropertyOutput_path);
PropertyRNA *prop = RNA_def_string(output_srna,
"attribute_name",
default_value.is_empty() ? nullptr : default_value.c_str(),
0,
name.c_str(),
description.c_str());
RNA_def_property_flag(prop, PROP_FORCE_GEOMETRY_EVAL);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
prop = RNA_def_pointer_runtime(srna, identifier.c_str(), output_srna, name.c_str(), "");
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
}
return srna;
}
static StructRNA *create_panels_srna(const bNodeTree &tree, GeneratedTreeSrnaData &r_generated)
{
StructRNA *srna = RNA_def_struct_ptr(
r_generated.generated_rna, "GeometryNodesInterfacePanels", RNA_PropertyGroup);
LinearAllocator<> &allocator = r_generated.scope.allocator();
tree.ensure_interface_cache();
for (const bNodeTreeInterfaceItem *item : tree.interface_items()) {
if (item->item_type != NodeTreeInterfaceItemType::Panel) {
continue;
}
const auto &panel = *reinterpret_cast<const bNodeTreeInterfacePanel *>(item);
const StringRefNull identifier = allocator.copy_string(
fmt::format("open_{}", panel.identifier));
PropertyRNA *prop = RNA_def_boolean(srna,
identifier.c_str(),
!(panel.flag & NODE_INTERFACE_PANEL_DEFAULT_CLOSED),
"Is Open",
"");
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
}
return srna;
}
std::shared_ptr<GeneratedTreeSrnaData> create_geometry_nodes_rna_for_modifier(
const bNodeTree &tree)
{
auto generated = std::make_unique<GeneratedTreeSrnaData>();
tree.ensure_interface_cache();
StructRNA *srna = RNA_def_struct_ptr(
generated->generated_rna, "GeometryNodesModifierInterface", RNA_NodesModifierProperties);
generated->properties_struct = srna;
StructRNA *inputs_srna = create_inputs_srna(tree, *generated);
StructRNA *outputs_srna = create_outputs_srna(tree, *generated);
StructRNA *panels_srna = create_panels_srna(tree, *generated);
PropertyRNA *prop;
prop = RNA_def_pointer_runtime(
srna, "inputs", inputs_srna, "Inputs", "Settings for input sockets");
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
prop = RNA_def_pointer_runtime(
srna, "outputs", outputs_srna, "Outputs", "Settings for output sockets");
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
prop = RNA_def_pointer_runtime(srna, "panels", panels_srna, "Panels", "Settings for panels");
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
return generated;
}
} // namespace blender::nodes

View File

@@ -0,0 +1,53 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLT_translation.hh"
#include "NOD_geometry_nodes_warning.hh"
#include "RNA_access.hh"
#include "RNA_enum_types.hh"
#include "UI_resources.hh"
namespace blender::nodes {
int node_warning_type_icon(const NodeWarningType type)
{
switch (type) {
case NodeWarningType::Error:
return ICON_CANCEL;
case NodeWarningType::Warning:
return ICON_ERROR;
case NodeWarningType::Info:
return ICON_INFO;
}
BLI_assert_unreachable();
return ICON_ERROR;
}
int node_warning_type_severity(const NodeWarningType type)
{
switch (type) {
case NodeWarningType::Error:
return 3;
case NodeWarningType::Warning:
return 2;
case NodeWarningType::Info:
return 1;
}
BLI_assert_unreachable();
return 0;
}
StringRefNull node_warning_type_name(const NodeWarningType type)
{
const char *name = nullptr;
RNA_enum_name_gettexted(
rna_enum_node_warning_type_items, int(type), BLT_I18NCONTEXT_DEFAULT, &name);
BLI_assert(name);
return name;
}
} // namespace blender::nodes

View File

@@ -0,0 +1,809 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <fmt/format.h>
#include "NOD_inverse_eval_params.hh"
#include "NOD_inverse_eval_path.hh"
#include "NOD_inverse_eval_run.hh"
#include "NOD_node_in_compute_context.hh"
#include "NOD_partial_eval.hh"
#include "NOD_value_elem_eval.hh"
#include "BKE_compute_contexts.hh"
#include "BKE_context.hh"
#include "BKE_library.hh"
#include "BKE_modifier.hh"
#include "BKE_node.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_type_conversions.hh"
#include "BLI_map.hh"
#include "BLI_math_euler.hh"
#include "BLI_set.hh"
#include "BLI_string.h"
#include "DEG_depsgraph.hh"
#include "ED_node.hh"
#include "RNA_access.hh"
#include "RNA_path.hh"
#include "MOD_nodes.hh"
#include "ANIM_keyframing.hh"
namespace blender::nodes::inverse_eval {
using namespace value_elem;
std::optional<SocketValueVariant> convert_single_socket_value(const bNodeSocket &old_socket,
const bNodeSocket &new_socket,
const SocketValueVariant &old_value)
{
const eNodeSocketDatatype old_type = old_socket.type;
const eNodeSocketDatatype new_type = new_socket.type;
if (old_type == new_type) {
return old_value;
}
const CPPType *old_cpp_type = old_socket.typeinfo->base_cpp_type;
const CPPType *new_cpp_type = new_socket.typeinfo->base_cpp_type;
if (!old_cpp_type || !new_cpp_type) {
return std::nullopt;
}
const bke::DataTypeConversions &type_conversions = bke::get_implicit_type_conversions();
if (type_conversions.is_convertible(*old_cpp_type, *new_cpp_type)) {
const void *old_value_ptr = old_value.get_single_ptr_raw();
SocketValueVariant new_value;
void *new_value_ptr = new_value.allocate_single(new_type);
type_conversions.convert_to_uninitialized(
*old_cpp_type, *new_cpp_type, old_value_ptr, new_value_ptr);
return new_value;
}
return std::nullopt;
}
static void evaluate_node_elem_upstream(const NodeInContext &ctx_node,
Vector<const bNodeSocket *> &r_modified_inputs,
Map<SocketInContext, ElemVariant> &elem_by_socket)
{
const bNode &node = *ctx_node.node;
const bke::bNodeType &ntype = *node.typeinfo;
if (!ntype.eval_inverse_elem) {
/* Node does not support inverse evaluation. */
return;
}
/* Build temporary map to be used by node evaluation function. */
Map<const bNodeSocket *, ElemVariant> elem_by_local_socket;
for (const bNodeSocket *output_socket : node.output_sockets()) {
if (const ElemVariant *elem = elem_by_socket.lookup_ptr({ctx_node.context, output_socket})) {
elem_by_local_socket.add(output_socket, *elem);
}
}
Vector<SocketElem> input_elems;
InverseElemEvalParams params{node, elem_by_local_socket, input_elems};
ntype.eval_inverse_elem(params);
/* Write back changed socket values to the map. */
for (const SocketElem &input_elem : input_elems) {
if (input_elem.elem) {
elem_by_socket.add({ctx_node.context, input_elem.socket}, input_elem.elem);
r_modified_inputs.append(input_elem.socket);
}
}
}
static bool propagate_socket_elem(const SocketInContext &ctx_from,
const SocketInContext &ctx_to,
Map<SocketInContext, ElemVariant> &elem_by_socket)
{
const ElemVariant *from_elem = elem_by_socket.lookup_ptr(ctx_from);
if (!from_elem) {
return false;
}
/* Perform implicit conversion if necessary. */
const std::optional<ElemVariant> to_elem = convert_socket_elem(
*ctx_from.socket, *ctx_to.socket, *from_elem);
if (!to_elem || !*to_elem) {
return false;
}
elem_by_socket.lookup_or_add(ctx_to, *to_elem).merge(*to_elem);
return true;
}
static void get_input_elems_to_propagate(const NodeInContext &ctx_node,
Vector<const bNodeSocket *> &r_sockets,
Map<SocketInContext, ElemVariant> &elem_by_socket)
{
for (const bNodeSocket *socket : ctx_node.node->input_sockets()) {
if (elem_by_socket.contains({ctx_node.context, socket})) {
r_sockets.append(socket);
}
}
}
LocalInverseEvalTargets find_local_inverse_eval_targets(const bNodeTree &tree,
const SocketElem &initial_socket_elem)
{
BLI_assert(!tree.has_available_link_cycle());
tree.ensure_topology_cache();
bke::ComputeContextCache compute_context_cache;
Map<SocketInContext, ElemVariant> elem_by_socket;
elem_by_socket.add({nullptr, initial_socket_elem.socket}, initial_socket_elem.elem);
const partial_eval::UpstreamEvalTargets upstream_eval_targets = partial_eval::eval_upstream(
{{nullptr, initial_socket_elem.socket}},
compute_context_cache,
/* Evaluate node. */
[&](const NodeInContext &ctx_node, Vector<const bNodeSocket *> &r_modified_inputs) {
evaluate_node_elem_upstream(ctx_node, r_modified_inputs, elem_by_socket);
},
/* Propagate value. */
[&](const SocketInContext &ctx_from, const SocketInContext &ctx_to) {
return propagate_socket_elem(ctx_from, ctx_to, elem_by_socket);
},
/* Get input sockets to propagate. */
[&](const NodeInContext &ctx_node, Vector<const bNodeSocket *> &r_sockets) {
get_input_elems_to_propagate(ctx_node, r_sockets, elem_by_socket);
});
LocalInverseEvalTargets targets;
for (const SocketInContext &ctx_socket : upstream_eval_targets.sockets) {
if (ctx_socket.context) {
/* Context should be empty because we only handle top-level sockets here. */
continue;
}
const ElemVariant *elem = elem_by_socket.lookup_ptr(ctx_socket);
if (!elem || !*elem) {
continue;
}
targets.input_sockets.append({ctx_socket.socket, *elem});
}
for (const NodeInContext ctx_node : upstream_eval_targets.value_nodes) {
if (ctx_node.context) {
/* Context should be empty because we only handle top-level nodes here. */
continue;
}
const bNodeSocket &socket = ctx_node.node->output_socket(0);
const ElemVariant *elem = elem_by_socket.lookup_ptr({nullptr, &socket});
if (!elem || !*elem) {
continue;
}
targets.value_nodes.append({ctx_node.node, *elem});
}
for (const int group_input_index : tree.interface_inputs().index_range()) {
const eNodeSocketDatatype type = eNodeSocketDatatype(
tree.interface_inputs()[group_input_index]->socket_typeinfo()->type);
std::optional<ElemVariant> elem = get_elem_variant_for_socket_type(type);
if (!elem) {
continue;
}
/* Combine the elems from each group input node. */
for (const bNode *node : tree.group_input_nodes()) {
const bNodeSocket &socket = node->output_socket(group_input_index);
if (const ElemVariant *socket_elem = elem_by_socket.lookup_ptr({nullptr, &socket})) {
elem->merge(*socket_elem);
}
}
if (!*elem) {
continue;
}
targets.group_inputs.append({group_input_index, *elem});
}
return targets;
}
static void evaluate_node_elem_downstream_filtered(
const NodeInContext &ctx_node,
const Map<SocketInContext, ElemVariant> &elem_by_socket_filter,
Map<SocketInContext, ElemVariant> &elem_by_socket,
Vector<const bNodeSocket *> &r_outputs_to_propagate)
{
const bNode &node = *ctx_node.node;
const bke::bNodeType &ntype = *node.typeinfo;
if (!ntype.eval_elem) {
return;
}
/* Build temporary map used by the node evaluation. */
Map<const bNodeSocket *, ElemVariant> elem_by_local_socket;
for (const bNodeSocket *input_socket : node.input_sockets()) {
if (const ElemVariant *elem = elem_by_socket.lookup_ptr({ctx_node.context, input_socket})) {
elem_by_local_socket.add(input_socket, *elem);
}
}
Vector<SocketElem> output_elems;
ElemEvalParams params{node, elem_by_local_socket, output_elems};
ntype.eval_elem(params);
/* Filter and store the outputs generated by the node evaluation. */
for (const SocketElem &output_elem : output_elems) {
if (output_elem.elem) {
if (const ElemVariant *elem_filter = elem_by_socket_filter.lookup_ptr(
{ctx_node.context, output_elem.socket}))
{
ElemVariant new_elem = *elem_filter;
new_elem.intersect(output_elem.elem);
elem_by_socket.add({ctx_node.context, output_elem.socket}, new_elem);
if (new_elem) {
r_outputs_to_propagate.append(output_elem.socket);
}
}
}
}
}
static bool propagate_value_elem_filtered(
const SocketInContext &ctx_from,
const SocketInContext &ctx_to,
const Map<SocketInContext, ElemVariant> &elem_by_socket_filter,
Map<SocketInContext, ElemVariant> &elem_by_socket)
{
const ElemVariant *from_elem = elem_by_socket.lookup_ptr(ctx_from);
if (!from_elem) {
return false;
}
const ElemVariant *to_elem_filter = elem_by_socket_filter.lookup_ptr(ctx_to);
if (!to_elem_filter) {
return false;
}
const std::optional<ElemVariant> converted_elem = convert_socket_elem(
*ctx_from.socket, *ctx_to.socket, *from_elem);
if (!converted_elem) {
return false;
}
if (ctx_to.socket->is_multi_input()) {
ElemVariant added_elem = *converted_elem;
added_elem.intersect(*to_elem_filter);
elem_by_socket.lookup_or_add(ctx_to, added_elem).merge(added_elem);
return true;
}
ElemVariant to_elem = *to_elem_filter;
to_elem.intersect(*converted_elem);
elem_by_socket.add(ctx_to, to_elem);
return true;
}
void foreach_element_on_inverse_eval_path(
const ComputeContext &initial_context,
const SocketElem &initial_socket_elem,
FunctionRef<void(const ComputeContext &context)> foreach_context_fn,
FunctionRef<void(const ComputeContext &context,
const bNodeSocket &socket,
const ElemVariant &elem)> foreach_socket_fn)
{
BLI_assert(initial_socket_elem.socket->is_input());
if (!initial_socket_elem.elem) {
return;
}
bke::ComputeContextCache compute_context_cache;
Map<SocketInContext, ElemVariant> upstream_elem_by_socket;
upstream_elem_by_socket.add({&initial_context, initial_socket_elem.socket},
initial_socket_elem.elem);
/* In a first pass, propagate upstream to find the upstream targets. */
const partial_eval::UpstreamEvalTargets upstream_eval_targets = partial_eval::eval_upstream(
{{&initial_context, initial_socket_elem.socket}},
compute_context_cache,
/* Evaluate node. */
[&](const NodeInContext &ctx_node, Vector<const bNodeSocket *> &r_modified_inputs) {
evaluate_node_elem_upstream(ctx_node, r_modified_inputs, upstream_elem_by_socket);
},
/* Propagate value. */
[&](const SocketInContext &ctx_from, const SocketInContext &ctx_to) {
return propagate_socket_elem(ctx_from, ctx_to, upstream_elem_by_socket);
},
/* Get input sockets to propagate. */
[&](const NodeInContext &ctx_node, Vector<const bNodeSocket *> &r_sockets) {
get_input_elems_to_propagate(ctx_node, r_sockets, upstream_elem_by_socket);
});
/* The upstream propagation may also follow node paths that don't end up in upstream targets.
* That can happen if there is a node on the path that does not support inverse evaluation. In
* this case, parts of the evaluation path has to be discarded again. This is done using a second
* pass. Now we start the evaluation at the discovered upstream targets and propagate the changed
* socket elements downstream. We only care about the sockets that have already been used by
* upstream evaluation, therefor the downstream evaluation is filtered. */
/* Gather all upstream evaluation targets to start downstream evaluation there. */
Vector<SocketInContext> initial_downstream_evaluation_sockets;
initial_downstream_evaluation_sockets.extend(upstream_eval_targets.sockets.begin(),
upstream_eval_targets.sockets.end());
initial_downstream_evaluation_sockets.extend(upstream_eval_targets.group_inputs.begin(),
upstream_eval_targets.group_inputs.end());
for (const NodeInContext &ctx_node : upstream_eval_targets.value_nodes) {
initial_downstream_evaluation_sockets.append(
{ctx_node.context, &ctx_node.node->output_socket(0)});
}
Map<SocketInContext, ElemVariant> final_elem_by_socket;
for (const SocketInContext &ctx_socket : initial_downstream_evaluation_sockets) {
final_elem_by_socket.add(ctx_socket, upstream_elem_by_socket.lookup(ctx_socket));
}
partial_eval::eval_downstream(
initial_downstream_evaluation_sockets,
compute_context_cache,
/* Evaluate node. */
[&](const NodeInContext &ctx_node, Vector<const bNodeSocket *> &r_outputs_to_propagate) {
evaluate_node_elem_downstream_filtered(
ctx_node, upstream_elem_by_socket, final_elem_by_socket, r_outputs_to_propagate);
},
/* Propagate value. */
[&](const SocketInContext &ctx_from, const SocketInContext &ctx_to) {
return propagate_value_elem_filtered(
ctx_from, ctx_to, upstream_elem_by_socket, final_elem_by_socket);
});
if (foreach_context_fn) {
Set<ComputeContextHash> handled_hashes;
for (const SocketInContext &ctx_socket : final_elem_by_socket.keys()) {
if (handled_hashes.add(ctx_socket.context->hash())) {
foreach_context_fn(*ctx_socket.context);
}
}
}
if (foreach_socket_fn) {
for (auto &&item : final_elem_by_socket.items()) {
foreach_socket_fn(*item.key.context, *item.key.socket, item.value);
}
}
}
using RNAValueVariant = std::variant<float, int, bool>;
static bool set_rna_property(bContext &C,
ID &id,
const StringRefNull rna_path,
const RNAValueVariant &value_variant)
{
if (!ID_IS_EDITABLE(&id)) {
return false;
}
PointerRNA id_ptr = RNA_id_pointer_create(&id);
PointerRNA value_ptr;
PropertyRNA *prop;
int index;
if (!RNA_path_resolve_property_full(&id_ptr, rna_path.c_str(), &value_ptr, &prop, &index)) {
return false;
}
/* In the future, we could check if there is a driver on the property and propagate the change
* backwards through the driver. */
const PropertyType dst_type = RNA_property_type(prop);
const int array_len = RNA_property_array_length(&value_ptr, prop);
Scene *scene = CTX_data_scene(&C);
const bool only_when_keyed = animrig::is_keying_flag(scene, AUTOKEY_FLAG_INSERTAVAILABLE);
switch (dst_type) {
case PROP_FLOAT: {
float value = std::visit([](auto v) { return float(v); }, value_variant);
float soft_min, soft_max, step, precision;
RNA_property_float_ui_range(&value_ptr, prop, &soft_min, &soft_max, &step, &precision);
value = std::clamp(value, soft_min, soft_max);
if (array_len == 0) {
RNA_property_float_set(&value_ptr, prop, value);
RNA_property_update(&C, &value_ptr, prop);
animrig::autokeyframe_property(
&C, scene, &value_ptr, prop, 0, scene->r.cfra, only_when_keyed);
return true;
}
if (index >= 0 && index < array_len) {
RNA_property_float_set_index(&value_ptr, prop, index, value);
RNA_property_update(&C, &value_ptr, prop);
animrig::autokeyframe_property(
&C, scene, &value_ptr, prop, index, scene->r.cfra, only_when_keyed);
return true;
}
break;
}
case PROP_INT: {
int value = std::visit([](auto v) { return int(v); }, value_variant);
int soft_min, soft_max, step;
RNA_property_int_ui_range(&value_ptr, prop, &soft_min, &soft_max, &step);
value = std::clamp(value, soft_min, soft_max);
if (array_len == 0) {
RNA_property_int_set(&value_ptr, prop, value);
RNA_property_update(&C, &value_ptr, prop);
animrig::autokeyframe_property(
&C, scene, &value_ptr, prop, 0, scene->r.cfra, only_when_keyed);
return true;
}
if (index >= 0 && index < array_len) {
RNA_property_int_set_index(&value_ptr, prop, index, value);
RNA_property_update(&C, &value_ptr, prop);
animrig::autokeyframe_property(
&C, scene, &value_ptr, prop, index, scene->r.cfra, only_when_keyed);
return true;
}
break;
}
case PROP_BOOLEAN: {
const bool value = std::visit([](auto v) { return bool(v); }, value_variant);
if (array_len == 0) {
RNA_property_boolean_set(&value_ptr, prop, value);
RNA_property_update(&C, &value_ptr, prop);
animrig::autokeyframe_property(
&C, scene, &value_ptr, prop, 0, scene->r.cfra, only_when_keyed);
return true;
}
if (index >= 0 && index < array_len) {
RNA_property_boolean_set_index(&value_ptr, prop, index, value);
RNA_property_update(&C, &value_ptr, prop);
animrig::autokeyframe_property(
&C, scene, &value_ptr, prop, index, scene->r.cfra, only_when_keyed);
return true;
}
break;
}
default:
break;
};
return false;
}
static bool set_rna_property_float3(bContext &C,
ID &id,
const StringRefNull rna_path,
const float3 &value)
{
bool any_success = false;
for (const int i : IndexRange(3)) {
const std::string rna_path_for_index = fmt::format("{}[{}]", rna_path, i);
any_success |= set_rna_property(C, id, rna_path_for_index, value[i]);
}
return any_success;
}
static bool set_socket_value(bContext &C,
bNodeSocket &socket,
const SocketValueVariant &value_variant)
{
bNode &node = socket.owner_node();
bNodeTree &tree = socket.owner_tree();
const std::string default_value_rna_path = fmt::format(
"nodes[\"{}\"].inputs[{}].default_value", BLI_str_escape(node.name), socket.index());
switch (socket.type) {
case SOCK_FLOAT: {
const float value = value_variant.get<float>();
return set_rna_property(C, tree.id, default_value_rna_path, value);
}
case SOCK_INT: {
const int value = value_variant.get<int>();
return set_rna_property(C, tree.id, default_value_rna_path, value);
}
case SOCK_BOOLEAN: {
const bool value = value_variant.get<bool>();
return set_rna_property(C, tree.id, default_value_rna_path, value);
}
case SOCK_VECTOR: {
const float3 value = value_variant.get<float3>();
return set_rna_property_float3(C, tree.id, default_value_rna_path, value);
}
case SOCK_ROTATION: {
const math::Quaternion rotation = value_variant.get<math::Quaternion>();
const float3 euler = float3(math::to_euler(rotation));
return set_rna_property_float3(C, tree.id, default_value_rna_path, euler);
}
default:
break;
}
return false;
}
static bool set_value_node_value(bContext &C, bNode &node, const SocketValueVariant &value_variant)
{
bNodeTree &tree = node.owner_tree();
switch (node.type_legacy) {
case SH_NODE_VALUE: {
const float value = value_variant.get<float>();
const std::string rna_path = fmt::format("nodes[\"{}\"].outputs[0].default_value",
BLI_str_escape(node.name));
return set_rna_property(C, tree.id, rna_path, value);
}
case FN_NODE_INPUT_INT: {
const int value = value_variant.get<int>();
const std::string rna_path = fmt::format("nodes[\"{}\"].integer", BLI_str_escape(node.name));
return set_rna_property(C, tree.id, rna_path, value);
}
case FN_NODE_INPUT_BOOL: {
const bool value = value_variant.get<bool>();
const std::string rna_path = fmt::format("nodes[\"{}\"].boolean", BLI_str_escape(node.name));
return set_rna_property(C, tree.id, rna_path, value);
}
case FN_NODE_INPUT_VECTOR: {
const float3 value = value_variant.get<float3>();
const std::string rna_path = fmt::format("nodes[\"{}\"].vector", BLI_str_escape(node.name));
return set_rna_property_float3(C, tree.id, rna_path, value);
}
case FN_NODE_INPUT_ROTATION: {
const math::Quaternion rotation = value_variant.get<math::Quaternion>();
const float3 euler = float3(math::to_euler(rotation));
const std::string rna_path = fmt::format("nodes[\"{}\"].rotation_euler",
BLI_str_escape(node.name));
return set_rna_property_float3(C, tree.id, rna_path, euler);
}
}
return false;
}
static bool set_modifier_value(bContext &C,
Object &object,
NodesModifierData &nmd,
const bNodeTreeInterfaceSocket &interface_socket,
const SocketValueVariant &value_variant)
{
DEG_id_tag_update(&object.id, ID_RECALC_GEOMETRY);
const std::string main_prop_rna_path = fmt::format(
"modifiers[\"{}\"].properties.inputs.{}.value",
BLI_str_escape(nmd.modifier.name),
interface_socket.identifier);
switch (interface_socket.socket_typeinfo()->type) {
case SOCK_FLOAT: {
const float value = value_variant.get<float>();
return set_rna_property(C, object.id, main_prop_rna_path, value);
}
case SOCK_INT: {
const int value = value_variant.get<int>();
return set_rna_property(C, object.id, main_prop_rna_path, value);
}
case SOCK_BOOLEAN: {
const bool value = value_variant.get<bool>();
return set_rna_property(C, object.id, main_prop_rna_path, value);
}
case SOCK_VECTOR: {
const float3 value = value_variant.get<float3>();
return set_rna_property_float3(C, object.id, main_prop_rna_path, value);
}
case SOCK_ROTATION: {
const math::Quaternion rotation = value_variant.get<math::Quaternion>();
const float3 euler = float3(math::to_euler(rotation));
return set_rna_property_float3(C, object.id, main_prop_rna_path, euler);
}
default:
return false;
}
}
std::optional<SocketValueVariant> get_logged_socket_value(eval_log::NodeTreeLog &tree_log,
const bNodeSocket &socket)
{
switch (socket.type) {
case SOCK_FLOAT: {
if (const std::optional<float> value = tree_log.find_primitive_socket_value<float>(socket)) {
return SocketValueVariant{*value};
}
break;
}
case SOCK_INT: {
if (const std::optional<int> value = tree_log.find_primitive_socket_value<int>(socket)) {
return SocketValueVariant{*value};
}
break;
}
case SOCK_BOOLEAN: {
if (const std::optional<bool> value = tree_log.find_primitive_socket_value<bool>(socket)) {
return SocketValueVariant{*value};
}
break;
}
case SOCK_VECTOR: {
if (const std::optional<float3> value = tree_log.find_primitive_socket_value<float3>(socket))
{
return SocketValueVariant{*value};
}
break;
}
case SOCK_ROTATION: {
if (const std::optional<math::Quaternion> value =
tree_log.find_primitive_socket_value<math::Quaternion>(socket))
{
return SocketValueVariant{*value};
}
break;
}
case SOCK_MATRIX: {
if (const std::optional<float4x4> value = tree_log.find_primitive_socket_value<float4x4>(
socket))
{
return SocketValueVariant{*value};
}
break;
}
default:
break;
}
return std::nullopt;
}
static void backpropagate_socket_values_through_node(
const NodeInContext &ctx_node,
eval_log::NodesEvalLog &eval_log,
Map<SocketInContext, SocketValueVariant> &value_by_socket,
Vector<const bNodeSocket *> &r_modified_inputs)
{
const bNode &node = *ctx_node.node;
const ComputeContext *context = ctx_node.context;
const bke::bNodeType &ntype = *node.typeinfo;
if (!ntype.eval_inverse) {
/* Node does not support inverse evaluation. */
return;
}
if (!context) {
/* We need a context here to access the tree log. */
return;
}
eval_log::NodeTreeLog &tree_log = eval_log.get_tree_log(context->hash());
tree_log.ensure_socket_values();
/* Build a temporary map of old socket values for the node evaluation. */
Map<const bNodeSocket *, SocketValueVariant> old_socket_values;
for (const bNodeSocket *socket : node.input_sockets()) {
if (!socket->is_available()) {
continue;
}
/* Retrieve input socket values from the log. */
if (const std::optional<SocketValueVariant> value = get_logged_socket_value(tree_log, *socket))
{
old_socket_values.add(socket, *value);
}
}
for (const bNodeSocket *socket : node.output_sockets()) {
if (!socket->is_available()) {
continue;
}
/* First check if there is an updated socket value for an output socket. */
if (const SocketValueVariant *value = value_by_socket.lookup_ptr({context, socket})) {
old_socket_values.add(socket, *value);
}
/* If not, retrieve the output socket value from the log. */
else if (const std::optional<SocketValueVariant> value = get_logged_socket_value(tree_log,
*socket))
{
old_socket_values.add(socket, *value);
}
}
Map<const bNodeSocket *, SocketValueVariant> updated_socket_values;
InverseEvalParams params{node, old_socket_values, updated_socket_values};
ntype.eval_inverse(params);
/* Write back new socket values. */
for (auto &&item : updated_socket_values.items()) {
const bNodeSocket &socket = *item.key;
value_by_socket.add({context, &socket}, std::move(item.value));
r_modified_inputs.append(&socket);
}
}
bool backpropagate_socket_values(bContext &C,
Object &object,
NodesModifierData &nmd,
eval_log::NodesEvalLog &eval_log,
const Span<SocketToUpdate> sockets_to_update)
{
nmd.node_group->ensure_topology_cache();
bke::ComputeContextCache compute_context_cache;
Map<SocketInContext, SocketValueVariant> value_by_socket;
Vector<SocketInContext> initial_sockets;
/* Gather starting values for the backpropagation. */
for (const SocketToUpdate &socket_to_update : sockets_to_update) {
if (socket_to_update.multi_input_link) {
BLI_assert(socket_to_update.multi_input_link->tosock == socket_to_update.socket);
const std::optional<SocketValueVariant> converted_value = convert_single_socket_value(
*socket_to_update.socket,
*socket_to_update.multi_input_link->fromsock,
socket_to_update.new_value);
if (!converted_value) {
continue;
}
value_by_socket.add({socket_to_update.context, socket_to_update.multi_input_link->fromsock},
*converted_value);
}
else {
value_by_socket.add({socket_to_update.context, socket_to_update.socket},
socket_to_update.new_value);
}
}
if (value_by_socket.is_empty()) {
return false;
}
for (const SocketInContext &ctx_socket : value_by_socket.keys()) {
initial_sockets.append(ctx_socket);
}
/* Actually backpropagate the socket values as far as possible in the node tree. */
const partial_eval::UpstreamEvalTargets upstream_eval_targets = partial_eval::eval_upstream(
initial_sockets,
compute_context_cache,
/* Evaluate node. */
[&](const NodeInContext &ctx_node, Vector<const bNodeSocket *> &r_modified_inputs) {
backpropagate_socket_values_through_node(
ctx_node, eval_log, value_by_socket, r_modified_inputs);
},
/* Propagate value. */
[&](const SocketInContext &ctx_from, const SocketInContext &ctx_to) {
const SocketValueVariant *from_value = value_by_socket.lookup_ptr(ctx_from);
if (!from_value) {
return false;
}
const std::optional<SocketValueVariant> converted_value = convert_single_socket_value(
*ctx_from.socket, *ctx_to.socket, *from_value);
if (!converted_value) {
return false;
}
value_by_socket.add(ctx_to, std::move(*converted_value));
return true;
},
/* Get input sockets to propagate. */
[&](const NodeInContext &ctx_node, Vector<const bNodeSocket *> &r_sockets) {
for (const bNodeSocket *socket : ctx_node.node->input_sockets()) {
if (value_by_socket.contains({ctx_node.context, socket})) {
r_sockets.append(socket);
}
}
});
bool any_success = false;
/* Set new values for sockets. */
for (const SocketInContext &ctx_socket : upstream_eval_targets.sockets) {
if (const SocketValueVariant *value = value_by_socket.lookup_ptr(ctx_socket)) {
bNodeSocket &socket_mutable = const_cast<bNodeSocket &>(*ctx_socket.socket);
any_success |= set_socket_value(C, socket_mutable, *value);
}
}
/* Set new values for value nodes. */
for (const NodeInContext &ctx_node : upstream_eval_targets.value_nodes) {
if (const SocketValueVariant *value = value_by_socket.lookup_ptr(
{ctx_node.context, &ctx_node.node->output_socket(0)}))
{
bNode &node_mutable = const_cast<bNode &>(*ctx_node.node);
any_success |= set_value_node_value(C, node_mutable, *value);
}
}
/* Set new values for modifier inputs. */
const bke::DataBlockComputeContext data_block_context{nullptr, object.id};
const bke::ModifierComputeContext modifier_context{&data_block_context, nmd};
for (const bNode *group_input_node : nmd.node_group->group_input_nodes()) {
for (const bNodeSocket *socket : group_input_node->output_sockets().drop_back(1)) {
if (const SocketValueVariant *value = value_by_socket.lookup_ptr(
{&modifier_context, socket}))
{
any_success |= set_modifier_value(
C, object, nmd, *nmd.node_group->interface_inputs()[socket->index()], *value);
}
}
}
return any_success;
}
InverseEvalParams::InverseEvalParams(
const bNode &node,
const Map<const bNodeSocket *, bke::SocketValueVariant> &socket_values,
Map<const bNodeSocket *, bke::SocketValueVariant> &updated_socket_values)
: socket_values_(socket_values), updated_socket_values_(updated_socket_values), node(node)
{
}
} // namespace blender::nodes::inverse_eval

View File

@@ -0,0 +1,193 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "NOD_geometry_exec.hh"
#include "NOD_geometry_nodes_lazy_function.hh"
#include "NOD_geometry_nodes_list.hh"
#include "list_function_eval.hh"
namespace blender::nodes {
GVArray ListFieldContext::get_varray_for_input(const FieldInput &field_input,
const IndexMask &mask,
ResourceScope & /*scope*/) const
{
const auto *id_field_input = dynamic_cast<const bke::IDAttributeFieldInput *>(&field_input);
const auto *index_field_input = dynamic_cast<const fn::IndexFieldInput *>(&field_input);
if (id_field_input == nullptr && index_field_input == nullptr) {
return {};
}
return fn::IndexFieldInput::get_index_varray(mask);
}
GListPtr evaluate_field_to_list(GField field, const int64_t count)
{
const CPPType &cpp_type = field.cpp_type();
GArray array(cpp_type, count);
ListFieldContext context{};
fn::FieldEvaluator evaluator{context, count};
evaluator.add_with_destination(std::move(field), array);
evaluator.evaluate();
return GList::from_garray(std::move(array));
}
SampleIndexFunction::SampleIndexFunction(GListPtr list) : list_(std::move(list))
{
mf::SignatureBuilder builder{"Sample Index", signature_};
builder.single_input<int>("Index");
builder.single_output("Value", list_->cpp_type());
this->set_signature(&signature_);
}
void SampleIndexFunction::call(const IndexMask &mask,
mf::Params params,
mf::Context /*context*/) const
{
const VArraySpan<int> indices = params.readonly_single_input<int>(0, "Index");
GMutableSpan dst = params.uninitialized_single_output(1, "Value");
IndexMaskMemory memory;
const IndexMask valid_indices = array_utils::indices_in_range(
mask, indices, IndexRange(list_->size()), memory);
if (valid_indices.size() != mask.size()) {
const IndexMask invalid_indices = valid_indices.complement(mask, memory);
list_->cpp_type().fill_construct_indices(
list_->cpp_type().default_value(), dst.data(), invalid_indices);
}
const GList::DataVariant &data = list_->data();
if (const auto *array_data = std::get_if<nodes::GList::ArrayData>(&data)) {
const GSpan src(list_->cpp_type(), array_data->data, list_->size());
valid_indices.foreach_index(
[&](const int i) { list_->cpp_type().copy_construct(src[indices[i]], dst[i]); });
}
else if (const auto *single_data = std::get_if<nodes::GList::SingleData>(&data)) {
list_->cpp_type().fill_construct_indices(single_data->value, dst.data(), valid_indices);
}
}
void SampleIndexFunction::hash_unique(UniqueHashBytes &hash) const
{
static constexpr int8_t id = 0;
hash.add(&id);
hash.add(list_.get());
}
static GListPtr create_repeated_list(GListPtr list, const int64_t dst_size)
{
if (list->size() >= dst_size) {
return list;
}
if (const auto *data = std::get_if<nodes::GList::ArrayData>(&list->data())) {
const int64_t size = list->size();
BLI_assert(size > 0);
const CPPType &cpp_type = list->cpp_type();
GArray new_data(cpp_type, dst_size, NoInitialization{});
const int64_t chunks = dst_size / size;
for (const int64_t i : IndexRange(chunks)) {
cpp_type.copy_construct_n(data->data, new_data[i * size], size);
}
const int64_t last_chunk_size = dst_size % size;
if (last_chunk_size > 0) {
cpp_type.copy_construct_n(data->data, new_data[chunks * size], last_chunk_size);
}
return GList::from_garray(std::move(new_data));
}
if (const auto *data = std::get_if<nodes::GList::SingleData>(&list->data())) {
const CPPType &cpp_type = list->cpp_type();
return GList::create(cpp_type, *data, dst_size);
}
BLI_assert_unreachable();
return {};
}
static void add_list_to_params(mf::ParamsBuilder &params,
const mf::ParamType &param_type,
const GList &list)
{
const CPPType &cpp_type = param_type.data_type().single_type();
BLI_assert(cpp_type == list.cpp_type());
if (const auto *array_data = std::get_if<nodes::GList::ArrayData>(&list.data())) {
params.add_readonly_single_input(GSpan(cpp_type, array_data->data, list.size()));
}
else if (const auto *single_data = std::get_if<nodes::GList::SingleData>(&list.data())) {
params.add_readonly_single_input(GPointer(cpp_type, single_data->value));
}
}
void execute_multi_function_on_value_variant__list(const MultiFunction &fn,
const Span<SocketValueVariant *> input_values,
const Span<SocketValueVariant *> output_values,
GeoNodesUserData *user_data)
{
int64_t max_size = 0;
for (const int i : input_values.index_range()) {
SocketValueVariant &input_variant = *input_values[i];
if (input_variant.is_list()) {
if (GListPtr list = input_variant.get<GListPtr>()) {
max_size = std::max(max_size, list->size());
}
}
}
const IndexMask mask(max_size);
mf::ParamsBuilder params{fn, &mask};
mf::ContextBuilder context;
context.user_data(user_data);
Array<GListPtr, 8> input_lists(input_values.size());
for (const int i : input_values.index_range()) {
const mf::ParamType param_type = fn.param_type(params.next_param_index());
const CPPType &cpp_type = param_type.data_type().single_type();
SocketValueVariant &input_variant = *input_values[i];
if (input_variant.is_single()) {
const void *value = input_variant.get_single_ptr_raw();
params.add_readonly_single_input(GPointer(cpp_type, value));
}
else if (input_variant.is_list()) {
GListPtr list_ptr = input_variant.get<GListPtr>();
if (!list_ptr || list_ptr->size() == 0) {
params.add_readonly_single_input(GPointer(cpp_type, cpp_type.default_value()));
continue;
}
input_lists[i] = create_repeated_list(std::move(list_ptr), max_size);
add_list_to_params(params, param_type, *input_lists[i]);
}
else if (input_variant.is_context_dependent_field()) {
fn::GField field = input_variant.extract<fn::GField>();
input_lists[i] = evaluate_field_to_list(std::move(field), max_size);
add_list_to_params(params, param_type, *input_lists[i]);
}
else {
/* This function should not be called when there are other types like grids in the inputs. */
BLI_assert_unreachable();
params.add_readonly_single_input(GPointer(cpp_type, cpp_type.default_value()));
}
}
for (const int i : output_values.index_range()) {
if (output_values[i] == nullptr) {
params.add_ignored_single_output("");
continue;
}
SocketValueVariant &output_variant = *output_values[i];
const mf::ParamType param_type = fn.param_type(params.next_param_index());
const CPPType &cpp_type = param_type.data_type().single_type();
GArray array(cpp_type, max_size, NoInitialization{});
params.add_uninitialized_single_output(GMutableSpan(cpp_type, array.data(), max_size));
output_variant.set(GList::from_garray(std::move(array)));
}
fn.call(mask, params, context);
}
} // namespace blender::nodes

View File

@@ -0,0 +1,45 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup nodes
*/
#pragma once
#include "FN_multi_function.hh"
#include "NOD_geometry_exec.hh"
#include "NOD_geometry_nodes_list.hh"
namespace blender::nodes {
class ListFieldContext : public FieldContext {
public:
ListFieldContext() = default;
GVArray get_varray_for_input(const FieldInput &field_input,
const IndexMask &mask,
ResourceScope & /*scope*/) const override;
};
class SampleIndexFunction : public mf::MultiFunction {
GListPtr list_;
mf::Signature signature_;
public:
SampleIndexFunction(GListPtr list);
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override;
void hash_unique(UniqueHashBytes &hash) const override;
};
void execute_multi_function_on_value_variant__list(const MultiFunction &fn,
const Span<SocketValueVariant *> input_values,
const Span<SocketValueVariant *> output_values,
GeoNodesUserData *user_data);
GListPtr evaluate_field_to_list(GField field, const int64_t count);
} // namespace blender::nodes

View File

@@ -0,0 +1,281 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_math_functions.hh"
#include "FN_multi_function_registry.hh"
namespace blender::nodes {
static const mf::MultiFunction *get_base_multi_function(const bNode &node)
{
const int mode = node.custom1;
const FloatMathOperationInfo *info = get_float_math_operation_info(mode);
if (!info) {
return nullptr;
}
return &fn::multi_function::registry::lookup(info->multi_function_name);
}
class ClampWrapperFunction : public mf::MultiFunction {
private:
const mf::MultiFunction &fn_;
public:
ClampWrapperFunction(const mf::MultiFunction &fn) : fn_(fn)
{
this->set_signature(&fn.signature());
}
void call(const IndexMask &mask, mf::Params params, mf::Context context) const override
{
fn_.call(mask, params, context);
/* Assumes the output parameter is the last one. */
const int output_param_index = this->param_amount() - 1;
/* This has actually been initialized in the call above. */
MutableSpan<float> results = params.uninitialized_single_output<float>(output_param_index);
mask.foreach_index_optimized<int>([&](const int i) {
float &value = results[i];
CLAMP(value, 0.0f, 1.0f);
});
}
void hash_unique(UniqueHashBytes &hash) const override
{
static constexpr int8_t id = 0;
hash.add(&id);
fn_.hash_unique(hash);
}
};
void node_math_build_multi_function(NodeMultiFunctionBuilder &builder)
{
const mf::MultiFunction *base_function = get_base_multi_function(builder.node());
const bool clamp_output = builder.node().custom2 != 0;
if (clamp_output) {
builder.construct_and_set_matching_fn<ClampWrapperFunction>(*base_function);
}
else {
builder.set_matching_fn(base_function);
}
}
const FloatMathOperationInfo *get_float_math_operation_info(const int operation)
{
#define RETURN_OPERATION_INFO(title_case_name, shader_name, multi_function_name) \
{ \
static const FloatMathOperationInfo info{title_case_name, shader_name, multi_function_name}; \
return &info; \
} \
((void)0)
switch (operation) {
case NODE_MATH_ADD:
RETURN_OPERATION_INFO("Add", "math_add", "float + float"_ustr);
case NODE_MATH_SUBTRACT:
RETURN_OPERATION_INFO("Subtract", "math_subtract", "float - float"_ustr);
case NODE_MATH_MULTIPLY:
RETURN_OPERATION_INFO("Multiply", "math_multiply", "float * float"_ustr);
case NODE_MATH_DIVIDE:
RETURN_OPERATION_INFO("Divide", "math_divide", "float / float"_ustr);
case NODE_MATH_SINE:
RETURN_OPERATION_INFO("Sine", "math_sine", "sin(float)"_ustr);
case NODE_MATH_COSINE:
RETURN_OPERATION_INFO("Cosine", "math_cosine", "cos(float)"_ustr);
case NODE_MATH_TANGENT:
RETURN_OPERATION_INFO("Tangent", "math_tangent", "tan(float)"_ustr);
case NODE_MATH_ARCSINE:
RETURN_OPERATION_INFO("Arc Sine", "math_arcsine", "asin(float)"_ustr);
case NODE_MATH_ARCCOSINE:
RETURN_OPERATION_INFO("Arc Cosine", "math_arccosine", "acos(float)"_ustr);
case NODE_MATH_ARCTANGENT:
RETURN_OPERATION_INFO("Arc Tangent", "math_arctangent", "atan(float)"_ustr);
case NODE_MATH_POWER:
RETURN_OPERATION_INFO("Power", "math_power", "float ** float"_ustr);
case NODE_MATH_LOGARITHM:
RETURN_OPERATION_INFO("Logarithm", "math_logarithm", "log(float, float)"_ustr);
case NODE_MATH_MINIMUM:
RETURN_OPERATION_INFO("Minimum", "math_minimum", "min(float, float)"_ustr);
case NODE_MATH_MAXIMUM:
RETURN_OPERATION_INFO("Maximum", "math_maximum", "max(float, float)"_ustr);
case NODE_MATH_ROUND:
RETURN_OPERATION_INFO("Round", "math_round", "round(float)"_ustr);
case NODE_MATH_LESS_THAN:
RETURN_OPERATION_INFO("Less Than", "math_less_than", "float(float < float)"_ustr);
case NODE_MATH_GREATER_THAN:
RETURN_OPERATION_INFO("Greater Than", "math_greater_than", "float(float > float)"_ustr);
case NODE_MATH_MODULO:
RETURN_OPERATION_INFO("Modulo", "math_modulo", "float % float"_ustr);
case NODE_MATH_FLOORED_MODULO:
RETURN_OPERATION_INFO(
"Floored Modulo", "math_floored_modulo", "floor_mod(float, float)"_ustr);
case NODE_MATH_ABSOLUTE:
RETURN_OPERATION_INFO("Absolute", "math_absolute", "abs(float)"_ustr);
case NODE_MATH_ARCTAN2:
RETURN_OPERATION_INFO("Arc Tangent 2", "math_arctan2", "atan2(float, float)"_ustr);
case NODE_MATH_FLOOR:
RETURN_OPERATION_INFO("Floor", "math_floor", "floor(float)"_ustr);
case NODE_MATH_CEIL:
RETURN_OPERATION_INFO("Ceil", "math_ceil", "ceil(float)"_ustr);
case NODE_MATH_FRACTION:
RETURN_OPERATION_INFO("Fraction", "math_fraction", "frac(float)"_ustr);
case NODE_MATH_SQRT:
RETURN_OPERATION_INFO("Sqrt", "math_sqrt", "sqrt(float)"_ustr);
case NODE_MATH_INV_SQRT:
RETURN_OPERATION_INFO("Inverse Sqrt", "math_inversesqrt", "inverse_sqrt(float)"_ustr);
case NODE_MATH_SIGN:
RETURN_OPERATION_INFO("Sign", "math_sign", "sign(float)"_ustr);
case NODE_MATH_EXPONENT:
RETURN_OPERATION_INFO("Exponent", "math_exponent", "exp(float)"_ustr);
case NODE_MATH_RADIANS:
RETURN_OPERATION_INFO("Radians", "math_radians", "radians(float)"_ustr);
case NODE_MATH_DEGREES:
RETURN_OPERATION_INFO("Degrees", "math_degrees", "degrees(float)"_ustr);
case NODE_MATH_SINH:
RETURN_OPERATION_INFO("Hyperbolic Sine", "math_sinh", "sinh(float)"_ustr);
case NODE_MATH_COSH:
RETURN_OPERATION_INFO("Hyperbolic Cosine", "math_cosh", "cosh(float)"_ustr);
case NODE_MATH_TANH:
RETURN_OPERATION_INFO("Hyperbolic Tangent", "math_tanh", "tanh(float)"_ustr);
case NODE_MATH_TRUNC:
RETURN_OPERATION_INFO("Truncate", "math_trunc", "trunc(float)"_ustr);
case NODE_MATH_SNAP:
RETURN_OPERATION_INFO("Snap", "math_snap", "snap(float, float)"_ustr);
case NODE_MATH_WRAP:
RETURN_OPERATION_INFO("Wrap", "math_wrap", "wrap(float, float, float)"_ustr);
case NODE_MATH_COMPARE:
RETURN_OPERATION_INFO("Compare", "math_compare", "compare(float, float, float)"_ustr);
case NODE_MATH_MULTIPLY_ADD:
RETURN_OPERATION_INFO("Multiply Add", "math_multiply_add", "float * float + float"_ustr);
case NODE_MATH_PINGPONG:
RETURN_OPERATION_INFO("Ping Pong", "math_pingpong", "pingpong(float, float)"_ustr);
case NODE_MATH_SMOOTH_MIN:
RETURN_OPERATION_INFO(
"Smooth Min", "math_smoothmin", "smooth_min(float, float, float)"_ustr);
case NODE_MATH_SMOOTH_MAX:
RETURN_OPERATION_INFO(
"Smooth Max", "math_smoothmax", "smooth_max(float, float, float)"_ustr);
}
#undef RETURN_OPERATION_INFO
return nullptr;
}
const FloatMathOperationInfo *get_float_compare_operation_info(const int operation)
{
#define RETURN_OPERATION_INFO(title_case_name, shader_name) \
{ \
static const FloatMathOperationInfo info{title_case_name, shader_name}; \
return &info; \
} \
((void)0)
switch (operation) {
case NODE_COMPARE_LESS_THAN:
RETURN_OPERATION_INFO("Less Than", "math_less_than");
case NODE_COMPARE_LESS_EQUAL:
RETURN_OPERATION_INFO("Less Than or Equal", "math_less_equal");
case NODE_COMPARE_GREATER_THAN:
RETURN_OPERATION_INFO("Greater Than", "math_greater_than");
case NODE_COMPARE_GREATER_EQUAL:
RETURN_OPERATION_INFO("Greater Than or Equal", "math_greater_equal");
case NODE_COMPARE_EQUAL:
RETURN_OPERATION_INFO("Equal", "math_equal");
case NODE_COMPARE_NOT_EQUAL:
RETURN_OPERATION_INFO("Not Equal", "math_not_equal");
}
#undef RETURN_OPERATION_INFO
return nullptr;
}
const FloatMathOperationInfo *get_float3_math_operation_info(const int operation)
{
#define RETURN_OPERATION_INFO(title_case_name, shader_name, multi_function_name) \
{ \
static const FloatMathOperationInfo info{title_case_name, shader_name, multi_function_name}; \
return &info; \
} \
((void)0)
switch (operation) {
case NODE_VECTOR_MATH_ADD:
RETURN_OPERATION_INFO("Add", "vector_math_add", "float3 + float3"_ustr);
case NODE_VECTOR_MATH_SUBTRACT:
RETURN_OPERATION_INFO("Subtract", "vector_math_subtract", "float3 - float3"_ustr);
case NODE_VECTOR_MATH_MULTIPLY:
RETURN_OPERATION_INFO("Multiply", "vector_math_multiply", "float3 * float3"_ustr);
case NODE_VECTOR_MATH_DIVIDE:
RETURN_OPERATION_INFO("Divide", "vector_math_divide", "float3 / float3"_ustr);
case NODE_VECTOR_MATH_CROSS_PRODUCT:
RETURN_OPERATION_INFO(
"Cross Product", "vector_math_cross", "cross_product(float3, float3)"_ustr);
case NODE_VECTOR_MATH_PROJECT:
RETURN_OPERATION_INFO("Project", "vector_math_project", "project(float3, float3)"_ustr);
case NODE_VECTOR_MATH_REFLECT:
RETURN_OPERATION_INFO("Reflect", "vector_math_reflect", "reflect(float3, float3)"_ustr);
case NODE_VECTOR_MATH_DOT_PRODUCT:
RETURN_OPERATION_INFO("Dot Product", "vector_math_dot", "dot_product(float3, float3)"_ustr);
case NODE_VECTOR_MATH_DISTANCE:
RETURN_OPERATION_INFO("Distance", "vector_math_distance", "distance(float3, float3)"_ustr);
case NODE_VECTOR_MATH_LENGTH:
RETURN_OPERATION_INFO("Length", "vector_math_length", "length(float3)"_ustr);
case NODE_VECTOR_MATH_SCALE:
RETURN_OPERATION_INFO("Scale", "vector_math_scale", "float3 * float"_ustr);
case NODE_VECTOR_MATH_NORMALIZE:
RETURN_OPERATION_INFO("Normalize", "vector_math_normalize", "normalize(float3)"_ustr);
case NODE_VECTOR_MATH_SNAP:
RETURN_OPERATION_INFO("Snap", "vector_math_snap", "snap(float3, float3)"_ustr);
case NODE_VECTOR_MATH_ROUND:
RETURN_OPERATION_INFO("Round", "vector_math_round", "round(float3)"_ustr);
case NODE_VECTOR_MATH_FLOOR:
RETURN_OPERATION_INFO("Floor", "vector_math_floor", "floor(float3)"_ustr);
case NODE_VECTOR_MATH_CEIL:
RETURN_OPERATION_INFO("Ceiling", "vector_math_ceil", "ceil(float3)"_ustr);
case NODE_VECTOR_MATH_MODULO:
RETURN_OPERATION_INFO("Modulo", "vector_math_modulo", "float3 % float3"_ustr);
case NODE_VECTOR_MATH_FRACTION:
RETURN_OPERATION_INFO("Fraction", "vector_math_fraction", "frac(float3)"_ustr);
case NODE_VECTOR_MATH_ABSOLUTE:
RETURN_OPERATION_INFO("Absolute", "vector_math_absolute", "abs(float3)"_ustr);
case NODE_VECTOR_MATH_MINIMUM:
RETURN_OPERATION_INFO("Minimum", "vector_math_minimum", "min(float3, float3)"_ustr);
case NODE_VECTOR_MATH_MAXIMUM:
RETURN_OPERATION_INFO("Maximum", "vector_math_maximum", "max(float3, float3)"_ustr);
case NODE_VECTOR_MATH_WRAP:
RETURN_OPERATION_INFO("Wrap", "vector_math_wrap", "wrap(float3, float3, float3)"_ustr);
case NODE_VECTOR_MATH_SINE:
RETURN_OPERATION_INFO("Sine", "vector_math_sine", "sin(float3)"_ustr);
case NODE_VECTOR_MATH_COSINE:
RETURN_OPERATION_INFO("Cosine", "vector_math_cosine", "cos(float3)"_ustr);
case NODE_VECTOR_MATH_TANGENT:
RETURN_OPERATION_INFO("Tangent", "vector_math_tangent", "tan(float3)"_ustr);
case NODE_VECTOR_MATH_REFRACT:
RETURN_OPERATION_INFO(
"Refract", "vector_math_refract", "refract(float3, float3, float)"_ustr);
case NODE_VECTOR_MATH_FACEFORWARD:
RETURN_OPERATION_INFO(
"Faceforward", "vector_math_faceforward", "faceforward(float3, float3, float3)"_ustr);
case NODE_VECTOR_MATH_MULTIPLY_ADD:
RETURN_OPERATION_INFO(
"Multiply Add", "vector_math_multiply_add", "float3 * float3 + float3"_ustr);
case NODE_VECTOR_MATH_POWER:
RETURN_OPERATION_INFO("Power", "vector_math_power", "float3 ** float3"_ustr);
case NODE_VECTOR_MATH_SIGN:
RETURN_OPERATION_INFO("Sign", "vector_math_sign", "sign(float3)"_ustr);
}
#undef RETURN_OPERATION_INFO
return nullptr;
}
} // namespace blender::nodes

View File

@@ -0,0 +1,176 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "DNA_node_tree_interface_types.h"
#include "NOD_caller_ui.hh"
#include "NOD_socket_usage_inference.hh"
#include "RNA_access.hh"
#include "RNA_types.hh"
#include "BLT_translation.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
namespace blender::nodes {
static bool interface_panel_has_socket(
const bNodeTreeInterfacePanel &interface_panel,
FunctionRef<bool(const bNodeTreeInterfaceSocket &)> fn_input_is_visible)
{
for (const bNodeTreeInterfaceItem *item : interface_panel.items()) {
if (item->item_type == NodeTreeInterfaceItemType::Socket) {
const bNodeTreeInterfaceSocket &socket = *reinterpret_cast<const bNodeTreeInterfaceSocket *>(
item);
if (socket.flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER) {
continue;
}
if (socket.flag & NODE_INTERFACE_SOCKET_INPUT) {
if (fn_input_is_visible(socket)) {
return true;
}
}
}
else if (item->item_type == NodeTreeInterfaceItemType::Panel) {
const auto &panel_item = *reinterpret_cast<const bNodeTreeInterfacePanel *>(item);
if (interface_panel_has_socket(panel_item, fn_input_is_visible)) {
return true;
}
}
}
return false;
}
static bool interface_panel_affects_output(
const bNodeTreeInterfacePanel &panel,
FunctionRef<bool(const bNodeTreeInterfaceSocket &)> fn_input_is_active)
{
for (const bNodeTreeInterfaceItem *item : panel.items()) {
if (item->item_type == NodeTreeInterfaceItemType::Socket) {
const auto &socket = *reinterpret_cast<const bNodeTreeInterfaceSocket *>(item);
if (socket.flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER) {
continue;
}
if (!(socket.flag & NODE_INTERFACE_SOCKET_INPUT)) {
continue;
}
if (fn_input_is_active(socket)) {
return true;
}
}
else if (item->item_type == NodeTreeInterfaceItemType::Panel) {
const auto &sub_interface_panel = *reinterpret_cast<const bNodeTreeInterfacePanel *>(item);
if (interface_panel_affects_output(sub_interface_panel, fn_input_is_active)) {
return true;
}
}
}
return false;
}
void draw_interface_panel_as_panel(
const bContext &C,
ui::Layout &layout,
PointerRNA *properties_ptr,
const bNodeTreeInterfacePanel &interface_panel,
FunctionRef<bool(const bNodeTreeInterfaceSocket &)> fn_input_is_visible,
FunctionRef<bool(const bNodeTreeInterfaceSocket &)> fn_input_is_active,
FunctionRef<void(ui::Layout &,
const bNodeTreeInterfaceSocket &,
PointerRNA *,
const std::optional<StringRef>)> fn_draw_property_for_socket)
{
if (!interface_panel_has_socket(interface_panel, fn_input_is_visible)) {
return;
}
PointerRNA panels_ptr = RNA_pointer_get(properties_ptr, "panels");
const std::string panel_open_name = fmt::format("open_{}", interface_panel.identifier);
ui::PanelLayout panel_layout;
bool skip_first = false;
/* Check if the panel should have a toggle in the header. */
const bNodeTreeInterfaceSocket *toggle_socket = interface_panel.header_toggle_socket();
const StringRef panel_name = interface_panel.name;
if (toggle_socket && !(toggle_socket->flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER)) {
PointerRNA inputs_ptr = RNA_pointer_get(properties_ptr, "inputs");
PointerRNA toggle_ptr = RNA_pointer_get(&inputs_ptr, toggle_socket->identifier);
panel_layout = layout.panel_prop_with_bool_header(
&C, &panels_ptr, panel_open_name, &toggle_ptr, "value", IFACE_(panel_name));
skip_first = true;
}
else {
panel_layout = layout.panel_prop(&C, &panels_ptr, panel_open_name);
panel_layout.header->label(IFACE_(panel_name), ICON_NONE);
}
if (!interface_panel_affects_output(interface_panel, fn_input_is_active)) {
panel_layout.header->active_set(false);
}
uiLayoutSetTooltipFunc(
panel_layout.header,
[](bContext * /*C*/, void *panel_arg, const StringRef /*tip*/) -> std::string {
const auto *panel = static_cast<bNodeTreeInterfacePanel *>(panel_arg);
return StringRef(panel->description);
},
const_cast<bNodeTreeInterfacePanel *>(&interface_panel),
nullptr,
nullptr);
if (panel_layout.body) {
draw_interface_panel_content(C,
*panel_layout.body,
properties_ptr,
interface_panel,
fn_input_is_visible,
fn_input_is_active,
fn_draw_property_for_socket,
skip_first,
panel_name);
}
}
void draw_interface_panel_content(
const bContext &C,
ui::Layout &layout,
PointerRNA *properties_ptr,
const bNodeTreeInterfacePanel &interface_panel,
FunctionRef<bool(const bNodeTreeInterfaceSocket &)> fn_input_is_visible,
FunctionRef<bool(const bNodeTreeInterfaceSocket &)> fn_input_is_active,
FunctionRef<void(ui::Layout &,
const bNodeTreeInterfaceSocket &,
PointerRNA *,
const std::optional<StringRef>)> fn_draw_property_for_socket,
const bool skip_first,
const std::optional<StringRef> parent_name)
{
for (const bNodeTreeInterfaceItem *item : interface_panel.items().drop_front(skip_first ? 1 : 0))
{
switch (item->item_type) {
case NodeTreeInterfaceItemType::Panel: {
const auto &sub_interface_panel = *reinterpret_cast<const bNodeTreeInterfacePanel *>(item);
draw_interface_panel_as_panel(C,
layout,
properties_ptr,
sub_interface_panel,
fn_input_is_visible,
fn_input_is_active,
fn_draw_property_for_socket);
break;
}
case NodeTreeInterfaceItemType::Socket: {
const auto &interface_socket = *reinterpret_cast<const bNodeTreeInterfaceSocket *>(item);
if (interface_socket.flag & NODE_INTERFACE_SOCKET_INPUT) {
if (!(interface_socket.flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER)) {
PointerRNA inputs_ptr = RNA_pointer_get(properties_ptr, "inputs");
PointerRNA socket_props_ptr = RNA_pointer_get(&inputs_ptr,
interface_socket.identifier);
fn_draw_property_for_socket(layout, interface_socket, &socket_props_ptr, parent_name);
}
}
break;
}
}
}
}
} // namespace blender::nodes

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup nodes
*/
#pragma once
#include <string>
namespace blender {
struct bNodeTree;
/** Groups display their internal tree name as label. */
void node_group_label(const struct bNodeTree *ntree,
const struct bNode *node,
char *label,
int label_maxncpy);
bool node_group_poll_instance(const struct bNode *node,
const struct bNodeTree *nodetree,
const char **r_disabled_hint);
/**
* Global update function for Reroute node types.
* This depends on connected nodes, so must be done as a tree-wide update.
*/
void ntree_update_reroute_nodes(struct bNodeTree *ntree);
std::string node_group_ui_description(const bNode &node);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,311 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup nodes
*/
#include "DNA_node_types.h"
#include "BLI_listbase.h"
#include "BLI_utildefines.h"
#include "BKE_global.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_node_tree_zones.hh"
#include "MEM_guardedalloc.h"
#include "node_exec.hh"
#include "node_util.hh"
namespace blender {
static int node_exec_socket_use_stack(bNodeSocket *sock)
{
/* NOTE: INT and BOOL supported as FLOAT. Only for EEVEE. */
return ELEM(sock->type,
SOCK_INT,
SOCK_BOOLEAN,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_SHADER,
SOCK_ROTATION);
}
bNodeStack *node_get_socket_stack(bNodeStack *stack, bNodeSocket *sock)
{
if (stack && sock && sock->stack_index >= 0) {
return stack + sock->stack_index;
}
return nullptr;
}
void node_get_stack(bNode *node, bNodeStack *stack, bNodeStack **in, bNodeStack **out)
{
/* build pointer stack */
if (in) {
for (bNodeSocket &sock : node->inputs) {
*(in++) = node_get_socket_stack(stack, &sock);
}
}
if (out) {
for (bNodeSocket &sock : node->outputs) {
*(out++) = node_get_socket_stack(stack, &sock);
}
}
}
static void node_init_input_index(bNodeSocket *sock, int *index)
{
/* Only consider existing link when the `from` socket is valid! */
if (sock->link && !(sock->link->flag & NODE_LINK_MUTED) && sock->link->fromsock &&
sock->link->fromsock->stack_index >= 0)
{
sock->stack_index = sock->link->fromsock->stack_index;
}
else {
if (node_exec_socket_use_stack(sock)) {
sock->stack_index = (*index)++;
}
else {
sock->stack_index = -1;
}
}
}
static void node_init_output_index_muted(bNodeSocket *sock,
int *index,
const MutableSpan<bNodeLink> internal_links)
{
const bNodeLink *link;
/* copy the stack index from internally connected input to skip the node */
for (bNodeLink &iter_link : internal_links) {
if (iter_link.tosock == sock) {
sock->stack_index = iter_link.fromsock->stack_index;
/* set the link pointer to indicate that this socket
* should not overwrite the stack value!
*/
sock->link = &iter_link;
link = &iter_link;
break;
}
}
/* if not internally connected, assign a new stack index anyway to avoid bad stack access */
if (!link) {
if (node_exec_socket_use_stack(sock)) {
sock->stack_index = (*index)++;
}
else {
sock->stack_index = -1;
}
}
}
static void node_init_output_index(bNodeSocket *sock, int *index)
{
if (node_exec_socket_use_stack(sock)) {
sock->stack_index = (*index)++;
}
else {
sock->stack_index = -1;
}
}
/* basic preparation of socket stacks */
static bNodeStack *setup_stack(bNodeStack *stack, bNodeTree *ntree, bNode *node, bNodeSocket *sock)
{
bNodeStack *ns = node_get_socket_stack(stack, sock);
if (!ns) {
return nullptr;
}
/* don't mess with remote socket stacks, these are initialized by other nodes! */
if (sock->link && !(sock->link->flag & NODE_LINK_MUTED)) {
return ns;
}
ns->sockettype = sock->type;
switch (sock->type) {
case SOCK_INT:
ns->vec[0] = node_socket_get_int(ntree, node, sock);
break;
case SOCK_BOOLEAN:
ns->vec[0] = node_socket_get_bool(ntree, node, sock);
break;
case SOCK_FLOAT:
ns->vec[0] = node_socket_get_float(ntree, node, sock);
break;
case SOCK_VECTOR:
node_socket_get_vector(ntree, node, sock, ns->vec);
break;
case SOCK_RGBA:
node_socket_get_color(ntree, node, sock, ns->vec);
break;
case SOCK_ROTATION:
node_socket_get_rotation(ntree, node, sock, ns->vec);
break;
default:
break;
}
return ns;
}
static Vector<bNode *> get_node_code_gen_order(bNodeTree &ntree)
{
ntree.ensure_topology_cache();
Vector<bNode *> nodes = ntree.toposort_left_to_right();
const bke::bNodeTreeZones *zones = ntree.zones();
if (!zones) {
return nodes;
}
/* Insertion sort to make sure that all nodes in a zone are packed together right before the zone
* output. */
for (int old_i = nodes.size() - 1; old_i >= 0; old_i--) {
bNode *node = nodes[old_i];
const bke::bNodeTreeZone *zone = zones->get_zone_by_node(node->identifier);
if (!zone) {
/* None outside of any zone can stay where they are. */
continue;
}
if (zone->output_node_id == node->identifier) {
/* The output of a zone should not be moved. */
continue;
}
for (int new_i = old_i + 1; new_i < nodes.size(); new_i++) {
bNode *next_node = nodes[new_i];
const bke::bNodeTreeZone *zone_to_check = zones->get_zone_by_node(next_node->identifier);
if (zone_to_check &&
(zone == zone_to_check || zone->contains_zone_recursively(*zone_to_check)))
{
/* Don't move the node further than the next node in the zone. */
break;
}
std::swap(nodes[new_i - 1], nodes[new_i]);
}
}
return nodes;
}
bNodeTreeExec *ntree_exec_begin(bNodeExecContext *context,
bNodeTree *ntree,
bNodeInstanceKey parent_key)
{
bNodeTreeExec *exec;
bNode *node;
bNodeExec *nodeexec;
bNodeInstanceKey nodekey;
bNodeStack *ns;
int index;
/* XXX: texture-nodes have threading issues with muting, have to disable it there. */
/* ensure all sock->link pointers and node levels are correct */
/* Using global main here is likely totally wrong, not sure what to do about that one though...
* We cannot even check ntree is in global main,
* since most of the time it won't be (thanks to ntree design)!!! */
BKE_ntree_update_after_single_tree_change(*G.main, *ntree);
ntree->ensure_topology_cache();
Vector<bNode *> nodelist = get_node_code_gen_order(*ntree);
/* XXX could let callbacks do this for specialized data */
exec = MEM_new_zeroed<bNodeTreeExec>("node tree execution data");
/* Back-pointer to node tree. */
exec->nodetree = ntree;
/* set stack indices */
index = 0;
for (const int n : nodelist.index_range()) {
node = nodelist[n];
/* init node socket stack indexes */
for (bNodeSocket &sock : node->inputs) {
node_init_input_index(&sock, &index);
}
if (node->is_muted() || node->is_reroute()) {
for (bNodeSocket &sock : node->outputs) {
node_init_output_index_muted(&sock, &index, node->runtime->internal_links);
}
}
else {
for (bNodeSocket &sock : node->outputs) {
node_init_output_index(&sock, &index);
}
}
}
/* allocated exec data pointers for nodes */
exec->totnodes = nodelist.size();
exec->nodeexec = MEM_new_array_zeroed<bNodeExec>(exec->totnodes, "node execution data");
/* allocate data pointer for node stack */
exec->stacksize = index;
exec->stack = MEM_new_array<bNodeStack>(exec->stacksize, "bNodeStack");
/* all non-const results are considered inputs */
int n;
for (n = 0; n < exec->stacksize; n++) {
exec->stack[n].hasinput = 1;
}
/* prepare all nodes for execution */
for (n = 0, nodeexec = exec->nodeexec; n < nodelist.size(); n++, nodeexec++) {
node = nodeexec->node = nodelist[n];
nodeexec->free_exec_fn = node->typeinfo->free_exec_fn;
/* tag inputs */
for (bNodeSocket &sock : node->inputs) {
/* disable the node if an input link is invalid */
if (sock.link && !(sock.link->flag & NODE_LINK_VALID)) {
node->runtime->need_exec = 0;
}
ns = setup_stack(exec->stack, ntree, node, &sock);
if (ns) {
ns->hasoutput = 1;
}
}
/* tag all outputs */
for (bNodeSocket &sock : node->outputs) {
/* ns = */ setup_stack(exec->stack, ntree, node, &sock);
}
nodekey = bke::node_instance_key(parent_key, ntree, node);
if (node->typeinfo->init_exec_fn) {
nodeexec->data.data = node->typeinfo->init_exec_fn(context, node, nodekey);
}
}
return exec;
}
void ntree_exec_end(bNodeTreeExec *exec)
{
bNodeExec *nodeexec;
int n;
if (exec->stack) {
MEM_delete(exec->stack);
}
for (n = 0, nodeexec = exec->nodeexec; n < exec->totnodes; n++, nodeexec++) {
if (nodeexec->free_exec_fn) {
nodeexec->free_exec_fn(nodeexec->data.data);
}
}
if (exec->nodeexec) {
MEM_delete(exec->nodeexec);
}
MEM_delete(exec);
}
} // namespace blender

View File

@@ -0,0 +1,63 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup nodes
*/
#pragma once
#include "DNA_listBase.h"
#include "BKE_node.hh"
#include "node_util.hh"
namespace blender {
struct bNode;
struct bNodeStack;
struct bNodeThreadStack;
struct bNodeTree;
/* Node execution data */
struct bNodeExec {
/** Back-pointer to node. */
bNode *node;
bNodeExecData data;
/** Free function, stored in exec itself to avoid dangling node pointer access. */
bke::NodeFreeExecFunction free_exec_fn;
};
/* Execution Data for each instance of node tree execution */
struct bNodeTreeExec {
bNodeTree *nodetree; /* Back-pointer to node tree. */
int totnodes; /* total node count */
bNodeExec *nodeexec; /* per-node execution data */
int stacksize;
bNodeStack *stack; /* socket data stack */
/* only used by material and texture trees to keep one stack for each thread */
ListBaseT<bNodeThreadStack> *threadstack; /* one instance of the stack for each thread */
};
/* stores one stack copy for each thread (material and texture trees) */
struct bNodeThreadStack {
bNodeThreadStack *next, *prev;
bNodeStack *stack;
bool used;
};
/** For a given socket, find the actual stack entry. */
bNodeStack *node_get_socket_stack(bNodeStack *stack, bNodeSocket *sock);
void node_get_stack(bNode *node, bNodeStack *stack, bNodeStack **in, bNodeStack **out);
bNodeTreeExec *ntree_exec_begin(bNodeExecContext *context,
bNodeTree *ntree,
bNodeInstanceKey parent_key);
void ntree_exec_end(bNodeTreeExec *exec);
} // namespace blender

View File

@@ -0,0 +1,260 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <iostream>
#include "DNA_curves_types.h"
#include "DNA_grease_pencil_types.h"
#include "DNA_mesh_types.h"
#include "DNA_pointcloud_types.h"
#include "DEG_depsgraph_query.hh"
#include "BKE_curves.hh"
#include "BKE_library.hh"
#include "BKE_main.hh"
#include "BKE_node_runtime.hh"
#include "BLI_path_utils.hh"
#include "BLI_string.h"
#include "BLT_translation.hh"
#include "NOD_geometry_exec.hh"
#include "node_geometry_util.hh"
namespace blender::nodes {
Main *GeoNodeExecParams::bmain() const
{
return DEG_get_bmain(this->depsgraph());
}
void GeoNodeExecParams::error_message_add(const NodeWarningType type,
const StringRef message) const
{
if (eval_log::NodeTreeLogger *tree_logger = this->get_local_tree_logger()) {
tree_logger->node_warnings.append(
*tree_logger->allocator,
{node_.identifier, {type, tree_logger->allocator->copy_string(message)}});
}
}
void GeoNodeExecParams::used_named_attribute(const StringRef attribute_name,
const NamedAttributeUsage usage)
{
if (eval_log::NodeTreeLogger *tree_logger = this->get_local_tree_logger()) {
tree_logger->used_named_attributes.append(
*tree_logger->allocator,
{node_.identifier, tree_logger->allocator->copy_string(attribute_name), usage});
}
}
void GeoNodeExecParams::check_input_geometry_set(UString identifier,
const GeometrySet &geometry_set) const
{
const SocketDeclaration &decl = *node_.input_by_identifier(identifier)->runtime->declaration;
const decl::Geometry *geo_decl = dynamic_cast<const decl::Geometry *>(&decl);
if (geo_decl == nullptr) {
return;
}
const bool only_realized_data = geo_decl->only_realized_data();
const bool only_instances = geo_decl->only_instances();
const Span<GeometryComponent::Type> supported_types = geo_decl->supported_types();
if (only_realized_data) {
if (geometry_set.has_instances()) {
this->error_message_add(NodeWarningType::Info,
TIP_("Instances in input geometry are ignored"));
}
}
if (only_instances) {
if (geometry_set.has_realized_data()) {
this->error_message_add(NodeWarningType::Info,
TIP_("Realized data in input geometry is ignored"));
}
}
if (supported_types.is_empty()) {
/* Assume all types are supported. */
return;
}
const Vector<GeometryComponent::Type> types_in_geometry = geometry_set.gather_component_types(
true, true);
for (const GeometryComponent::Type type : types_in_geometry) {
if (type == GeometryComponent::Type::Instance) {
continue;
}
if (supported_types.contains(type)) {
continue;
}
std::string message = RPT_("Input geometry has unsupported type: ");
switch (type) {
case GeometryComponent::Type::Mesh: {
if (const Mesh *mesh = geometry_set.get_mesh()) {
if (mesh->verts_num == 0) {
continue;
}
}
message += RPT_("Mesh");
break;
}
case GeometryComponent::Type::PointCloud: {
if (const PointCloud *pointcloud = geometry_set.get_pointcloud()) {
if (pointcloud->totpoint == 0) {
continue;
}
}
message += RPT_("Point Cloud");
break;
}
case GeometryComponent::Type::Instance: {
BLI_assert_unreachable();
break;
}
case GeometryComponent::Type::Volume: {
message += CTX_RPT_(BLT_I18NCONTEXT_ID_ID, "Volume");
break;
}
case GeometryComponent::Type::Curve: {
if (const Curves *curves = geometry_set.get_curves()) {
if (curves->geometry.point_num == 0) {
continue;
}
}
message += RPT_("Curve");
break;
}
case GeometryComponent::Type::Edit: {
continue;
}
case GeometryComponent::Type::GreasePencil: {
if (const GreasePencil *grease_pencil = geometry_set.get_grease_pencil()) {
if (grease_pencil->drawing_array_num == 0) {
continue;
}
}
message += RPT_("Grease Pencil");
break;
}
}
this->error_message_add(NodeWarningType::Info, std::move(message));
}
}
void GeoNodeExecParams::check_output_geometry_set(const GeometrySet &geometry_set) const
{
UNUSED_VARS_NDEBUG(geometry_set);
#ifndef NDEBUG
if (const bke::CurvesEditHints *curve_edit_hints = geometry_set.get_curve_edit_hints()) {
/* If this is not valid, it's likely that the number of stored deformed points does not match
* the number of points in the original data. */
BLI_assert(curve_edit_hints->is_valid());
}
#endif
}
void GeoNodeExecParams::set_default_remaining_outputs()
{
set_default_remaining_node_outputs(params_, node_);
}
void GeoNodeExecParams::check_input_access(const UString identifier) const
{
const bNodeSocket *found_socket = nullptr;
for (const bNodeSocket *socket : node_.input_sockets()) {
if (socket->identifier_ustr() == identifier) {
found_socket = socket;
break;
}
}
if (found_socket == nullptr) {
std::cout << "Did not find an input socket with the identifier '" << identifier.ref()
<< "'.\n";
std::cout << "Possible identifiers are: ";
for (const bNodeSocket *socket : node_.input_sockets()) {
if (socket->is_available()) {
std::cout << "'" << socket->identifier << "', ";
}
}
std::cout << "\n";
BLI_assert_unreachable();
}
else if (found_socket->flag & SOCK_UNAVAIL) {
std::cout << "The socket corresponding to the identifier '" << identifier.ref()
<< "' is disabled.\n";
BLI_assert_unreachable();
}
}
void GeoNodeExecParams::check_output_access(const UString identifier) const
{
const bNodeSocket *found_socket = nullptr;
for (const bNodeSocket *socket : node_.output_sockets()) {
if (socket->identifier == identifier) {
found_socket = socket;
break;
}
}
if (found_socket == nullptr) {
std::cout << "Did not find an output socket with the identifier '" << identifier.ref()
<< "'.\n";
std::cout << "Possible identifiers are: ";
for (const bNodeSocket *socket : node_.output_sockets()) {
if (socket->is_available()) {
std::cout << "'" << socket->identifier << "', ";
}
}
std::cout << "\n";
BLI_assert_unreachable();
}
else if (found_socket->flag & SOCK_UNAVAIL) {
std::cout << "The socket corresponding to the identifier '" << identifier.ref()
<< "' is disabled.\n";
BLI_assert_unreachable();
}
else if (params_.output_was_set(this->get_output_index(identifier))) {
std::cout << "The identifier '" << identifier.ref() << "' has been set already.\n";
BLI_assert_unreachable();
}
}
AttributeFilter::Result NodeAttributeFilter::filter(const StringRef attribute_name) const
{
if (!bke::attribute_name_is_anonymous(attribute_name)) {
return AttributeFilter::Result::Process;
}
if (!set_.names) {
return AttributeFilter::Result::AllowSkip;
}
if (set_.names->contains(attribute_name)) {
return AttributeFilter::Result::Process;
}
return AttributeFilter::Result::AllowSkip;
}
std::optional<std::string> GeoNodeExecParams::ensure_absolute_path(const StringRefNull path) const
{
if (path.is_empty()) {
return std::nullopt;
}
if (!BLI_path_is_rel(path.c_str())) {
return path;
}
const Main &bmain = *this->bmain();
const bNodeTree &tree = node_.owner_tree();
const char *base_path = ID_BLEND_PATH(&bmain, &tree.id);
if (!base_path || base_path[0] == '\0') {
return std::nullopt;
}
char absolute_path[FILE_MAX];
STRNCPY(absolute_path, path.c_str());
BLI_path_abs(absolute_path, base_path);
return absolute_path;
}
} // namespace blender::nodes

View File

@@ -0,0 +1,232 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "testing/testing.h"
/* Allow using `Scene->nodetree` because it's still relevant for backward compatibility. */
#define DNA_DEPRECATED_ALLOW
#include "DNA_material_types.h"
#include "DNA_scene_types.h"
#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_gtest_base.hh"
#include "BKE_idtype.hh"
#include "BKE_main.hh"
#include "BKE_material.hh"
#include "BKE_node.hh"
#include "BKE_scene.hh"
#include "ED_node_c.hh"
#include "RNA_define.hh"
#include "NOD_defaults.hh"
namespace blender::nodes::tests {
class NodeTest : public bke::BlenderGTestBase {
protected:
struct IteratorResult {
Vector<bNodeTree *> node_trees;
Vector<ID *> ids;
};
IteratorResult get_node_trees(Main *bmain)
{
IteratorResult iter_result;
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
iter_result.node_trees.append(ntree);
iter_result.ids.append(id);
}
FOREACH_NODETREE_END;
return iter_result;
};
};
class TestData {
public:
Main *bmain = nullptr;
bContext *C = nullptr;
TestData()
{
if (bmain == nullptr) {
bmain = BKE_main_new();
G.main = bmain;
}
if (C == nullptr) {
C = CTX_create();
CTX_data_main_set(C, bmain);
}
}
~TestData()
{
if (bmain != nullptr) {
BKE_main_free(bmain);
bmain = nullptr;
G.main = nullptr;
}
if (C != nullptr) {
CTX_free(C);
C = nullptr;
}
}
};
TEST_F(NodeTest, tree_iterator_empty)
{
TestData context;
IteratorResult iter_result = this->get_node_trees(context.bmain);
EXPECT_EQ(iter_result.node_trees.size(), 0);
EXPECT_EQ(iter_result.ids.size(), 0);
}
TEST_F(NodeTest, tree_iterator_1_mat)
{
TestData context;
Material *material = BKE_material_add(context.bmain, "Material");
nodes::node_tree_shader_default(context.C, context.bmain, &material->id);
IteratorResult iter_result = this->get_node_trees(context.bmain);
ASSERT_EQ(iter_result.node_trees.size(), 1);
ASSERT_EQ(iter_result.ids.size(), 1);
EXPECT_EQ(GS(iter_result.ids[0]->name), ID_MA);
}
TEST_F(NodeTest, tree_iterator_scene_no_tree)
{
TestData context;
Material *material = BKE_material_add(context.bmain, "Material");
nodes::node_tree_shader_default(context.C, context.bmain, &material->id);
BKE_scene_add(context.bmain, "Scene");
IteratorResult iter_result = this->get_node_trees(context.bmain);
ASSERT_EQ(iter_result.node_trees.size(), 1);
ASSERT_EQ(iter_result.ids.size(), 1);
EXPECT_EQ(GS(iter_result.ids[0]->name), ID_MA);
}
TEST_F(NodeTest, tree_iterator_1mat_1scene)
{
TestData context;
const char SCENE_NAME[MAX_ID_NAME] = "Scene for testing";
Material *material = BKE_material_add(context.bmain, "Material");
nodes::node_tree_shader_default(context.C, context.bmain, &material->id);
Scene *scene = BKE_scene_add(context.bmain, SCENE_NAME);
/* Embedded compositing trees are deprecated, but still relevant for versioning/backward
* compatibility. */
scene->nodetree = bke::node_tree_add_tree_embedded(
context.bmain, &scene->id, "compositing nodetree", "CompositorNodeTree");
IteratorResult iter_result = this->get_node_trees(context.bmain);
ASSERT_EQ(iter_result.node_trees.size(), 2);
ASSERT_EQ(iter_result.ids.size(), 2);
EXPECT_EQ(GS(iter_result.ids[1]->name), ID_MA);
EXPECT_EQ(GS(iter_result.ids[0]->name), ID_SCE);
EXPECT_STREQ(iter_result.ids[0]->name + 2, SCENE_NAME);
/* `scene->nodetree` is not managed by the scene anymore, i.e. `scene_free_data()` doesn't free
* its embedded node-trees, so we need to free it manually here. */
bke::node_tree_free_embedded_tree(scene->nodetree);
MEM_delete(scene->nodetree);
scene->nodetree = nullptr;
}
TEST_F(NodeTest, tree_iterator_1mat_3scenes)
{
TestData context;
const char SCENE_NAME_1[MAX_ID_NAME] = "Scene 1";
const char SCENE_NAME_2[MAX_ID_NAME] = "Scene 2";
const char SCENE_NAME_3[MAX_ID_NAME] = "Scene 3";
const char NTREE_NAME[MAX_NAME] = "Test Composisiting Nodetree";
/* Name is hard-coded in #nodes::node_tree_shader_default(). */
const char MATERIAL_NTREE_NAME[MAX_NAME] = "Shader Nodetree";
Material *material = BKE_material_add(context.bmain, "Material");
nodes::node_tree_shader_default(context.C, context.bmain, &material->id);
BKE_scene_add(context.bmain, SCENE_NAME_1);
/* Note: no node tree for scene 1. */
Scene *scene2 = BKE_scene_add(context.bmain, SCENE_NAME_2);
scene2->nodetree = bke::node_tree_add_tree_embedded(
context.bmain, &scene2->id, NTREE_NAME, "CompositorNodeTree");
BKE_scene_add(context.bmain, SCENE_NAME_3);
/* Also no node tree for scene 3. */
IteratorResult iter_result = this->get_node_trees(context.bmain);
ASSERT_EQ(iter_result.node_trees.size(), 2);
ASSERT_EQ(iter_result.ids.size(), 2);
/* Expect that scenes with no node-trees don't have side effects for node trees. */
EXPECT_EQ(GS(iter_result.ids[0]->name), ID_SCE);
EXPECT_STREQ(iter_result.ids[0]->name + 2, SCENE_NAME_2);
EXPECT_STREQ(iter_result.node_trees[0]->id.name + 2, NTREE_NAME);
EXPECT_EQ(GS(iter_result.ids[1]->name), ID_MA);
EXPECT_STREQ(iter_result.node_trees[1]->id.name + 2, MATERIAL_NTREE_NAME);
/* `scene->nodetree` is not managed by the scene anymore, i.e. `scene_free_data()` doesn't free
* its embedded node-trees, so we need to free it manually here. */
bke::node_tree_free_embedded_tree(scene2->nodetree);
MEM_delete(scene2->nodetree);
scene2->nodetree = nullptr;
}
TEST_F(NodeTest, tree_iterator_1mat_1scene_2compositing_trees)
{
TestData context;
const char SCENE_NAME_1[MAX_ID_NAME - 2] = "Scene 1";
const char NTREE_NAME_1[MAX_ID_NAME - 2] = "Test Composisiting Node Tree 1";
const char NTREE_NAME_2[MAX_ID_NAME - 2] = "Test Composisiting Node Tree 2";
const char MATERIAL_NTREE_NAME[MAX_NAME] = "Shader Nodetree";
Material *material = BKE_material_add(context.bmain, "Material");
nodes::node_tree_shader_default(context.C, context.bmain, &material->id);
BKE_scene_add(context.bmain, SCENE_NAME_1);
bke::node_tree_add_tree(context.bmain, NTREE_NAME_1, "CompositorNodeTree");
bke::node_tree_add_tree(context.bmain, NTREE_NAME_2, "CompositorNodeTree");
IteratorResult iter_result = this->get_node_trees(context.bmain);
ASSERT_EQ(iter_result.node_trees.size(), 3);
ASSERT_EQ(iter_result.ids.size(), 3);
/* Iterator should return 2 compositing node trees and no scene node tree. */
EXPECT_EQ(GS(iter_result.ids[0]->name), ID_NT);
EXPECT_STREQ(iter_result.ids[0]->name + 2, NTREE_NAME_1);
EXPECT_FALSE((iter_result.ids[0]->flag & ID_FLAG_EMBEDDED_DATA));
EXPECT_EQ(GS(iter_result.ids[1]->name), ID_NT);
EXPECT_STREQ(iter_result.ids[1]->name + 2, NTREE_NAME_2);
EXPECT_FALSE((iter_result.ids[1]->flag & ID_FLAG_EMBEDDED_DATA));
EXPECT_EQ(GS(iter_result.ids[2]->name), ID_MA);
EXPECT_STREQ(iter_result.node_trees[2]->id.name + 2, MATERIAL_NTREE_NAME);
}
} // namespace blender::nodes::tests

View File

@@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_multi_function.hh"
#include "BKE_node_runtime.hh"
namespace blender::nodes {
NodeMultiFunctions::NodeMultiFunctions(const bNodeTree &tree,
const std::shared_ptr<const bNodeTree> &shared_tree)
{
tree.ensure_topology_cache();
for (const bNode *bnode : tree.all_nodes()) {
if (bnode->typeinfo->build_multi_function == nullptr) {
continue;
}
NodeMultiFunctionBuilder builder{*bnode, tree, shared_tree};
bnode->typeinfo->build_multi_function(builder);
if (builder.built_fn_ != nullptr) {
map_.add_new(bnode, {builder.built_fn_, std::move(builder.owned_built_fn_)});
}
}
}
} // namespace blender::nodes

View File

@@ -0,0 +1,161 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_composite.hh"
#include "NOD_geometry.hh"
#include "NOD_register.hh"
#include "NOD_socket.hh"
#include "BKE_node.hh"
#include "BKE_node_legacy_types.hh"
#include "BLT_translation.hh"
#include "UI_resources.hh"
namespace blender {
static bool node_undefined_poll(const bke::bNodeType * /*ntype*/,
const bNodeTree * /*nodetree*/,
const char ** /*r_disabled_hint*/)
{
/* this type can not be added deliberately, it's just a placeholder */
return false;
}
/* register fallback types used for undefined tree, nodes, sockets */
static void register_undefined_types()
{
/* NOTE: these types are not registered in the type hashes,
* they are just used as placeholders in case the actual types are not registered.
*/
bke::NodeTreeTypeUndefined.type = NTREE_UNDEFINED;
bke::NodeTreeTypeUndefined.idname = "NodeTreeUndefined"_ustr;
bke::NodeTreeTypeUndefined.ui_name = N_("Undefined");
bke::NodeTreeTypeUndefined.ui_description = N_("Undefined Node Tree Type");
bke::node_type_base_custom(bke::NodeTypeUndefined, "NodeUndefined", "Undefined", "UNDEFINED", 0);
bke::NodeTypeUndefined.poll = node_undefined_poll;
bke::NodeSocketTypeUndefined.idname = "NodeSocketUndefined"_ustr;
/* extra type info for standard socket types */
bke::NodeSocketTypeUndefined.type = SOCK_CUSTOM;
bke::NodeSocketTypeUndefined.subtype = PROP_NONE;
bke::NodeSocketTypeUndefined.use_link_limits_of_type = true;
bke::NodeSocketTypeUndefined.input_link_limit = 0xFFF;
bke::NodeSocketTypeUndefined.output_link_limit = 0xFFF;
}
class SimulationZoneType : public bke::bNodeZoneType {
public:
SimulationZoneType()
{
this->input_idname = "GeometryNodeSimulationInput"_ustr;
this->output_idname = "GeometryNodeSimulationOutput"_ustr;
this->input_type = GEO_NODE_SIMULATION_INPUT;
this->output_type = GEO_NODE_SIMULATION_OUTPUT;
this->theme_id = TH_NODE_ZONE_SIMULATION;
}
const int &get_corresponding_output_id(const bNode &input_bnode) const override
{
BLI_assert(input_bnode.type_legacy == this->input_type);
return static_cast<NodeGeometrySimulationInput *>(input_bnode.storage)->output_node_id;
}
};
class RepeatZoneType : public bke::bNodeZoneType {
public:
RepeatZoneType()
{
this->input_idname = "GeometryNodeRepeatInput"_ustr;
this->output_idname = "GeometryNodeRepeatOutput"_ustr;
this->input_type = GEO_NODE_REPEAT_INPUT;
this->output_type = GEO_NODE_REPEAT_OUTPUT;
this->theme_id = TH_NODE_ZONE_REPEAT;
}
const int &get_corresponding_output_id(const bNode &input_bnode) const override
{
BLI_assert(input_bnode.type_legacy == this->input_type);
return static_cast<NodeGeometryRepeatInput *>(input_bnode.storage)->output_node_id;
}
};
class ForeachGeometryElementZoneType : public bke::bNodeZoneType {
public:
ForeachGeometryElementZoneType()
{
this->input_idname = "GeometryNodeForeachGeometryElementInput"_ustr;
this->output_idname = "GeometryNodeForeachGeometryElementOutput"_ustr;
this->input_type = GEO_NODE_FOREACH_GEOMETRY_ELEMENT_INPUT;
this->output_type = GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT;
this->theme_id = TH_NODE_ZONE_FOREACH_GEOMETRY_ELEMENT;
}
const int &get_corresponding_output_id(const bNode &input_bnode) const override
{
BLI_assert(input_bnode.type_legacy == this->input_type);
return static_cast<NodeGeometryForeachGeometryElementInput *>(input_bnode.storage)
->output_node_id;
}
};
class ClosureZoneType : public bke::bNodeZoneType {
public:
ClosureZoneType()
{
this->input_idname = "NodeClosureInput"_ustr;
this->output_idname = "NodeClosureOutput"_ustr;
this->input_type = NODE_CLOSURE_INPUT;
this->output_type = 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<NodeClosureInput *>(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;
bke::register_node_zone_type(simulation_zone_type);
bke::register_node_zone_type(repeat_zone_type);
bke::register_node_zone_type(foreach_geometry_element_zone_type);
bke::register_node_zone_type(closure_zone_type);
}
void register_nodes()
{
register_zone_types();
register_undefined_types();
register_standard_node_socket_types();
register_node_tree_type_geo();
register_node_tree_type_cmp();
register_node_type_frame();
register_node_type_reroute();
register_node_type_implicit_conversion();
register_node_type_group_input();
register_node_type_group_output();
register_compositor_nodes();
register_shader_nodes();
register_texture_nodes();
register_geometry_nodes();
register_function_nodes();
}
} // namespace blender

View File

@@ -0,0 +1,77 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_rna_define.hh"
namespace blender::nodes {
const EnumPropertyItem *enum_items_filter(const EnumPropertyItem *original_item_array,
FunctionRef<bool(const EnumPropertyItem &item)> fn)
{
EnumPropertyItem *item_array = nullptr;
int items_len = 0;
for (const EnumPropertyItem *item = original_item_array; item->identifier != nullptr; item++) {
if (fn(*item)) {
RNA_enum_item_add(&item_array, &items_len, item);
}
}
RNA_enum_item_end(&item_array, &items_len);
return item_array;
}
PropertyRNA *RNA_def_node_enum(StructRNA *srna,
const char *identifier,
const char *ui_name,
const char *ui_description,
const EnumPropertyItem *static_items,
const EnumRNAAccessors accessors,
std::optional<int> default_value,
const EnumPropertyItemFunc item_func,
const bool allow_animation)
{
PropertyRNA *prop = RNA_def_property(srna, identifier, PROP_ENUM, PROP_NONE);
RNA_def_property_enum_funcs_runtime(
prop, accessors.getter, accessors.setter, item_func, nullptr, nullptr);
RNA_def_property_enum_items(prop, static_items);
if (default_value.has_value()) {
RNA_def_property_enum_default(prop, *default_value);
}
RNA_def_property_ui_text(prop, ui_name, ui_description);
if (allow_animation) {
RNA_def_property_update_runtime(prop, rna_Node_update);
}
else {
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_update_runtime(prop, rna_Node_socket_update);
}
RNA_def_property_update_notifier(prop, NC_NODE | NA_EDITED);
return prop;
}
PropertyRNA *RNA_def_node_boolean(StructRNA *srna,
const char *identifier,
const char *ui_name,
const char *ui_description,
const BooleanRNAAccessors accessors,
std::optional<bool> default_value,
bool allow_animation)
{
PropertyRNA *prop = RNA_def_property(srna, identifier, PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_funcs_runtime(
prop, accessors.getter, accessors.setter, nullptr, nullptr);
if (default_value.has_value()) {
RNA_def_property_boolean_default(prop, *default_value);
}
RNA_def_property_ui_text(prop, ui_name, ui_description);
if (!allow_animation) {
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
}
RNA_def_property_update_runtime(prop, rna_Node_socket_update);
RNA_def_property_update_notifier(prop, NC_NODE | NA_EDITED);
return prop;
}
} // namespace blender::nodes

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,80 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "testing/testing.h"
#include "BKE_global.hh"
#include "BKE_gtest_base.hh"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.hh"
#include "DNA_node_types.h"
namespace blender::nodes::tests {
class StructureTypeInferenceTest : public bke::BlenderGTestBase {};
class TestData {
public:
Main *bmain = nullptr;
TestData()
{
bmain = BKE_main_new();
G.main = bmain;
}
~TestData()
{
BKE_main_free(bmain);
G.main = nullptr;
}
};
static bNode &add_field_boolean_node(bNodeTree &tree)
{
return *bke::node_add_node(nullptr, tree, "GeometryNodeInputNamedAttribute"_ustr);
}
static bNode &add_switch_node(Main &bmain, bNodeTree &tree, const eNodeSocketDatatype socket_type)
{
bNode &node = *bke::node_add_node(nullptr, tree, "GeometryNodeSwitch"_ustr);
NodeSwitch &storage = *static_cast<NodeSwitch *>(node.storage);
storage.input_type = socket_type;
BKE_ntree_update_after_single_tree_change(bmain, tree);
return node;
}
static StructureType infer_switch_output_structure_with_field_condition(
const eNodeSocketDatatype socket_type)
{
TestData data;
bNodeTree &tree = *bke::node_tree_add_tree(data.bmain, "Test", "GeometryNodeTree");
bNode &field_node = add_field_boolean_node(tree);
bNode &switch_node = add_switch_node(*data.bmain, tree, socket_type);
bke::node_add_link(tree,
field_node,
*field_node.output_by_identifier("Exists"_ustr),
switch_node,
*switch_node.input_by_identifier("Switch"_ustr));
BKE_ntree_update_after_single_tree_change(*data.bmain, tree);
return switch_node.output_by_identifier("Output"_ustr)->runtime->inferred_structure_type;
}
TEST_F(StructureTypeInferenceTest, GeometryOutputDoesNotBecomeField)
{
EXPECT_EQ(infer_switch_output_structure_with_field_condition(SOCK_GEOMETRY),
StructureType::Single);
}
TEST_F(StructureTypeInferenceTest, ValueOutputCanBecomeField)
{
EXPECT_EQ(infer_switch_output_structure_with_field_condition(SOCK_FLOAT), StructureType::Field);
}
} // namespace blender::nodes::tests

View File

@@ -0,0 +1,178 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "DNA_material_types.h"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
#include "DNA_world_types.h"
#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_material.hh"
#include "BKE_node.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.hh"
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_string_utf8.h"
#include "BLT_translation.hh"
#include "NOD_composite.hh"
#include "NOD_defaults.hh"
#include "NOD_shader.h"
namespace blender::nodes {
void node_tree_shader_default(const bContext *C, Main *bmain, ID *id)
{
if (GS(id->name) == ID_MA) {
/* Materials */
Object *ob = (C) ? CTX_data_active_object(C) : nullptr;
Material *ma = reinterpret_cast<Material *>(id);
Material *ma_default;
if (ob && ob->type == OB_VOLUME) {
ma_default = BKE_material_default_volume();
}
else {
ma_default = BKE_material_default_surface();
}
if (ma->nodetree) {
bke::node_tree_free_embedded_tree(ma->nodetree);
MEM_delete(ma->nodetree);
ma->nodetree = nullptr;
}
ma->nodetree = bke::node_tree_copy_tree(bmain, *ma_default->nodetree);
ma->nodetree->owner_id = &ma->id;
for (bNode *node_iter : ma->nodetree->all_nodes()) {
STRNCPY_UTF8(node_iter->name, DATA_(node_iter->name));
bke::node_unique_name(*ma->nodetree, *node_iter);
}
BKE_ntree_update_after_single_tree_change(*bmain, *ma->nodetree);
}
else if (ELEM(GS(id->name), ID_WO, ID_LA)) {
/* Emission */
bNode *shader, *output;
bNodeTree *ntree = nullptr;
if (GS(id->name) == ID_WO) {
World *world = reinterpret_cast<World *>(id);
ntree = world->nodetree;
shader = bke::node_add_static_node(nullptr, *ntree, SH_NODE_BACKGROUND);
output = bke::node_add_static_node(nullptr, *ntree, SH_NODE_OUTPUT_WORLD);
bke::node_add_link(*ntree,
*shader,
*bke::node_find_socket(*shader, SOCK_OUT, "Background"_ustr),
*output,
*bke::node_find_socket(*output, SOCK_IN, "Surface"_ustr));
bNodeSocket *color_sock = bke::node_find_socket(*shader, SOCK_IN, "Color"_ustr);
copy_v3_v3((reinterpret_cast<bNodeSocketValueRGBA *>(color_sock->default_value))->value,
&world->horr);
}
else {
ntree = bke::node_tree_add_tree_embedded(
nullptr, id, "Shader Nodetree", ntreeType_Shader->idname.ref());
shader = bke::node_add_static_node(nullptr, *ntree, SH_NODE_EMISSION);
output = bke::node_add_static_node(nullptr, *ntree, SH_NODE_OUTPUT_LIGHT);
bke::node_add_link(*ntree,
*shader,
*bke::node_find_socket(*shader, SOCK_OUT, "Emission"_ustr),
*output,
*bke::node_find_socket(*output, SOCK_IN, "Surface"_ustr));
}
shader->location[0] = -200.0f;
shader->location[1] = 100.0f;
output->location[0] = 200.0f;
output->location[1] = 100.0f;
bke::node_set_active(*ntree, *output);
BKE_ntree_update_after_single_tree_change(*bmain, *ntree);
}
else {
printf("node_tree_shader_default() called on wrong ID type.\n");
return;
}
}
void node_tree_composit_default(const bContext *C, Scene *sce)
{
Main *bmain = CTX_data_main(C);
/* but lets check it anyway */
if (sce->compositing_node_group) {
if (G.debug & G_DEBUG) {
printf("error in composite initialize\n");
}
return;
}
sce->compositing_node_group = bke::node_tree_add_tree(
bmain, DATA_("Compositor Nodes"), ntreeType_Composite->idname.ref());
node_tree_composit_default_init(C, sce->compositing_node_group);
BKE_ntree_update_after_single_tree_change(*bmain, *sce->compositing_node_group);
}
void node_tree_composit_default_init(const bContext *C, bNodeTree *ntree)
{
BLI_assert(ntree != nullptr && ntree->type == NTREE_COMPOSIT);
BLI_assert(ntree->nodes.count() == 0);
ntree->tree_interface.add_socket(
DATA_("Image"), "", "NodeSocketColor", NODE_INTERFACE_SOCKET_INPUT, nullptr);
ntree->tree_interface.add_socket(
DATA_("Image"), "", "NodeSocketColor", NODE_INTERFACE_SOCKET_OUTPUT, nullptr);
bNode *composite = bke::node_add_node(C, *ntree, "NodeGroupOutput"_ustr);
composite->location[0] = 200.0f;
/* The asset shelf is visible by default, so add a small offset to keep nodes centered in the
* visible area.*/
composite->location[1] = 100.0f;
bNode *in = bke::node_add_static_node(C, *ntree, CMP_NODE_R_LAYERS);
in->location[0] = -150.0f - in->width;
in->location[1] = 100.0f;
bke::node_set_active(*ntree, *in);
in->flag &= ~NODE_PREVIEW;
bNode *reroute = bke::node_add_static_node(C, *ntree, NODE_REROUTE);
reroute->location[0] = 100.0f;
reroute->location[1] = 65.0f;
bNode *viewer = bke::node_add_static_node(C, *ntree, CMP_NODE_VIEWER);
viewer->location[0] = 200.0f;
viewer->location[1] = 20.0f;
/* Viewer and Composite nodes are linked to Render Layer's output image socket through a reroute
* node. */
bke::node_add_link(*ntree,
*in,
*reinterpret_cast<bNodeSocket *>(in->outputs.first),
*reroute,
*reinterpret_cast<bNodeSocket *>(reroute->inputs.first));
bke::node_add_link(*ntree,
*reroute,
*reinterpret_cast<bNodeSocket *>(reroute->outputs.first),
*composite,
*reinterpret_cast<bNodeSocket *>(composite->inputs.first));
bke::node_add_link(*ntree,
*reroute,
*reinterpret_cast<bNodeSocket *>(reroute->outputs.first),
*viewer,
*reinterpret_cast<bNodeSocket *>(viewer->inputs.first));
BKE_ntree_update_after_single_tree_change(*CTX_data_main(C), *ntree);
}
} // namespace blender::nodes

View File

@@ -0,0 +1,321 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup nodes
*/
#include <cctype>
#include <cstring>
#include "DNA_node_types.h"
#include "BLI_listbase.h"
#include "BLI_math_rotation.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "BLT_translation.hh"
#include "BKE_colortools.hh"
#include "BKE_node.hh"
#include "BKE_node_tree_update.hh"
#include "RNA_access.hh"
#include "RNA_enum_types.hh"
#include "RNA_prototypes.hh"
#include "MEM_guardedalloc.h"
#include "node_util.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Storage Data
* \{ */
void node_free_curves(bNode *node)
{
BKE_curvemapping_free(static_cast<CurveMapping *>(node->storage));
}
void node_free_standard_storage(bNode *node)
{
if (node->storage) {
MEM_delete_void(node->storage);
}
}
void node_copy_curves(bNodeTree * /*dest_ntree*/, bNode *dest_node, const bNode *src_node)
{
dest_node->storage = BKE_curvemapping_copy(static_cast<CurveMapping *>(src_node->storage));
}
void node_copy_standard_storage(bNodeTree * /*dest_ntree*/,
bNode *dest_node,
const bNode *src_node)
{
dest_node->storage = MEM_dupalloc_void(src_node->storage);
}
void *node_initexec_curves(bNodeExecContext * /*context*/, bNode *node, bNodeInstanceKey /*key*/)
{
BKE_curvemapping_init(static_cast<CurveMapping *>(node->storage));
return nullptr; /* unused return */
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Updates
* \{ */
void node_sock_label(bNodeSocket *sock, const char *name)
{
STRNCPY_UTF8(sock->label, name);
}
void node_sock_label_clear(bNodeSocket *sock)
{
if (sock->label[0] != '\0') {
sock->label[0] = '\0';
}
}
void node_math_update(bNodeTree *ntree, bNode *node)
{
bNodeSocket *sock2 = static_cast<bNodeSocket *>(BLI_findlink(&node->inputs, 1));
bNodeSocket *sock3 = static_cast<bNodeSocket *>(BLI_findlink(&node->inputs, 2));
bke::node_set_socket_availability(*ntree,
*sock2,
!ELEM(node->custom1,
NODE_MATH_SQRT,
NODE_MATH_SIGN,
NODE_MATH_CEIL,
NODE_MATH_SINE,
NODE_MATH_ROUND,
NODE_MATH_FLOOR,
NODE_MATH_COSINE,
NODE_MATH_ARCSINE,
NODE_MATH_TANGENT,
NODE_MATH_ABSOLUTE,
NODE_MATH_RADIANS,
NODE_MATH_DEGREES,
NODE_MATH_FRACTION,
NODE_MATH_ARCCOSINE,
NODE_MATH_ARCTANGENT) &&
!ELEM(node->custom1,
NODE_MATH_INV_SQRT,
NODE_MATH_TRUNC,
NODE_MATH_EXPONENT,
NODE_MATH_COSH,
NODE_MATH_SINH,
NODE_MATH_TANH));
bke::node_set_socket_availability(*ntree,
*sock3,
ELEM(node->custom1,
NODE_MATH_COMPARE,
NODE_MATH_MULTIPLY_ADD,
NODE_MATH_WRAP,
NODE_MATH_SMOOTH_MIN,
NODE_MATH_SMOOTH_MAX));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Labels
* \{ */
void node_blend_label(const bNodeTree * /*ntree*/,
const bNode *node,
char *label,
int label_maxncpy)
{
const char *name;
bool enum_label = RNA_enum_name(rna_enum_ramp_blend_items, node->custom1, &name);
if (!enum_label) {
name = CTX_N_(BLT_I18NCONTEXT_COLOR, "Unknown");
}
BLI_strncpy_utf8(label, CTX_IFACE_(BLT_I18NCONTEXT_COLOR, name), label_maxncpy);
}
void node_image_label(const bNodeTree * /*ntree*/,
const bNode *node,
char *label,
int label_maxncpy)
{
if (node->id == nullptr) {
BLI_strncpy(label, IFACE_(node->typeinfo->ui_name.c_str()), label_maxncpy);
return;
}
BLI_strncpy(label, node->id->name + 2, label_maxncpy);
}
void node_math_label(const bNodeTree * /*ntree*/,
const bNode *node,
char *label,
int label_maxncpy)
{
const char *name;
bool enum_label = RNA_enum_name(rna_enum_node_math_items, node->custom1, &name);
if (!enum_label) {
name = CTX_N_(BLT_I18NCONTEXT_ID_NODETREE, "Unknown");
}
BLI_strncpy_utf8(label, CTX_IFACE_(BLT_I18NCONTEXT_ID_NODETREE, name), label_maxncpy);
}
void node_vector_math_label(const bNodeTree * /*ntree*/,
const bNode *node,
char *label,
int label_maxncpy)
{
const char *name;
bool enum_label = RNA_enum_name(rna_enum_node_vec_math_items, node->custom1, &name);
if (!enum_label) {
name = CTX_N_(BLT_I18NCONTEXT_ID_NODETREE, "Unknown");
}
BLI_strncpy_utf8(label, CTX_IFACE_(BLT_I18NCONTEXT_ID_NODETREE, name), label_maxncpy);
}
void node_combsep_color_label(const ListBaseT<bNodeSocket> *sockets, NodeCombSepColorMode mode)
{
bNodeSocket *sock1 = static_cast<bNodeSocket *>(sockets->first);
bNodeSocket *sock2 = sock1->next;
bNodeSocket *sock3 = sock2->next;
node_sock_label_clear(sock1);
node_sock_label_clear(sock2);
node_sock_label_clear(sock3);
switch (mode) {
case NODE_COMBSEP_COLOR_RGB:
node_sock_label(sock1, "Red");
node_sock_label(sock2, "Green");
node_sock_label(sock3, "Blue");
break;
case NODE_COMBSEP_COLOR_HSL:
node_sock_label(sock1, "Hue");
node_sock_label(sock2, "Saturation");
node_sock_label(sock3, "Lightness");
break;
case NODE_COMBSEP_COLOR_HSV:
node_sock_label(sock1, "Hue");
node_sock_label(sock2, "Saturation");
node_sock_label(sock3, "Value");
break;
default: {
BLI_assert_unreachable();
break;
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Link Insertion
* \{ */
bool node_insert_link_default(bke::NodeInsertLinkParams & /*params*/)
{
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Default value RNA access
* \{ */
int node_socket_get_int(bNodeTree *ntree, bNode * /*node*/, bNodeSocket *sock)
{
PointerRNA ptr = RNA_pointer_create_discrete(id_cast<ID *>(ntree), RNA_NodeSocket, sock);
return RNA_int_get(&ptr, "default_value");
}
void node_socket_set_int(bNodeTree *ntree, bNode * /*node*/, bNodeSocket *sock, int value)
{
PointerRNA ptr = RNA_pointer_create_discrete(id_cast<ID *>(ntree), RNA_NodeSocket, sock);
RNA_int_set(&ptr, "default_value", value);
}
bool node_socket_get_bool(bNodeTree *ntree, bNode * /*node*/, bNodeSocket *sock)
{
PointerRNA ptr = RNA_pointer_create_discrete(id_cast<ID *>(ntree), RNA_NodeSocket, sock);
return RNA_boolean_get(&ptr, "default_value");
}
void node_socket_set_bool(bNodeTree *ntree, bNode * /*node*/, bNodeSocket *sock, bool value)
{
PointerRNA ptr = RNA_pointer_create_discrete(id_cast<ID *>(ntree), RNA_NodeSocket, sock);
RNA_boolean_set(&ptr, "default_value", value);
}
float node_socket_get_float(bNodeTree *ntree, bNode * /*node*/, bNodeSocket *sock)
{
PointerRNA ptr = RNA_pointer_create_discrete(id_cast<ID *>(ntree), RNA_NodeSocket, sock);
return RNA_float_get(&ptr, "default_value");
}
void node_socket_set_float(bNodeTree *ntree, bNode * /*node*/, bNodeSocket *sock, float value)
{
PointerRNA ptr = RNA_pointer_create_discrete(id_cast<ID *>(ntree), RNA_NodeSocket, sock);
RNA_float_set(&ptr, "default_value", value);
}
void node_socket_get_color(bNodeTree *ntree, bNode * /*node*/, bNodeSocket *sock, float *value)
{
PointerRNA ptr = RNA_pointer_create_discrete(id_cast<ID *>(ntree), RNA_NodeSocket, sock);
RNA_float_get_array(&ptr, "default_value", value);
}
void node_socket_set_color(bNodeTree *ntree,
bNode * /*node*/,
bNodeSocket *sock,
const float *value)
{
PointerRNA ptr = RNA_pointer_create_discrete(id_cast<ID *>(ntree), RNA_NodeSocket, sock);
RNA_float_set_array(&ptr, "default_value", value);
}
void node_socket_get_vector(bNodeTree *ntree, bNode * /*node*/, bNodeSocket *sock, float *value)
{
PointerRNA ptr = RNA_pointer_create_discrete(id_cast<ID *>(ntree), RNA_NodeSocket, sock);
RNA_float_get_array(&ptr, "default_value", value);
}
void node_socket_set_vector(bNodeTree *ntree,
bNode * /*node*/,
bNodeSocket *sock,
const float *value)
{
PointerRNA ptr = RNA_pointer_create_discrete(id_cast<ID *>(ntree), RNA_NodeSocket, sock);
RNA_float_set_array(&ptr, "default_value", value);
}
void node_socket_get_rotation(bNodeTree *ntree, bNode * /*node*/, bNodeSocket *sock, float *value)
{
PointerRNA ptr = RNA_pointer_create_discrete(id_cast<ID *>(ntree), RNA_NodeSocket, sock);
float euler[3];
RNA_float_get_array(&ptr, "default_value", euler);
eul_to_quat(value, euler);
}
void node_socket_set_rotation(bNodeTree *ntree,
bNode * /*node*/,
bNodeSocket *sock,
const float *value)
{
PointerRNA ptr = RNA_pointer_create_discrete(id_cast<ID *>(ntree), RNA_NodeSocket, sock);
float euler[3];
quat_to_eul(euler, value);
RNA_float_set_array(&ptr, "default_value", euler);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,75 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup nodes
*/
#pragma once
#include "DNA_node_types.h"
#include "BKE_node.hh"
namespace blender {
struct bNode;
struct bNodeTree;
struct bContext;
/* data for initializing node execution */
struct bNodeExecContext {};
struct bNodeExecData {
void *data; /* custom data storage */
};
/**** Storage Data ****/
void node_free_curves(bNode *node);
void node_free_standard_storage(bNode *node);
void node_copy_curves(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node);
void node_copy_standard_storage(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node);
void *node_initexec_curves(bNodeExecContext *context, bNode *node, bNodeInstanceKey key);
/**** Updates ****/
void node_sock_label(bNodeSocket *sock, const char *name);
void node_sock_label_clear(bNodeSocket *sock);
void node_math_update(bNodeTree *ntree, bNode *node);
/**** Labels ****/
void node_blend_label(const bNodeTree *ntree, const bNode *node, char *label, int label_maxncpy);
void node_image_label(const bNodeTree *ntree, const bNode *node, char *label, int label_maxncpy);
void node_math_label(const bNodeTree *ntree, const bNode *node, char *label, int label_maxncpy);
void node_vector_math_label(const bNodeTree *ntree,
const bNode *node,
char *label,
int label_maxncpy);
void node_combsep_color_label(const ListBaseT<bNodeSocket> *sockets, NodeCombSepColorMode mode);
/*** Link Handling */
/**
* By default there are no links we don't want to connect, when inserting.
*/
bool node_insert_link_default(bke::NodeInsertLinkParams &params);
int node_socket_get_int(bNodeTree *ntree, bNode *node, bNodeSocket *sock);
void node_socket_set_int(bNodeTree *ntree, bNode *node, bNodeSocket *sock, int value);
bool node_socket_get_bool(bNodeTree *ntree, bNode *node, bNodeSocket *sock);
void node_socket_set_bool(bNodeTree *ntree, bNode *node, bNodeSocket *sock, bool value);
float node_socket_get_float(bNodeTree *ntree, bNode *node, bNodeSocket *sock);
void node_socket_set_float(bNodeTree *ntree, bNode *node, bNodeSocket *sock, float value);
void node_socket_get_color(bNodeTree *ntree, bNode *node, bNodeSocket *sock, float *value);
void node_socket_set_color(bNodeTree *ntree, bNode *node, bNodeSocket *sock, const float *value);
void node_socket_get_vector(bNodeTree *ntree, bNode *node, bNodeSocket *sock, float *value);
void node_socket_set_vector(bNodeTree *ntree, bNode *node, bNodeSocket *sock, const float *value);
void node_socket_get_rotation(bNodeTree *ntree, bNode *node, bNodeSocket *sock, float *value);
void node_socket_set_rotation(bNodeTree *ntree,
bNode *node,
bNodeSocket *sock,
const float *value);
} // namespace blender

View File

@@ -0,0 +1,428 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <queue>
#include "NOD_partial_eval.hh"
#include "BKE_compute_contexts.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_node_runtime.hh"
namespace blender::nodes::partial_eval {
bool is_supported_value_node(const bNode &node)
{
return ELEM(node.type_legacy,
SH_NODE_VALUE,
FN_NODE_INPUT_VECTOR,
FN_NODE_INPUT_BOOL,
FN_NODE_INPUT_INT,
FN_NODE_INPUT_ROTATION);
}
/**
* Creates a vector of integer for a node in a context that can be used to order them for
* evaluation.
*/
static Vector<int> get_global_node_sort_vector_right_to_left(const ComputeContext *initial_context,
const bNode &initial_node)
{
Vector<int> vec;
vec.append(initial_node.runtime->toposort_right_to_left_index);
for (const ComputeContext *context = initial_context; context; context = context->parent()) {
if (const auto *group_context = dynamic_cast<const bke::GroupNodeComputeContext *>(context)) {
const bNode *caller_group_node = group_context->node();
BLI_assert(caller_group_node != nullptr);
vec.append(caller_group_node->runtime->toposort_right_to_left_index);
}
}
std::reverse(vec.begin(), vec.end());
return vec;
}
/** Same as above but for the case when evaluating nodes in the opposite order. */
static Vector<int> get_global_node_sort_vector_left_to_right(const ComputeContext *initial_context,
const bNode &initial_node)
{
Vector<int> vec;
vec.append(initial_node.runtime->toposort_left_to_right_index);
for (const ComputeContext *context = initial_context; context; context = context->parent()) {
if (const auto *group_context = dynamic_cast<const bke::GroupNodeComputeContext *>(context)) {
const bNode *caller_group_node = group_context->node();
BLI_assert(caller_group_node != nullptr);
vec.append(caller_group_node->runtime->toposort_left_to_right_index);
}
}
std::reverse(vec.begin(), vec.end());
return vec;
}
/**
* Defines a partial order of #NodeInContext that can be used to evaluate nodes right to left
* (upstream).
* - Downstream nodes are sorted before upstream nodes.
* - Nodes inside a node group are sorted before the group node.
*/
struct NodeInContextUpstreamComparator {
bool operator()(const NodeInContext &a, const NodeInContext &b) const
{
const Vector<int> a_sort_vec = get_global_node_sort_vector_right_to_left(a.context, *a.node);
const Vector<int> b_sort_vec = get_global_node_sort_vector_right_to_left(b.context, *b.node);
const int common_length = std::min(a_sort_vec.size(), b_sort_vec.size());
const Span<int> a_common = Span<int>(a_sort_vec).take_front(common_length);
const Span<int> b_common = Span<int>(b_sort_vec).take_front(common_length);
if (a_common == b_common) {
return a_sort_vec.size() < b_sort_vec.size();
}
return std::lexicographical_compare(
b_common.begin(), b_common.end(), a_common.begin(), a_common.end());
}
};
/**
* Defines a partial order of #NodeInContext that can be used to evaluate nodes left to right
* (downstream).
* - Upstream nodes are sorted before downstream nodes.
* - Nodes inside a node group are sorted before the group node.
*/
struct NodeInContextDownstreamComparator {
bool operator()(const NodeInContext &a, const NodeInContext &b) const
{
const Vector<int> a_sort_vec = get_global_node_sort_vector_left_to_right(a.context, *a.node);
const Vector<int> b_sort_vec = get_global_node_sort_vector_left_to_right(b.context, *b.node);
const int common_length = std::min(a_sort_vec.size(), b_sort_vec.size());
const Span<int> a_common = Span<int>(a_sort_vec).take_front(common_length);
const Span<int> b_common = Span<int>(b_sort_vec).take_front(common_length);
if (a_common == b_common) {
return a_sort_vec.size() < b_sort_vec.size();
}
return std::lexicographical_compare(
b_common.begin(), b_common.end(), a_common.begin(), a_common.end());
}
};
void eval_downstream(
const Span<SocketInContext> initial_sockets,
bke::ComputeContextCache &compute_context_cache,
FunctionRef<void(const NodeInContext &ctx_node,
Vector<const bNodeSocket *> &r_outputs_to_propagate)> evaluate_node_fn,
FunctionRef<bool(const SocketInContext &ctx_from, const SocketInContext &ctx_to)>
propagate_value_fn)
{
/* Priority queue that makes sure that nodes are evaluated in the right order. */
std::priority_queue<NodeInContext, std::vector<NodeInContext>, NodeInContextDownstreamComparator>
scheduled_nodes_queue;
/* Used to make sure that the same node is not scheduled more than once. */
Set<NodeInContext> scheduled_nodes_set;
const auto schedule_node = [&](const NodeInContext &ctx_node) {
if (scheduled_nodes_set.add(ctx_node)) {
scheduled_nodes_queue.push(ctx_node);
}
};
const auto forward_group_node_input_into_group =
[&](const SocketInContext &ctx_group_node_input) {
const bNode &node = ctx_group_node_input.socket->owner_node();
BLI_assert(node.is_group());
const bNodeTree *group_tree = reinterpret_cast<const bNodeTree *>(node.id);
if (!group_tree) {
return;
}
group_tree->ensure_topology_cache();
if (group_tree->has_available_link_cycle()) {
return;
}
const auto &group_context = compute_context_cache.for_group_node(
ctx_group_node_input.context, node.identifier, &node.owner_tree());
const int socket_index = ctx_group_node_input.socket->index();
/* Forward the value to every group input node. */
for (const bNode *group_input_node : group_tree->group_input_nodes()) {
if (propagate_value_fn(ctx_group_node_input,
{&group_context, &group_input_node->output_socket(socket_index)}))
{
schedule_node({&group_context, group_input_node});
}
}
};
const auto forward_output = [&](const SocketInContext &ctx_output_socket) {
const ComputeContext *context = ctx_output_socket.context;
for (const bNodeLink *link : ctx_output_socket.socket->directly_linked_links()) {
if (!link->is_used()) {
continue;
}
const bNode &target_node = *link->tonode;
const bNodeSocket &target_socket = *link->tosock;
if (!propagate_value_fn(ctx_output_socket, {context, &target_socket})) {
continue;
}
schedule_node({context, &target_node});
if (target_node.is_group()) {
forward_group_node_input_into_group({context, &target_socket});
}
}
};
/* Do initial scheduling based on initial sockets. */
for (const SocketInContext &ctx_socket : initial_sockets) {
if (ctx_socket.socket->is_input()) {
const bNode &node = ctx_socket.socket->owner_node();
if (node.is_group()) {
forward_group_node_input_into_group(ctx_socket);
}
schedule_node({ctx_socket.context, &node});
}
else {
forward_output(ctx_socket);
}
}
/* Reused in multiple places to avoid allocating it multiple times. Should be cleared before
* using it. */
Vector<const bNodeSocket *> sockets_vec;
/* Handle all scheduled nodes in the right order until no more nodes are scheduled. */
while (!scheduled_nodes_queue.empty()) {
const NodeInContext ctx_node = scheduled_nodes_queue.top();
scheduled_nodes_queue.pop();
const bNode &node = *ctx_node.node;
const ComputeContext *context = ctx_node.context;
if (node.is_reroute()) {
if (propagate_value_fn({context, &node.input_socket(0)}, {context, &node.output_socket(0)}))
{
forward_output({context, &node.output_socket(0)});
}
}
if (node.is_type("NodeImplicitConversion"_ustr)) {
if (propagate_value_fn({context, &node.input_socket(0)}, {context, &node.output_socket(0)}))
{
forward_output({context, &node.output_socket(0)});
}
}
else if (node.is_muted()) {
for (const bNodeLink &link : node.internal_links()) {
if (propagate_value_fn({context, link.fromsock}, {context, link.tosock})) {
forward_output({context, link.tosock});
}
}
}
else if (node.is_group()) {
const bNodeTree *group = reinterpret_cast<const bNodeTree *>(node.id);
if (!group) {
continue;
}
group->ensure_topology_cache();
if (group->has_available_link_cycle()) {
continue;
}
const bNode *group_output = group->group_output_node();
if (!group_output) {
continue;
}
const ComputeContext &group_context = compute_context_cache.for_group_node(
context, node.identifier, &node.owner_tree());
/* Propagate the values from the group output node to the outputs of the group node and
* continue forwarding them from there. */
for (const int index : group->interface_outputs().index_range()) {
if (propagate_value_fn({&group_context, &group_output->input_socket(index)},
{context, &node.output_socket(index)}))
{
forward_output({context, &node.output_socket(index)});
}
}
}
else if (node.is_group_input()) {
for (const bNodeSocket *output_socket : node.output_sockets()) {
forward_output({context, output_socket});
}
}
else {
sockets_vec.clear();
evaluate_node_fn(ctx_node, sockets_vec);
for (const bNodeSocket *socket : sockets_vec) {
forward_output({context, socket});
}
}
}
}
UpstreamEvalTargets eval_upstream(
const Span<SocketInContext> initial_sockets,
bke::ComputeContextCache &compute_context_cache,
FunctionRef<void(const NodeInContext &ctx_node,
Vector<const bNodeSocket *> &r_modified_inputs)> evaluate_node_fn,
FunctionRef<bool(const SocketInContext &ctx_from, const SocketInContext &ctx_to)>
propagate_value_fn,
FunctionRef<void(const NodeInContext &ctx_node, Vector<const bNodeSocket *> &r_sockets)>
get_inputs_to_propagate_fn)
{
/* Priority queue that makes sure that nodes are evaluated in the right order. */
std::priority_queue<NodeInContext, std::vector<NodeInContext>, NodeInContextUpstreamComparator>
scheduled_nodes_queue;
/* Used to make sure that the same node is not scheduled more than once. */
Set<NodeInContext> scheduled_nodes_set;
UpstreamEvalTargets eval_targets;
const auto schedule_node = [&](const NodeInContext &ctx_node) {
if (scheduled_nodes_set.add(ctx_node)) {
scheduled_nodes_queue.push(ctx_node);
}
};
const auto forward_group_node_output_into_group = [&](const SocketInContext &ctx_output_socket) {
const ComputeContext *context = ctx_output_socket.context;
const bNode &group_node = ctx_output_socket.socket->owner_node();
const bNodeTree *group = reinterpret_cast<const bNodeTree *>(group_node.id);
if (!group) {
return;
}
group->ensure_topology_cache();
if (group->has_available_link_cycle()) {
return;
}
const bNode *group_output = group->group_output_node();
if (!group_output) {
return;
}
const ComputeContext &group_context = compute_context_cache.for_group_node(
context, group_node.identifier, &group_node.owner_tree());
propagate_value_fn(
ctx_output_socket,
{&group_context, &group_output->input_socket(ctx_output_socket.socket->index())});
schedule_node({&group_context, group_output});
};
const auto forward_group_input_to_parent = [&](const SocketInContext &ctx_output_socket) {
const auto *group_context = dynamic_cast<const bke::GroupNodeComputeContext *>(
ctx_output_socket.context);
if (!group_context) {
eval_targets.group_inputs.add(ctx_output_socket);
return;
}
const bNodeTree &caller_tree = *group_context->tree();
caller_tree.ensure_topology_cache();
if (caller_tree.has_available_link_cycle()) {
return;
}
const bNode &caller_node = *group_context->node();
const bNodeSocket &caller_input_socket = caller_node.input_socket(
ctx_output_socket.socket->index());
const ComputeContext *parent_context = ctx_output_socket.context->parent();
/* Note that we might propagate multiple values to the same input of the group node. The
* callback has to handle that case gracefully. */
propagate_value_fn(ctx_output_socket, {parent_context, &caller_input_socket});
schedule_node({parent_context, &caller_node});
};
const auto forward_input = [&](const SocketInContext &ctx_input_socket) {
const ComputeContext *context = ctx_input_socket.context;
if (!ctx_input_socket.socket->is_logically_linked()) {
eval_targets.sockets.add(ctx_input_socket);
return;
}
for (const bNodeLink *link : ctx_input_socket.socket->directly_linked_links()) {
if (!link->is_used()) {
continue;
}
const bNode &origin_node = *link->fromnode;
const bNodeSocket &origin_socket = *link->fromsock;
if (!propagate_value_fn(ctx_input_socket, {context, &origin_socket})) {
continue;
}
schedule_node({context, &origin_node});
if (origin_node.is_group()) {
forward_group_node_output_into_group({context, &origin_socket});
continue;
}
if (origin_node.is_group_input()) {
forward_group_input_to_parent({context, &origin_socket});
continue;
}
}
};
/* Do initial scheduling based on initial sockets. */
for (const SocketInContext &ctx_socket : initial_sockets) {
if (ctx_socket.socket->is_input()) {
forward_input(ctx_socket);
}
else {
const bNode &node = ctx_socket.socket->owner_node();
if (node.is_group()) {
forward_group_node_output_into_group(ctx_socket);
}
else if (node.is_group_input()) {
forward_group_input_to_parent(ctx_socket);
}
else {
schedule_node({ctx_socket.context, &node});
}
}
}
/* Reused in multiple places to avoid allocating it multiple times. Should be cleared before
* using it. */
Vector<const bNodeSocket *> sockets_vec;
/* Handle all nodes in the right order until there are no more nodes to evaluate. */
while (!scheduled_nodes_queue.empty()) {
const NodeInContext ctx_node = scheduled_nodes_queue.top();
scheduled_nodes_queue.pop();
const bNode &node = *ctx_node.node;
const ComputeContext *context = ctx_node.context;
if (is_supported_value_node(node)) {
/* Can't go back further from here, but remember that we reached a value node. */
eval_targets.value_nodes.add(ctx_node);
}
else if (node.is_reroute()) {
propagate_value_fn({context, &node.output_socket(0)}, {context, &node.input_socket(0)});
forward_input({context, &node.input_socket(0)});
}
else if (node.is_type("NodeImplicitConversion"_ustr)) {
propagate_value_fn({context, &node.output_socket(0)}, {context, &node.input_socket(0)});
forward_input({context, &node.input_socket(0)});
}
else if (node.is_muted()) {
for (const bNodeLink &link : node.internal_links()) {
if (propagate_value_fn({context, link.tosock}, {context, link.fromsock})) {
forward_input({context, link.fromsock});
}
}
}
else if (node.is_group()) {
/* Once we get here, the nodes within the group have all been evaluated already and the
* inputs of the group node are already set properly by #forward_group_input_to_parent. */
sockets_vec.clear();
get_inputs_to_propagate_fn(ctx_node, sockets_vec);
for (const bNodeSocket *socket : sockets_vec) {
forward_input({context, socket});
}
}
else if (node.is_group_output()) {
sockets_vec.clear();
get_inputs_to_propagate_fn(ctx_node, sockets_vec);
for (const bNodeSocket *socket : sockets_vec) {
forward_input({context, socket});
}
}
else {
sockets_vec.clear();
evaluate_node_fn(ctx_node, sockets_vec);
for (const bNodeSocket *input_socket : sockets_vec) {
forward_input({context, input_socket});
}
}
}
return eval_targets;
}
} // namespace blender::nodes::partial_eval

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,206 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <fmt/format.h>
#include "BLI_listbase.h"
#include "BLI_set.hh"
#include "BKE_context.hh"
#include "BKE_node.hh"
#include "UI_interface.hh"
#include "BLT_translation.hh"
#include "NOD_node_declaration.hh"
#include "NOD_socket.hh"
#include "NOD_socket_search_link.hh"
namespace blender::nodes {
void GatherLinkSearchOpParams::add_item(std::string socket_name,
SocketLinkOperation::LinkSocketFn fn,
const int weight)
{
std::string name = fmt::format("{}{} " UI_MENU_ARROW_SEP " {}",
IFACE_(node_type_.ui_name),
node_type_.deprecation_notice ? IFACE_(" (Deprecated)") : "",
socket_name);
this->add_item_full_name(std::move(name), std::move(fn), weight);
}
void GatherLinkSearchOpParams::add_item_full_name(std::string name,
SocketLinkOperation::LinkSocketFn fn,
int weight)
{
items_.append({std::move(name), std::move(fn), weight});
}
const bNodeSocket &GatherLinkSearchOpParams::other_socket() const
{
return other_socket_;
}
const SpaceNode &GatherLinkSearchOpParams::space_node() const
{
return snode_;
}
const bNodeTree &GatherLinkSearchOpParams::node_tree() const
{
return node_tree_;
}
const bke::bNodeType &GatherLinkSearchOpParams::node_type() const
{
return node_type_;
}
eNodeSocketInOut GatherLinkSearchOpParams::in_out() const
{
return other_socket_.in_out == SOCK_IN ? SOCK_OUT : SOCK_IN;
}
void LinkSearchOpParams::connect_available_socket(bNode &new_node, UString socket_name)
{
const eNodeSocketInOut in_out = socket.in_out == SOCK_IN ? SOCK_OUT : SOCK_IN;
bNodeSocket *new_node_socket = bke::node_find_enabled_socket(
new_node, in_out, socket_name.ref());
if (new_node_socket == nullptr) {
/* If the socket isn't found, some node's search gather functions probably aren't configured
* properly. It's likely enough that it's worth avoiding a crash in a release build though. */
BLI_assert_unreachable();
return;
}
this->connect_socket(new_node, *new_node_socket);
}
void LinkSearchOpParams::connect_available_socket_by_identifier(bNode &new_node,
const UString socket_identifier)
{
const eNodeSocketInOut in_out = this->socket.in_out == SOCK_IN ? SOCK_OUT : SOCK_IN;
bNodeSocket *new_node_socket = bke::node_find_socket(new_node, in_out, socket_identifier);
BLI_assert(new_node_socket);
this->connect_socket(new_node, *new_node_socket);
}
void LinkSearchOpParams::connect_socket(bNode &new_node, bNodeSocket &new_socket)
{
bke::node_add_link(this->node_tree, new_node, new_socket, this->node, this->socket);
if (new_socket.in_out == SOCK_OUT) {
/* If the old socket already contained a value, then transfer it to a new one, from
* which this value will get there. */
bke::node_socket_move_default_value(
*CTX_data_main(&C), this->node_tree, this->socket, new_socket);
}
}
bNode &LinkSearchOpParams::add_node(UString idname)
{
bNode *node = bke::node_add_node(&C, node_tree, idname);
BLI_assert(node != nullptr);
added_nodes_.append(node);
return *node;
}
bNode &LinkSearchOpParams::add_node(const bke::bNodeType &node_type)
{
return this->add_node(node_type.idname);
}
void LinkSearchOpParams::update_and_connect_available_socket_by_identifier(
bNode &new_node, UString socket_identifier)
{
update_node_declaration_and_sockets(this->node_tree, new_node);
if (new_node.typeinfo->updatefunc) {
new_node.typeinfo->updatefunc(&node_tree, &new_node);
}
this->connect_available_socket_by_identifier(new_node, socket_identifier);
}
void LinkSearchOpParams::update_and_connect_available_socket(bNode &new_node, UString socket_name)
{
update_node_declaration_and_sockets(this->node_tree, new_node);
if (new_node.typeinfo->updatefunc) {
new_node.typeinfo->updatefunc(&node_tree, &new_node);
}
this->connect_available_socket(new_node, socket_name);
}
void search_link_ops_for_declarations(GatherLinkSearchOpParams &params,
Span<SocketDeclaration *> declarations)
{
const bke::bNodeType &node_type = params.node_type();
const SocketDeclaration *main_socket = nullptr;
Vector<const SocketDeclaration *> connectable_sockets;
Set<UString> socket_names;
for (const int i : declarations.index_range()) {
const SocketDeclaration &socket = *declarations[i];
if (!socket_names.add(socket.name)) {
/* Don't add sockets with the same name to the search. Needed to support being called from
* #search_link_ops_for_basic_node, which should have "okay" behavior for nodes with
* duplicate socket names. */
continue;
}
if (!socket.can_connect(params.other_socket())) {
continue;
}
if (socket.is_default_link_socket || main_socket == nullptr) {
/* Either the first connectable or explicitly tagged socket is the main socket. */
main_socket = &socket;
}
connectable_sockets.append(&socket);
}
for (const int i : connectable_sockets.index_range()) {
const SocketDeclaration &socket = *connectable_sockets[i];
/* Give non-main sockets a lower weight so that they don't show up at the top of the search
* when they are not explicitly searched for. The -1 is used to make sure that the first socket
* has a smaller weight than zero so that it does not have the same weight as the main socket.
* Negative weights are used to avoid making the highest weight dependent on the number of
* sockets. */
const int weight = (&socket == main_socket) ? 0 : -1 - i;
params.add_item(
IFACE_(socket.name.ref()),
[&node_type, &socket](LinkSearchOpParams &params) {
bNode &node = params.add_node(node_type);
socket.make_available(node);
params.update_and_connect_available_socket(node, socket.name);
},
weight);
}
}
void search_link_ops_for_basic_node(GatherLinkSearchOpParams &params)
{
const bke::bNodeType &node_type = params.node_type();
if (!node_type.static_declaration) {
return;
}
const NodeDeclaration &declaration = *node_type.static_declaration;
search_link_ops_for_declarations(params, declaration.sockets(params.in_out()));
}
void search_filtered_link_ops_for_basic_node(GatherLinkSearchOpParams &params,
const Set<UString> &skip_socket_identifiers)
{
const bke::bNodeType &node_type = params.node_type();
if (!node_type.static_declaration) {
return;
}
const NodeDeclaration &declaration = *node_type.static_declaration;
Vector<SocketDeclaration *> socket_declarations;
socket_declarations.reserve(declaration.sockets(params.in_out()).size());
for (SocketDeclaration *socket_decl : declaration.sockets(params.in_out())) {
if (skip_socket_identifiers.contains(socket_decl->identifier)) {
continue;
}
socket_declarations.append_unchecked(socket_decl);
}
search_link_ops_for_declarations(params, socket_declarations);
}
} // namespace blender::nodes

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLT_translation.hh"
#include "NOD_string_pattern.hh"
namespace blender::nodes {
const EnumPropertyItem string_pattern_mode_items[] = {
{int(StringPatternMode::Exact),
"EXACT",
0,
N_("Exact"),
N_("Remove the one attribute with the given name")},
{int(StringPatternMode::Wildcard),
"WILDCARD",
0,
N_("Wildcard"),
N_("Remove all attributes that match the pattern which is allowed to contain a single "
"wildcard (*)")},
{0, nullptr, 0, nullptr, nullptr},
};
std::optional<StringPattern> StringPattern::from_str(StringPatternMode mode,
StringRef pattern,
std::string &r_error)
{
switch (mode) {
case StringPatternMode::Exact: {
return StringPattern(pattern, Exact{pattern});
}
case blender::nodes::StringPatternMode::Wildcard: {
const int wildcard_count = Span(pattern.data(), pattern.size()).count('*');
if (wildcard_count == 0) {
return StringPattern(pattern, Exact{pattern});
}
if (wildcard_count >= 2) {
r_error = TIP_("Only one * is supported in the pattern");
return std::nullopt;
}
const int wildcard_index = pattern.find('*');
const StringRef prefix = StringRef(pattern).substr(0, wildcard_index);
const StringRef suffix = StringRef(pattern).substr(wildcard_index + 1);
return StringPattern(pattern, Wildcard{prefix, suffix});
}
}
r_error = TIP_("Invalid pattern");
return std::nullopt;
}
bool StringPattern::match(const StringRef query) const
{
return std::visit([&](const auto &variant) { return variant.match(query); }, variant_);
}
} // namespace blender::nodes

View File

@@ -0,0 +1,812 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <fmt/format.h>
#include <fmt/ranges.h>
#include "DNA_node_types.h"
#include "DNA_space_types.h"
#include "RNA_access.hh"
#include "WM_api.hh"
#include "BKE_compute_context_cache.hh"
#include "BKE_context.hh"
#include "BKE_main.hh"
#include "BKE_main_invariants.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_report.hh"
#include "BKE_workspace.hh"
#include "ED_node.hh"
#include "ED_screen.hh"
#include "BLI_listbase.h"
#include "BLT_translation.hh"
#include "NOD_bundle_type.hh"
#include "NOD_geo_bundle.hh"
#include "NOD_geo_closure.hh"
#include "NOD_geo_closure_to_list.hh"
#include "NOD_socket_items.hh"
#include "NOD_sync_sockets.hh"
#include "NOD_trace_values.hh"
namespace blender::nodes {
enum class NodeSyncState {
Synced,
CanBeSynced,
NoSyncSource,
ConflictingSyncSources,
};
struct BundleSyncState {
NodeSyncState state;
std::optional<nodes::BundleSignature> source_signature;
};
struct ClosureSyncState {
NodeSyncState state;
std::optional<nodes::ClosureSignature> source_signature;
};
static BundleSyncState get_sync_state_separate_bundle(
const SpaceNode &snode,
const bNode &separate_bundle_node,
const bNodeSocket *src_bundle_socket = nullptr)
{
BLI_assert(separate_bundle_node.is_type("NodeSeparateBundle"_ustr));
snode.edittree->ensure_topology_cache();
if (!src_bundle_socket) {
src_bundle_socket = &separate_bundle_node.input_socket(0);
}
BLI_assert(src_bundle_socket->type == SOCK_BUNDLE);
bke::ComputeContextCache compute_context_cache;
const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket(
snode, compute_context_cache, *src_bundle_socket);
if (!current_context) {
return {NodeSyncState::NoSyncSource};
}
const LinkedBundleSignatures linked_signatures = gather_linked_origin_bundle_signatures(
current_context, *src_bundle_socket, compute_context_cache);
if (linked_signatures.items.is_empty()) {
return {NodeSyncState::NoSyncSource};
}
std::optional<BundleSignature> merged_signature = linked_signatures.get_merged_signature();
if (!merged_signature.has_value()) {
return {NodeSyncState::ConflictingSyncSources};
}
if (!linked_signatures.has_type_definition()) {
merged_signature->set_auto_structure_types();
}
const nodes::BundleSignature &current_signature =
nodes::BundleSignature::from_separate_bundle_node(separate_bundle_node, true);
if (*merged_signature != current_signature) {
return {NodeSyncState::CanBeSynced, std::move(merged_signature)};
}
return {NodeSyncState::Synced};
}
static LinkedBundleSignatures get_expected_combine_bundle_signatures(
const SpaceNode &snode, const bNode &combine_bundle_node, const bNodeSocket *src_bundle_socket)
{
BLI_assert(combine_bundle_node.is_type("NodeCombineBundle"_ustr));
snode.edittree->ensure_topology_cache();
if (!src_bundle_socket) {
src_bundle_socket = &combine_bundle_node.output_socket(0);
}
BLI_assert(src_bundle_socket->type == SOCK_BUNDLE);
bke::ComputeContextCache compute_context_cache;
const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket(
snode, compute_context_cache, *src_bundle_socket);
if (!current_context) {
return {};
}
const std::optional<StringRef> type = combine_bundle_node_type(*snode.edittree,
combine_bundle_node);
if (type) {
if (const FlatBundleTypePtr flat_bundle_type = BundleTypeRegistry::try_find_single_flat(*type))
{
SocketInContext socket = {current_context, src_bundle_socket};
LinkedBundleSignatures result;
result.items.append({flat_bundle_type->to_bundle_signature(), true, socket});
return result;
}
}
return gather_linked_target_bundle_signatures(
current_context, *src_bundle_socket, compute_context_cache);
}
static BundleSyncState get_sync_state_combine_bundle(
const SpaceNode &snode,
const bNode &combine_bundle_node,
const bNodeSocket *src_bundle_socket = nullptr)
{
const LinkedBundleSignatures source_signatures = get_expected_combine_bundle_signatures(
snode, combine_bundle_node, src_bundle_socket);
if (source_signatures.items.is_empty()) {
return {NodeSyncState::NoSyncSource};
}
std::optional<BundleSignature> merged_signature = source_signatures.get_merged_signature();
if (!merged_signature.has_value()) {
return {NodeSyncState::ConflictingSyncSources};
}
if (!source_signatures.has_type_definition()) {
merged_signature->set_auto_structure_types();
}
const nodes::BundleSignature &current_signature =
nodes::BundleSignature::from_combine_bundle_node(combine_bundle_node, true);
if (*merged_signature != current_signature) {
return {NodeSyncState::CanBeSynced, std::move(merged_signature)};
}
return {NodeSyncState::Synced};
}
static ClosureSyncState get_sync_state_closure_output(
const SpaceNode &snode,
const bNode &closure_output_node,
const bNodeSocket *src_closure_socket = nullptr)
{
snode.edittree->ensure_topology_cache();
if (!src_closure_socket) {
src_closure_socket = &closure_output_node.output_socket(0);
}
BLI_assert(src_closure_socket->type == SOCK_CLOSURE);
bke::ComputeContextCache compute_context_cache;
const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket(
snode, compute_context_cache, *src_closure_socket);
if (!current_context) {
return {NodeSyncState::NoSyncSource};
}
const LinkedClosureSignatures linked_signatures = gather_linked_target_closure_signatures(
current_context, *src_closure_socket, compute_context_cache);
if (linked_signatures.items.is_empty()) {
return {NodeSyncState::NoSyncSource};
}
std::optional<ClosureSignature> merged_signature = linked_signatures.get_merged_signature();
if (!merged_signature.has_value()) {
return {NodeSyncState::ConflictingSyncSources};
}
if (!linked_signatures.has_type_definition()) {
merged_signature->set_auto_structure_types();
}
const nodes::ClosureSignature &current_signature =
nodes::ClosureSignature::from_closure_output_node(closure_output_node, true);
if (*merged_signature != current_signature) {
return {NodeSyncState::CanBeSynced, merged_signature};
}
return {NodeSyncState::Synced};
}
static ClosureSyncState get_sync_state_evaluate_closure(
const SpaceNode &snode,
const bNode &evaluate_closure_node,
const bNodeSocket *src_closure_socket = nullptr)
{
snode.edittree->ensure_topology_cache();
if (!src_closure_socket) {
src_closure_socket = &evaluate_closure_node.input_socket(0);
}
BLI_assert(src_closure_socket->type == SOCK_CLOSURE);
bke::ComputeContextCache compute_context_cache;
const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket(
snode, compute_context_cache, *src_closure_socket);
if (!current_context) {
return {NodeSyncState::NoSyncSource};
}
const LinkedClosureSignatures linked_signatures = gather_linked_origin_closure_signatures(
current_context, *src_closure_socket, compute_context_cache);
if (linked_signatures.items.is_empty()) {
return {NodeSyncState::NoSyncSource};
}
std::optional<ClosureSignature> merged_signature = linked_signatures.get_merged_signature();
if (!merged_signature.has_value()) {
return {NodeSyncState::ConflictingSyncSources};
}
if (!linked_signatures.has_type_definition()) {
merged_signature->set_auto_structure_types();
}
const nodes::ClosureSignature &current_signature =
nodes::ClosureSignature::from_evaluate_closure_node(evaluate_closure_node, true);
if (*merged_signature != current_signature) {
return {NodeSyncState::CanBeSynced, merged_signature};
}
return {NodeSyncState::Synced};
}
static ClosureSyncState get_sync_state_closure_to_list(
const SpaceNode &snode,
const bNode &closure_to_list_node,
const bNodeSocket *src_closure_socket = nullptr)
{
snode.edittree->ensure_topology_cache();
if (!src_closure_socket) {
src_closure_socket = closure_to_list_node.input_by_identifier("Closure"_ustr);
}
bke::ComputeContextCache compute_context_cache;
const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket(
snode, compute_context_cache, *src_closure_socket);
if (!current_context) {
return {NodeSyncState::NoSyncSource};
}
const LinkedClosureSignatures linked_signatures = gather_linked_origin_closure_signatures(
current_context, *src_closure_socket, compute_context_cache);
if (linked_signatures.items.is_empty()) {
return {NodeSyncState::NoSyncSource};
}
std::optional<ClosureSignature> merged_signature = linked_signatures.get_merged_signature();
if (!merged_signature.has_value()) {
return {NodeSyncState::ConflictingSyncSources};
}
const ClosureSignature &current_signature = ClosureSignature::from_closure_to_list_node(
closure_to_list_node);
if (*merged_signature != current_signature) {
return {NodeSyncState::CanBeSynced, merged_signature};
}
return {NodeSyncState::Synced};
}
void sync_sockets_separate_bundle(SpaceNode &snode,
bNode &separate_bundle_node,
ReportList *reports,
const bNodeSocket *src_bundle_socket)
{
const BundleSyncState sync_state = get_sync_state_separate_bundle(
snode, separate_bundle_node, src_bundle_socket);
switch (sync_state.state) {
case NodeSyncState::Synced:
return;
case NodeSyncState::NoSyncSource:
BKE_report(reports, RPT_INFO, "No bundle signature found");
return;
case NodeSyncState::ConflictingSyncSources:
BKE_report(reports, RPT_INFO, "Found conflicting bundle signatures");
return;
case NodeSyncState::CanBeSynced:
break;
}
auto &storage = *static_cast<NodeSeparateBundle *>(separate_bundle_node.storage);
Map<std::string, int> old_identifiers;
for (const int i : IndexRange(storage.items_num)) {
const NodeSeparateBundleItem &item = storage.items[i];
old_identifiers.add_new(StringRef(item.name), item.identifier);
}
nodes::socket_items::clear<nodes::SeparateBundleItemsAccessor>(separate_bundle_node);
for (const nodes::BundleSignature::Item &item : sync_state.source_signature->items) {
NodeSeparateBundleItem &new_item = *nodes::socket_items::add_item_with_socket_type_and_name<
nodes ::SeparateBundleItemsAccessor>(
*snode.edittree, separate_bundle_node, item.type->type, item.key.c_str());
new_item.structure_type = item.structure_type;
if (const std::optional<int> old_identifier = old_identifiers.lookup_try(item.key)) {
new_item.identifier = *old_identifier;
}
}
BKE_ntree_update_tag_node_property(snode.edittree, &separate_bundle_node);
}
void sync_sockets_combine_bundle(SpaceNode &snode,
bNode &combine_bundle_node,
ReportList *reports,
const bNodeSocket *src_bundle_socket)
{
const BundleSyncState sync_state = get_sync_state_combine_bundle(
snode, combine_bundle_node, src_bundle_socket);
switch (sync_state.state) {
case NodeSyncState::Synced:
return;
case NodeSyncState::NoSyncSource:
BKE_report(reports, RPT_INFO, "No bundle signature found");
return;
case NodeSyncState::ConflictingSyncSources:
BKE_report(reports, RPT_INFO, "Found conflicting bundle signatures");
return;
case NodeSyncState::CanBeSynced:
break;
}
auto &storage = *static_cast<NodeCombineBundle *>(combine_bundle_node.storage);
Map<std::string, int> old_identifiers;
for (const int i : IndexRange(storage.items_num)) {
const NodeCombineBundleItem &item = storage.items[i];
old_identifiers.add_new(StringRef(item.name), item.identifier);
}
nodes::socket_items::clear<nodes::CombineBundleItemsAccessor>(combine_bundle_node);
for (const nodes::BundleSignature::Item &item : sync_state.source_signature->items) {
NodeCombineBundleItem &new_item = *nodes::socket_items::add_item_with_socket_type_and_name<
nodes ::CombineBundleItemsAccessor>(
*snode.edittree, combine_bundle_node, item.type->type, item.key.c_str());
new_item.structure_type = item.structure_type;
if (const std::optional<int> old_identifier = old_identifiers.lookup_try(item.key)) {
new_item.identifier = *old_identifier;
}
}
BKE_ntree_update_tag_node_property(snode.edittree, &combine_bundle_node);
}
void sync_sockets_evaluate_closure(SpaceNode &snode,
bNode &evaluate_closure_node,
ReportList *reports,
const bNodeSocket *src_closure_socket)
{
const ClosureSyncState sync_state = get_sync_state_evaluate_closure(
snode, evaluate_closure_node, src_closure_socket);
switch (sync_state.state) {
case NodeSyncState::Synced:
return;
case NodeSyncState::NoSyncSource:
BKE_report(reports, RPT_INFO, "No closure signature found");
return;
case NodeSyncState::ConflictingSyncSources:
BKE_report(reports, RPT_INFO, "Found conflicting closure signatures");
return;
case NodeSyncState::CanBeSynced:
break;
}
auto &storage = *static_cast<NodeEvaluateClosure *>(evaluate_closure_node.storage);
Map<std::string, int> old_input_identifiers;
Map<std::string, int> old_output_identifiers;
for (const int i : IndexRange(storage.input_items.items_num)) {
const NodeEvaluateClosureInputItem &item = storage.input_items.items[i];
old_input_identifiers.add_new(StringRef(item.name), item.identifier);
}
for (const int i : IndexRange(storage.output_items.items_num)) {
const NodeEvaluateClosureOutputItem &item = storage.output_items.items[i];
old_output_identifiers.add_new(StringRef(item.name), item.identifier);
}
nodes::socket_items::clear<nodes::EvaluateClosureInputItemsAccessor>(evaluate_closure_node);
nodes::socket_items::clear<nodes::EvaluateClosureOutputItemsAccessor>(evaluate_closure_node);
for (const nodes::ClosureSignature::Item &item : sync_state.source_signature->inputs) {
NodeEvaluateClosureInputItem &new_item =
*nodes::socket_items::add_item_with_socket_type_and_name<
nodes::EvaluateClosureInputItemsAccessor>(
*snode.edittree, evaluate_closure_node, item.type->type, item.key.c_str());
new_item.structure_type = item.structure_type;
if (const std::optional<int> old_identifier = old_input_identifiers.lookup_try(item.key)) {
new_item.identifier = *old_identifier;
}
}
for (const nodes::ClosureSignature::Item &item : sync_state.source_signature->outputs) {
NodeEvaluateClosureOutputItem &new_item =
*nodes::socket_items::add_item_with_socket_type_and_name<
nodes::EvaluateClosureOutputItemsAccessor>(
*snode.edittree, evaluate_closure_node, item.type->type, item.key.c_str());
new_item.structure_type = item.structure_type;
if (const std::optional<int> old_identifier = old_output_identifiers.lookup_try(item.key)) {
new_item.identifier = *old_identifier;
}
}
BKE_ntree_update_tag_node_property(snode.edittree, &evaluate_closure_node);
}
void sync_sockets_closure(SpaceNode &snode,
bNode &closure_input_node,
bNode &closure_output_node,
ReportList *reports,
const bNodeSocket *src_closure_socket)
{
const ClosureSyncState sync_state = get_sync_state_closure_output(
snode, closure_output_node, src_closure_socket);
switch (sync_state.state) {
case NodeSyncState::Synced:
return;
case NodeSyncState::NoSyncSource:
BKE_report(reports, RPT_INFO, "No closure signature found");
return;
case NodeSyncState::ConflictingSyncSources:
BKE_report(reports, RPT_INFO, "Found conflicting closure signatures");
return;
case NodeSyncState::CanBeSynced:
break;
}
const nodes::ClosureSignature &signature = *sync_state.source_signature;
auto &storage = *static_cast<NodeClosureOutput *>(closure_output_node.storage);
Map<std::string, int> old_input_identifiers;
Map<std::string, int> old_output_identifiers;
for (const int i : IndexRange(storage.input_items.items_num)) {
const NodeClosureInputItem &item = storage.input_items.items[i];
old_input_identifiers.add_new(StringRef(item.name), item.identifier);
}
for (const int i : IndexRange(storage.output_items.items_num)) {
const NodeClosureOutputItem &item = storage.output_items.items[i];
old_output_identifiers.add_new(StringRef(item.name), item.identifier);
}
nodes::socket_items::clear<nodes::ClosureInputItemsAccessor>(closure_output_node);
nodes::socket_items::clear<nodes::ClosureOutputItemsAccessor>(closure_output_node);
for (const nodes::ClosureSignature::Item &item : signature.inputs) {
NodeClosureInputItem &new_item =
*nodes::socket_items::add_item_with_socket_type_and_name<nodes::ClosureInputItemsAccessor>(
*snode.edittree, closure_output_node, item.type->type, item.key.c_str());
new_item.structure_type = item.structure_type;
if (const std::optional<int> old_identifier = old_input_identifiers.lookup_try(item.key)) {
new_item.identifier = *old_identifier;
}
}
for (const nodes::ClosureSignature::Item &item : signature.outputs) {
NodeClosureOutputItem &new_item = *nodes::socket_items::add_item_with_socket_type_and_name<
nodes::ClosureOutputItemsAccessor>(
*snode.edittree, closure_output_node, item.type->type, item.key.c_str());
new_item.structure_type = item.structure_type;
if (const std::optional<int> old_identifier = old_output_identifiers.lookup_try(item.key)) {
new_item.identifier = *old_identifier;
}
}
BKE_ntree_update_tag_node_property(snode.edittree, &closure_input_node);
BKE_ntree_update_tag_node_property(snode.edittree, &closure_output_node);
nodes::update_node_declaration_and_sockets(*snode.edittree, closure_input_node);
nodes::update_node_declaration_and_sockets(*snode.edittree, closure_output_node);
/* Create internal zone links for newly created sockets. */
snode.edittree->ensure_topology_cache();
Vector<std::pair<bNodeSocket *, bNodeSocket *>> internal_links;
for (const int input_i : signature.inputs.index_range()) {
const nodes::ClosureSignature::Item &input_item = signature.inputs[input_i];
if (old_input_identifiers.contains(input_item.key)) {
continue;
}
for (const int output_i : signature.outputs.index_range()) {
const nodes::ClosureSignature::Item &output_item = signature.outputs[output_i];
if (old_output_identifiers.contains(output_item.key)) {
continue;
}
if (input_item.key == output_item.key) {
internal_links.append({&closure_input_node.output_socket(input_i),
&closure_output_node.input_socket(output_i)});
}
};
}
for (auto &&[from_socket, to_socket] : internal_links) {
if (!snode.edittree->typeinfo->validate_link ||
snode.edittree->typeinfo->validate_link(from_socket->typeinfo->type,
to_socket->typeinfo->type))
{
bke::node_add_link(
*snode.edittree, closure_input_node, *from_socket, closure_output_node, *to_socket);
}
}
}
void sync_sockets_closure_to_list(SpaceNode &snode,
bNode &closure_to_list_node,
ReportList *reports,
const bNodeSocket *src_closure_socket)
{
const ClosureSyncState sync_state = get_sync_state_closure_to_list(
snode, closure_to_list_node, src_closure_socket);
switch (sync_state.state) {
case NodeSyncState::Synced:
return;
case NodeSyncState::NoSyncSource:
BKE_report(reports, RPT_INFO, "No closure signature found");
return;
case NodeSyncState::ConflictingSyncSources:
BKE_report(reports, RPT_WARNING, "Found conflicting closure signatures");
return;
case NodeSyncState::CanBeSynced:
break;
}
const ClosureSignature &signature = *sync_state.source_signature;
auto &storage = *static_cast<GeometryNodeClosureToList *>(closure_to_list_node.storage);
Map<std::string, int> old_identifiers;
for (const int i : IndexRange(storage.items_num)) {
const GeometryNodeClosureToListItem &item = storage.items[i];
old_identifiers.add_new(StringRef(item.name), item.identifier);
}
nodes::socket_items::clear<ClosureToListItemsAccessor>(closure_to_list_node);
for (const nodes::ClosureSignature::Item &item : signature.outputs) {
GeometryNodeClosureToListItem &new_item =
*socket_items::add_item_with_socket_type_and_name<ClosureToListItemsAccessor>(
*snode.edittree, closure_to_list_node, item.type->type, item.key.c_str());
new_item.structure_type = item.structure_type;
if (const std::optional<int> old_identifier = old_identifiers.lookup_try(item.key)) {
new_item.identifier = *old_identifier;
}
}
BKE_ntree_update_tag_node_property(snode.edittree, &closure_to_list_node);
update_node_declaration_and_sockets(*snode.edittree, closure_to_list_node);
}
static std::string get_bundle_sync_tooltip(const nodes::BundleSignature &old_signature,
const nodes::BundleSignature &new_signature)
{
Vector<StringRef> added_items;
Vector<StringRef> removed_items;
Vector<StringRef> changed_items;
bool order_changed = false;
for (const int new_item_i : new_signature.items.index_range()) {
const BundleSignature::Item &new_item = new_signature.items[new_item_i];
const int old_item_i = old_signature.items.index_of_try_as(new_item.key);
if (old_item_i == -1) {
added_items.append(new_item.key);
}
else {
const BundleSignature::Item &old_item = old_signature.items[old_item_i];
if (new_item != old_item) {
changed_items.append(new_item.key);
}
if (old_item_i != new_item_i) {
order_changed = true;
}
}
}
for (const nodes::BundleSignature::Item &old_item : old_signature.items) {
if (!new_signature.items.contains_as(old_item.key)) {
removed_items.append(old_item.key);
}
}
fmt::memory_buffer string_buffer;
auto buf = fmt::appender(string_buffer);
if (!added_items.is_empty()) {
fmt::format_to(buf, "\u2022 {}: {}\n", TIP_("Add"), fmt::join(added_items, ", "));
}
if (!removed_items.is_empty()) {
fmt::format_to(buf, "\u2022 {}: {}\n", TIP_("Remove"), fmt::join(removed_items, ", "));
}
if (!changed_items.is_empty()) {
fmt::format_to(buf, "\u2022 {}: {}\n", TIP_("Change"), fmt::join(changed_items, ", "));
}
if (order_changed) {
fmt::format_to(buf, "\u2022 {}", TIP_("Reorder"));
}
fmt::format_to(buf, "\n{}", TIP_("Update based on linked bundle signature"));
return fmt::to_string(string_buffer);
}
static std::string get_closure_sync_tooltip(const nodes::ClosureSignature &old_signature,
const nodes::ClosureSignature &new_signature)
{
Vector<StringRef> added_inputs;
Vector<StringRef> removed_inputs;
Vector<StringRef> changed_inputs;
bool input_order = false;
Vector<StringRef> added_outputs;
Vector<StringRef> removed_outputs;
Vector<StringRef> changed_outputs;
bool output_order = false;
for (const int new_item_i : new_signature.inputs.index_range()) {
const nodes::ClosureSignature::Item &new_item = new_signature.inputs[new_item_i];
const int old_item_i = old_signature.inputs.index_of_try_as(new_item.key);
if (old_item_i == -1) {
added_inputs.append(new_item.key);
}
else {
const nodes::ClosureSignature::Item &old_item = old_signature.inputs[old_item_i];
if (new_item != old_item) {
changed_inputs.append(new_item.key);
}
if (old_item_i != new_item_i) {
input_order = true;
}
}
}
for (const nodes::ClosureSignature::Item &old_item : old_signature.inputs) {
if (!new_signature.inputs.contains_as(old_item.key)) {
removed_inputs.append(old_item.key);
}
}
for (const int new_item_i : new_signature.outputs.index_range()) {
const nodes::ClosureSignature::Item &new_item = new_signature.outputs[new_item_i];
const int old_item_i = old_signature.outputs.index_of_try_as(new_item.key);
if (old_item_i == -1) {
added_outputs.append(new_item.key);
}
else {
const nodes::ClosureSignature::Item &old_item = old_signature.outputs[old_item_i];
if (new_item != old_item) {
changed_outputs.append(new_item.key);
}
if (old_item_i != new_item_i) {
output_order = true;
}
}
}
for (const nodes::ClosureSignature::Item &old_item : old_signature.outputs) {
if (!new_signature.outputs.contains_as(old_item.key)) {
removed_outputs.append(old_item.key);
}
}
fmt::memory_buffer string_buffer;
auto buf = fmt::appender(string_buffer);
if (!added_inputs.is_empty()) {
fmt::format_to(buf, "\u2022 {}: {}\n", TIP_("Add Inputs"), fmt::join(added_inputs, ", "));
}
if (!removed_inputs.is_empty()) {
fmt::format_to(buf, "\u2022 {}: {}\n", TIP_("Remove Inputs"), fmt::join(removed_inputs, ", "));
}
if (!changed_inputs.is_empty()) {
fmt::format_to(buf, "\u2022 {}: {}\n", TIP_("Change Inputs"), fmt::join(changed_inputs, ", "));
}
if (input_order) {
fmt::format_to(buf, "\u2022 {}\n", TIP_("Reorder Inputs"));
}
if (!added_outputs.is_empty()) {
fmt::format_to(buf, "\u2022 {}: {}\n", TIP_("Add Outputs"), fmt::join(added_outputs, ", "));
}
if (!removed_outputs.is_empty()) {
fmt::format_to(
buf, "\u2022 {}: {}\n", TIP_("Remove Outputs"), fmt::join(removed_outputs, ", "));
}
if (!changed_outputs.is_empty()) {
fmt::format_to(
buf, "\u2022 {}: {}\n", TIP_("Change Outputs"), fmt::join(changed_outputs, ", "));
}
if (output_order) {
fmt::format_to(buf, "\u2022 {}\n", TIP_("Reorder Outputs"));
}
fmt::format_to(buf, "\n{}", TIP_("Update based on linked closure signature"));
return fmt::to_string(string_buffer);
}
void sync_node(bContext &C, bNode &node, ReportList *reports)
{
const bke::bNodeZoneType &closure_zone_type = *bke::zone_type_by_node_type(NODE_CLOSURE_OUTPUT);
SpaceNode &snode = *CTX_wm_space_node(&C);
if (node.is_type("NodeEvaluateClosure"_ustr)) {
sync_sockets_evaluate_closure(snode, node, reports);
}
else if (node.is_type("NodeSeparateBundle"_ustr)) {
sync_sockets_separate_bundle(snode, node, reports);
}
else if (node.is_type("NodeCombineBundle"_ustr)) {
sync_sockets_combine_bundle(snode, node, reports);
}
else if (node.is_type("NodeClosureInput"_ustr)) {
bNode &closure_input_node = node;
if (bNode *closure_output_node = closure_zone_type.get_corresponding_output(
*snode.edittree, closure_input_node))
{
sync_sockets_closure(snode, closure_input_node, *closure_output_node, reports);
}
}
else if (node.is_type("NodeClosureOutput"_ustr)) {
bNode &closure_output_node = node;
if (bNode *closure_input_node = closure_zone_type.get_corresponding_input(*snode.edittree,
closure_output_node))
{
sync_sockets_closure(snode, *closure_input_node, closure_output_node, reports);
}
}
else if (node.is_type("GeometryNodeClosureToList"_ustr)) {
sync_sockets_closure_to_list(snode, node, reports);
}
}
std::string sync_node_description_get(const bContext &C, const bNode &node)
{
const SpaceNode *snode = CTX_wm_space_node(&C);
if (!snode) {
return "";
}
if (node.is_type("NodeSeparateBundle"_ustr)) {
const nodes::BundleSignature old_signature = nodes::BundleSignature::from_separate_bundle_node(
node, true);
if (const std::optional<nodes::BundleSignature> new_signature =
get_sync_state_separate_bundle(*snode, node).source_signature)
{
return get_bundle_sync_tooltip(old_signature, *new_signature);
}
}
else if (node.is_type("NodeCombineBundle"_ustr)) {
const nodes::BundleSignature old_signature = nodes::BundleSignature::from_combine_bundle_node(
node, true);
if (const std::optional<nodes::BundleSignature> new_signature =
get_sync_state_combine_bundle(*snode, node).source_signature)
{
return get_bundle_sync_tooltip(old_signature, *new_signature);
}
}
else if (node.is_type("NodeEvaluateClosure"_ustr)) {
const nodes::ClosureSignature old_signature =
nodes::ClosureSignature::from_evaluate_closure_node(node, true);
if (const std::optional<nodes::ClosureSignature> new_signature =
get_sync_state_evaluate_closure(*snode, node).source_signature)
{
return get_closure_sync_tooltip(old_signature, *new_signature);
}
}
else if (node.is_type("NodeClosureOutput"_ustr)) {
const nodes::ClosureSignature old_signature =
nodes::ClosureSignature::from_closure_output_node(node, true);
if (const std::optional<nodes::ClosureSignature> new_signature =
get_sync_state_closure_output(*snode, node).source_signature)
{
return get_closure_sync_tooltip(old_signature, *new_signature);
}
}
else if (node.is_type("GeometryNodeClosureToList"_ustr)) {
const nodes::ClosureSignature old_signature =
nodes::ClosureSignature::from_closure_to_list_node(node);
if (const std::optional<nodes::ClosureSignature> new_signature =
get_sync_state_closure_to_list(*snode, node).source_signature)
{
return get_closure_sync_tooltip(old_signature, *new_signature);
}
}
return "";
}
bool node_can_sync_sockets(const bContext &C, const bNodeTree & /*tree*/, const bNode &node)
{
SpaceNode *snode = CTX_wm_space_node(&C);
if (!snode) {
return false;
}
Map<int, bool> &cache = ed::space_node::node_can_sync_cache_get(*snode);
const bool can_sync = cache.lookup_or_add_cb(node.identifier, [&]() {
if (node.is_type("NodeEvaluateClosure"_ustr)) {
return get_sync_state_evaluate_closure(*snode, node).source_signature.has_value();
}
if (node.is_type("NodeClosureOutput"_ustr)) {
return get_sync_state_closure_output(*snode, node).source_signature.has_value();
}
if (node.is_type("NodeCombineBundle"_ustr)) {
return get_sync_state_combine_bundle(*snode, node).source_signature.has_value();
}
if (node.is_type("NodeSeparateBundle"_ustr)) {
return get_sync_state_separate_bundle(*snode, node).source_signature.has_value();
}
if (node.is_type("GeometryNodeClosureToList"_ustr)) {
return get_sync_state_closure_to_list(*snode, node).source_signature.has_value();
}
return false;
});
return can_sync;
}
void node_can_sync_cache_clear(Main &bmain)
{
if (wmWindowManager *wm = static_cast<wmWindowManager *>(bmain.wm.first)) {
for (wmWindow &window : wm->windows) {
bScreen *screen = BKE_workspace_active_screen_get(window.workspace_hook);
for (ScrArea &area : screen->areabase) {
SpaceLink *sl = static_cast<SpaceLink *>(area.spacedata.first);
if (sl->spacetype == SPACE_NODE) {
SpaceNode *snode = reinterpret_cast<SpaceNode *>(sl);
Map<int, bool> &cache = ed::space_node::node_can_sync_cache_get(*snode);
cache.clear();
}
}
}
}
}
} // namespace blender::nodes

View File

@@ -0,0 +1,760 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_stack.hh"
#include "NOD_geometry_nodes_bundle_signature.hh"
#include "NOD_geometry_nodes_closure_location.hh"
#include "NOD_geometry_nodes_closure_signature.hh"
#include "NOD_node_in_compute_context.hh"
#include "NOD_socket_declarations.hh"
#include "NOD_trace_values.hh"
#include "BKE_compute_context_cache.hh"
#include "BKE_node_tree_zones.hh"
#include "ED_node.hh"
namespace blender::nodes {
static bool target_socket_evaluates_closure(const SocketInContext &socket)
{
if (!socket->is_input()) {
return false;
}
if (socket->index() == 0 && socket.owner_node()->is_type("NodeEvaluateClosure"_ustr)) {
return true;
}
if (const SocketDeclaration *decl = socket->runtime->declaration) {
if (const auto *closure_decl = dynamic_cast<const decl::Closure *>(decl)) {
return bool(closure_decl->create_signature);
}
}
return false;
}
static bool is_closure_zone_output_socket(const SocketInContext &socket)
{
return socket->owner_node().is_type("NodeClosureOutput"_ustr) && socket->is_output();
}
static bool use_link_for_tracing(const bNodeLink &link)
{
if (!link.is_used()) {
return false;
}
const bNodeTree &tree = link.fromnode->owner_tree();
if (tree.typeinfo->validate_link &&
!tree.typeinfo->validate_link(link.fromsock->type, link.tosock->type))
{
return false;
}
return true;
}
static Vector<SocketInContext> find_origin_sockets_through_contexts(
SocketInContext start_socket,
bke::ComputeContextCache &compute_context_cache,
FunctionRef<bool(const SocketInContext &)> handle_possible_origin_socket_fn,
bool find_all);
static Vector<SocketInContext> find_target_sockets_through_contexts(
const SocketInContext start_socket,
bke::ComputeContextCache &compute_context_cache,
const FunctionRef<bool(const SocketInContext &)> handle_possible_target_socket_fn,
const bool find_all)
{
using BundlePath = Vector<std::string, 0>;
struct SocketToCheck {
SocketInContext socket;
BundlePath bundle_path;
};
Stack<SocketToCheck> sockets_to_check;
Set<SocketInContext> added_sockets;
auto add_if_new = [&](const SocketInContext &socket, BundlePath bundle_path) {
if (added_sockets.add(socket)) {
sockets_to_check.push({socket, std::move(bundle_path)});
}
};
add_if_new(start_socket, {});
VectorSet<SocketInContext> found_targets;
while (!sockets_to_check.is_empty()) {
const SocketToCheck socket_to_check = sockets_to_check.pop();
const SocketInContext socket = socket_to_check.socket;
const BundlePath &bundle_path = socket_to_check.bundle_path;
const NodeInContext &node = socket.owner_node();
if (socket->is_input()) {
if (node->is_muted()) {
for (const bNodeLink &link : node->internal_links()) {
if (link.fromsock == socket.socket) {
add_if_new({socket.context, link.tosock}, bundle_path);
}
}
continue;
}
if (bundle_path.is_empty() && handle_possible_target_socket_fn(socket)) {
found_targets.add(socket);
if (!find_all) {
break;
}
continue;
}
if (node->is_reroute()) {
add_if_new(node.output_socket(0), bundle_path);
continue;
}
if (node->is_group()) {
if (const bNodeTree *group = reinterpret_cast<const bNodeTree *>(node->id)) {
group->ensure_topology_cache();
const ComputeContext &group_compute_context = compute_context_cache.for_group_node(
socket.context, node->identifier, &node->owner_tree());
for (const bNode *input_node : group->group_input_nodes()) {
if (const bNodeSocket *group_input_socket = input_node->output_by_identifier(
socket->identifier_ustr()))
{
if (group_input_socket->is_directly_linked()) {
add_if_new({&group_compute_context, group_input_socket}, bundle_path);
}
}
}
}
continue;
}
if (node->is_group_output()) {
if (const auto *group_context = dynamic_cast<const bke::GroupNodeComputeContext *>(
socket.context))
{
const bNodeTree *caller_group = group_context->tree();
const bNode *caller_group_node = group_context->node();
if (caller_group && caller_group_node) {
caller_group->ensure_topology_cache();
if (const bNodeSocket *output_socket = caller_group_node->output_by_identifier(
socket->identifier_ustr()))
{
add_if_new({group_context->parent(), output_socket}, bundle_path);
}
}
}
continue;
}
if (node->is_type("NodeCombineBundle"_ustr)) {
const auto &storage = *static_cast<const NodeCombineBundle *>(node->storage);
BundlePath new_bundle_path = bundle_path;
new_bundle_path.append(storage.items[socket->index()].name);
add_if_new(node.output_socket(0), std::move(new_bundle_path));
continue;
}
if (node->is_type("NodeSeparateBundle"_ustr)) {
if (bundle_path.is_empty()) {
continue;
}
const StringRef last_key = bundle_path.last();
const auto &storage = *static_cast<const NodeSeparateBundle *>(node->storage);
for (const int output_i : IndexRange(storage.items_num)) {
if (last_key == storage.items[output_i].name) {
add_if_new(node.output_socket(output_i), bundle_path.as_span().drop_back(1));
}
}
continue;
}
if (node->is_type("NodeClosureOutput"_ustr)) {
const auto &closure_storage = *static_cast<const NodeClosureOutput *>(node->storage);
const StringRef key = closure_storage.output_items.items[socket->index()].name;
const Vector<SocketInContext> target_sockets = find_target_sockets_through_contexts(
node.output_socket(0), compute_context_cache, target_socket_evaluates_closure, true);
for (const auto &target_socket : target_sockets) {
const NodeInContext evaluate_node = target_socket.owner_node();
if (!evaluate_node->is_type("NodeEvaluateClosure"_ustr)) {
continue;
}
const auto &evaluate_storage = *static_cast<const NodeEvaluateClosure *>(
evaluate_node->storage);
for (const int i : IndexRange(evaluate_storage.output_items.items_num)) {
const NodeEvaluateClosureOutputItem &item = evaluate_storage.output_items.items[i];
if (key == item.name) {
add_if_new(evaluate_node.output_socket(i), bundle_path);
}
}
}
continue;
}
if (node->is_type("NodeEvaluateClosure"_ustr)) {
if (socket->index() == 0) {
continue;
}
const auto &evaluate_storage = *static_cast<const NodeEvaluateClosure *>(node->storage);
const StringRef key = evaluate_storage.input_items.items[socket->index() - 1].name;
const Vector<SocketInContext> origin_sockets = find_origin_sockets_through_contexts(
node.input_socket(0), compute_context_cache, is_closure_zone_output_socket, true);
for (const SocketInContext origin_socket : origin_sockets) {
const bNodeTree &closure_tree = origin_socket->owner_tree();
const bke::bNodeTreeZones *closure_tree_zones = closure_tree.zones();
if (!closure_tree_zones) {
continue;
}
const auto &closure_output_node = origin_socket.owner_node();
const bke::bNodeTreeZone *closure_zone = closure_tree_zones->get_zone_by_node(
closure_output_node->identifier);
if (!closure_zone) {
continue;
}
const bNode *closure_input_node = closure_zone->input_node();
if (!closure_input_node) {
continue;
}
const bke::EvaluateClosureComputeContext &closure_context =
compute_context_cache.for_evaluate_closure(
node.context,
node->identifier,
&node->owner_tree(),
ClosureSourceLocation{&closure_tree,
closure_output_node->identifier,
origin_socket.context_hash(),
origin_socket.context});
if (closure_context.is_recursive()) {
continue;
}
const auto &closure_output_storage = *static_cast<const NodeClosureOutput *>(
closure_output_node->storage);
for (const int i : IndexRange(closure_output_storage.input_items.items_num)) {
const NodeClosureInputItem &item = closure_output_storage.input_items.items[i];
if (key == item.name) {
add_if_new({&closure_context, &closure_input_node->output_socket(i)}, bundle_path);
}
}
}
continue;
}
if (node->is_type("GeometryNodeSimulationInput"_ustr)) {
const ComputeContext &simulation_compute_context =
compute_context_cache.for_simulation_zone(socket.context, *node);
add_if_new({&simulation_compute_context, &node->output_socket(socket->index() + 1)},
bundle_path);
continue;
}
if (node->is_type("GeometryNodeSimulationOutput"_ustr)) {
const int output_index = socket->index();
if (output_index >= 1) {
BLI_assert(dynamic_cast<const bke::SimulationZoneComputeContext *>(socket.context));
add_if_new({socket.context->parent(), &node->output_socket(output_index - 1)},
bundle_path);
}
continue;
}
if (node->is_type("GeometryNodeRepeatInput"_ustr)) {
const int index = socket->index();
if (index >= 1) {
const ComputeContext &repeat_compute_context = compute_context_cache.for_repeat_zone(
socket.context, *node, 0);
add_if_new({&repeat_compute_context, &node->output_socket(index)}, bundle_path);
const auto &storage = *static_cast<NodeGeometryRepeatInput *>(node->storage);
if (const bNode *repeat_output_node = node->owner_tree().node_by_id(
storage.output_node_id))
{
add_if_new({socket.context, &repeat_output_node->output_socket(index - 1)},
bundle_path);
}
}
continue;
}
if (node->is_type("GeometryNodeRepeatOutput"_ustr)) {
BLI_assert(dynamic_cast<const bke::RepeatZoneComputeContext *>(socket.context));
add_if_new({socket.context->parent(), &node->output_socket(socket->index())}, bundle_path);
continue;
}
for (const bNodeSocket *output_socket : node->output_sockets()) {
const SocketDeclaration *output_decl = output_socket->runtime->declaration;
if (!output_decl) {
continue;
}
if (const decl::Bundle *bundle_decl = dynamic_cast<const decl::Bundle *>(output_decl)) {
if (bundle_decl->pass_through_input_index == socket->index()) {
add_if_new({socket.context, output_socket}, bundle_path);
}
}
}
}
else {
const bke::bNodeTreeZones *zones = node->owner_tree().zones();
if (!zones) {
continue;
}
const bke::bNodeTreeZone *from_zone = zones->get_zone_by_socket(*socket.socket);
for (const bNodeLink *link : socket->directly_linked_links()) {
if (!use_link_for_tracing(*link)) {
continue;
}
bNodeSocket *to_socket = link->tosock;
const bke::bNodeTreeZone *to_zone = zones->get_zone_by_socket(*to_socket);
if (!zones->link_between_zones_is_allowed(from_zone, to_zone)) {
continue;
}
const Vector<const bke::bNodeTreeZone *> zones_to_enter = zones->get_zones_to_enter(
from_zone, to_zone);
const ComputeContext *compute_context = ed::space_node::compute_context_for_zones(
zones_to_enter, compute_context_cache, socket.context);
if (!compute_context) {
continue;
}
add_if_new({compute_context, to_socket}, bundle_path);
}
}
}
return found_targets.extract_vector();
}
[[nodiscard]] const ComputeContext *compute_context_for_closure_evaluation(
const ComputeContext *closure_socket_context,
const bNodeSocket &closure_socket,
bke::ComputeContextCache &compute_context_cache,
const std::optional<ClosureSourceLocation> &source_location)
{
const Vector<SocketInContext> target_sockets = find_target_sockets_through_contexts(
{closure_socket_context, &closure_socket},
compute_context_cache,
target_socket_evaluates_closure,
false);
if (target_sockets.is_empty()) {
return nullptr;
}
const SocketInContext target_socket = target_sockets[0];
const NodeInContext target_node = target_socket.owner_node();
if (!target_node->is_type("NodeEvaluateClosure"_ustr)) {
return nullptr;
}
return &compute_context_cache.for_evaluate_closure(target_socket.context,
target_node->identifier,
&target_socket->owner_tree(),
source_location);
}
static Vector<SocketInContext> find_origin_sockets_through_contexts(
const SocketInContext start_socket,
bke::ComputeContextCache &compute_context_cache,
const FunctionRef<bool(const SocketInContext &)> handle_possible_origin_socket_fn,
const bool find_all)
{
using BundlePath = Vector<std::string, 0>;
struct SocketToCheck {
SocketInContext socket;
BundlePath bundle_path;
};
Stack<SocketToCheck> sockets_to_check;
Set<SocketInContext> added_sockets;
auto add_if_new = [&](const SocketInContext &socket, BundlePath bundle_path) {
if (added_sockets.add(socket)) {
sockets_to_check.push({socket, std::move(bundle_path)});
}
};
add_if_new(start_socket, {});
VectorSet<SocketInContext> found_origins;
while (!sockets_to_check.is_empty()) {
const SocketToCheck socket_to_check = sockets_to_check.pop();
const SocketInContext socket = socket_to_check.socket;
const BundlePath &bundle_path = socket_to_check.bundle_path;
const NodeInContext &node = socket.owner_node();
const SocketDeclaration *socket_decl = socket->runtime->declaration;
if (socket->is_input()) {
if (bundle_path.is_empty() && handle_possible_origin_socket_fn(socket)) {
found_origins.add(socket);
if (!find_all) {
break;
}
continue;
}
const bke::bNodeTreeZones *zones = node->owner_tree().zones();
if (!zones) {
continue;
}
const bke::bNodeTreeZone *to_zone = zones->get_zone_by_socket(*socket.socket);
for (const bNodeLink *link : socket->directly_linked_links()) {
if (!use_link_for_tracing(*link)) {
continue;
}
const bNodeSocket *from_socket = link->fromsock;
const bke::bNodeTreeZone *from_zone = zones->get_zone_by_socket(*from_socket);
if (!zones->link_between_zones_is_allowed(from_zone, to_zone)) {
continue;
}
const ComputeContext *compute_context = socket.context;
for (const bke::bNodeTreeZone *zone = to_zone; zone != from_zone; zone = zone->parent_zone)
{
if (const auto *evaluate_closure_context =
dynamic_cast<const bke::EvaluateClosureComputeContext *>(compute_context))
{
const std::optional<nodes::ClosureSourceLocation> &source_location =
evaluate_closure_context->closure_source_location();
/* This is expected to be available during value tracing. */
BLI_assert(source_location);
BLI_assert(source_location->compute_context);
compute_context = source_location->compute_context;
}
else {
compute_context = compute_context->parent();
}
}
add_if_new({compute_context, from_socket}, bundle_path);
}
}
else {
if (node->is_muted()) {
for (const bNodeLink &link : node->internal_links()) {
if (link.tosock == socket.socket) {
add_if_new({socket.context, link.fromsock}, bundle_path);
}
}
continue;
}
if (bundle_path.is_empty() && handle_possible_origin_socket_fn(socket)) {
found_origins.add(socket);
if (!find_all) {
break;
}
continue;
}
if (node->is_reroute()) {
add_if_new(node.input_socket(0), bundle_path);
continue;
}
if (node->is_group()) {
if (const bNodeTree *group = reinterpret_cast<const bNodeTree *>(node->id)) {
group->ensure_topology_cache();
if (const bNode *group_output_node = group->group_output_node()) {
const ComputeContext &group_compute_context = compute_context_cache.for_group_node(
socket.context, node->identifier, &node->owner_tree());
if (const bNodeSocket *group_output_socket = group_output_node->input_by_identifier(
socket->identifier_ustr()))
{
add_if_new({&group_compute_context, group_output_socket}, bundle_path);
}
}
}
continue;
}
if (node->is_group_input()) {
if (const auto *group_context = dynamic_cast<const bke::GroupNodeComputeContext *>(
socket.context))
{
const bNodeTree *caller_group = group_context->tree();
const bNode *caller_group_node = group_context->node();
if (caller_group && caller_group_node) {
caller_group->ensure_topology_cache();
if (const bNodeSocket *input_socket = caller_group_node->input_by_identifier(
socket->identifier_ustr()))
{
add_if_new({group_context->parent(), input_socket}, bundle_path);
}
}
}
continue;
}
if (node->is_type("NodeJoinBundle"_ustr)) {
add_if_new(node.input_socket(0), bundle_path);
continue;
}
if (node->is_type("NodeEvaluateClosure"_ustr)) {
const auto &evaluate_storage = *static_cast<const NodeEvaluateClosure *>(node->storage);
const StringRef key = evaluate_storage.output_items.items[socket->index()].name;
const Vector<SocketInContext> origin_sockets = find_origin_sockets_through_contexts(
node.input_socket(0), compute_context_cache, is_closure_zone_output_socket, true);
for (const SocketInContext origin_socket : origin_sockets) {
const bNodeTree &closure_tree = origin_socket->owner_tree();
const NodeInContext closure_output_node = origin_socket.owner_node();
const auto &closure_storage = *static_cast<const NodeClosureOutput *>(
closure_output_node->storage);
const bke::EvaluateClosureComputeContext &closure_context =
compute_context_cache.for_evaluate_closure(
node.context,
node->identifier,
&node->owner_tree(),
ClosureSourceLocation{&closure_tree,
closure_output_node->identifier,
origin_socket.context_hash(),
origin_socket.context});
if (closure_context.is_recursive()) {
continue;
}
for (const int i : IndexRange(closure_storage.output_items.items_num)) {
const NodeClosureOutputItem &item = closure_storage.output_items.items[i];
if (key == item.name) {
add_if_new({&closure_context, &closure_output_node->input_socket(i)}, bundle_path);
}
}
}
continue;
}
if (node->is_type("NodeClosureInput"_ustr)) {
const auto &input_storage = *static_cast<const NodeClosureInput *>(node->storage);
const bNode *closure_output_node = node->owner_tree().node_by_id(
input_storage.output_node_id);
if (!closure_output_node) {
continue;
}
const auto &output_storage = *static_cast<const NodeClosureOutput *>(
closure_output_node->storage);
const StringRef key = output_storage.input_items.items[socket->index()].name;
const bNodeSocket &closure_output_socket = closure_output_node->output_socket(0);
const Vector<SocketInContext> target_sockets = find_target_sockets_through_contexts(
{socket.context, &closure_output_socket},
compute_context_cache,
target_socket_evaluates_closure,
true);
for (const SocketInContext &target_socket : target_sockets) {
const NodeInContext target_node = target_socket.owner_node();
if (!target_node->is_type("NodeEvaluateClosure"_ustr)) {
continue;
}
const auto &evaluate_storage = *static_cast<const NodeEvaluateClosure *>(
target_node.node->storage);
for (const int i : IndexRange(evaluate_storage.input_items.items_num)) {
const NodeEvaluateClosureInputItem &item = evaluate_storage.input_items.items[i];
if (key == item.name) {
add_if_new(target_node.input_socket(i + 1), bundle_path);
}
}
}
continue;
}
if (node->is_type("NodeCombineBundle"_ustr)) {
if (bundle_path.is_empty()) {
continue;
}
const StringRef last_key = bundle_path.last();
const auto &storage = *static_cast<const NodeCombineBundle *>(node->storage);
for (const int input_i : IndexRange(storage.items_num)) {
if (last_key == storage.items[input_i].name) {
add_if_new(node.input_socket(input_i), bundle_path.as_span().drop_back(1));
}
}
continue;
}
if (node->is_type("NodeSeparateBundle"_ustr)) {
const auto &storage = *static_cast<const NodeSeparateBundle *>(node->storage);
BundlePath new_bundle_path = bundle_path;
new_bundle_path.append(storage.items[socket->index()].name);
add_if_new(node.input_socket(0), std::move(new_bundle_path));
continue;
}
if (node->is_type("GeometryNodeSimulationInput"_ustr)) {
const int output_index = socket->index();
if (output_index >= 1) {
BLI_assert(dynamic_cast<const bke::SimulationZoneComputeContext *>(socket.context));
add_if_new({socket.context->parent(), &node->input_socket(output_index - 1)},
bundle_path);
}
continue;
}
if (node->is_type("GeometryNodeSimulationOutput"_ustr)) {
const ComputeContext &simulation_compute_context =
compute_context_cache.for_simulation_zone(socket.context, *node);
add_if_new({&simulation_compute_context, &node->input_socket(socket->index() + 1)},
bundle_path);
continue;
}
if (node->is_type("GeometryNodeRepeatInput"_ustr)) {
const int index = socket->index();
if (index >= 1) {
BLI_assert(dynamic_cast<const bke::RepeatZoneComputeContext *>(socket.context));
add_if_new({socket.context->parent(), &node->input_socket(index)}, bundle_path);
}
continue;
}
if (node->is_type("GeometryNodeRepeatOutput"_ustr)) {
const int index = socket->index();
const ComputeContext &repeat_compute_context = compute_context_cache.for_repeat_zone(
socket.context, *node, 0);
add_if_new({&repeat_compute_context, &node->input_socket(index)}, bundle_path);
const bke::bNodeZoneType &zone_type = *bke::zone_type_by_node_type(node->type_legacy);
if (const bNode *repeat_input_node = zone_type.get_corresponding_input(node->owner_tree(),
*node))
{
add_if_new({socket.context, &repeat_input_node->input_socket(index + 1)}, bundle_path);
}
continue;
}
if (socket_decl) {
if (const decl::Bundle *bundle_decl = dynamic_cast<const decl::Bundle *>(socket_decl)) {
if (bundle_decl->pass_through_input_index) {
const int input_index = *bundle_decl->pass_through_input_index;
add_if_new(node.input_socket(input_index), bundle_path);
}
}
}
}
}
return found_origins.extract_vector();
}
LinkedBundleSignatures gather_linked_target_bundle_signatures(
const ComputeContext *bundle_socket_context,
const bNodeSocket &bundle_socket,
bke::ComputeContextCache &compute_context_cache)
{
LinkedBundleSignatures result;
find_target_sockets_through_contexts(
{bundle_socket_context, &bundle_socket},
compute_context_cache,
[&](const SocketInContext &socket) {
const bNode &node = socket->owner_node();
if (socket->is_input() && node.is_type("NodeSeparateBundle"_ustr)) {
const auto &storage = *static_cast<const NodeSeparateBundle *>(node.storage);
result.items.append({BundleSignature::from_separate_bundle_node(node, false),
bool(storage.flag & NODE_SEPARATE_BUNDLE_FLAG_DEFINE_SIGNATURE),
socket});
return true;
}
return false;
},
true);
return result;
}
LinkedBundleSignatures gather_linked_origin_bundle_signatures(
const ComputeContext *bundle_socket_context,
const bNodeSocket &bundle_socket,
bke::ComputeContextCache &compute_context_cache)
{
LinkedBundleSignatures result;
find_origin_sockets_through_contexts(
{bundle_socket_context, &bundle_socket},
compute_context_cache,
[&](const SocketInContext &socket) {
const NodeInContext node = socket.owner_node();
if (socket->is_output()) {
if (node->is_type("NodeCombineBundle"_ustr)) {
const auto &storage = *static_cast<const NodeCombineBundle *>(node->storage);
result.items.append({BundleSignature::from_combine_bundle_node(*node, false),
bool(storage.flag & NODE_COMBINE_BUNDLE_FLAG_DEFINE_SIGNATURE),
socket});
return true;
}
}
if (node->is_type("NodeJoinBundle"_ustr)) {
const SocketInContext input_socket = node.input_socket(0);
BundleSignature joined_signature;
bool is_signature_definition = true;
for (const bNodeLink *link : input_socket->directly_linked_links()) {
if (!link->is_used()) {
continue;
}
const bNodeSocket *socket_from = link->fromsock;
const LinkedBundleSignatures sub_signatures = gather_linked_origin_bundle_signatures(
node.context, *socket_from, compute_context_cache);
for (const LinkedBundleSignatures::Item &sub_signature : sub_signatures.items) {
if (!sub_signature.is_signature_definition) {
is_signature_definition = false;
}
for (const BundleSignature::Item &item : sub_signature.signature.items) {
joined_signature.items.add(item);
}
}
}
result.items.append({joined_signature, is_signature_definition, socket});
return true;
}
return false;
},
true);
return result;
}
LinkedClosureSignatures gather_linked_target_closure_signatures(
const ComputeContext *closure_socket_context,
const bNodeSocket &closure_socket,
bke::ComputeContextCache &compute_context_cache)
{
LinkedClosureSignatures result;
find_target_sockets_through_contexts(
{closure_socket_context, &closure_socket},
compute_context_cache,
[&](const SocketInContext &socket) {
const bNode &node = socket->owner_node();
if (const SocketDeclaration *decl = socket.socket->runtime->declaration) {
if (const auto *closure_decl = dynamic_cast<const decl::Closure *>(decl)) {
if (closure_decl->create_signature) {
bool define_signature = false;
if (node.is_type("NodeEvaluateClosure"_ustr)) {
const auto &storage = *static_cast<const NodeEvaluateClosure *>(node.storage);
define_signature = bool(storage.flag &
NODE_EVALUATE_CLOSURE_FLAG_DEFINE_SIGNATURE);
}
else if (node.is_type("GeometryNodeClosureToList"_ustr)) {
define_signature = true;
}
result.items.append(
{(*closure_decl->create_signature)(node), define_signature, socket});
return true;
}
}
}
return false;
},
true);
return result;
}
LinkedClosureSignatures gather_linked_origin_closure_signatures(
const ComputeContext *closure_socket_context,
const bNodeSocket &closure_socket,
bke::ComputeContextCache &compute_context_cache)
{
LinkedClosureSignatures result;
find_origin_sockets_through_contexts(
{closure_socket_context, &closure_socket},
compute_context_cache,
[&](const SocketInContext &socket) {
const bNode &node = socket->owner_node();
if (is_closure_zone_output_socket(socket)) {
const auto &storage = *static_cast<const NodeClosureOutput *>(node.storage);
result.items.append({ClosureSignature::from_closure_output_node(node, false),
bool(storage.flag & NODE_CLOSURE_FLAG_DEFINE_SIGNATURE),
socket});
return true;
}
return false;
},
true);
return result;
}
std::optional<NodeInContext> find_origin_index_menu_switch(
const SocketInContext &src_socket, bke::ComputeContextCache &compute_context_cache)
{
std::optional<NodeInContext> result;
find_origin_sockets_through_contexts(
src_socket,
compute_context_cache,
[&](const SocketInContext &socket) {
if (socket->is_input()) {
return false;
}
const NodeInContext node = socket.owner_node();
if (!node->is_type("GeometryNodeMenuSwitch"_ustr)) {
return false;
}
const auto &storage = *static_cast<const NodeMenuSwitch *>(node->storage);
if (storage.data_type != SOCK_INT) {
return false;
}
result = socket.owner_node();
return true;
},
false);
return result;
}
} // namespace blender::nodes

View File

@@ -0,0 +1,100 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "NOD_value_elem_eval.hh"
namespace blender::nodes::value_elem {
std::optional<ElemVariant> get_elem_variant_for_socket_type(const eNodeSocketDatatype type)
{
switch (type) {
case SOCK_FLOAT:
return {{FloatElem()}};
case SOCK_INT:
return {{IntElem()}};
case SOCK_BOOLEAN:
return {{BoolElem()}};
case SOCK_VECTOR:
return {{VectorElem()}};
case SOCK_ROTATION:
return {{RotationElem()}};
case SOCK_MATRIX:
return {{MatrixElem()}};
default:
return std::nullopt;
}
}
std::optional<ElemVariant> convert_socket_elem(const bNodeSocket &old_socket,
const bNodeSocket &new_socket,
const ElemVariant &old_elem)
{
const eNodeSocketDatatype old_type = old_socket.type;
const eNodeSocketDatatype new_type = new_socket.type;
if (old_type == new_type) {
return old_elem;
}
if (ELEM(old_type, SOCK_INT, SOCK_FLOAT, SOCK_BOOLEAN) &&
ELEM(new_type, SOCK_INT, SOCK_FLOAT, SOCK_BOOLEAN))
{
std::optional<ElemVariant> new_elem = get_elem_variant_for_socket_type(new_type);
if (old_elem) {
new_elem->set_all();
}
return new_elem;
}
switch (old_type) {
case SOCK_MATRIX: {
const MatrixElem &transform_elem = std::get<MatrixElem>(old_elem.elem);
if (new_type == SOCK_ROTATION) {
return ElemVariant{transform_elem.rotation};
}
break;
}
case SOCK_ROTATION: {
const RotationElem &rotation_elem = std::get<RotationElem>(old_elem.elem);
if (new_type == SOCK_MATRIX) {
MatrixElem matrix_elem;
matrix_elem.rotation = rotation_elem;
return ElemVariant{matrix_elem};
}
if (new_type == SOCK_VECTOR) {
return ElemVariant{rotation_elem.euler};
}
break;
}
case SOCK_VECTOR: {
const VectorElem &vector_elem = std::get<VectorElem>(old_elem.elem);
if (new_type == SOCK_ROTATION) {
RotationElem rotation_elem;
rotation_elem.euler = vector_elem;
if (rotation_elem) {
rotation_elem.angle = FloatElem::all();
rotation_elem.axis = VectorElem::all();
}
return ElemVariant{rotation_elem};
}
}
default:
break;
}
return std::nullopt;
}
ElemEvalParams::ElemEvalParams(const bNode &node,
const Map<const bNodeSocket *, ElemVariant> &elem_by_socket,
Vector<SocketElem> &output_elems)
: elem_by_socket_(elem_by_socket), output_elems_(output_elems), node(node)
{
}
InverseElemEvalParams::InverseElemEvalParams(
const bNode &node,
const Map<const bNodeSocket *, ElemVariant> &elem_by_socket,
Vector<SocketElem> &input_elems)
: elem_by_socket_(elem_by_socket), input_elems_(input_elems), node(node)
{
}
} // namespace blender::nodes::value_elem

View File

@@ -0,0 +1,87 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "FN_multi_function.hh"
#include "BKE_node_socket_value.hh"
#include "BKE_volume_grid.hh"
#include "BKE_volume_grid_multi_function_eval.hh"
#include "BLT_translation.hh"
#include "volume_grid_function_eval.hh"
namespace blender::nodes {
namespace grid = bke::volume_grid;
#ifdef WITH_OPENVDB
bool execute_multi_function_on_value_variant__volume_grid(
const mf::MultiFunction &fn,
const Span<bke::SocketValueVariant *> input_values,
const Span<bke::SocketValueVariant *> output_values,
std::string &r_error_message)
{
using namespace bke::volume_grid::multi_function_eval;
const int inputs_num = input_values.size();
Vector<bke::volume_grid::multi_function_eval::InputVariant> inputs(inputs_num);
Array<bke::volume_grid::GVolumeGrid> input_grids(inputs_num);
Array<std::optional<GField>> input_fields(inputs_num);
Array<bke::VolumeTreeAccessToken> input_tree_tokens(inputs_num);
for (const int i : input_values.index_range()) {
bke::SocketValueVariant &input_value = *input_values[i];
if (input_value.is_volume_grid()) {
input_grids[i] = input_value.extract<bke::volume_grid::GVolumeGrid>();
inputs[i] = &input_grids[i]->grid(input_tree_tokens[i]);
}
else if (input_value.is_context_dependent_field()) {
input_fields[i] = input_value.extract<GField>();
inputs[i] = &*input_fields[i];
}
else {
input_value.convert_to_single();
inputs[i] = input_value.get_single_ptr();
}
}
Array<bool> output_usages(output_values.size());
for (const int i : output_values.index_range()) {
output_usages[i] = output_values[i] != nullptr;
}
EvalResult result = evaluate_multi_function_on_grid(fn, inputs, output_usages);
if (const auto *failure = std::get_if<EvalResult::Failure>(&result.result)) {
r_error_message = failure->error_message;
return false;
}
auto &success = std::get<EvalResult::Success>(result.result);
for (const int i : output_values.index_range()) {
if (output_usages[i]) {
output_values[i]->set(bke::GVolumeGrid(std::move(success.output_grids[i])));
}
}
return true;
}
#else
bool execute_multi_function_on_value_variant__volume_grid(
const mf::MultiFunction & /*fn*/,
const Span<bke::SocketValueVariant *> /*input_values*/,
const Span<bke::SocketValueVariant *> /*output_values*/,
std::string &r_error_message)
{
r_error_message = TIP_("Compiled without OpenVDB");
return false;
}
#endif
} // namespace blender::nodes

View File

@@ -0,0 +1,34 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup nodes
*/
#pragma once
#include "FN_multi_function.hh"
#include "NOD_geometry_exec.hh"
namespace blender::nodes {
/**
* Execute the multi-function with the given parameters. It is assumed that at least one of the
* inputs is a grid. Otherwise the topology of the output grids is not known.
*
* \param fn: The multi-function to call.
* \param input_values: All input values which may be grids, fields or single values.
* \param output_values: Where the output grids will be stored.
* \param r_error_message: An error message that is set if false is returned.
*
* \return False if an error occurred. In this case the output values should not be used.
*/
[[nodiscard]] bool execute_multi_function_on_value_variant__volume_grid(
const mf::MultiFunction &fn,
const Span<SocketValueVariant *> input_values,
const Span<SocketValueVariant *> output_values,
std::string &r_error_message);
} // namespace blender::nodes