Geometry Nodes: Initial very basic list support
This includes a new list structure type and socket shape, a node to create lists, a node to retrieve values from lists, and a node to retrieve the length of lists. It also implements multi-function support so that function nodes work on lists. There are three nodes included in this PR. - **List** Creates a list of elements with a given size. The values are computed with a field that can use the index as an input. - **Get List Item** A field node that retrieves an element from a a list at a given index. The index input is dynamic, so if the input is a list, the output will be a list too. - **List Length** Just gives the length of a list. When a function node is used with multiple list inputs, the shorter lists are repeated to extend it to the length of the longest. The list nodes and structure type are hidden behind an experimental feature until we can be sure they're useful for an actual use case. Pull Request: https://projects.blender.org/blender/blender/pulls/140679
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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")),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<typename T> 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<typename T> T SocketValueVariant::extract()
|
||||
BLI_assert(static_type_is_base_socket_type<typename T::base_type>(socket_type_));
|
||||
return T(this->extract<fn::GField>());
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, nodes::ListPtr>) {
|
||||
if (kind_ != Kind::List) {
|
||||
return {};
|
||||
}
|
||||
return std::move(value_.get<nodes::ListPtr>());
|
||||
}
|
||||
#ifdef WITH_OPENVDB
|
||||
else if constexpr (std::is_same_v<T, GVolumeGrid>) {
|
||||
switch (kind_) {
|
||||
@@ -147,6 +155,7 @@ template<typename T> T SocketValueVariant::extract()
|
||||
return std::move(value_.get<GVolumeGrid>());
|
||||
}
|
||||
case Kind::Single:
|
||||
case Kind::List:
|
||||
case Kind::Field: {
|
||||
const std::optional<VolumeGridType> grid_type = socket_type_to_grid_type(socket_type_);
|
||||
BLI_assert(grid_type);
|
||||
@@ -174,6 +183,9 @@ template<typename T> T SocketValueVariant::extract()
|
||||
fn::evaluate_constant_field(value_.get<fn::GField>(), &ret_value);
|
||||
return ret_value;
|
||||
}
|
||||
if (kind_ == Kind::List) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return T();
|
||||
@@ -201,6 +213,14 @@ template<typename T> void SocketValueVariant::store_impl(T value)
|
||||
/* Always store #Field<T> as #GField. */
|
||||
this->store_impl<fn::GField>(std::move(value));
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, nodes::ListPtr>) {
|
||||
kind_ = Kind::List;
|
||||
const std::optional<eNodeSocketDatatype> 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<nodes::ListPtr>(std::move(value));
|
||||
}
|
||||
#ifdef WITH_OPENVDB
|
||||
else if constexpr (std::is_same_v<T, GVolumeGrid>) {
|
||||
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<float4x4>)
|
||||
|
||||
@@ -87,7 +87,7 @@ static Array<nodes::StructureTypeInterface> 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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -329,6 +329,9 @@ class SocketTooltipBuilder {
|
||||
{
|
||||
this->build_tooltip_value_closure_log(*closure_log);
|
||||
}
|
||||
else if (const auto *list_log = dynamic_cast<const geo_log::ListInfoLog *>(&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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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). */
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
75
source/blender/nodes/NOD_geometry_nodes_list.hh
Normal file
75
source/blender/nodes/NOD_geometry_nodes_list.hh
Normal file
@@ -0,0 +1,75 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
|
||||
#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<ArrayData, SingleData>;
|
||||
|
||||
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<List>(__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
|
||||
14
source/blender/nodes/NOD_geometry_nodes_list_fwd.hh
Normal file
14
source/blender/nodes/NOD_geometry_nodes_list_fwd.hh
Normal file
@@ -0,0 +1,14 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_implicit_sharing_ptr.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
class List;
|
||||
using ListPtr = ImplicitSharingPtr<List>;
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -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<ClosureEvalLog> 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.
|
||||
|
||||
@@ -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<typename T>
|
||||
static constexpr bool geo_nodes_type_stored_as_SocketValueVariant_v =
|
||||
std::is_enum_v<T> || geo_nodes_is_field_base_type_v<T> || fn::is_field_v<T> ||
|
||||
bke::is_VolumeGrid_v<T> ||
|
||||
is_same_any_v<T, fn::GField, bke::GVolumeGrid, nodes::BundlePtr, nodes::ClosurePtr>;
|
||||
is_same_any_v<T,
|
||||
fn::GField,
|
||||
bke::GVolumeGrid,
|
||||
nodes::BundlePtr,
|
||||
nodes::ClosurePtr,
|
||||
nodes::ListPtr>;
|
||||
|
||||
[[nodiscard]] bool execute_multi_function_on_value_variant(
|
||||
const mf::MultiFunction &fn,
|
||||
const std::shared_ptr<mf::MultiFunction> &owned_fn,
|
||||
const Span<bke::SocketValueVariant *> input_values,
|
||||
const Span<bke::SocketValueVariant *> output_values,
|
||||
GeoNodesUserData *user_data,
|
||||
std::string &r_error_message);
|
||||
|
||||
/**
|
||||
* Performs implicit conversion between socket types. Returns false if the conversion is not
|
||||
|
||||
@@ -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
|
||||
|
||||
121
source/blender/nodes/geometry/nodes/node_geo_list.cc
Normal file
121
source/blender/nodes/geometry/nodes/node_geo_list.cc
Normal file
@@ -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<decl::Int>("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<int>("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<GField>("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
|
||||
159
source/blender/nodes/geometry/nodes/node_geo_list_get_item.cc
Normal file
159
source/blender/nodes/geometry/nodes/node_geo_list_get_item.cc
Normal file
@@ -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<decl::Int>("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<int>("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<int> &indices = params.readonly_single_input<int>(0, "Index");
|
||||
GMutableSpan dst = params.uninitialized_single_output(1, "Value");
|
||||
const List::DataVariant &data = list_->data();
|
||||
if (const auto *array_data = std::get_if<nodes::List::ArrayData>(&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<nodes::List::SingleData>(&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<bke::SocketValueVariant>("Index");
|
||||
ListPtr list = params.extract_input<ListPtr>("List");
|
||||
if (!list) {
|
||||
params.set_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
|
||||
auto fn_ptr = std::make_shared<SampleIndexFunction>(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
|
||||
109
source/blender/nodes/geometry/nodes/node_geo_list_length.cc
Normal file
109
source/blender/nodes/geometry/nodes/node_geo_list_length.cc
Normal file
@@ -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<decl::Int>("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<ListPtr>("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
|
||||
@@ -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 <fmt/format.h>
|
||||
@@ -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<MultiFunction> &owned_fn,
|
||||
const Span<SocketValueVariant *> 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;
|
||||
|
||||
127
source/blender/nodes/intern/geometry_nodes_list.cc
Normal file
127
source/blender/nodes/intern/geometry_nodes_list.cc
Normal file
@@ -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<ArrayImplicitSharingData>(__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<SingleImplicitSharingData>(__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
|
||||
@@ -246,6 +246,15 @@ ClosureValueLog::ClosureValueLog(Vector<Item> 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<GridInfoLog>(grid));
|
||||
}
|
||||
#endif
|
||||
else if (value_variant.is_list()) {
|
||||
const ListPtr list = value_variant.extract<ListPtr>();
|
||||
store_logged_value(this->allocator->construct<ListInfoLog>(list.get()));
|
||||
}
|
||||
else if (value_variant.valid_for_socket(SOCK_BUNDLE)) {
|
||||
Vector<BundleValueLog::Item> items;
|
||||
if (const BundlePtr bundle = value_variant.extract<BundlePtr>()) {
|
||||
|
||||
160
source/blender/nodes/intern/list_function_eval.cc
Normal file
160
source/blender/nodes/intern/list_function_eval.cc
Normal file
@@ -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<const bke::IDAttributeFieldInput *>(&field_input);
|
||||
|
||||
const fn::IndexFieldInput *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);
|
||||
}
|
||||
};
|
||||
|
||||
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<nodes::List::ArrayData>(&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<nodes::List::SingleData>(&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<nodes::List::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::List::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 (ListPtr list = input_variant.get<ListPtr>()) {
|
||||
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<ListPtr, 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()) {
|
||||
ListPtr list_ptr = input_variant.get<ListPtr>();
|
||||
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();
|
||||
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
|
||||
24
source/blender/nodes/intern/list_function_eval.hh
Normal file
24
source/blender/nodes/intern/list_function_eval.hh
Normal file
@@ -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<SocketValueVariant *> input_values,
|
||||
const Span<SocketValueVariant *> output_values,
|
||||
GeoNodesUserData *user_data);
|
||||
|
||||
ListPtr evaluate_field_to_list(GField field, const int64_t count);
|
||||
|
||||
} // namespace blender::nodes
|
||||
BIN
tests/files/modeling/geometry_nodes/list/basic_eval.blend
(Stored with Git LFS)
Normal file
BIN
tests/files/modeling/geometry_nodes/list/basic_eval.blend
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/modeling/geometry_nodes/list/get_item_dynamic.blend
(Stored with Git LFS)
Normal file
BIN
tests/files/modeling/geometry_nodes/list/get_item_dynamic.blend
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/modeling/geometry_nodes/list/to_single_value.blend
(Stored with Git LFS)
Normal file
BIN
tests/files/modeling/geometry_nodes/list/to_single_value.blend
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -1051,6 +1051,7 @@ if(TEST_SRC_DIR_EXISTS)
|
||||
grease_pencil
|
||||
import
|
||||
instance
|
||||
list
|
||||
repeat_zone
|
||||
mesh_primitives
|
||||
mesh
|
||||
|
||||
Reference in New Issue
Block a user