Geometry Nodes: improve internal bundle api
This extends the API for bundles in two main ways: * Adds support for working with paths to reference nested bundles directly. * Adds support for typed add/lookup, also taking into account implicit conversions. Some unit tests have been added as well. Pull Request: https://projects.blender.org/blender/blender/pulls/142942
This commit is contained in:
@@ -4883,6 +4883,25 @@ std::optional<eNodeSocketDatatype> geo_nodes_base_cpp_type_to_socket_type(const
|
||||
if (type.is<nodes::ClosurePtr>()) {
|
||||
return SOCK_CLOSURE;
|
||||
}
|
||||
if (type.is<GeometrySet>()) {
|
||||
return SOCK_GEOMETRY;
|
||||
}
|
||||
if (type.is<Material *>()) {
|
||||
return SOCK_MATERIAL;
|
||||
}
|
||||
if (type.is<Tex *>()) {
|
||||
return SOCK_TEXTURE;
|
||||
}
|
||||
if (type.is<Object *>()) {
|
||||
return SOCK_OBJECT;
|
||||
}
|
||||
if (type.is<Collection *>()) {
|
||||
return SOCK_COLLECTION;
|
||||
}
|
||||
if (type.is<Image *>()) {
|
||||
return SOCK_IMAGE;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
@@ -240,6 +240,7 @@ if(WITH_GTESTS)
|
||||
)
|
||||
set(TEST_SRC
|
||||
intern/node_iterator_tests.cc
|
||||
intern/geometry_nodes_bundle_tests.cc
|
||||
)
|
||||
set(TEST_LIB
|
||||
bf_nodes
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "NOD_derived_node_tree.hh"
|
||||
#include "NOD_geometry_nodes_lazy_function.hh"
|
||||
#include "NOD_geometry_nodes_values.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
@@ -96,22 +97,6 @@ class GeoNodeExecParams {
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static constexpr bool is_field_base_type_v = is_same_any_v<T,
|
||||
float,
|
||||
int,
|
||||
bool,
|
||||
ColorGeometry4f,
|
||||
float3,
|
||||
std::string,
|
||||
math::Quaternion,
|
||||
float4x4>;
|
||||
|
||||
template<typename T>
|
||||
static constexpr bool stored_as_SocketValueVariant_v =
|
||||
is_field_base_type_v<T> || fn::is_field_v<T> || bke::is_VolumeGrid_v<T> ||
|
||||
is_same_any_v<T, GField, bke::GVolumeGrid, nodes::BundlePtr, nodes::ClosurePtr>;
|
||||
|
||||
/**
|
||||
* Get the input value for the input socket with the given identifier.
|
||||
*
|
||||
@@ -122,7 +107,7 @@ class GeoNodeExecParams {
|
||||
if constexpr (std::is_enum_v<T>) {
|
||||
return T(this->extract_input<int>(identifier));
|
||||
}
|
||||
else if constexpr (stored_as_SocketValueVariant_v<T>) {
|
||||
else if constexpr (geo_nodes_type_stored_as_SocketValueVariant_v<T>) {
|
||||
SocketValueVariant value_variant = this->extract_input<SocketValueVariant>(identifier);
|
||||
return value_variant.extract<T>();
|
||||
}
|
||||
@@ -154,7 +139,7 @@ class GeoNodeExecParams {
|
||||
if constexpr (std::is_enum_v<T>) {
|
||||
return T(this->get_input<int>(identifier));
|
||||
}
|
||||
else if constexpr (stored_as_SocketValueVariant_v<T>) {
|
||||
else if constexpr (geo_nodes_type_stored_as_SocketValueVariant_v<T>) {
|
||||
auto value_variant = this->get_input<SocketValueVariant>(identifier);
|
||||
return value_variant.extract<T>();
|
||||
}
|
||||
@@ -191,7 +176,7 @@ class GeoNodeExecParams {
|
||||
template<typename T> void set_output(StringRef identifier, T &&value)
|
||||
{
|
||||
using StoredT = std::decay_t<T>;
|
||||
if constexpr (stored_as_SocketValueVariant_v<StoredT>) {
|
||||
if constexpr (geo_nodes_type_stored_as_SocketValueVariant_v<StoredT>) {
|
||||
this->set_output(identifier, SocketValueVariant::From(std::forward<T>(value)));
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
|
||||
#include "BKE_node.hh"
|
||||
|
||||
#include "BKE_node_socket_value.hh"
|
||||
#include "NOD_geometry_nodes_bundle_fwd.hh"
|
||||
#include "NOD_geometry_nodes_values.hh"
|
||||
#include "NOD_socket_interface_key.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
@@ -16,6 +18,8 @@ namespace blender::nodes {
|
||||
/**
|
||||
* A bundle is a map containing keys and their corresponding values. Values are stored as the type
|
||||
* they have in Geometry Nodes (#bNodeSocketType::geometry_nodes_cpp_type).
|
||||
*
|
||||
* The API also supports working with paths in nested bundles like `root/child/data`.
|
||||
*/
|
||||
class Bundle : public ImplicitSharingMixin {
|
||||
public:
|
||||
@@ -33,6 +37,12 @@ class Bundle : public ImplicitSharingMixin {
|
||||
struct Item {
|
||||
const bke::bNodeSocketType *type;
|
||||
const void *value;
|
||||
|
||||
/**
|
||||
* Attempts to cast the stored value to the given type. This may do implicit conversions.
|
||||
*/
|
||||
template<typename T> std::optional<T> as(const bke::bNodeSocketType &socket_type) const;
|
||||
template<typename T> std::optional<T> as() const;
|
||||
};
|
||||
|
||||
Bundle();
|
||||
@@ -42,25 +52,183 @@ class Bundle : public ImplicitSharingMixin {
|
||||
Bundle &operator=(Bundle &&other) noexcept;
|
||||
~Bundle();
|
||||
|
||||
static BundlePtr create()
|
||||
{
|
||||
return BundlePtr(MEM_new<Bundle>(__func__));
|
||||
}
|
||||
static BundlePtr create();
|
||||
|
||||
void add_new(SocketInterfaceKey key, const bke::bNodeSocketType &type, const void *value);
|
||||
bool add(const SocketInterfaceKey &key, const bke::bNodeSocketType &type, const void *value);
|
||||
bool add(SocketInterfaceKey &&key, const bke::bNodeSocketType &type, const void *value);
|
||||
void add_new(SocketInterfaceKey key, const bke::bNodeSocketType &type, const void *value);
|
||||
void add_override(const SocketInterfaceKey &key,
|
||||
const bke::bNodeSocketType &type,
|
||||
const void *value);
|
||||
bool add_path(StringRef path, const bke::bNodeSocketType &type, const void *value);
|
||||
void add_path_new(StringRef path, const bke::bNodeSocketType &type, const void *value);
|
||||
void add_path_override(StringRef path, const bke::bNodeSocketType &type, const void *value);
|
||||
|
||||
template<typename T> void add(const SocketInterfaceKey &key, T value);
|
||||
template<typename T> void add_override(const SocketInterfaceKey &key, T value);
|
||||
template<typename T> void add_path(StringRef path, T value);
|
||||
template<typename T> void add_path_override(StringRef path, T value);
|
||||
|
||||
bool remove(const SocketInterfaceKey &key);
|
||||
bool contains(const SocketInterfaceKey &key) const;
|
||||
bool contains_path(StringRef path) const;
|
||||
|
||||
std::optional<Item> lookup(const SocketInterfaceKey &key) const;
|
||||
std::optional<Item> lookup_path(Span<StringRef> path) const;
|
||||
std::optional<Item> lookup_path(StringRef path) const;
|
||||
template<typename T> std::optional<T> lookup(const SocketInterfaceKey &key) const;
|
||||
template<typename T> std::optional<T> lookup_path(StringRef path) const;
|
||||
|
||||
Span<StoredItem> items() const
|
||||
{
|
||||
return items_;
|
||||
}
|
||||
bool is_empty() const;
|
||||
int64_t size() const;
|
||||
|
||||
Span<StoredItem> items() const;
|
||||
|
||||
BundlePtr copy() const;
|
||||
|
||||
void delete_self() override;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
inline std::optional<T> Bundle::Item::as(const bke::bNodeSocketType &socket_type) const
|
||||
{
|
||||
if (!this->value || !this->type) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const void *converted_value = this->value;
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(*socket_type.geometry_nodes_cpp_type, buffer);
|
||||
if (this->type != &socket_type) {
|
||||
if (!implicitly_convert_socket_value(*this->type, this->value, socket_type, buffer)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
converted_value = buffer;
|
||||
}
|
||||
if constexpr (geo_nodes_type_stored_as_SocketValueVariant_v<T>) {
|
||||
const auto &value_variant = *static_cast<const bke::SocketValueVariant *>(converted_value);
|
||||
return value_variant.get<T>();
|
||||
}
|
||||
return *static_cast<const T *>(converted_value);
|
||||
}
|
||||
|
||||
template<typename T> constexpr bool is_valid_static_bundle_item_type()
|
||||
{
|
||||
if (geo_nodes_is_field_base_type_v<T>) {
|
||||
return true;
|
||||
}
|
||||
if constexpr (fn::is_field_v<T>) {
|
||||
return geo_nodes_is_field_base_type_v<typename T::base_type>;
|
||||
}
|
||||
if constexpr (is_same_any_v<T, BundlePtr, ClosurePtr>) {
|
||||
return true;
|
||||
}
|
||||
return !geo_nodes_type_stored_as_SocketValueVariant_v<T>;
|
||||
}
|
||||
|
||||
template<typename T> inline const bke::bNodeSocketType *socket_type_info_by_static_type()
|
||||
{
|
||||
if constexpr (fn::is_field_v<T>) {
|
||||
if constexpr (geo_nodes_is_field_base_type_v<typename T::base_type>) {
|
||||
const std::optional<eNodeSocketDatatype> socket_type =
|
||||
bke::geo_nodes_base_cpp_type_to_socket_type(CPPType::get<typename T::base_type>());
|
||||
BLI_assert(socket_type);
|
||||
const bke::bNodeSocketType *socket_type_info = bke::node_socket_type_find_static(
|
||||
*socket_type);
|
||||
BLI_assert(socket_type_info);
|
||||
return socket_type_info;
|
||||
}
|
||||
}
|
||||
const std::optional<eNodeSocketDatatype> socket_type =
|
||||
bke::geo_nodes_base_cpp_type_to_socket_type(CPPType::get<T>());
|
||||
if (!socket_type) {
|
||||
return nullptr;
|
||||
}
|
||||
return bke::node_socket_type_find_static(*socket_type);
|
||||
}
|
||||
|
||||
template<typename T> inline std::optional<T> Bundle::Item::as() const
|
||||
{
|
||||
static_assert(is_valid_static_bundle_item_type<T>());
|
||||
if (const bke::bNodeSocketType *socket_type = socket_type_info_by_static_type<T>()) {
|
||||
return this->as<T>(*socket_type);
|
||||
}
|
||||
/* Can't lookup this type directly currently. */
|
||||
BLI_assert_unreachable();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<typename T> inline std::optional<T> Bundle::lookup(const SocketInterfaceKey &key) const
|
||||
{
|
||||
const std::optional<Item> item = this->lookup(key);
|
||||
if (!item) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return item->as<T>();
|
||||
}
|
||||
|
||||
template<typename T> inline std::optional<T> Bundle::lookup_path(const StringRef path) const
|
||||
{
|
||||
const std::optional<Item> item = this->lookup_path(path);
|
||||
if (!item) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return item->as<T>();
|
||||
}
|
||||
|
||||
template<typename T, typename Fn> inline void to_stored_type(T &&value, Fn &&fn)
|
||||
{
|
||||
using DecayT = std::decay_t<T>;
|
||||
static_assert(is_valid_static_bundle_item_type<DecayT>());
|
||||
const bke::bNodeSocketType *socket_type = socket_type_info_by_static_type<DecayT>();
|
||||
BLI_assert(socket_type);
|
||||
if constexpr (geo_nodes_type_stored_as_SocketValueVariant_v<DecayT>) {
|
||||
auto value_variant = bke::SocketValueVariant::From(std::forward<T>(value));
|
||||
fn(*socket_type, &value_variant);
|
||||
}
|
||||
else {
|
||||
fn(*socket_type, &value);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T> inline void Bundle::add(const SocketInterfaceKey &key, T value)
|
||||
{
|
||||
to_stored_type(value, [&](const bke::bNodeSocketType &type, const void *value) {
|
||||
this->add(key, type, value);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T> inline void Bundle::add_path(const StringRef path, T value)
|
||||
{
|
||||
to_stored_type(value, [&](const bke::bNodeSocketType &type, const void *value) {
|
||||
this->add_path(path, type, value);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T> inline void Bundle::add_override(const SocketInterfaceKey &key, T value)
|
||||
{
|
||||
to_stored_type(value, [&](const bke::bNodeSocketType &type, const void *value) {
|
||||
this->add_override(key, type, value);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T> inline void Bundle::add_path_override(const StringRef path, T value)
|
||||
{
|
||||
to_stored_type(value, [&](const bke::bNodeSocketType &type, const void *value) {
|
||||
this->add_path_override(path, type, value);
|
||||
});
|
||||
}
|
||||
|
||||
inline Span<Bundle::StoredItem> Bundle::items() const
|
||||
{
|
||||
return items_;
|
||||
}
|
||||
|
||||
inline bool Bundle::is_empty() const
|
||||
{
|
||||
return items_.is_empty();
|
||||
}
|
||||
|
||||
inline int64_t Bundle::size() const
|
||||
{
|
||||
return items_.size();
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
|
||||
@@ -629,23 +629,6 @@ std::string zone_wrapper_output_name(const ZoneBuildInfo &zone_info,
|
||||
const Span<lf::Output> outputs,
|
||||
const int lf_socket_i);
|
||||
|
||||
/**
|
||||
* Performs implicit conversion between socket types. Returns false if the conversion is not
|
||||
* possible. In that case, r_to_value is left uninitialized.
|
||||
*/
|
||||
[[nodiscard]] bool implicitly_convert_socket_value(const bke::bNodeSocketType &from_type,
|
||||
const void *from_value,
|
||||
const bke::bNodeSocketType &to_type,
|
||||
void *r_to_value);
|
||||
|
||||
/**
|
||||
* Builds a lazy-function that can convert between socket types. Returns null if the conversion is
|
||||
* never possible.
|
||||
*/
|
||||
const LazyFunction *build_implicit_conversion_lazy_function(const bke::bNodeSocketType &from_type,
|
||||
const bke::bNodeSocketType &to_type,
|
||||
ResourceScope &scope);
|
||||
|
||||
/**
|
||||
* Report an error from a multi-function evaluation within a Geometry Nodes evaluation.
|
||||
*
|
||||
|
||||
61
source/blender/nodes/NOD_geometry_nodes_values.hh
Normal file
61
source/blender/nodes/NOD_geometry_nodes_values.hh
Normal file
@@ -0,0 +1,61 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_volume_grid_fwd.hh"
|
||||
|
||||
#include "BLI_color.hh"
|
||||
#include "BLI_math_quaternion_types.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_memory_utils.hh"
|
||||
|
||||
#include "FN_field.hh"
|
||||
#include "FN_lazy_function.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_bundle_fwd.hh"
|
||||
#include "NOD_geometry_nodes_closure_fwd.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
/** True if a static type can also exist as field in Geometry Nodes. */
|
||||
template<typename T>
|
||||
static constexpr bool geo_nodes_is_field_base_type_v = is_same_any_v<T,
|
||||
float,
|
||||
int,
|
||||
bool,
|
||||
ColorGeometry4f,
|
||||
float3,
|
||||
std::string,
|
||||
math::Quaternion,
|
||||
float4x4>;
|
||||
|
||||
/** True if Geometry Nodes sockets can store values of the given type and the type is stored
|
||||
* embedded in a #SocketValueVariant. */
|
||||
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>;
|
||||
|
||||
/**
|
||||
* Performs implicit conversion between socket types. Returns false if the conversion is not
|
||||
* possible. In that case, r_to_value is left uninitialized.
|
||||
*/
|
||||
[[nodiscard]] bool implicitly_convert_socket_value(const bke::bNodeSocketType &from_type,
|
||||
const void *from_value,
|
||||
const bke::bNodeSocketType &to_type,
|
||||
void *r_to_value);
|
||||
|
||||
/**
|
||||
* Builds a lazy-function that can convert between socket types. Returns null if the conversion is
|
||||
* never possible.
|
||||
*/
|
||||
const fn::lazy_function::LazyFunction *build_implicit_conversion_lazy_function(
|
||||
const bke::bNodeSocketType &from_type,
|
||||
const bke::bNodeSocketType &to_type,
|
||||
ResourceScope &scope);
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -2,6 +2,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_node_socket_value.hh"
|
||||
#include "BLI_cpp_type.hh"
|
||||
|
||||
#include "BKE_node_runtime.hh"
|
||||
@@ -104,6 +105,11 @@ Bundle::Bundle(Bundle &&other) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
BundlePtr Bundle::create()
|
||||
{
|
||||
return BundlePtr(MEM_new<Bundle>(__func__));
|
||||
}
|
||||
|
||||
Bundle &Bundle::operator=(const Bundle &other)
|
||||
{
|
||||
if (this == &other) {
|
||||
@@ -135,6 +141,14 @@ void Bundle::add_new(SocketInterfaceKey key, const bke::bNodeSocketType &type, c
|
||||
buffers_.append(buffer);
|
||||
}
|
||||
|
||||
void Bundle::add_override(const SocketInterfaceKey &key,
|
||||
const bke::bNodeSocketType &type,
|
||||
const void *value)
|
||||
{
|
||||
this->remove(key);
|
||||
this->add_new(key, type, value);
|
||||
}
|
||||
|
||||
bool Bundle::add(const SocketInterfaceKey &key,
|
||||
const bke::bNodeSocketType &type,
|
||||
const void *value)
|
||||
@@ -146,15 +160,56 @@ bool Bundle::add(const SocketInterfaceKey &key,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bundle::add(SocketInterfaceKey &&key, const bke::bNodeSocketType &type, const void *value)
|
||||
void Bundle::add_path_override(const StringRef path,
|
||||
const bke::bNodeSocketType &type,
|
||||
const void *value)
|
||||
{
|
||||
if (this->contains(key)) {
|
||||
BLI_assert(!path.is_empty());
|
||||
BLI_assert(!path.endswith("/"));
|
||||
BLI_assert(this->is_mutable());
|
||||
const int sep = path.find_first_of('/');
|
||||
if (sep == StringRef::not_found) {
|
||||
this->remove(SocketInterfaceKey{path});
|
||||
this->add_new(SocketInterfaceKey{path}, type, value);
|
||||
return;
|
||||
}
|
||||
const StringRef first_part = path.substr(0, sep);
|
||||
BundlePtr child_bundle;
|
||||
const std::optional<Bundle::Item> item = this->lookup(SocketInterfaceKey{first_part});
|
||||
if (item && item->type->type == SOCK_BUNDLE) {
|
||||
child_bundle = static_cast<const bke::SocketValueVariant *>(item->value)->get<BundlePtr>();
|
||||
}
|
||||
if (!child_bundle) {
|
||||
child_bundle = Bundle::create();
|
||||
}
|
||||
this->remove(SocketInterfaceKey{first_part});
|
||||
if (!child_bundle->is_mutable()) {
|
||||
child_bundle = child_bundle->copy();
|
||||
}
|
||||
child_bundle->tag_ensured_mutable();
|
||||
const_cast<Bundle &>(*child_bundle).add_path_override(path.substr(sep + 1), type, value);
|
||||
bke::SocketValueVariant child_bundle_value = bke::SocketValueVariant::From(
|
||||
std::move(child_bundle));
|
||||
this->add(SocketInterfaceKey{first_part},
|
||||
*bke::node_socket_type_find_static(SOCK_BUNDLE),
|
||||
&child_bundle_value);
|
||||
}
|
||||
|
||||
bool Bundle::add_path(StringRef path, const bke::bNodeSocketType &type, const void *value)
|
||||
{
|
||||
if (this->contains_path(path)) {
|
||||
return false;
|
||||
}
|
||||
this->add_new(std::move(key), type, value);
|
||||
this->add_path_new(path, type, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Bundle::add_path_new(StringRef path, const bke::bNodeSocketType &type, const void *value)
|
||||
{
|
||||
BLI_assert(!this->contains_path(path));
|
||||
this->add_path_override(path, type, value);
|
||||
}
|
||||
|
||||
std::optional<Bundle::Item> Bundle::lookup(const SocketInterfaceKey &key) const
|
||||
{
|
||||
for (const StoredItem &item : items_) {
|
||||
@@ -165,6 +220,60 @@ std::optional<Bundle::Item> Bundle::lookup(const SocketInterfaceKey &key) const
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<Bundle::Item> Bundle::lookup_path(const Span<StringRef> path) const
|
||||
{
|
||||
BLI_assert(!path.is_empty());
|
||||
const StringRef first_elem = path[0];
|
||||
const std::optional<Bundle::Item> item = this->lookup(SocketInterfaceKey(first_elem));
|
||||
if (!item) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (path.size() == 1) {
|
||||
return item;
|
||||
}
|
||||
if (item->type->type != SOCK_BUNDLE) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const BundlePtr child_bundle =
|
||||
static_cast<const bke::SocketValueVariant *>(item->value)->get<BundlePtr>();
|
||||
if (!child_bundle) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return child_bundle->lookup_path(path.drop_front(1));
|
||||
}
|
||||
|
||||
static Vector<StringRef> split_path(const StringRef path)
|
||||
{
|
||||
Vector<StringRef> path_elems;
|
||||
StringRef remaining = path;
|
||||
while (!remaining.is_empty()) {
|
||||
const int sep = remaining.find_first_of('/');
|
||||
if (sep == StringRef::not_found) {
|
||||
path_elems.append(remaining);
|
||||
break;
|
||||
}
|
||||
path_elems.append(remaining.substr(0, sep));
|
||||
remaining = remaining.substr(sep + 1);
|
||||
}
|
||||
return path_elems;
|
||||
}
|
||||
|
||||
std::optional<Bundle::Item> Bundle::lookup_path(const StringRef path) const
|
||||
{
|
||||
const Vector<StringRef> path_elems = split_path(path);
|
||||
return this->lookup_path(path_elems);
|
||||
}
|
||||
|
||||
BundlePtr Bundle::copy() const
|
||||
{
|
||||
BundlePtr copy_ptr = Bundle::create();
|
||||
Bundle © = const_cast<Bundle &>(*copy_ptr);
|
||||
for (const StoredItem &item : items_) {
|
||||
copy.add_new(item.key, *item.type, item.value);
|
||||
}
|
||||
return copy_ptr;
|
||||
}
|
||||
|
||||
bool Bundle::remove(const SocketInterfaceKey &key)
|
||||
{
|
||||
const int removed_num = items_.remove_if([&key](StoredItem &item) {
|
||||
@@ -187,6 +296,11 @@ bool Bundle::contains(const SocketInterfaceKey &key) const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bundle::contains_path(const StringRef path) const
|
||||
{
|
||||
return this->lookup_path(path).has_value();
|
||||
}
|
||||
|
||||
void Bundle::delete_self()
|
||||
{
|
||||
MEM_delete(this);
|
||||
|
||||
106
source/blender/nodes/intern/geometry_nodes_bundle_tests.cc
Normal file
106
source/blender/nodes/intern/geometry_nodes_bundle_tests.cc
Normal file
@@ -0,0 +1,106 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
#include "BKE_appdir.hh"
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_global.hh"
|
||||
#include "BKE_idtype.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_material.hh"
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_scene.hh"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "RNA_define.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_bundle.hh"
|
||||
|
||||
namespace blender::nodes::tests {
|
||||
|
||||
class BundleTest : public ::testing::Test {
|
||||
|
||||
protected:
|
||||
static void SetUpTestSuite()
|
||||
{
|
||||
CLG_init();
|
||||
BKE_idtype_init();
|
||||
RNA_init();
|
||||
blender::bke::node_system_init();
|
||||
BKE_appdir_init();
|
||||
IMB_init();
|
||||
BKE_materials_init();
|
||||
}
|
||||
|
||||
static void TearDownTestSuite()
|
||||
{
|
||||
BKE_materials_exit();
|
||||
bke::node_system_exit();
|
||||
RNA_exit();
|
||||
BKE_appdir_exit();
|
||||
IMB_exit();
|
||||
CLG_exit();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(BundleTest, DefaultBundle)
|
||||
{
|
||||
BundlePtr bundle = Bundle::create();
|
||||
EXPECT_TRUE(bundle);
|
||||
EXPECT_TRUE(bundle->is_empty());
|
||||
}
|
||||
|
||||
TEST_F(BundleTest, AddItems)
|
||||
{
|
||||
BundlePtr bundle_ptr = Bundle::create();
|
||||
Bundle &bundle = const_cast<Bundle &>(*bundle_ptr);
|
||||
bundle.add(SocketInterfaceKey{"a"}, 3);
|
||||
EXPECT_EQ(bundle.size(), 1);
|
||||
EXPECT_TRUE(bundle.contains(SocketInterfaceKey{"a"}));
|
||||
EXPECT_EQ(bundle.lookup<int>(SocketInterfaceKey{"a"}), 3);
|
||||
}
|
||||
|
||||
TEST_F(BundleTest, AddLookupPath)
|
||||
{
|
||||
BundlePtr bundle_ptr = Bundle::create();
|
||||
Bundle &bundle = const_cast<Bundle &>(*bundle_ptr);
|
||||
bundle.add_path("a/b/c", 3);
|
||||
bundle.add_path("a/b/d", 4);
|
||||
EXPECT_EQ(bundle.size(), 1);
|
||||
EXPECT_EQ((*bundle.lookup_path<BundlePtr>("a"))->size(), 1);
|
||||
EXPECT_EQ((*bundle.lookup_path<BundlePtr>("a/b"))->size(), 2);
|
||||
EXPECT_EQ(bundle.lookup_path<int>("a/b/c"), 3);
|
||||
EXPECT_EQ(bundle.lookup_path<int>("a/b/d"), 4);
|
||||
EXPECT_EQ(bundle.lookup_path<BundlePtr>("a/b/c"), std::nullopt);
|
||||
EXPECT_EQ(bundle.lookup_path<BundlePtr>("a/b/x"), std::nullopt);
|
||||
}
|
||||
|
||||
TEST_F(BundleTest, LookupConversion)
|
||||
{
|
||||
BundlePtr bundle_ptr = Bundle::create();
|
||||
Bundle &bundle = const_cast<Bundle &>(*bundle_ptr);
|
||||
bundle.add_path("a/b", -3.4f);
|
||||
EXPECT_EQ(bundle.lookup_path<float>("a/b"), -3.4f);
|
||||
EXPECT_EQ(bundle.lookup_path<int>("a/b"), -3);
|
||||
EXPECT_EQ(bundle.lookup_path<bool>("a/b"), false);
|
||||
EXPECT_EQ(bundle.lookup_path<float3>("a/b"), float3(-3.4f));
|
||||
EXPECT_EQ(bundle.lookup_path<std::string>("a/b"), std::nullopt);
|
||||
}
|
||||
|
||||
TEST_F(BundleTest, AddOverride)
|
||||
{
|
||||
BundlePtr bundle_ptr = Bundle::create();
|
||||
Bundle &bundle = const_cast<Bundle &>(*bundle_ptr);
|
||||
bundle.add_path("a/b", 4);
|
||||
EXPECT_EQ(bundle.lookup_path<int>("a/b"), 4);
|
||||
bundle.add_path_override("a/b", 10);
|
||||
EXPECT_EQ(bundle.lookup_path<int>("a/b"), 10);
|
||||
bundle.add_path("a/b", 15);
|
||||
EXPECT_EQ(bundle.lookup_path<int>("a/b"), 10);
|
||||
}
|
||||
|
||||
} // namespace blender::nodes::tests
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "NOD_geo_closure.hh"
|
||||
#include "NOD_geometry_nodes_closure.hh"
|
||||
#include "NOD_geometry_nodes_values.hh"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user