diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index 8c5743d944c..cd56c2794d2 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -660,6 +660,8 @@ class NODE_MT_category_GEO_UTILITIES(Menu): layout.separator() layout.menu("NODE_MT_category_GEO_UTILITIES_FIELD") layout.menu("NODE_MT_category_GEO_UTILITIES_MATH") + if context.preferences.experimental.use_geometry_nodes_lists: + layout.menu("NODE_MT_category_utilities_list") layout.menu("NODE_MT_category_utilities_matrix") layout.menu("NODE_MT_category_GEO_UTILITIES_ROTATION") layout.menu("NODE_MT_category_GEO_UTILITIES_DEPRECATED") @@ -745,6 +747,18 @@ class NODE_MT_category_utilities_matrix(Menu): node_add_menu.draw_assets_for_catalog(layout, "Utilities/Matrix") +class NODE_MT_category_utilities_list(Menu): + bl_idname = "NODE_MT_category_utilities_list" + bl_label = "List" + + def draw(self, _context): + layout = self.layout + node_add_menu.add_node_type(layout, "GeometryNodeList") + node_add_menu.add_node_type(layout, "GeometryNodeListGetItem") + node_add_menu.add_node_type(layout, "GeometryNodeListLength") + node_add_menu.draw_assets_for_catalog(layout, "Utilities/List") + + class NODE_MT_category_GEO_UTILITIES_MATH(Menu): bl_idname = "NODE_MT_category_GEO_UTILITIES_MATH" bl_label = "Math" @@ -961,6 +975,7 @@ classes = ( NODE_MT_category_GEO_UTILITIES_MATH, NODE_MT_category_GEO_UTILITIES_ROTATION, NODE_MT_geometry_node_GEO_INPUT_GIZMO, + NODE_MT_category_utilities_list, NODE_MT_category_utilities_matrix, NODE_MT_category_GEO_UTILITIES_DEPRECATED, NODE_MT_category_GEO_GROUP, diff --git a/scripts/startup/bl_ui/space_userpref.py b/scripts/startup/bl_ui/space_userpref.py index 0b774e6612b..befde901bde 100644 --- a/scripts/startup/bl_ui/space_userpref.py +++ b/scripts/startup/bl_ui/space_userpref.py @@ -2865,6 +2865,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel): ({"property": "use_shader_node_previews"}, ("blender/blender/issues/110353", "#110353")), ({"property": "use_bundle_and_closure_nodes"}, ("blender/blender/issues/134029", "#134029")), ({"property": "use_socket_structure_type"}, ("blender/blender/issues/127106", "#127106")), + ({"property": "use_geometry_nodes_lists"}, ("blender/blender/issues/140918", "#140918")), ), ) diff --git a/source/blender/blenkernel/BKE_node_socket_value.hh b/source/blender/blenkernel/BKE_node_socket_value.hh index a4519a701a9..d0e642b89e6 100644 --- a/source/blender/blenkernel/BKE_node_socket_value.hh +++ b/source/blender/blenkernel/BKE_node_socket_value.hh @@ -56,6 +56,8 @@ class SocketValueVariant { * Indicates that there is a `GVolumeGrid` stored. */ Grid, + /** Indicates that there is a `ListPtr` stored. */ + List, }; /** @@ -148,6 +150,11 @@ class SocketValueVariant { */ bool is_single() const; + /** + * The stored value is a list. + */ + bool is_list() const; + /** * Convert the stored value into a single value. For simple value access, this is not necessary, * because #get does the conversion implicitly. However, it is necessary if one wants to use diff --git a/source/blender/blenkernel/intern/cpp_types.cc b/source/blender/blenkernel/intern/cpp_types.cc index 6999191eb4d..3736e90c304 100644 --- a/source/blender/blenkernel/intern/cpp_types.cc +++ b/source/blender/blenkernel/intern/cpp_types.cc @@ -13,6 +13,7 @@ #include "NOD_geometry_nodes_bundle.hh" #include "NOD_geometry_nodes_closure.hh" +#include "NOD_geometry_nodes_list.hh" #include "DNA_meshdata_types.h" @@ -34,6 +35,7 @@ BLI_CPP_TYPE_MAKE(Material *, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(MStringProperty, CPPTypeFlags::None); BLI_CPP_TYPE_MAKE(blender::nodes::BundlePtr, CPPTypeFlags::None); BLI_CPP_TYPE_MAKE(blender::nodes::ClosurePtr, CPPTypeFlags::None); +BLI_CPP_TYPE_MAKE(blender::nodes::ListPtr, CPPTypeFlags::None); BLI_CPP_TYPE_MAKE(blender::bke::GeometryNodesReferenceSet, CPPTypeFlags::None); BLI_CPP_TYPE_MAKE(blender::bke::SocketValueVariant, CPPTypeFlags::Printable); @@ -57,6 +59,7 @@ void BKE_cpp_types_init() BLI_CPP_TYPE_REGISTER(MStringProperty); BLI_CPP_TYPE_REGISTER(blender::nodes::BundlePtr); BLI_CPP_TYPE_REGISTER(blender::nodes::ClosurePtr); + BLI_CPP_TYPE_REGISTER(blender::nodes::ListPtr); BLI_CPP_TYPE_REGISTER(blender::bke::GeometryNodesReferenceSet); BLI_CPP_TYPE_REGISTER(blender::bke::SocketValueVariant); diff --git a/source/blender/blenkernel/intern/node_socket_value.cc b/source/blender/blenkernel/intern/node_socket_value.cc index a739ba34d54..4c2ab7114e9 100644 --- a/source/blender/blenkernel/intern/node_socket_value.cc +++ b/source/blender/blenkernel/intern/node_socket_value.cc @@ -13,6 +13,7 @@ #include "BKE_volume_grid.hh" #include "NOD_geometry_nodes_bundle.hh" #include "NOD_geometry_nodes_closure.hh" +#include "NOD_geometry_nodes_list.hh" #include "BLI_color.hh" #include "BLI_math_rotation_types.hh" @@ -124,6 +125,7 @@ template T SocketValueVariant::extract() const GPointer single_value = this->get_single_ptr(); return fn::make_constant_field(*single_value.type(), single_value.get()); } + case Kind::List: case Kind::Grid: { const CPPType *cpp_type = socket_type_to_geo_nodes_base_cpp_type(socket_type_); BLI_assert(cpp_type); @@ -139,6 +141,12 @@ template T SocketValueVariant::extract() BLI_assert(static_type_is_base_socket_type(socket_type_)); return T(this->extract()); } + else if constexpr (std::is_same_v) { + if (kind_ != Kind::List) { + return {}; + } + return std::move(value_.get()); + } #ifdef WITH_OPENVDB else if constexpr (std::is_same_v) { switch (kind_) { @@ -147,6 +155,7 @@ template T SocketValueVariant::extract() return std::move(value_.get()); } case Kind::Single: + case Kind::List: case Kind::Field: { const std::optional grid_type = socket_type_to_grid_type(socket_type_); BLI_assert(grid_type); @@ -174,6 +183,9 @@ template T SocketValueVariant::extract() fn::evaluate_constant_field(value_.get(), &ret_value); return ret_value; } + if (kind_ == Kind::List) { + return {}; + } } BLI_assert_unreachable(); return T(); @@ -201,6 +213,14 @@ template void SocketValueVariant::store_impl(T value) /* Always store #Field as #GField. */ this->store_impl(std::move(value)); } + else if constexpr (std::is_same_v) { + kind_ = Kind::List; + const std::optional new_socket_type = + geo_nodes_base_cpp_type_to_socket_type(value->cpp_type()); + BLI_assert(new_socket_type); + socket_type_ = *new_socket_type; + value_.emplace(std::move(value)); + } #ifdef WITH_OPENVDB else if constexpr (std::is_same_v) { BLI_assert(value); @@ -300,6 +320,11 @@ bool SocketValueVariant::is_single() const return kind_ == Kind::Single; } +bool SocketValueVariant::is_list() const +{ + return kind_ == Kind::List; +} + void SocketValueVariant::convert_to_single() { switch (kind_) { @@ -315,6 +340,7 @@ void SocketValueVariant::convert_to_single() fn::evaluate_constant_field(field, buffer); break; } + case Kind::List: case Kind::Grid: { /* Can't convert a grid to a single value, so just use the default value of the current * socket type. */ @@ -434,6 +460,7 @@ INSTANTIATE(std::string) INSTANTIATE(fn::GField) INSTANTIATE(blender::nodes::BundlePtr) INSTANTIATE(blender::nodes::ClosurePtr) +INSTANTIATE(blender::nodes::ListPtr) INSTANTIATE(float4x4) INSTANTIATE(fn::Field) diff --git a/source/blender/blenkernel/intern/node_tree_structure_type_inferencing.cc b/source/blender/blenkernel/intern/node_tree_structure_type_inferencing.cc index 76828f3a62d..acb870749da 100644 --- a/source/blender/blenkernel/intern/node_tree_structure_type_inferencing.cc +++ b/source/blender/blenkernel/intern/node_tree_structure_type_inferencing.cc @@ -87,7 +87,7 @@ static Array calc_node_interfaces(const bNodeTree return interfaces; } -enum class DataRequirement : int8_t { None, Field, Single, Grid, Invalid }; +enum class DataRequirement : int8_t { None, Field, Single, Grid, List, Invalid }; static DataRequirement merge(const DataRequirement a, const DataRequirement b) { @@ -120,6 +120,8 @@ static StructureType data_requirement_to_auto_structure_type(const DataRequireme return StructureType::Single; case DataRequirement::Grid: return StructureType::Grid; + case DataRequirement::List: + return StructureType::List; case DataRequirement::Invalid: return StructureType::Dynamic; } @@ -166,6 +168,10 @@ static void init_input_requirements(const bNodeTree &tree, requirement = DataRequirement::Field; break; } + case StructureType::List: { + requirement = DataRequirement::List; + break; + } } } } @@ -403,7 +409,8 @@ static void propagate_right_to_left(const bNodeTree &tree, break; } case DataRequirement::Field: - case DataRequirement::Grid: { + case DataRequirement::Grid: + case DataRequirement::List: { /* When a data requirement could be provided by multiple node inputs (i.e. only a * single node input involved in a math operation has to be a volume grid for the * output to be a grid), it's better to not propagate the data requirement than diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc index 3423317ed62..7255d3d7f2b 100644 --- a/source/blender/blenkernel/intern/node_tree_update.cc +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -901,6 +901,8 @@ class NodeTreeMainUpdater { return SOCK_DISPLAY_SHAPE_DIAMOND; case StructureType::Grid: return SOCK_DISPLAY_SHAPE_VOLUME_GRID; + case StructureType::List: + return SOCK_DISPLAY_SHAPE_LIST; } BLI_assert_unreachable(); return SOCK_DISPLAY_SHAPE_CIRCLE; @@ -925,6 +927,9 @@ class NodeTreeMainUpdater { case StructureType::Grid: { return SOCK_DISPLAY_SHAPE_VOLUME_GRID; } + case StructureType::List: { + return SOCK_DISPLAY_SHAPE_LIST; + } } BLI_assert_unreachable(); return SOCK_DISPLAY_SHAPE_CIRCLE; diff --git a/source/blender/editors/space_node/node_socket_tooltip.cc b/source/blender/editors/space_node/node_socket_tooltip.cc index 666c05769ed..63efeaa5f82 100644 --- a/source/blender/editors/space_node/node_socket_tooltip.cc +++ b/source/blender/editors/space_node/node_socket_tooltip.cc @@ -329,6 +329,9 @@ class SocketTooltipBuilder { { this->build_tooltip_value_closure_log(*closure_log); } + else if (const auto *list_log = dynamic_cast(&value_log)) { + this->build_tooltip_value_list_log(*list_log); + } } void build_tooltip_value_and_type_oneline(const StringRef value, const StringRef type) @@ -737,6 +740,13 @@ class SocketTooltipBuilder { this->add_text_field_mono(TIP_("Type: Closure")); } + void build_tooltip_value_list_log(const geo_log::ListInfoLog &list_log) + { + this->add_text_field_mono(fmt::format("{}: {}", TIP_("Length"), list_log.size)); + this->add_space(); + this->add_text_field_mono(TIP_("Type: List")); + } + void build_tooltip_value_implicit_default(const NodeDefaultInputType &type) { switch (type) { @@ -814,6 +824,9 @@ class SocketTooltipBuilder { case nodes::StructureType::Grid: { return TIP_("Volume Grid"); } + case nodes::StructureType::List: { + return TIP_("List"); + } } BLI_assert_unreachable(); return "Unknown"; diff --git a/source/blender/gpu/shaders/gpu_shader_2D_node_socket_frag.glsl b/source/blender/gpu/shaders/gpu_shader_2D_node_socket_frag.glsl index 4103adef316..8775ae5f6cc 100644 --- a/source/blender/gpu/shaders/gpu_shader_2D_node_socket_frag.glsl +++ b/source/blender/gpu/shaders/gpu_shader_2D_node_socket_frag.glsl @@ -17,6 +17,7 @@ FRAGMENT_SHADER_CREATE_INFO(gpu_shader_2D_node_socket_inst) #define SOCK_DISPLAY_SHAPE_DIAMOND_DOT 5 #define SOCK_DISPLAY_SHAPE_LINE 6 #define SOCK_DISPLAY_SHAPE_VOLUME_GRID 7 +#define SOCK_DISPLAY_SHAPE_LIST 8 /* Calculates a squared distance field of a square. */ float square_sdf(float2 absCo, float2 half_size) @@ -110,6 +111,17 @@ void main() alpha_threshold = corner_rounding; break; } + case SOCK_DISPLAY_SHAPE_LIST: { + constexpr float2 rect_side_length = float2(0.5f, 0.25f); + const float2 oversize = float2(0.0f, square_radius * 1.4) / 2.5f; + const float2 rect_corner = max(rect_side_length, extrusion / 2.0f + oversize) + + finalOutlineThickness / 4.0f; + const float2 mirrored_uv = float2( + abs(uv.x), abs(abs(abs(uv.y) - rect_corner.y / 1.5f) - rect_corner.y / 1.5f)); + distance_squared = square_sdf( + mirrored_uv, (rect_corner + finalOutlineThickness / 2.0f) / float2(1.0f, 1.5f)); + break; + } } float2 alpha_thresholds = calculate_thresholds(alpha_threshold); diff --git a/source/blender/makesdna/DNA_node_tree_interface_types.h b/source/blender/makesdna/DNA_node_tree_interface_types.h index 39261406564..51f2401b50d 100644 --- a/source/blender/makesdna/DNA_node_tree_interface_types.h +++ b/source/blender/makesdna/DNA_node_tree_interface_types.h @@ -79,6 +79,7 @@ typedef enum NodeSocketInterfaceStructureType { NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_DYNAMIC = 2, NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_FIELD = 3, NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_GRID = 4, + NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_LIST = 5, } NodeSocketInterfaceStructureType; // TODO: Move out of DNA. @@ -89,6 +90,7 @@ enum class StructureType : int8_t { Dynamic = NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_DYNAMIC, Field = NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_FIELD, Grid = NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_GRID, + List = NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_LIST, }; } #endif diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 92d911b258c..880ad47ffa2 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -329,6 +329,7 @@ typedef enum eNodeSocketDisplayShape { SOCK_DISPLAY_SHAPE_DIAMOND_DOT = 5, SOCK_DISPLAY_SHAPE_LINE = 6, SOCK_DISPLAY_SHAPE_VOLUME_GRID = 7, + SOCK_DISPLAY_SHAPE_LIST = 8, } eNodeSocketDisplayShape; /** Socket side (input/output). */ diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 2001abe44ac..cfb899d13c4 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -227,7 +227,8 @@ typedef struct UserDef_Experimental { char use_shader_node_previews; char use_bundle_and_closure_nodes; char use_socket_structure_type; - char _pad[5]; + char use_geometry_nodes_lists; + char _pad[4]; } UserDef_Experimental; #define USER_EXPERIMENTAL_TEST(userdef, member) \ diff --git a/source/blender/makesrna/intern/rna_node_tree_interface.cc b/source/blender/makesrna/intern/rna_node_tree_interface.cc index eccb6ea2847..b299a1e23ef 100644 --- a/source/blender/makesrna/intern/rna_node_tree_interface.cc +++ b/source/blender/makesrna/intern/rna_node_tree_interface.cc @@ -39,6 +39,7 @@ const EnumPropertyItem rna_enum_node_socket_structure_type_items[] = { "Socket can work with different kinds of structures"}, {NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_FIELD, "FIELD", 0, "Field", "Socket expects a field"}, {NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_GRID, "GRID", 0, "Grid", "Socket expects a grid"}, + {NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_LIST, "LIST", 0, "List", "Socket expects a list"}, {NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_SINGLE, "SINGLE", 0, @@ -535,6 +536,14 @@ static const EnumPropertyItem *rna_NodeTreeInterfaceSocket_structure_type_itemf( } break; } + case NODE_INTERFACE_SOCKET_STRUCTURE_TYPE_LIST: { + if (U.experimental.use_socket_structure_type && U.experimental.use_geometry_nodes_lists) { + if (supports_grids) { + RNA_enum_item_add(&items, &items_count, item); + } + } + break; + } } } RNA_enum_item_end(&items, &items_count); diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index cd2a32d4551..75211605940 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -10890,6 +10890,9 @@ static void rna_def_nodes(BlenderRNA *brna) define("GeometryNode", "GeometryNodeInterpolateCurves"); define("GeometryNode", "GeometryNodeIsViewport"); define("GeometryNode", "GeometryNodeJoinGeometry"); + define("GeometryNode", "GeometryNodeList"); + define("GeometryNode", "GeometryNodeListGetItem"); + define("GeometryNode", "GeometryNodeListLength"); define("GeometryNode", "GeometryNodeMaterialSelection"); define("GeometryNode", "GeometryNodeMenuSwitch", def_geo_menu_switch); define("GeometryNode", "GeometryNodeMergeByDistance"); diff --git a/source/blender/makesrna/intern/rna_userdef.cc b/source/blender/makesrna/intern/rna_userdef.cc index beda7c72241..8d8220caa8f 100644 --- a/source/blender/makesrna/intern/rna_userdef.cc +++ b/source/blender/makesrna/intern/rna_userdef.cc @@ -7597,6 +7597,9 @@ static void rna_def_userdef_experimental(BlenderRNA *brna) "Node Structure Types", "Enables new visualization of socket data compatibility in Geometry Nodes"); + prop = RNA_def_property(srna, "use_geometry_nodes_lists", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_ui_text(prop, "Geometry Nodes Lists", "Enable new list types and nodes"); + prop = RNA_def_property(srna, "use_extensions_debug", PROP_BOOLEAN, PROP_NONE); RNA_def_property_ui_text( prop, diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index e76571c59fc..40361bd7ab0 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -922,7 +922,7 @@ static void check_property_socket_sync(const Object *ob, if (i == 0 && type == SOCK_GEOMETRY) { continue; } - if (input_structure_types[i] == nodes::StructureType::Grid) { + if (ELEM(input_structure_types[i], nodes::StructureType::Grid, nodes::StructureType::List)) { continue; } diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index 5cd780c79bc..da12b0e13de 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -78,10 +78,12 @@ set(SRC intern/geometry_nodes_foreach_geometry_element_zone.cc intern/geometry_nodes_gizmos.cc intern/geometry_nodes_lazy_function.cc + intern/geometry_nodes_list.cc intern/geometry_nodes_log.cc intern/geometry_nodes_repeat_zone.cc intern/geometry_nodes_warning.cc intern/inverse_eval.cc + intern/list_function_eval.cc intern/math_functions.cc intern/node_common.cc intern/node_declaration.cc @@ -117,6 +119,8 @@ set(SRC NOD_geometry_nodes_execute.hh NOD_geometry_nodes_gizmos.hh NOD_geometry_nodes_lazy_function.hh + NOD_geometry_nodes_list.hh + NOD_geometry_nodes_list_fwd.hh NOD_geometry_nodes_log.hh NOD_geometry_nodes_warning.hh NOD_inverse_eval_params.hh @@ -148,6 +152,7 @@ set(SRC intern/node_common.h intern/node_exec.hh intern/node_util.hh + intern/list_function_eval.hh intern/volume_grid_function_eval.hh ) diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index 781f374c029..8231461ff49 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -19,6 +19,7 @@ #include "BKE_volume_grid_fwd.hh" #include "NOD_geometry_nodes_bundle_fwd.hh" #include "NOD_geometry_nodes_closure_fwd.hh" +#include "NOD_geometry_nodes_list_fwd.hh" #include "DNA_node_types.h" diff --git a/source/blender/nodes/NOD_geometry_nodes_list.hh b/source/blender/nodes/NOD_geometry_nodes_list.hh new file mode 100644 index 00000000000..8627b28f2e4 --- /dev/null +++ b/source/blender/nodes/NOD_geometry_nodes_list.hh @@ -0,0 +1,75 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include + +#include "BLI_generic_pointer.hh" + +#include "NOD_geometry_nodes_list_fwd.hh" + +namespace blender::nodes { + +class List : public ImplicitSharingMixin { + public: + class ArrayData { + public: + void *data; + ImplicitSharingPtr<> sharing_info; + static ArrayData ForValue(const GPointer &value, int64_t size); + static ArrayData ForDefaultValue(const CPPType &type, int64_t size); + static ArrayData ForConstructed(const CPPType &type, int64_t size); + static ArrayData ForUninitialized(const CPPType &type, int64_t size); + }; + + class SingleData { + public: + void *value; + ImplicitSharingPtr<> sharing_info; + static SingleData ForValue(const GPointer &value); + static SingleData ForDefaultValue(const CPPType &type); + }; + + using DataVariant = std::variant; + + private: + const CPPType &cpp_type_; + DataVariant data_; + int64_t size_ = 0; + + public: + explicit List(const CPPType &type, DataVariant data, const int64_t size) + : cpp_type_(type), data_(std::move(data)), size_(size) + { + } + + static ListPtr create(const CPPType &type, DataVariant data, const int64_t size) + { + return ListPtr(MEM_new(__func__, type, std::move(data), size)); + } + + const DataVariant &data() const; + const CPPType &cpp_type() const; + int64_t size() const; + + void delete_self() override; +}; + +inline const List::DataVariant &List::data() const +{ + return data_; +} + +inline const CPPType &List::cpp_type() const +{ + return cpp_type_; +} + +inline int64_t List::size() const +{ + return size_; +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/NOD_geometry_nodes_list_fwd.hh b/source/blender/nodes/NOD_geometry_nodes_list_fwd.hh new file mode 100644 index 00000000000..2862f9b2e05 --- /dev/null +++ b/source/blender/nodes/NOD_geometry_nodes_list_fwd.hh @@ -0,0 +1,14 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_implicit_sharing_ptr.hh" + +namespace blender::nodes { + +class List; +using ListPtr = ImplicitSharingPtr; + +} // namespace blender::nodes diff --git a/source/blender/nodes/NOD_geometry_nodes_log.hh b/source/blender/nodes/NOD_geometry_nodes_log.hh index 6562e509340..5a62286d740 100644 --- a/source/blender/nodes/NOD_geometry_nodes_log.hh +++ b/source/blender/nodes/NOD_geometry_nodes_log.hh @@ -42,6 +42,7 @@ #include "BKE_volume_grid_fwd.hh" #include "NOD_geometry_nodes_closure_location.hh" +#include "NOD_geometry_nodes_list.hh" #include "NOD_geometry_nodes_warning.hh" #include "FN_field.hh" @@ -226,6 +227,13 @@ class ClosureValueLog : public ValueLog { std::shared_ptr eval_log); }; +class ListInfoLog : public ValueLog { + public: + int64_t size; + + ListInfoLog(const List *list); +}; + /** * Data logged by a viewer node when it is executed. In this case, we do want to log the entire * geometry. diff --git a/source/blender/nodes/NOD_geometry_nodes_values.hh b/source/blender/nodes/NOD_geometry_nodes_values.hh index f97bed5295e..b11a78c54e3 100644 --- a/source/blender/nodes/NOD_geometry_nodes_values.hh +++ b/source/blender/nodes/NOD_geometry_nodes_values.hh @@ -14,9 +14,20 @@ #include "FN_field.hh" #include "FN_lazy_function.hh" +#include "FN_multi_function.hh" #include "NOD_geometry_nodes_bundle_fwd.hh" #include "NOD_geometry_nodes_closure_fwd.hh" +#include "NOD_geometry_nodes_list_fwd.hh" + +namespace blender { +namespace bke { +class SocketValueVariant; +} +namespace nodes { +struct GeoNodesUserData; +} +} // namespace blender namespace blender::nodes { @@ -38,7 +49,20 @@ template static constexpr bool geo_nodes_type_stored_as_SocketValueVariant_v = std::is_enum_v || geo_nodes_is_field_base_type_v || fn::is_field_v || bke::is_VolumeGrid_v || - is_same_any_v; + is_same_any_v; + +[[nodiscard]] bool execute_multi_function_on_value_variant( + const mf::MultiFunction &fn, + const std::shared_ptr &owned_fn, + const Span input_values, + const Span output_values, + GeoNodesUserData *user_data, + std::string &r_error_message); /** * Performs implicit conversion between socket types. Returns false if the conversion is not diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index bd14279b618..5134c929fb9 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -146,6 +146,9 @@ set(SRC nodes/node_geo_interpolate_curves.cc nodes/node_geo_is_viewport.cc nodes/node_geo_join_geometry.cc + nodes/node_geo_list.cc + nodes/node_geo_list_get_item.cc + nodes/node_geo_list_length.cc nodes/node_geo_material_replace.cc nodes/node_geo_material_selection.cc nodes/node_geo_menu_switch.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_list.cc b/source/blender/nodes/geometry/nodes/node_geo_list.cc new file mode 100644 index 00000000000..c59ac8b2e78 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_list.cc @@ -0,0 +1,121 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "NOD_geometry_nodes_list.hh" +#include "NOD_rna_define.hh" +#include "NOD_socket.hh" +#include "NOD_socket_search_link.hh" + +#include "RNA_enum_types.hh" + +#include "UI_interface_layout.hh" +#include "UI_resources.hh" + +#include "list_function_eval.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_list_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + const bNode *node = b.node_or_null(); + + b.add_input("Count").default_value(1).min(1).description( + "The number of elements in the list"); + + if (node != nullptr) { + const eNodeSocketDatatype type = eNodeSocketDatatype(node->custom1); + b.add_input(type, "Value").field_on_all(); + } + + if (node != nullptr) { + const eNodeSocketDatatype type = eNodeSocketDatatype(node->custom1); + b.add_output(type, "List").structure_type(StructureType::List); + } +} + +static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + layout->prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE); +} + +class SocketSearchOp { + public: + const StringRef socket_name; + eNodeSocketDatatype socket_type; + void operator()(LinkSearchOpParams ¶ms) + { + bNode &node = params.add_node("GeometryNodeList"); + node.custom1 = socket_type; + params.update_and_connect_available_socket(node, socket_name); + } +}; + +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) +{ + if (!U.experimental.use_geometry_nodes_lists) { + return; + } + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(params.other_socket().type); + if (params.in_out() == SOCK_IN) { + if (params.node_tree().typeinfo->validate_link(socket_type, SOCK_INT)) { + params.add_item(IFACE_("Count"), SocketSearchOp{"Count", SOCK_INT}); + } + params.add_item(IFACE_("Value"), SocketSearchOp{"Value", socket_type}); + } + else { + params.add_item(IFACE_("List"), SocketSearchOp{"List", socket_type}); + } +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + const int count = params.extract_input("Count"); + if (count < 0) { + params.error_message_add(NodeWarningType::Error, "Count must not be negative"); + params.set_default_remaining_outputs(); + return; + } + + GField field = params.extract_input("Value"); + params.set_output("List", evaluate_field_to_list(std::move(field), count)); +} + +static void node_rna(StructRNA *srna) +{ + RNA_def_node_enum( + srna, + "data_type", + "Data Type", + "", + rna_enum_node_socket_data_type_items, + NOD_inline_enum_accessors(custom1), + SOCK_GEOMETRY, + [](bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) { + *r_free = true; + return enum_items_filter( + rna_enum_node_socket_data_type_items, [](const EnumPropertyItem &item) -> bool { + return socket_type_supports_fields(eNodeSocketDatatype(item.value)); + }); + }); +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + geo_node_type_base(&ntype, "GeometryNodeList"); + ntype.ui_name = "List"; + ntype.ui_description = "Create a list of values"; + ntype.nclass = NODE_CLASS_CONVERTER; + ntype.geometry_node_execute = node_geo_exec; + ntype.declare = node_declare; + ntype.draw_buttons = node_layout; + ntype.gather_link_search_ops = node_gather_link_searches; + blender::bke::node_register_type(ntype); + node_rna(ntype.rna_ext.srna); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_list_cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_list_get_item.cc b/source/blender/nodes/geometry/nodes/node_geo_list_get_item.cc new file mode 100644 index 00000000000..749950daf64 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_list_get_item.cc @@ -0,0 +1,159 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "NOD_geometry_nodes_list.hh" +#include "NOD_geometry_nodes_values.hh" +#include "NOD_rna_define.hh" +#include "NOD_socket.hh" +#include "NOD_socket_search_link.hh" + +#include "RNA_enum_types.hh" + +#include "UI_interface_layout.hh" +#include "UI_resources.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_list_get_item_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + const bNode *node = b.node_or_null(); + + if (node != nullptr) { + const eNodeSocketDatatype type = eNodeSocketDatatype(node->custom1); + b.add_input(type, "List").structure_type(StructureType::List).hide_value(); + } + + b.add_input("Index").min(0).structure_type(StructureType::Dynamic); + + if (node != nullptr) { + const eNodeSocketDatatype type = eNodeSocketDatatype(node->custom1); + b.add_output(type, "Value").dependent_field({1}); + } +} + +static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + layout->prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE); +} + +class SocketSearchOp { + public: + const StringRef socket_name; + eNodeSocketDatatype socket_type; + void operator()(LinkSearchOpParams ¶ms) + { + bNode &node = params.add_node("GeometryNodeListGetItem"); + node.custom1 = socket_type; + params.update_and_connect_available_socket(node, socket_name); + } +}; + +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) +{ + if (!U.experimental.use_geometry_nodes_lists) { + return; + } + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(params.other_socket().type); + if (params.in_out() == SOCK_IN) { + if (params.node_tree().typeinfo->validate_link(socket_type, SOCK_INT)) { + params.add_item(IFACE_("Index"), SocketSearchOp{"Index", SOCK_INT}); + } + params.add_item(IFACE_("List"), SocketSearchOp{"List", socket_type}); + } + else { + params.add_item(IFACE_("Value"), SocketSearchOp{"Value", socket_type}); + } +} + +class SampleIndexFunction : public mf::MultiFunction { + ListPtr list_; + mf::Signature signature_; + + public: + SampleIndexFunction(ListPtr list) : list_(std::move(list)) + { + mf::SignatureBuilder builder{"Sample Index", signature_}; + builder.single_input("Index"); + builder.single_output("Value", list_->cpp_type()); + this->set_signature(&signature_); + } + + void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override + { + const VArray &indices = params.readonly_single_input(0, "Index"); + GMutableSpan dst = params.uninitialized_single_output(1, "Value"); + const List::DataVariant &data = list_->data(); + if (const auto *array_data = std::get_if(&data)) { + const GSpan span(list_->cpp_type(), array_data->data, list_->size()); + bke::copy_with_checked_indices(GVArray::from_span(span), indices, mask, dst); + } + else if (const auto *single_data = std::get_if(&data)) { + list_->cpp_type().fill_construct_indices(single_data->value, dst.data(), mask); + } + } +}; + +static void node_rna(StructRNA *srna) +{ + RNA_def_node_enum( + srna, + "data_type", + "Data Type", + "", + rna_enum_node_socket_data_type_items, + NOD_inline_enum_accessors(custom1), + SOCK_GEOMETRY, + [](bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) { + *r_free = true; + return enum_items_filter( + rna_enum_node_socket_data_type_items, [](const EnumPropertyItem &item) -> bool { + return socket_type_supports_fields(eNodeSocketDatatype(item.value)); + }); + }); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + bke::SocketValueVariant index = params.extract_input("Index"); + ListPtr list = params.extract_input("List"); + if (!list) { + params.set_default_remaining_outputs(); + return; + } + + auto fn_ptr = std::make_shared(std::move(list)); + const mf::MultiFunction &fn = *fn_ptr; + + bke::SocketValueVariant output_value; + std::string error_message; + const bool success = execute_multi_function_on_value_variant( + fn, std::move(fn_ptr), {&index}, {&output_value}, params.user_data(), error_message); + if (!success) { + params.set_default_remaining_outputs(); + params.error_message_add(NodeWarningType::Error, std::move(error_message)); + return; + } + + params.set_output("Value", std::move(output_value)); +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + geo_node_type_base(&ntype, "GeometryNodeListGetItem"); + ntype.ui_name = "Get List Item"; + ntype.ui_description = "Retrieve a value from a list"; + ntype.nclass = NODE_CLASS_CONVERTER; + ntype.geometry_node_execute = node_geo_exec; + ntype.draw_buttons = node_layout; + ntype.declare = node_declare; + ntype.gather_link_search_ops = node_gather_link_searches; + blender::bke::node_register_type(ntype); + node_rna(ntype.rna_ext.srna); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_list_get_item_cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_list_length.cc b/source/blender/nodes/geometry/nodes/node_geo_list_length.cc new file mode 100644 index 00000000000..ef6e3329d66 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_list_length.cc @@ -0,0 +1,109 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "NOD_geometry_nodes_list.hh" +#include "NOD_rna_define.hh" +#include "NOD_socket.hh" +#include "NOD_socket_search_link.hh" + +#include "RNA_enum_types.hh" + +#include "UI_interface_layout.hh" +#include "UI_resources.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_list_length_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + const bNode *node = b.node_or_null(); + + if (node != nullptr) { + const eNodeSocketDatatype type = eNodeSocketDatatype(node->custom1); + b.add_input(type, "List").structure_type(StructureType::List).hide_value(); + } + + b.add_output("Length"); +} + +static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + layout->prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE); +} + +class SocketSearchOp { + public: + const StringRef socket_name; + eNodeSocketDatatype socket_type; + void operator()(LinkSearchOpParams ¶ms) + { + bNode &node = params.add_node("GeometryNodeListLength"); + node.custom1 = socket_type; + params.update_and_connect_available_socket(node, socket_name); + } +}; + +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) +{ + if (!U.experimental.use_geometry_nodes_lists) { + return; + } + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(params.other_socket().type); + if (params.in_out() == SOCK_IN) { + params.add_item(IFACE_("List"), SocketSearchOp{"List", socket_type}); + } + else { + if (params.node_tree().typeinfo->validate_link(socket_type, SOCK_INT)) { + params.add_item(IFACE_("Length"), SocketSearchOp{"Length", SOCK_INT}); + } + } +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + ListPtr list = params.extract_input("List"); + if (!list) { + params.set_default_remaining_outputs(); + return; + } + params.set_output("Length", int(list->size())); +} + +static void node_rna(StructRNA *srna) +{ + RNA_def_node_enum( + srna, + "data_type", + "Data Type", + "", + rna_enum_node_socket_data_type_items, + NOD_inline_enum_accessors(custom1), + SOCK_GEOMETRY, + [](bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) { + *r_free = true; + return enum_items_filter( + rna_enum_node_socket_data_type_items, [](const EnumPropertyItem &item) -> bool { + return socket_type_supports_fields(eNodeSocketDatatype(item.value)); + }); + }); +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + geo_node_type_base(&ntype, "GeometryNodeListLength"); + ntype.ui_name = "List Length"; + ntype.ui_description = "Return the length of a list"; + ntype.nclass = NODE_CLASS_CONVERTER; + ntype.geometry_node_execute = node_geo_exec; + ntype.declare = node_declare; + ntype.draw_buttons = node_layout; + ntype.gather_link_search_ops = node_gather_link_searches; + blender::bke::node_register_type(ntype); + node_rna(ntype.rna_ext.srna); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_list_length_cc diff --git a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc index db40bb46448..8e586a0af2e 100644 --- a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -22,6 +22,7 @@ #include "NOD_geometry_exec.hh" #include "NOD_geometry_nodes_lazy_function.hh" +#include "NOD_geometry_nodes_list.hh" #include "NOD_multi_function.hh" #include "NOD_node_declaration.hh" @@ -53,6 +54,7 @@ #include "DEG_depsgraph_query.hh" +#include "list_function_eval.hh" #include "volume_grid_function_eval.hh" #include @@ -536,7 +538,7 @@ static void execute_multi_function_on_value_variant__field( * Executes a multi-function. If all inputs are single values, the results will also be single * values. If any input is a field, the outputs will also be fields. */ -[[nodiscard]] static bool execute_multi_function_on_value_variant( +[[nodiscard]] bool execute_multi_function_on_value_variant( const MultiFunction &fn, const std::shared_ptr &owned_fn, const Span input_values, @@ -547,6 +549,7 @@ static void execute_multi_function_on_value_variant__field( /* Check input types which determine how the function is evaluated. */ bool any_input_is_field = false; bool any_input_is_volume_grid = false; + bool any_input_is_list = false; for (const int i : input_values.index_range()) { const SocketValueVariant &value = *input_values[i]; if (value.is_context_dependent_field()) { @@ -555,12 +558,19 @@ static void execute_multi_function_on_value_variant__field( else if (value.is_volume_grid()) { any_input_is_volume_grid = true; } + else if (value.is_list()) { + any_input_is_list = true; + } } if (any_input_is_volume_grid) { return execute_multi_function_on_value_variant__volume_grid( fn, input_values, output_values, r_error_message); } + if (any_input_is_list) { + execute_multi_function_on_value_variant__list(fn, input_values, output_values, user_data); + return true; + } if (any_input_is_field) { execute_multi_function_on_value_variant__field(fn, owned_fn, input_values, output_values); return true; diff --git a/source/blender/nodes/intern/geometry_nodes_list.cc b/source/blender/nodes/intern/geometry_nodes_list.cc new file mode 100644 index 00000000000..7469b8bcddc --- /dev/null +++ b/source/blender/nodes/intern/geometry_nodes_list.cc @@ -0,0 +1,127 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#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(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(__func__, data, size, type)); +} + +List::ArrayData List::ArrayData::ForValue(const GPointer &value, const int64_t size) +{ + List::ArrayData data{}; + const CPPType &type = *value.type(); + const void *value_ptr = type.default_value(); + + /* Prefer `calloc` to zeroing after allocation since it is faster. */ + if (BLI_memory_is_zero(value_ptr, type.size)) { + data.data = MEM_calloc_arrayN_aligned(size, type.size, type.alignment, __func__); + } + else { + data.data = MEM_malloc_arrayN_aligned(size, type.size, type.alignment, __func__); + type.fill_construct_n(value_ptr, data.data, size); + } + + data.sharing_info = sharing_ptr_for_array(data.data, size, type); + return data; +} + +List::ArrayData List::ArrayData::ForDefaultValue(const CPPType &type, const int64_t size) +{ + return ForValue(GPointer(type, type.default_value()), size); +} + +List::ArrayData List::ArrayData::ForConstructed(const CPPType &type, const int64_t size) +{ + List::ArrayData data{}; + data.data = MEM_malloc_arrayN_aligned(size, type.size, type.alignment, __func__); + type.default_construct_n(data.data, size); + data.sharing_info = sharing_ptr_for_array(data.data, size, type); + return data; +} + +List::ArrayData List::ArrayData::ForUninitialized(const CPPType &type, const int64_t size) +{ + List::ArrayData data{}; + data.data = MEM_malloc_arrayN_aligned(size, type.size, type.alignment, __func__); + data.sharing_info = sharing_ptr_for_array(data.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(__func__, data, type)); +} + +List::SingleData List::SingleData::ForValue(const GPointer &value) +{ + List::SingleData data{}; + const CPPType &type = *value.type(); + data.value = MEM_mallocN_aligned(type.size, type.alignment, __func__); + type.copy_construct(value.get(), data.value); + data.sharing_info = sharing_ptr_for_value(data.value, type); + return data; +} + +List::SingleData List::SingleData::ForDefaultValue(const CPPType &type) +{ + return ForValue(GPointer(type, type.default_value())); +} + +void List::delete_self() +{ + MEM_delete(this); +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/intern/geometry_nodes_log.cc b/source/blender/nodes/intern/geometry_nodes_log.cc index e14c8c0043f..f2206ec9c1e 100644 --- a/source/blender/nodes/intern/geometry_nodes_log.cc +++ b/source/blender/nodes/intern/geometry_nodes_log.cc @@ -246,6 +246,15 @@ ClosureValueLog::ClosureValueLog(Vector inputs, } } +ListInfoLog::ListInfoLog(const List *list) +{ + if (!list) { + this->size = 0; + return; + } + this->size = list->size(); +} + NodeWarning::NodeWarning(const Report &report) { switch (report.type) { @@ -314,6 +323,10 @@ void GeoTreeLogger::log_value(const bNode &node, const bNodeSocket &socket, cons store_logged_value(this->allocator->construct(grid)); } #endif + else if (value_variant.is_list()) { + const ListPtr list = value_variant.extract(); + store_logged_value(this->allocator->construct(list.get())); + } else if (value_variant.valid_for_socket(SOCK_BUNDLE)) { Vector items; if (const BundlePtr bundle = value_variant.extract()) { diff --git a/source/blender/nodes/intern/list_function_eval.cc b/source/blender/nodes/intern/list_function_eval.cc new file mode 100644 index 00000000000..7a6d9efc201 --- /dev/null +++ b/source/blender/nodes/intern/list_function_eval.cc @@ -0,0 +1,160 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#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 { + +class ListFieldContext : public FieldContext { + public: + ListFieldContext() = default; + + GVArray get_varray_for_input(const FieldInput &field_input, + const IndexMask &mask, + ResourceScope & /*scope*/) const override + { + const bke::IDAttributeFieldInput *id_field_input = + dynamic_cast(&field_input); + + const fn::IndexFieldInput *index_field_input = dynamic_cast( + &field_input); + + if (id_field_input == nullptr && index_field_input == nullptr) { + return {}; + } + + return fn::IndexFieldInput::get_index_varray(mask); + } +}; + +ListPtr evaluate_field_to_list(GField field, const int64_t count) +{ + const CPPType &cpp_type = field.cpp_type(); + List::ArrayData array_data = List::ArrayData::ForConstructed(cpp_type, count); + GMutableSpan span(cpp_type, array_data.data, count); + + ListFieldContext context{}; + fn::FieldEvaluator evaluator{context, count}; + evaluator.add_with_destination(std::move(field), span); + evaluator.evaluate(); + + return List::create(cpp_type, std::move(array_data), count); +} + +static ListPtr create_repeated_list(ListPtr list, const int64_t dst_size) +{ + if (list->size() >= dst_size) { + return list; + } + if (const auto *data = std::get_if(&list->data())) { + const int64_t size = list->size(); + BLI_assert(size > 0); + const CPPType &cpp_type = list->cpp_type(); + List::ArrayData new_data = List::ArrayData::ForUninitialized(cpp_type, dst_size); + const int64_t chunks = dst_size / size; + for (const int64_t i : IndexRange(chunks)) { + const int64_t offset = cpp_type.size * i * size; + cpp_type.copy_construct_n(data->data, POINTER_OFFSET(new_data.data, offset), size); + } + const int64_t last_chunk_size = dst_size % size; + if (last_chunk_size > 0) { + const int64_t offset = cpp_type.size * chunks * size; + cpp_type.copy_construct_n( + data->data, POINTER_OFFSET(new_data.data, offset), last_chunk_size); + } + + return List::create(cpp_type, std::move(new_data), dst_size); + } + if (const auto *data = std::get_if(&list->data())) { + const CPPType &cpp_type = list->cpp_type(); + return List::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 List &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(&list.data())) { + params.add_readonly_single_input(GSpan(cpp_type, array_data->data, list.size())); + } + else if (const auto *single_data = std::get_if(&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 input_values, + const Span 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 (ListPtr list = input_variant.get()) { + 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 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()) { + ListPtr list_ptr = input_variant.get(); + 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(); + 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(); + List::ArrayData array_data = List::ArrayData::ForUninitialized(cpp_type, max_size); + + params.add_uninitialized_single_output(GMutableSpan(cpp_type, array_data.data, max_size)); + output_variant.set(List::create(cpp_type, std::move(array_data), max_size)); + } + fn.call(mask, params, context); +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/intern/list_function_eval.hh b/source/blender/nodes/intern/list_function_eval.hh new file mode 100644 index 00000000000..9928688007c --- /dev/null +++ b/source/blender/nodes/intern/list_function_eval.hh @@ -0,0 +1,24 @@ +/* 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 { + +void execute_multi_function_on_value_variant__list(const MultiFunction &fn, + const Span input_values, + const Span output_values, + GeoNodesUserData *user_data); + +ListPtr evaluate_field_to_list(GField field, const int64_t count); + +} // namespace blender::nodes diff --git a/tests/files/modeling/geometry_nodes/list/basic_eval.blend b/tests/files/modeling/geometry_nodes/list/basic_eval.blend new file mode 100644 index 00000000000..9ca6c865afe --- /dev/null +++ b/tests/files/modeling/geometry_nodes/list/basic_eval.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d2c8686571a42ebd7d5e27b9b010bc9734fb39fdd32402d76984d5828de6243 +size 87525 diff --git a/tests/files/modeling/geometry_nodes/list/get_item_dynamic.blend b/tests/files/modeling/geometry_nodes/list/get_item_dynamic.blend new file mode 100644 index 00000000000..fe444775f75 --- /dev/null +++ b/tests/files/modeling/geometry_nodes/list/get_item_dynamic.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3ac25a4ff184aae3689b1124fa94679a94384cc1e0ae13d4623a367ff804597 +size 84405 diff --git a/tests/files/modeling/geometry_nodes/list/to_single_value.blend b/tests/files/modeling/geometry_nodes/list/to_single_value.blend new file mode 100644 index 00000000000..b54cbd236bb --- /dev/null +++ b/tests/files/modeling/geometry_nodes/list/to_single_value.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c10af4e7b5d43254d9ce75279bd80b3d60fa89b8172731a48772b8eff0bd522c +size 86277 diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 0dc19d79162..633c290e5e9 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -1051,6 +1051,7 @@ if(TEST_SRC_DIR_EXISTS) grease_pencil import instance + list repeat_zone mesh_primitives mesh