Add Chromium-only Blender WebEngine parity work
This commit is contained in:
96
blender-5.2.0/source/blender/nodes/intern/bundle_type.cc
Normal file
96
blender-5.2.0/source/blender/nodes/intern/bundle_type.cc
Normal 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 ®istry = 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 ®istry = 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 ®istry = get_bundle_type_registry();
|
||||
registry.types_.lookup_or_add_default(bundle_type.name()).add(bundle_type);
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
1411
blender-5.2.0/source/blender/nodes/intern/compositor_gizmos.cc
Normal file
1411
blender-5.2.0/source/blender/nodes/intern/compositor_gizmos.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
324
blender-5.2.0/source/blender/nodes/intern/dependencies.cc
Normal file
324
blender-5.2.0/source/blender/nodes/intern/dependencies.cc
Normal 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
|
||||
132
blender-5.2.0/source/blender/nodes/intern/discover_nodes.py
Normal file
132
blender-5.2.0/source/blender/nodes/intern/discover_nodes.py
Normal 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())
|
||||
1168
blender-5.2.0/source/blender/nodes/intern/eval_log.cc
Normal file
1168
blender-5.2.0/source/blender/nodes/intern/eval_log.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 © = 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
|
||||
@@ -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
@@ -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
|
||||
@@ -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 ¶ms, 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 ¶ms, 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 ¶ms) 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 ¶ms)
|
||||
{
|
||||
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
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
335
blender-5.2.0/source/blender/nodes/intern/geometry_nodes_list.cc
Normal file
335
blender-5.2.0/source/blender/nodes/intern/geometry_nodes_list.cc
Normal 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
|
||||
@@ -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
|
||||
@@ -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 ¶ms,
|
||||
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 ¶ms, 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 ¶ms,
|
||||
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
|
||||
227
blender-5.2.0/source/blender/nodes/intern/geometry_nodes_srna.cc
Normal file
227
blender-5.2.0/source/blender/nodes/intern/geometry_nodes_srna.cc
Normal 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
|
||||
@@ -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
|
||||
809
blender-5.2.0/source/blender/nodes/intern/inverse_eval.cc
Normal file
809
blender-5.2.0/source/blender/nodes/intern/inverse_eval.cc
Normal 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
|
||||
193
blender-5.2.0/source/blender/nodes/intern/list_function_eval.cc
Normal file
193
blender-5.2.0/source/blender/nodes/intern/list_function_eval.cc
Normal 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 ¶ms,
|
||||
const mf::ParamType ¶m_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
|
||||
@@ -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
|
||||
281
blender-5.2.0/source/blender/nodes/intern/math_functions.cc
Normal file
281
blender-5.2.0/source/blender/nodes/intern/math_functions.cc
Normal 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
|
||||
176
blender-5.2.0/source/blender/nodes/intern/node_caller_ui.cc
Normal file
176
blender-5.2.0/source/blender/nodes/intern/node_caller_ui.cc
Normal 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
|
||||
1211
blender-5.2.0/source/blender/nodes/intern/node_common.cc
Normal file
1211
blender-5.2.0/source/blender/nodes/intern/node_common.cc
Normal file
File diff suppressed because it is too large
Load Diff
34
blender-5.2.0/source/blender/nodes/intern/node_common.h
Normal file
34
blender-5.2.0/source/blender/nodes/intern/node_common.h
Normal 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
|
||||
1217
blender-5.2.0/source/blender/nodes/intern/node_declaration.cc
Normal file
1217
blender-5.2.0/source/blender/nodes/intern/node_declaration.cc
Normal file
File diff suppressed because it is too large
Load Diff
311
blender-5.2.0/source/blender/nodes/intern/node_exec.cc
Normal file
311
blender-5.2.0/source/blender/nodes/intern/node_exec.cc
Normal 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
|
||||
63
blender-5.2.0/source/blender/nodes/intern/node_exec.hh
Normal file
63
blender-5.2.0/source/blender/nodes/intern/node_exec.hh
Normal 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
|
||||
260
blender-5.2.0/source/blender/nodes/intern/node_geometry_exec.cc
Normal file
260
blender-5.2.0/source/blender/nodes/intern/node_geometry_exec.cc
Normal 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
|
||||
232
blender-5.2.0/source/blender/nodes/intern/node_iterator_tests.cc
Normal file
232
blender-5.2.0/source/blender/nodes/intern/node_iterator_tests.cc
Normal 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
|
||||
@@ -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
|
||||
161
blender-5.2.0/source/blender/nodes/intern/node_register.cc
Normal file
161
blender-5.2.0/source/blender/nodes/intern/node_register.cc
Normal 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
|
||||
77
blender-5.2.0/source/blender/nodes/intern/node_rna_define.cc
Normal file
77
blender-5.2.0/source/blender/nodes/intern/node_rna_define.cc
Normal 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
|
||||
2381
blender-5.2.0/source/blender/nodes/intern/node_socket.cc
Normal file
2381
blender-5.2.0/source/blender/nodes/intern/node_socket.cc
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
178
blender-5.2.0/source/blender/nodes/intern/node_tree_defaults.cc
Normal file
178
blender-5.2.0/source/blender/nodes/intern/node_tree_defaults.cc
Normal 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
|
||||
321
blender-5.2.0/source/blender/nodes/intern/node_util.cc
Normal file
321
blender-5.2.0/source/blender/nodes/intern/node_util.cc
Normal 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
|
||||
75
blender-5.2.0/source/blender/nodes/intern/node_util.hh
Normal file
75
blender-5.2.0/source/blender/nodes/intern/node_util.hh
Normal 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 ¶ms);
|
||||
|
||||
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
|
||||
428
blender-5.2.0/source/blender/nodes/intern/partial_eval.cc
Normal file
428
blender-5.2.0/source/blender/nodes/intern/partial_eval.cc
Normal 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
|
||||
1587
blender-5.2.0/source/blender/nodes/intern/shader_nodes_inline.cc
Normal file
1587
blender-5.2.0/source/blender/nodes/intern/shader_nodes_inline.cc
Normal file
File diff suppressed because it is too large
Load Diff
206
blender-5.2.0/source/blender/nodes/intern/socket_search_link.cc
Normal file
206
blender-5.2.0/source/blender/nodes/intern/socket_search_link.cc
Normal 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 ¶ms,
|
||||
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 ¶ms) {
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms,
|
||||
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
|
||||
1136
blender-5.2.0/source/blender/nodes/intern/socket_usage_inference.cc
Normal file
1136
blender-5.2.0/source/blender/nodes/intern/socket_usage_inference.cc
Normal file
File diff suppressed because it is too large
Load Diff
1042
blender-5.2.0/source/blender/nodes/intern/socket_value_inference.cc
Normal file
1042
blender-5.2.0/source/blender/nodes/intern/socket_value_inference.cc
Normal file
File diff suppressed because it is too large
Load Diff
58
blender-5.2.0/source/blender/nodes/intern/string_pattern.cc
Normal file
58
blender-5.2.0/source/blender/nodes/intern/string_pattern.cc
Normal 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
|
||||
812
blender-5.2.0/source/blender/nodes/intern/sync_sockets.cc
Normal file
812
blender-5.2.0/source/blender/nodes/intern/sync_sockets.cc
Normal 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 ¤t_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 ¤t_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 ¤t_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 ¤t_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 ¤t_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
|
||||
760
blender-5.2.0/source/blender/nodes/intern/trace_values.cc
Normal file
760
blender-5.2.0/source/blender/nodes/intern/trace_values.cc
Normal 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
|
||||
100
blender-5.2.0/source/blender/nodes/intern/value_elem.cc
Normal file
100
blender-5.2.0/source/blender/nodes/intern/value_elem.cc
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user