diff --git a/source/blender/CMakeLists.txt b/source/blender/CMakeLists.txt index 9c16149255a..907b5f74736 100644 --- a/source/blender/CMakeLists.txt +++ b/source/blender/CMakeLists.txt @@ -53,6 +53,7 @@ set(SRC_DNA_INC ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_movieclip_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_nla_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_node_types.h + ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_node_tree_interface_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_object_enums.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_object_fluidsim_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_object_force_types.h diff --git a/source/blender/blenkernel/BKE_node_tree_interface.hh b/source/blender/blenkernel/BKE_node_tree_interface.hh new file mode 100644 index 00000000000..db43142a532 --- /dev/null +++ b/source/blender/blenkernel/BKE_node_tree_interface.hh @@ -0,0 +1,220 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "DNA_node_tree_interface_types.h" +#include "DNA_node_types.h" + +#include "BKE_node.h" + +#include +#include + +#include "BLI_parameter_pack_utils.hh" +#include "BLI_vector.hh" + +namespace blender::bke { + +/* Runtime topology cache for linear access to items. */ +struct bNodeTreeInterfaceCache { + Vector items; + Vector inputs; + Vector outputs; + + void rebuild(bNodeTreeInterface &tree_interface); +}; + +namespace node_interface { + +namespace detail { + +template static bool item_is_type(const bNodeTreeInterfaceItem &item) +{ + bool match = false; + switch (item.item_type) { + case NODE_INTERFACE_SOCKET: { + match |= std::is_same::value; + break; + } + case NODE_INTERFACE_PANEL: { + match |= std::is_same::value; + break; + } + } + return match; +} + +} // namespace detail + +template T &get_item_as(bNodeTreeInterfaceItem &item) +{ + BLI_assert(detail::item_is_type(item)); + return reinterpret_cast(item); +} + +template const T &get_item_as(const bNodeTreeInterfaceItem &item) +{ + BLI_assert(detail::item_is_type(item)); + return reinterpret_cast(item); +} + +template T *get_item_as(bNodeTreeInterfaceItem *item) +{ + if (item && detail::item_is_type(*item)) { + return reinterpret_cast(item); + } + return nullptr; +} + +template const T *get_item_as(const bNodeTreeInterfaceItem *item) +{ + if (item && detail::item_is_type(*item)) { + return reinterpret_cast(item); + } + return nullptr; +} + +namespace socket_types { + +constexpr const char *node_socket_data_float = "NodeSocketFloat"; +constexpr const char *node_socket_data_int = "NodeSocketInt"; +constexpr const char *node_socket_data_bool = "NodeSocketBool"; +constexpr const char *node_socket_data_rotation = "NodeSocketRotation"; +constexpr const char *node_socket_data_vector = "NodeSocketVector"; +constexpr const char *node_socket_data_color = "NodeSocketColor"; +constexpr const char *node_socket_data_string = "NodeSocketString"; +constexpr const char *node_socket_data_object = "NodeSocketObject"; +constexpr const char *node_socket_data_image = "NodeSocketImage"; +constexpr const char *node_socket_data_collection = "NodeSocketCollection"; +constexpr const char *node_socket_data_texture = "NodeSocketTexture"; +constexpr const char *node_socket_data_material = "NodeSocketMaterial"; + +template void socket_data_to_static_type(const char *socket_type, const Fn &fn) +{ + if (STREQ(socket_type, socket_types::node_socket_data_float)) { + fn.template operator()(); + } + else if (STREQ(socket_type, socket_types::node_socket_data_int)) { + fn.template operator()(); + } + else if (STREQ(socket_type, socket_types::node_socket_data_bool)) { + fn.template operator()(); + } + else if (STREQ(socket_type, socket_types::node_socket_data_rotation)) { + fn.template operator()(); + } + else if (STREQ(socket_type, socket_types::node_socket_data_vector)) { + fn.template operator()(); + } + else if (STREQ(socket_type, socket_types::node_socket_data_color)) { + fn.template operator()(); + } + else if (STREQ(socket_type, socket_types::node_socket_data_string)) { + fn.template operator()(); + } + else if (STREQ(socket_type, socket_types::node_socket_data_object)) { + fn.template operator()(); + } + else if (STREQ(socket_type, socket_types::node_socket_data_image)) { + fn.template operator()(); + } + else if (STREQ(socket_type, socket_types::node_socket_data_collection)) { + fn.template operator()(); + } + else if (STREQ(socket_type, socket_types::node_socket_data_texture)) { + fn.template operator()(); + } + else if (STREQ(socket_type, socket_types::node_socket_data_material)) { + fn.template operator()(); + } +} + +namespace detail { + +template struct TypeTagExecutor { + const Fn &fn; + + TypeTagExecutor(const Fn &fn_) : fn(fn_) {} + + template void operator()() const + { + fn(TypeTag{}); + } +}; + +} // namespace detail + +template void socket_data_to_static_type_tag(const char *socket_type, const Fn &fn) +{ + detail::TypeTagExecutor executor{fn}; + socket_data_to_static_type(socket_type, executor); +} + +} // namespace socket_types + +template bool socket_data_is_type(const char *socket_type) +{ + bool match = false; + socket_types::socket_data_to_static_type_tag(socket_type, [&match](auto type_tag) { + using SocketDataType = typename decltype(type_tag)::type; + match |= std::is_same_v; + }); + return match; +} + +template T &get_socket_data_as(bNodeTreeInterfaceSocket &item) +{ + BLI_assert(socket_data_is_type(item.socket_type)); + return *static_cast(item.socket_data); +} + +template const T &get_socket_data_as(const bNodeTreeInterfaceSocket &item) +{ + BLI_assert(socket_data_is_type(item.socket_type)); + return *static_cast(item.socket_data); +} + +inline bNodeTreeInterfaceSocket *add_interface_socket_from_node(bNodeTree &ntree, + const bNode &from_node, + const bNodeSocket &from_sock, + const StringRefNull socket_type, + const StringRefNull name) +{ + eNodeTreeInterfaceSocketFlag flag = eNodeTreeInterfaceSocketFlag(0); + SET_FLAG_FROM_TEST(flag, from_sock.in_out & SOCK_IN, NODE_INTERFACE_SOCKET_INPUT); + SET_FLAG_FROM_TEST(flag, from_sock.in_out & SOCK_OUT, NODE_INTERFACE_SOCKET_OUTPUT); + + bNodeTreeInterfaceSocket *iosock = ntree.tree_interface.add_socket( + name.data(), from_sock.description, socket_type, flag, nullptr); + if (iosock == nullptr) { + return nullptr; + } + const bNodeSocketType *typeinfo = iosock->socket_typeinfo(); + if (typeinfo->interface_from_socket) { + /* XXX Enable when bNodeSocketType callbacks have been updated. */ + // typeinfo->interface_from_socket(ntree.id, iosock, &from_node, &from_sock); + } + return iosock; +} + +inline bNodeTreeInterfaceSocket *add_interface_socket_from_node(bNodeTree &ntree, + const bNode &from_node, + const bNodeSocket &from_sock, + const StringRefNull socket_type) +{ + return add_interface_socket_from_node(ntree, from_node, from_sock, socket_type, from_sock.name); +} + +inline bNodeTreeInterfaceSocket *add_interface_socket_from_node(bNodeTree &ntree, + const bNode &from_node, + const bNodeSocket &from_sock) +{ + return add_interface_socket_from_node( + ntree, from_node, from_sock, from_sock.typeinfo->idname, from_sock.name); +} + +} // namespace node_interface + +} // namespace blender::bke diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 4f34adc5c70..1c9561767b7 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -235,6 +235,7 @@ set(SRC intern/node_tree_anonymous_attributes.cc intern/node_tree_dot_export.cc intern/node_tree_field_inferencing.cc + intern/node_tree_interface.cc intern/node_tree_update.cc intern/node_tree_zones.cc intern/object.cc @@ -454,6 +455,7 @@ set(SRC BKE_node_runtime.hh BKE_node_tree_anonymous_attributes.hh BKE_node_tree_dot_export.hh + BKE_node_tree_interface.hh BKE_node_tree_update.h BKE_node_tree_zones.hh BKE_object.h diff --git a/source/blender/blenkernel/intern/node_tree_interface.cc b/source/blender/blenkernel/intern/node_tree_interface.cc new file mode 100644 index 00000000000..6bc828a5e13 --- /dev/null +++ b/source/blender/blenkernel/intern/node_tree_interface.cc @@ -0,0 +1,1333 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_idprop.h" +#include "BKE_lib_id.h" +#include "BKE_lib_query.h" +#include "BKE_node.hh" +#include "BKE_node_tree_interface.hh" + +#include "BLI_math.h" +#include "BLI_set.hh" +#include "BLI_stack.hh" +#include "BLI_string.h" +#include "BLI_vector.hh" + +#include "BLO_read_write.h" + +#include "DNA_collection_types.h" +#include "DNA_material_types.h" +#include "DNA_node_tree_interface_types.h" +#include "DNA_node_types.h" + +#include "RNA_access.h" +#include "RNA_prototypes.h" + +namespace blender::bke::node_interface { + +namespace socket_types { + +/* Try to get a supported socket type from some final type. + * Built-in socket can have multiple registered RNA types for the base type, e.g. + * `NodeSocketFloatUnsigned`, `NodeSocketFloatFactor`. Only the "base type" (`NodeSocketFloat`) + * is considered valid for interface sockets. + */ +static const char *try_get_supported_socket_type(StringRefNull socket_type) +{ + const bNodeSocketType *typeinfo = nodeSocketTypeFind(socket_type.c_str()); + if (typeinfo == nullptr) { + return nullptr; + } + /* For builtin socket types only the base type is supported. */ + if (nodeIsStaticSocketType(typeinfo)) { + return nodeStaticSocketType(typeinfo->type, PROP_NONE); + } + return typeinfo->idname; +} + +/* -------------------------------------------------------------------- */ +/** \name ID User Increment in Socket Data + * \{ */ + +template void socket_data_id_user_increment(T & /*data*/) {} +template<> void socket_data_id_user_increment(bNodeSocketValueObject &data) +{ + id_us_plus(reinterpret_cast(data.value)); +} +template<> void socket_data_id_user_increment(bNodeSocketValueImage &data) +{ + id_us_plus(reinterpret_cast(data.value)); +} +template<> void socket_data_id_user_increment(bNodeSocketValueCollection &data) +{ + id_us_plus(reinterpret_cast(data.value)); +} +template<> void socket_data_id_user_increment(bNodeSocketValueTexture &data) +{ + id_us_plus(reinterpret_cast(data.value)); +} +template<> void socket_data_id_user_increment(bNodeSocketValueMaterial &data) +{ + id_us_plus(reinterpret_cast(data.value)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name ID User Decrement in Socket Data + * \{ */ + +template void socket_data_id_user_decrement(T & /*data*/) {} +template<> void socket_data_id_user_decrement(bNodeSocketValueObject &data) +{ + id_us_min(reinterpret_cast(data.value)); +} +template<> void socket_data_id_user_decrement(bNodeSocketValueImage &data) +{ + id_us_min(reinterpret_cast(data.value)); +} +template<> void socket_data_id_user_decrement(bNodeSocketValueCollection &data) +{ + id_us_min(reinterpret_cast(data.value)); +} +template<> void socket_data_id_user_decrement(bNodeSocketValueTexture &data) +{ + id_us_min(reinterpret_cast(data.value)); +} +template<> void socket_data_id_user_decrement(bNodeSocketValueMaterial &data) +{ + id_us_min(reinterpret_cast(data.value)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Initialize Socket Data + * \{ */ + +template void socket_data_init_impl(T & /*data*/) {} +template<> void socket_data_init_impl(bNodeSocketValueFloat &data) +{ + data.subtype = PROP_NONE; + data.value = 0.0f; + data.min = -FLT_MAX; + data.max = FLT_MAX; +} +template<> void socket_data_init_impl(bNodeSocketValueInt &data) +{ + data.subtype = PROP_NONE; + data.value = 0; + data.min = INT_MIN; + data.max = INT_MAX; +} +template<> void socket_data_init_impl(bNodeSocketValueBoolean &data) +{ + data.value = false; +} +template<> void socket_data_init_impl(bNodeSocketValueRotation & /*data*/) {} +template<> void socket_data_init_impl(bNodeSocketValueVector &data) +{ + static float default_value[] = {0.0f, 0.0f, 0.0f}; + data.subtype = PROP_NONE; + copy_v3_v3(data.value, default_value); + data.min = -FLT_MAX; + data.max = FLT_MAX; +} +template<> void socket_data_init_impl(bNodeSocketValueRGBA &data) +{ + static float default_value[] = {0.0f, 0.0f, 0.0f, 1.0f}; + copy_v4_v4(data.value, default_value); +} +template<> void socket_data_init_impl(bNodeSocketValueString &data) +{ + data.subtype = PROP_NONE; + data.value[0] = '\0'; +} +template<> void socket_data_init_impl(bNodeSocketValueObject &data) +{ + data.value = nullptr; +} +template<> void socket_data_init_impl(bNodeSocketValueImage &data) +{ + data.value = nullptr; +} +template<> void socket_data_init_impl(bNodeSocketValueCollection &data) +{ + data.value = nullptr; +} +template<> void socket_data_init_impl(bNodeSocketValueTexture &data) +{ + data.value = nullptr; +} +template<> void socket_data_init_impl(bNodeSocketValueMaterial &data) +{ + data.value = nullptr; +} + +static void *make_socket_data(const char *socket_type) +{ + void *socket_data = nullptr; + socket_data_to_static_type_tag(socket_type, [&socket_data](auto type_tag) { + using SocketDataType = typename decltype(type_tag)::type; + SocketDataType *new_socket_data = MEM_cnew(__func__); + socket_data_init_impl(*new_socket_data); + socket_data = new_socket_data; + }); + return socket_data; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Free Allocated Socket Data + * \{ */ + +template void socket_data_free_impl(T & /*data*/, const bool /*do_id_user*/) {} + +static void socket_data_free(bNodeTreeInterfaceSocket &socket, const bool do_id_user) +{ + socket_data_to_static_type_tag(socket.socket_type, [&](auto type_tag) { + using SocketDataType = typename decltype(type_tag)::type; + if (do_id_user) { + socket_data_id_user_decrement(get_socket_data_as(socket)); + } + socket_data_free_impl(get_socket_data_as(socket), do_id_user); + }); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Copy Allocated Socket Data + * \{ */ + +template void socket_data_copy_impl(T & /*dst*/, const T & /*src*/) {} + +static void socket_data_copy(bNodeTreeInterfaceSocket &dst, + const bNodeTreeInterfaceSocket &src, + int flag) +{ + socket_data_to_static_type_tag(dst.socket_type, [&](auto type_tag) { + using SocketDataType = typename decltype(type_tag)::type; + dst.socket_data = MEM_dupallocN(src.socket_data); + socket_data_copy_impl(get_socket_data_as(dst), + get_socket_data_as(src)); + if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) { + socket_data_id_user_increment(get_socket_data_as(dst)); + } + }); +} + +/* Copy socket data from a raw pointer, e.g. from a #bNodeSocket. */ +static void socket_data_copy_ptr(bNodeTreeInterfaceSocket &dst, + const void *src_socket_data, + int flag) +{ + socket_data_to_static_type_tag(dst.socket_type, [&](auto type_tag) { + using SocketDataType = typename decltype(type_tag)::type; + + if (dst.socket_data != nullptr) { + socket_data_free(dst, true); + MEM_SAFE_FREE(dst.socket_data); + } + + dst.socket_data = MEM_dupallocN(src_socket_data); + socket_data_copy_impl(get_socket_data_as(dst), + *static_cast(src_socket_data)); + if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) { + socket_data_id_user_increment(get_socket_data_as(dst)); + } + }); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Write Socket Data to Blend File + * \{ */ + +/* Note: no default implementation, every used type must write at least the base struct. */ + +inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueFloat &data) +{ + BLO_write_struct(writer, bNodeSocketValueFloat, &data); +} +inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueInt &data) +{ + BLO_write_struct(writer, bNodeSocketValueInt, &data); +} +inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueBoolean &data) +{ + BLO_write_struct(writer, bNodeSocketValueBoolean, &data); +} +inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueRotation &data) +{ + BLO_write_struct(writer, bNodeSocketValueRotation, &data); +} +inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueVector &data) +{ + BLO_write_struct(writer, bNodeSocketValueVector, &data); +} +inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueRGBA &data) +{ + BLO_write_struct(writer, bNodeSocketValueRGBA, &data); +} +inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueString &data) +{ + BLO_write_struct(writer, bNodeSocketValueString, &data); +} +inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueObject &data) +{ + BLO_write_struct(writer, bNodeSocketValueObject, &data); +} +inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueImage &data) +{ + BLO_write_struct(writer, bNodeSocketValueImage, &data); +} +inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueCollection &data) +{ + BLO_write_struct(writer, bNodeSocketValueCollection, &data); +} +inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueTexture &data) +{ + BLO_write_struct(writer, bNodeSocketValueTexture, &data); +} +inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueMaterial &data) +{ + BLO_write_struct(writer, bNodeSocketValueMaterial, &data); +} + +static void socket_data_write(BlendWriter *writer, bNodeTreeInterfaceSocket &socket) +{ + socket_data_to_static_type_tag(socket.socket_type, [&](auto type_tag) { + using SocketDataType = typename decltype(type_tag)::type; + socket_data_write_impl(writer, get_socket_data_as(socket)); + }); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Read Socket Data from Blend File + * \{ */ + +template void socket_data_read_data_impl(BlendDataReader *reader, T **data) +{ + BLO_read_data_address(reader, data); +} + +static void socket_data_read_data(BlendDataReader *reader, bNodeTreeInterfaceSocket &socket) +{ + socket_data_to_static_type_tag(socket.socket_type, [&](auto type_tag) { + using SocketDataType = typename decltype(type_tag)::type; + socket_data_read_data_impl(reader, reinterpret_cast(&socket.socket_data)); + }); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Read ID Pointer Data + * \{ */ + +template +void socket_data_read_lib_impl(BlendLibReader * /*reader*/, ID * /*id*/, T & /*data*/) +{ +} +template<> +void socket_data_read_lib_impl(BlendLibReader *reader, ID *id, bNodeSocketValueObject &data) +{ + BLI_assert(id != nullptr); + BLO_read_id_address(reader, id, &data.value); +} +template<> +void socket_data_read_lib_impl(BlendLibReader *reader, ID *id, bNodeSocketValueImage &data) +{ + BLI_assert(id != nullptr); + BLO_read_id_address(reader, id, &data.value); +} +template<> +void socket_data_read_lib_impl(BlendLibReader *reader, ID *id, bNodeSocketValueCollection &data) +{ + BLI_assert(id != nullptr); + BLO_read_id_address(reader, id, &data.value); +} +template<> +void socket_data_read_lib_impl(BlendLibReader *reader, ID *id, bNodeSocketValueTexture &data) +{ + BLI_assert(id != nullptr); + BLO_read_id_address(reader, id, &data.value); +} +template<> +void socket_data_read_lib_impl(BlendLibReader *reader, ID *id, bNodeSocketValueMaterial &data) +{ + BLI_assert(id != nullptr); + BLO_read_id_address(reader, id, &data.value); +} + +static void socket_data_read_lib(BlendLibReader *reader, ID *id, bNodeTreeInterfaceSocket &socket) +{ + socket_data_to_static_type_tag(socket.socket_type, [&](auto type_tag) { + using SocketDataType = typename decltype(type_tag)::type; + socket_data_read_lib_impl(reader, id, get_socket_data_as(socket)); + }); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Expand Socket Data + * \{ */ + +template void socket_data_expand_impl(BlendExpander * /*expander*/, T & /*data*/) {} +template<> void socket_data_expand_impl(BlendExpander *expander, bNodeSocketValueObject &data) +{ + BLO_expand(expander, &data.value); +} +template<> void socket_data_expand_impl(BlendExpander *expander, bNodeSocketValueImage &data) +{ + BLO_expand(expander, &data.value); +} +template<> void socket_data_expand_impl(BlendExpander *expander, bNodeSocketValueCollection &data) +{ + BLO_expand(expander, &data.value); +} +template<> void socket_data_expand_impl(BlendExpander *expander, bNodeSocketValueTexture &data) +{ + BLO_expand(expander, &data.value); +} +template<> void socket_data_expand_impl(BlendExpander *expander, bNodeSocketValueMaterial &data) +{ + BLO_expand(expander, &data.value); +} + +static void socket_data_expand(BlendExpander *expander, bNodeTreeInterfaceSocket &socket) +{ + socket_data_to_static_type_tag(socket.socket_type, [&](auto type_tag) { + using SocketDataType = typename decltype(type_tag)::type; + socket_data_expand_impl(expander, get_socket_data_as(socket)); + }); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Callback per ID Pointer + * \{ */ + +template +void socket_data_foreach_id_impl(LibraryForeachIDData * /*data*/, T & /*data*/) +{ +} +template<> void socket_data_foreach_id_impl(LibraryForeachIDData *cb, bNodeSocketValueObject &data) +{ + BKE_LIB_FOREACHID_PROCESS_IDSUPER(cb, data.value, IDWALK_CB_USER); +} +template<> void socket_data_foreach_id_impl(LibraryForeachIDData *cb, bNodeSocketValueImage &data) +{ + BKE_LIB_FOREACHID_PROCESS_IDSUPER(cb, data.value, IDWALK_CB_USER); +} +template<> +void socket_data_foreach_id_impl(LibraryForeachIDData *cb, bNodeSocketValueCollection &data) +{ + BKE_LIB_FOREACHID_PROCESS_IDSUPER(cb, data.value, IDWALK_CB_USER); +} +template<> +void socket_data_foreach_id_impl(LibraryForeachIDData *cb, bNodeSocketValueTexture &data) +{ + BKE_LIB_FOREACHID_PROCESS_IDSUPER(cb, data.value, IDWALK_CB_USER); +} +template<> +void socket_data_foreach_id_impl(LibraryForeachIDData *cb, bNodeSocketValueMaterial &data) +{ + BKE_LIB_FOREACHID_PROCESS_IDSUPER(cb, data.value, IDWALK_CB_USER); +} + +static void socket_data_foreach_id(LibraryForeachIDData *data, bNodeTreeInterfaceSocket &socket) +{ + socket_data_to_static_type_tag(socket.socket_type, [&](auto type_tag) { + using SocketDataType = typename decltype(type_tag)::type; + socket_data_foreach_id_impl(data, get_socket_data_as(socket)); + }); +} + +/** \} */ + +} // namespace socket_types + +namespace item_types { + +static void item_copy(bNodeTreeInterfaceItem &dst, + const bNodeTreeInterfaceItem &src, + const int flag) +{ + switch (dst.item_type) { + case NODE_INTERFACE_SOCKET: { + bNodeTreeInterfaceSocket &dst_socket = reinterpret_cast(dst); + const bNodeTreeInterfaceSocket &src_socket = + reinterpret_cast(src); + BLI_assert(src_socket.name != nullptr); + BLI_assert(src_socket.socket_type != nullptr); + + dst_socket.name = BLI_strdup(src_socket.name); + dst_socket.description = BLI_strdup_null(src_socket.description); + dst_socket.socket_type = BLI_strdup(src_socket.socket_type); + dst_socket.default_attribute_name = BLI_strdup_null(src_socket.default_attribute_name); + dst_socket.identifier = BLI_strdup(src_socket.identifier); + if (src_socket.properties) { + dst_socket.properties = IDP_CopyProperty_ex(src_socket.properties, flag); + } + if (src_socket.socket_data != nullptr) { + socket_types::socket_data_copy(dst_socket, src_socket, flag); + } + break; + } + case NODE_INTERFACE_PANEL: { + bNodeTreeInterfacePanel &dst_panel = reinterpret_cast(dst); + const bNodeTreeInterfacePanel &src_panel = reinterpret_cast( + src); + BLI_assert(src_panel.name != nullptr); + + dst_panel.name = BLI_strdup(src_panel.name); + dst_panel.copy_from(src_panel.items(), flag); + break; + } + } +} + +static void item_free(bNodeTreeInterfaceItem &item, const bool do_id_user) +{ + switch (item.item_type) { + case NODE_INTERFACE_SOCKET: { + bNodeTreeInterfaceSocket &socket = reinterpret_cast(item); + + if (socket.socket_data != nullptr) { + socket_types::socket_data_free(socket, do_id_user); + MEM_SAFE_FREE(socket.socket_data); + } + + MEM_SAFE_FREE(socket.name); + MEM_SAFE_FREE(socket.description); + MEM_SAFE_FREE(socket.socket_type); + MEM_SAFE_FREE(socket.default_attribute_name); + MEM_SAFE_FREE(socket.identifier); + if (socket.properties) { + IDP_FreePropertyContent_ex(socket.properties, do_id_user); + MEM_freeN(socket.properties); + } + break; + } + case NODE_INTERFACE_PANEL: { + bNodeTreeInterfacePanel &panel = reinterpret_cast(item); + + panel.clear(do_id_user); + MEM_SAFE_FREE(panel.name); + break; + } + } + + MEM_freeN(&item); +} + +void item_write_struct(BlendWriter *writer, bNodeTreeInterfaceItem &item); + +static void item_write_data(BlendWriter *writer, bNodeTreeInterfaceItem &item) +{ + switch (item.item_type) { + case NODE_INTERFACE_SOCKET: { + bNodeTreeInterfaceSocket &socket = reinterpret_cast(item); + BLO_write_string(writer, socket.name); + BLO_write_string(writer, socket.description); + BLO_write_string(writer, socket.socket_type); + BLO_write_string(writer, socket.default_attribute_name); + if (socket.properties) { + IDP_BlendWrite(writer, socket.properties); + } + + socket_types::socket_data_write(writer, socket); + break; + } + case NODE_INTERFACE_PANEL: { + bNodeTreeInterfacePanel &panel = reinterpret_cast(item); + BLO_write_string(writer, panel.name); + BLO_write_pointer_array(writer, panel.items_num, panel.items_array); + for (bNodeTreeInterfaceItem *child_item : panel.items()) { + item_write_struct(writer, *child_item); + } + break; + } + } +} + +void item_write_struct(BlendWriter *writer, bNodeTreeInterfaceItem &item) +{ + switch (item.item_type) { + case NODE_INTERFACE_SOCKET: { + BLO_write_struct(writer, bNodeTreeInterfaceSocket, &item); + break; + } + case NODE_INTERFACE_PANEL: { + BLO_write_struct(writer, bNodeTreeInterfacePanel, &item); + break; + } + } + + item_write_data(writer, item); +} + +static void item_read_data(BlendDataReader *reader, bNodeTreeInterfaceItem &item) +{ + switch (item.item_type) { + case NODE_INTERFACE_SOCKET: { + bNodeTreeInterfaceSocket &socket = reinterpret_cast(item); + BLO_read_data_address(reader, &socket.name); + BLO_read_data_address(reader, &socket.description); + BLO_read_data_address(reader, &socket.socket_type); + BLO_read_data_address(reader, &socket.default_attribute_name); + BLO_read_data_address(reader, &socket.identifier); + BLO_read_data_address(reader, &socket.properties); + IDP_BlendDataRead(reader, &socket.properties); + + socket_types::socket_data_read_data(reader, socket); + break; + } + case NODE_INTERFACE_PANEL: { + bNodeTreeInterfacePanel &panel = reinterpret_cast(item); + BLO_read_data_address(reader, &panel.name); + BLO_read_pointer_array(reader, reinterpret_cast(&panel.items_array)); + for (const int i : blender::IndexRange(panel.items_num)) { + BLO_read_data_address(reader, &panel.items_array[i]); + item_read_data(reader, *panel.items_array[i]); + } + break; + } + } +} + +static void item_read_lib(BlendLibReader *reader, ID *id, bNodeTreeInterfaceItem &item) +{ + switch (item.item_type) { + case NODE_INTERFACE_SOCKET: { + bNodeTreeInterfaceSocket &socket = reinterpret_cast(item); + IDP_BlendReadLib(reader, id, socket.properties); + socket_types::socket_data_read_lib(reader, id, socket); + break; + } + case NODE_INTERFACE_PANEL: { + bNodeTreeInterfacePanel &panel = reinterpret_cast(item); + for (bNodeTreeInterfaceItem *item : panel.items()) { + item_read_lib(reader, id, *item); + } + break; + } + } +} + +static void item_read_expand(BlendExpander *expander, bNodeTreeInterfaceItem &item) +{ + switch (item.item_type) { + case NODE_INTERFACE_SOCKET: { + bNodeTreeInterfaceSocket &socket = reinterpret_cast(item); + IDP_BlendReadExpand(expander, socket.properties); + socket_types::socket_data_expand(expander, socket); + break; + } + case NODE_INTERFACE_PANEL: { + bNodeTreeInterfacePanel &panel = reinterpret_cast(item); + for (bNodeTreeInterfaceItem *item : panel.items()) { + item_read_expand(expander, *item); + } + break; + } + } +} + +static void item_foreach_id(LibraryForeachIDData *data, bNodeTreeInterfaceItem &item) +{ + switch (item.item_type) { + case NODE_INTERFACE_SOCKET: { + bNodeTreeInterfaceSocket &socket = reinterpret_cast(item); + + BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL( + data, + IDP_foreach_property(socket.properties, + IDP_TYPE_FILTER_ID, + BKE_lib_query_idpropertiesForeachIDLink_callback, + data)); + + socket_types::socket_data_foreach_id(data, socket); + break; + } + case NODE_INTERFACE_PANEL: { + bNodeTreeInterfacePanel &panel = reinterpret_cast(item); + for (bNodeTreeInterfaceItem *item : panel.items()) { + item_foreach_id(data, *item); + } + break; + } + } +} + +/* Move all child items to the new parent. */ +static Span item_children(bNodeTreeInterfaceItem &item) +{ + switch (item.item_type) { + case NODE_INTERFACE_SOCKET: { + return {}; + } + case NODE_INTERFACE_PANEL: { + bNodeTreeInterfacePanel &panel = reinterpret_cast(item); + return panel.items(); + } + } + return {}; +} + +} // namespace item_types + +} // namespace blender::bke::node_interface + +using namespace blender::bke::node_interface; + +bNodeSocketType *bNodeTreeInterfaceSocket::socket_typeinfo() const +{ + return nodeSocketTypeFind(socket_type); +} + +blender::ColorGeometry4f bNodeTreeInterfaceSocket::socket_color() const +{ + bNodeSocketType *typeinfo = this->socket_typeinfo(); + if (!typeinfo || !typeinfo->draw_color) { + return blender::ColorGeometry4f(1.0f, 0.0f, 1.0f, 1.0f); + } + + float color[4]; + typeinfo->draw_color(nullptr, nullptr, nullptr, color); + return blender::ColorGeometry4f(color); +} + +bool bNodeTreeInterfaceSocket::set_socket_type(const char *new_socket_type) +{ + const char *idname = socket_types::try_get_supported_socket_type(new_socket_type); + if (idname == nullptr) { + return false; + } + + if (this->socket_data != nullptr) { + socket_types::socket_data_free(*this, true); + MEM_SAFE_FREE(this->socket_data); + } + MEM_SAFE_FREE(this->socket_type); + + this->socket_type = BLI_strdup(new_socket_type); + this->socket_data = socket_types::make_socket_data(new_socket_type); + + return true; +} + +void bNodeTreeInterfaceSocket::init_from_socket_instance(const bNodeSocket *socket) +{ + const char *idname = socket_types::try_get_supported_socket_type(socket->idname); + BLI_assert(idname != nullptr); + + if (this->socket_data != nullptr) { + socket_types::socket_data_free(*this, true); + MEM_SAFE_FREE(this->socket_data); + } + MEM_SAFE_FREE(this->socket_type); + + this->socket_type = BLI_strdup(idname); + this->socket_data = socket_types::make_socket_data(idname); + socket_types::socket_data_copy_ptr(*this, socket->default_value, 0); +} + +blender::IndexRange bNodeTreeInterfacePanel::items_range() const +{ + return blender::IndexRange(items_num); +} + +blender::Span bNodeTreeInterfacePanel::items() const +{ + return blender::Span(items_array, items_num); +} + +blender::MutableSpan bNodeTreeInterfacePanel::items() +{ + return blender::MutableSpan(items_array, items_num); +} + +bool bNodeTreeInterfacePanel::contains(const bNodeTreeInterfaceItem &item) const +{ + return items().contains(&item); +} + +bool bNodeTreeInterfacePanel::contains_recursive(const bNodeTreeInterfaceItem &item) const +{ + bool is_child = false; + /* Have to capture item address here instead of just a reference, + * otherwise pointer comparison will not work. */ + this->foreach_item( + [&](const bNodeTreeInterfaceItem &titem) { + if (&titem == &item) { + is_child = true; + return false; + } + return true; + }, + true); + return is_child; +} + +int bNodeTreeInterfacePanel::item_position(const bNodeTreeInterfaceItem &item) const +{ + return items().first_index_try(&item); +} + +int bNodeTreeInterfacePanel::item_index(const bNodeTreeInterfaceItem &item) const +{ + int index = 0; + bool found = false; + /* Have to capture item address here instead of just a reference, + * otherwise pointer comparison will not work. */ + this->foreach_item([&](const bNodeTreeInterfaceItem &titem) { + if (&titem == &item) { + found = true; + return false; + } + ++index; + return true; + }); + return found ? index : -1; +} + +const bNodeTreeInterfaceItem *bNodeTreeInterfacePanel::item_at_index(int index) const +{ + int i = 0; + const bNodeTreeInterfaceItem *result = nullptr; + this->foreach_item([&](const bNodeTreeInterfaceItem &item) { + if (i == index) { + result = &item; + return false; + } + ++i; + return true; + }); + return result; +} + +bNodeTreeInterfacePanel *bNodeTreeInterfacePanel::find_parent_recursive( + const bNodeTreeInterfaceItem &item) +{ + std::queue queue; + + if (this->contains(item)) { + return this; + } + queue.push(this); + + while (!queue.empty()) { + bNodeTreeInterfacePanel *parent = queue.front(); + queue.pop(); + + for (bNodeTreeInterfaceItem *titem : parent->items()) { + if (titem->item_type != NODE_INTERFACE_PANEL) { + continue; + } + + bNodeTreeInterfacePanel *tpanel = get_item_as(titem); + if (tpanel->contains(item)) { + return tpanel; + } + queue.push(tpanel); + } + } + + return nullptr; +} + +void bNodeTreeInterfacePanel::add_item(bNodeTreeInterfaceItem &item) +{ + blender::MutableSpan old_items = this->items(); + items_num++; + items_array = MEM_cnew_array(items_num, __func__); + this->items().drop_back(1).copy_from(old_items); + this->items().last() = &item; + + if (old_items.data()) { + MEM_freeN(old_items.data()); + } +} + +void bNodeTreeInterfacePanel::insert_item(bNodeTreeInterfaceItem &item, int position) +{ + position = std::min(std::max(position, 0), items_num); + + blender::MutableSpan old_items = this->items(); + items_num++; + items_array = MEM_cnew_array(items_num, __func__); + this->items().take_front(position).copy_from(old_items.take_front(position)); + this->items().drop_front(position + 1).copy_from(old_items.drop_front(position)); + this->items()[position] = &item; + + if (old_items.data()) { + MEM_freeN(old_items.data()); + } +} + +bool bNodeTreeInterfacePanel::remove_item(bNodeTreeInterfaceItem &item, bool free) +{ + const int position = this->item_position(item); + if (!this->items().index_range().contains(position)) { + return false; + } + + blender::MutableSpan old_items = this->items(); + items_num--; + items_array = MEM_cnew_array(items_num, __func__); + this->items().take_front(position).copy_from(old_items.take_front(position)); + this->items().drop_front(position).copy_from(old_items.drop_front(position + 1)); + + /* Guaranteed not empty, contains at least the removed item */ + MEM_freeN(old_items.data()); + + if (free) { + item_types::item_free(item, true); + } + + return true; +} + +void bNodeTreeInterfacePanel::clear(bool do_id_user) +{ + for (bNodeTreeInterfaceItem *item : this->items()) { + item_types::item_free(*item, do_id_user); + } + MEM_SAFE_FREE(items_array); + items_array = nullptr; + items_num = 0; +} + +bool bNodeTreeInterfacePanel::move_item(bNodeTreeInterfaceItem &item, const int new_position) +{ + const int old_position = this->item_position(item); + if (!this->items().index_range().contains(old_position) || + !this->items().index_range().contains(new_position)) + { + return false; + } + + if (old_position == new_position) { + /* Nothing changes. */ + return true; + } + else if (old_position < new_position) { + const blender::Span moved_items = this->items().slice( + old_position + 1, new_position - old_position); + bNodeTreeInterfaceItem *tmp = this->items()[old_position]; + std::copy( + moved_items.begin(), moved_items.end(), this->items().drop_front(old_position).data()); + this->items()[new_position] = tmp; + } + else /* old_position > new_position */ { + const blender::Span moved_items = this->items().slice( + new_position, old_position - new_position); + bNodeTreeInterfaceItem *tmp = this->items()[old_position]; + std::copy_backward( + moved_items.begin(), moved_items.end(), this->items().drop_front(old_position + 1).data()); + this->items()[new_position] = tmp; + } + + return true; +} + +void bNodeTreeInterfacePanel::foreach_item( + blender::FunctionRef fn, bool include_self) +{ + using ItemSpan = blender::Span; + blender::Stack stack; + + if (include_self && fn(this->item) == false) { + return; + } + stack.push(this->items()); + + while (!stack.is_empty()) { + const ItemSpan current_items = stack.pop(); + + for (const int index : current_items.index_range()) { + bNodeTreeInterfaceItem *item = current_items[index]; + if (fn(*item) == false) { + return; + } + + if (item->item_type == NODE_INTERFACE_PANEL) { + bNodeTreeInterfacePanel *panel = reinterpret_cast(item); + /* Reinsert remaining items. */ + if (index < current_items.size() - 1) { + const ItemSpan remaining_items = current_items.drop_front(index + 1); + stack.push(remaining_items); + } + /* Handle child items first before continuing with current span. */ + stack.push(panel->items()); + break; + } + } + } +} + +void bNodeTreeInterfacePanel::foreach_item( + blender::FunctionRef fn, bool include_self) const +{ + using ItemSpan = blender::Span; + blender::Stack stack; + + if (include_self && fn(this->item) == false) { + return; + } + stack.push(this->items()); + + while (!stack.is_empty()) { + const ItemSpan current_items = stack.pop(); + + for (const int index : current_items.index_range()) { + const bNodeTreeInterfaceItem *item = current_items[index]; + if (fn(*item) == false) { + return; + } + + if (item->item_type == NODE_INTERFACE_PANEL) { + const bNodeTreeInterfacePanel *panel = reinterpret_cast( + item); + /* Reinsert remaining items. */ + if (index < current_items.size() - 1) { + const ItemSpan remaining_items = current_items.drop_front(index + 1); + stack.push(remaining_items); + } + /* Handle child items first before continuing with current span. */ + stack.push(panel->items()); + break; + } + } + } +} + +static bNodeTreeInterfaceSocket *make_socket(const int uid, + blender::StringRefNull name, + blender::StringRefNull description, + blender::StringRefNull socket_type, + const eNodeTreeInterfaceSocketFlag flag) +{ + BLI_assert(name.c_str() != nullptr); + BLI_assert(socket_type.c_str() != nullptr); + + const char *idname = socket_types::try_get_supported_socket_type(socket_type.c_str()); + if (idname == nullptr) { + return nullptr; + } + + bNodeTreeInterfaceSocket *new_socket = MEM_cnew(__func__); + BLI_assert(new_socket); + + /* Init common socket properties. */ + new_socket->identifier = BLI_sprintfN("Socket_%d", uid); + new_socket->item.item_type = NODE_INTERFACE_SOCKET; + new_socket->name = BLI_strdup(name.c_str()); + new_socket->description = BLI_strdup_null(description.c_str()); + new_socket->socket_type = BLI_strdup(socket_type.c_str()); + new_socket->flag = flag; + + new_socket->socket_data = socket_types::make_socket_data(socket_type.c_str()); + + return new_socket; +} + +static bNodeTreeInterfacePanel *make_panel(const int uid, blender::StringRefNull name) +{ + BLI_assert(name.c_str() != nullptr); + + bNodeTreeInterfacePanel *new_panel = MEM_cnew(__func__); + new_panel->item.item_type = NODE_INTERFACE_PANEL; + new_panel->name = BLI_strdup(name.c_str()); + new_panel->identifier = uid; + return new_panel; +} + +void bNodeTreeInterfacePanel::copy_from( + const blender::Span items_src, int flag) +{ + items_num = items_src.size(); + items_array = MEM_cnew_array(items_num, __func__); + + /* Copy buffers. */ + for (const int i : items_src.index_range()) { + const bNodeTreeInterfaceItem *item_src = items_src[i]; + items_array[i] = static_cast(MEM_dupallocN(item_src)); + item_types::item_copy(*items_array[i], *item_src, flag); + } +} + +void bNodeTreeInterface::copy_data(const bNodeTreeInterface &src, int flag) +{ + this->root_panel.copy_from(src.root_panel.items(), flag); + this->active_index = src.active_index; +} + +void bNodeTreeInterface::free_data() +{ + /* Called when freeing the main database, don't do user refcount here. */ + this->root_panel.clear(false); +} + +void bNodeTreeInterface::write(BlendWriter *writer) +{ + BLO_write_struct(writer, bNodeTreeInterface, this); + /* Don't write the root panel struct itself, it's nested in the interface struct. */ + item_types::item_write_data(writer, this->root_panel.item); +} + +void bNodeTreeInterface::read_data(BlendDataReader *reader) +{ + item_types::item_read_data(reader, this->root_panel.item); +} + +void bNodeTreeInterface::read_lib(BlendLibReader *reader, ID *id) +{ + item_types::item_read_lib(reader, id, this->root_panel.item); +} + +void bNodeTreeInterface::read_expand(BlendExpander *expander) +{ + item_types::item_read_expand(expander, this->root_panel.item); +} + +bNodeTreeInterfaceItem *bNodeTreeInterface::active_item() +{ + bNodeTreeInterfaceItem *active = nullptr; + int count = active_index; + this->foreach_item([&](bNodeTreeInterfaceItem &item) { + if (count == 0) { + active = &item; + return false; + } + --count; + return true; + }); + return active; +} + +const bNodeTreeInterfaceItem *bNodeTreeInterface::active_item() const +{ + const bNodeTreeInterfaceItem *active = nullptr; + int count = active_index; + this->foreach_item([&](const bNodeTreeInterfaceItem &item) { + if (count == 0) { + active = &item; + return false; + } + --count; + return true; + }); + return active; +} + +void bNodeTreeInterface::active_item_set(bNodeTreeInterfaceItem *item) +{ + active_index = 0; + int count = 0; + this->foreach_item([&](bNodeTreeInterfaceItem &titem) { + if (&titem == item) { + active_index = count; + return false; + } + ++count; + return true; + }); +} + +bNodeTreeInterfaceSocket *bNodeTreeInterface::add_socket(blender::StringRefNull name, + blender::StringRefNull description, + blender::StringRefNull socket_type, + const eNodeTreeInterfaceSocketFlag flag, + bNodeTreeInterfacePanel *parent) +{ + if (parent == nullptr) { + parent = &root_panel; + } + BLI_assert(this->find_item(parent->item)); + + bNodeTreeInterfaceSocket *new_socket = make_socket( + next_uid++, name, description, socket_type, flag); + if (new_socket) { + parent->add_item(new_socket->item); + } + return new_socket; +} + +bNodeTreeInterfaceSocket *bNodeTreeInterface::insert_socket( + blender::StringRefNull name, + blender::StringRefNull description, + blender::StringRefNull socket_type, + const eNodeTreeInterfaceSocketFlag flag, + bNodeTreeInterfacePanel *parent, + const int position) +{ + if (parent == nullptr) { + parent = &root_panel; + } + BLI_assert(this->find_item(parent->item)); + + bNodeTreeInterfaceSocket *new_socket = make_socket( + next_uid++, name, description, socket_type, flag); + if (new_socket) { + parent->insert_item(new_socket->item, position); + } + return new_socket; +} + +bNodeTreeInterfacePanel *bNodeTreeInterface::add_panel(blender::StringRefNull name, + bNodeTreeInterfacePanel *parent) +{ + if (parent == nullptr) { + parent = &root_panel; + } + BLI_assert(this->find_item(parent->item)); + + bNodeTreeInterfacePanel *new_panel = make_panel(next_uid++, name); + if (new_panel) { + parent->add_item(new_panel->item); + } + return new_panel; +} + +bNodeTreeInterfacePanel *bNodeTreeInterface::insert_panel(blender::StringRefNull name, + bNodeTreeInterfacePanel *parent, + const int position) +{ + if (parent == nullptr) { + parent = &root_panel; + } + BLI_assert(this->find_item(parent->item)); + + bNodeTreeInterfacePanel *new_panel = make_panel(next_uid++, name); + if (new_panel) { + parent->insert_item(new_panel->item, position); + } + return new_panel; +} + +bNodeTreeInterfaceItem *bNodeTreeInterface::add_item_copy(const bNodeTreeInterfaceItem &item, + bNodeTreeInterfacePanel *parent) +{ + if (parent == nullptr) { + parent = &root_panel; + } + BLI_assert(this->find_item(item)); + BLI_assert(this->find_item(parent->item)); + + if (parent == nullptr) { + parent = &root_panel; + } + + bNodeTreeInterfaceItem *citem = static_cast(MEM_dupallocN(&item)); + item_types::item_copy(*citem, item, 0); + parent->add_item(*citem); + + return citem; +} + +bNodeTreeInterfaceItem *bNodeTreeInterface::insert_item_copy(const bNodeTreeInterfaceItem &item, + bNodeTreeInterfacePanel *parent, + int position) +{ + if (parent == nullptr) { + parent = &root_panel; + } + BLI_assert(this->find_item(item)); + BLI_assert(this->find_item(parent->item)); + + bNodeTreeInterfaceItem *citem = static_cast(MEM_dupallocN(&item)); + item_types::item_copy(*citem, item, 0); + parent->insert_item(*citem, position); + + return citem; +} + +bool bNodeTreeInterface::remove_item(bNodeTreeInterfaceItem &item, bool move_content_to_parent) +{ + bNodeTreeInterfacePanel *parent = this->find_item_parent(item); + if (parent == nullptr) { + return false; + } + if (move_content_to_parent) { + int position = parent->item_position(item); + /* Cache children to avoid invalidating the iterator. */ + blender::Array children(item_types::item_children(item)); + for (bNodeTreeInterfaceItem *child : children) { + this->move_item_to_parent(*child, parent, position++); + } + } + if (parent->remove_item(item, true)) { + return true; + } + return false; +} + +void bNodeTreeInterface::clear_items() +{ + root_panel.clear(true); +} + +bool bNodeTreeInterface::move_item(bNodeTreeInterfaceItem &item, const int new_position) +{ + bNodeTreeInterfacePanel *parent = this->find_item_parent(item); + if (parent == nullptr) { + return false; + } + return parent->move_item(item, new_position); +} + +bool bNodeTreeInterface::move_item_to_parent(bNodeTreeInterfaceItem &item, + bNodeTreeInterfacePanel *new_parent, + int new_position) +{ + bNodeTreeInterfacePanel *parent = this->find_item_parent(item); + if (parent == nullptr) { + return false; + } + if (parent->remove_item(item, false)) { + new_parent->insert_item(item, new_position); + return true; + } + return false; +} + +void bNodeTreeInterface::foreach_id(LibraryForeachIDData *cb) +{ + item_types::item_foreach_id(cb, root_panel.item); +} + +namespace blender::bke { + +void bNodeTreeInterfaceCache::rebuild(bNodeTreeInterface &interface) +{ + /* Rebuild draw-order list of interface items for linear access. */ + items.clear(); + inputs.clear(); + outputs.clear(); + + interface.foreach_item([&](bNodeTreeInterfaceItem &item) { + items.append(&item); + if (bNodeTreeInterfaceSocket *socket = get_item_as(&item)) { + if (socket->flag & NODE_INTERFACE_SOCKET_INPUT) { + inputs.append(socket); + } + if (socket->flag & NODE_INTERFACE_SOCKET_OUTPUT) { + outputs.append(socket); + } + } + return true; + }); +} + +} // namespace blender::bke diff --git a/source/blender/blenlib/BLI_string.h b/source/blender/blenlib/BLI_string.h index 0337491fa60..63c081b7a4d 100644 --- a/source/blender/blenlib/BLI_string.h +++ b/source/blender/blenlib/BLI_string.h @@ -53,6 +53,15 @@ char *BLI_strdupn(const char *str, size_t len) ATTR_MALLOC ATTR_WARN_UNUSED_RESU */ char *BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC; +/** + * Duplicates the C-string \a str into a newly mallocN'd + * string and returns it. + * + * \param str: The string to be duplicated, can be null + * \retval Returns the duplicated string or null if \a str is null + */ +char *BLI_strdup_null(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_MALLOC; + /** * Appends the two strings, and returns new mallocN'ed string * \param str1: first string for copy diff --git a/source/blender/blenlib/intern/string.c b/source/blender/blenlib/intern/string.c index 3157ec15e26..cbd1800a61d 100644 --- a/source/blender/blenlib/intern/string.c +++ b/source/blender/blenlib/intern/string.c @@ -42,6 +42,11 @@ char *BLI_strdup(const char *str) return BLI_strdupn(str, strlen(str)); } +char *BLI_strdup_null(const char *str) +{ + return (str != NULL) ? BLI_strdupn(str, strlen(str)) : NULL; +} + char *BLI_strdupcat(const char *__restrict str1, const char *__restrict str2) { /* include the NULL terminator of str2 only */ diff --git a/source/blender/editors/space_node/link_drag_search.cc b/source/blender/editors/space_node/link_drag_search.cc index 1e4f97afb69..743122ad411 100644 --- a/source/blender/editors/space_node/link_drag_search.cc +++ b/source/blender/editors/space_node/link_drag_search.cc @@ -90,7 +90,6 @@ static void add_group_input_node_fn(nodes::LinkSearchOpParams ¶ms) bNodeSocket *interface_socket = bke::ntreeAddSocketInterfaceFromSocket( ¶ms.node_tree, ¶ms.node, ¶ms.socket); const int group_input_index = BLI_findindex(¶ms.node_tree.inputs, interface_socket); - bNode &group_input = params.add_node("NodeGroupInput"); /* This is necessary to create the new sockets in the other input nodes. */ diff --git a/source/blender/makesdna/DNA_node_tree_interface_types.h b/source/blender/makesdna/DNA_node_tree_interface_types.h new file mode 100644 index 00000000000..e1f77b00465 --- /dev/null +++ b/source/blender/makesdna/DNA_node_tree_interface_types.h @@ -0,0 +1,365 @@ +/* SPDX-FileCopyrightText: 2005 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup DNA + */ + +#pragma once + +#include "BLI_utildefines.h" + +#ifdef __cplusplus +# include "BLI_color.hh" +# include "BLI_function_ref.hh" +# include "BLI_span.hh" +# include "BLI_string_ref.hh" + +# include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct bContext; +struct bNodeSocket; +struct bNodeSocketType; +struct bNodeTreeInterfaceItem; +struct bNodeTreeInterfacePanel; +struct bNodeTreeInterfaceSocket; +struct ID; +struct IDProperty; +struct LibraryForeachIDData; +struct PointerRNA; +struct uiLayout; +struct BlendWriter; +struct BlendDataReader; +struct BlendLibReader; +struct BlendExpander; + +/** Type of interface item. */ +typedef enum NodeTreeInterfaceItemType { + NODE_INTERFACE_PANEL = 0, + NODE_INTERFACE_SOCKET = 1, +} eNodeTreeInterfaceItemType; + +/** Describes a socket and all necessary details for a node declaration. */ +typedef struct bNodeTreeInterfaceItem { + /* eNodeTreeInterfaceItemType */ + char item_type; + char _pad[7]; +} bNodeTreeInterfaceItem; + +/* Socket interface flags */ +typedef enum eNodeTreeInterfaceSocketFlag { + NODE_INTERFACE_SOCKET_INPUT = 1 << 0, + NODE_INTERFACE_SOCKET_OUTPUT = 1 << 1, + NODE_INTERFACE_SOCKET_HIDE_VALUE = 1 << 2, + NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER = 1 << 3, +} eNodeTreeInterfaceSocketFlag; +ENUM_OPERATORS(eNodeTreeInterfaceSocketFlag, NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER); + +typedef struct bNodeTreeInterfaceSocket { + bNodeTreeInterfaceItem item; + + /* UI name of the socket. */ + char *name; + char *description; + /* Type idname of the socket to generate, e.g. "NodeSocketFloat". */ + char *socket_type; + /* eNodeTreeInterfaceSocketFlag */ + int flag; + + /* eAttrDomain */ + int attribute_domain; + char *default_attribute_name; + + /* Unique identifier for generated sockets. */ + char *identifier; + /* Socket default value and associated data, e.g. bNodeSocketValueFloat. */ + void *socket_data; + + IDProperty *properties; + +#ifdef __cplusplus + bNodeSocketType *socket_typeinfo() const; + blender::ColorGeometry4f socket_color() const; + + /** + * Set the \a socket_type and replace the \a socket_data. + * \param new_socket_type: Socket type idname, e.g. "NodeSocketFloat" + */ + bool set_socket_type(const char *new_socket_type); + + /** + * Use an existing socket to define an interface socket. + * Replaces the current \a socket_type and any existing \a socket data if called again. + */ + void init_from_socket_instance(const bNodeSocket *socket); +#endif +} bNodeTreeInterfaceSocket; + +typedef struct bNodeTreeInterfacePanel { + bNodeTreeInterfaceItem item; + + /* UI name of the panel. */ + char *name; + + bNodeTreeInterfaceItem **items_array; + int items_num; + + /* Internal unique identifier for validating panel states. */ + int identifier; + +#ifdef __cplusplus + blender::IndexRange items_range() const; + blender::Span items() const; + blender::MutableSpan items(); + + /** + * Check if the item is a direct child of the panel. + */ + bool contains(const bNodeTreeInterfaceItem &item) const; + /** + * Search for an item in the interface. + * \return True if the item was found. + */ + bool contains_recursive(const bNodeTreeInterfaceItem &item) const; + /** + * Get the position of an item in this panel. + * \return Position relative to the start of the panel items or -1 if the item is not in the + * panel. + */ + int item_position(const bNodeTreeInterfaceItem &item) const; + /** + * Get the index of the item in the interface. + * \return Index if the item was found or -1 otherwise. + */ + int item_index(const bNodeTreeInterfaceItem &item) const; + /** + * Get the item at the given index of the interface draw list. + */ + const bNodeTreeInterfaceItem *item_at_index(int index) const; + /** + * Find the panel containing the item among this panel and all children. + * \return Parent panel containing the item. + */ + bNodeTreeInterfacePanel *find_parent_recursive(const bNodeTreeInterfaceItem &item); + + /** Create a copy of items in the span and add them to the interface. */ + void copy_from(blender::Span items_src, int flag); + /** Remove all items from the panel. */ + void clear(bool do_id_user); + + /** + * Add item at the end of the panel. + * \note Takes ownership of the item. + */ + void add_item(bNodeTreeInterfaceItem &item); + /** + * Insert an item at the given position. + * \note Takes ownership of the item. + */ + void insert_item(bNodeTreeInterfaceItem &item, int position); + /** + * Remove item from the panel. + * \param free: Destruct and deallocate the item. + * \return True if the item was found. + */ + bool remove_item(bNodeTreeInterfaceItem &item, bool free); + /** + * Move item to a new position within the panel. + * \return True if the item was found. + */ + bool move_item(bNodeTreeInterfaceItem &item, int new_position); + + /** + * Apply a function to every item in the panel, including child panels. + * \note: The items are visited in drawing order from top to bottom. + * + * \param fn: Function to execute for each item, iterations stops if false is returned. + * \param include_self: Include the panel itself in the iteration. + */ + void foreach_item(blender::FunctionRef fn, + bool include_self = false); + /** Same as above but for a const interface. */ + void foreach_item(blender::FunctionRef fn, + bool include_self = false) const; +#endif +} bNodeTreeInterfacePanel; + +typedef struct bNodeTreeInterface { + bNodeTreeInterfacePanel root_panel; + + /* Global index of the active item. */ + int active_index; + int next_uid; + +#ifdef __cplusplus + + /** Copy data from another interface. + * \param flag: ID creation/copying flags, e.g. LIB_ID_CREATE_NO_MAIN. + */ + void copy_data(const bNodeTreeInterface &src, int flag); + /** Free data before the owning data block is freed. + * \note Does not decrement ID user counts, this has to be done by the caller. + */ + void free_data(); + + /** Read/write blend file data. */ + void write(BlendWriter *writer); + void read_data(BlendDataReader *reader); + void read_lib(BlendLibReader *reader, ID *id); + void read_expand(BlendExpander *expander); + + bNodeTreeInterfaceItem *active_item(); + const bNodeTreeInterfaceItem *active_item() const; + void active_item_set(bNodeTreeInterfaceItem *item); + + /** + * Get the index of the item in the interface. + * \return Index if the item was found or -1 otherwise. + */ + int find_item_index(const bNodeTreeInterfaceItem &item) const + { + return root_panel.item_index(item); + } + /** + * Search for an item in the interface. + * \return True if the item was found. + */ + bool find_item(const bNodeTreeInterfaceItem &item) const + { + return root_panel.contains_recursive(item); + } + /** + * Get the item at the given index of the interface draw list. + */ + const bNodeTreeInterfaceItem *get_item_at_index(int index) const + { + return root_panel.item_at_index(index); + } + /** + * Find the panel containing the item. + * \return Parent panel containing the item. + */ + bNodeTreeInterfacePanel *find_item_parent(const bNodeTreeInterfaceItem &item) + { + return root_panel.find_parent_recursive(item); + } + + /** + * Add a new socket at the end of the items list. + * \param parent: Panel in which to add the socket. If parent is null the socket is added in the + * root panel. + */ + bNodeTreeInterfaceSocket *add_socket(blender::StringRefNull name, + blender::StringRefNull description, + blender::StringRefNull socket_type, + eNodeTreeInterfaceSocketFlag flag, + bNodeTreeInterfacePanel *parent); + /** + * Insert a new socket. + * \param parent: Panel in which to add the socket. If parent is null the socket is added in the + * root panel. + * \param position: Position of the socket within the parent panel. + */ + bNodeTreeInterfaceSocket *insert_socket(blender::StringRefNull name, + blender::StringRefNull description, + blender::StringRefNull socket_type, + eNodeTreeInterfaceSocketFlag flag, + bNodeTreeInterfacePanel *parent, + int position); + + /** + * Add a new panel at the end of the items list. + * \param parent: Panel in which the new panel is aded as a child. If parent is null the new + * panel is made a child of the root panel. + */ + bNodeTreeInterfacePanel *add_panel(blender::StringRefNull name, bNodeTreeInterfacePanel *parent); + /** + * Insert a new panel. + * \param parent: Panel in which the new panel is aded as a child. If parent is null the new + * panel is made a child of the root panel. + * \param position: Position of the child panel within the parent panel. + */ + bNodeTreeInterfacePanel *insert_panel(blender::StringRefNull name, + bNodeTreeInterfacePanel *parent, + int position); + + /** + * Add a copy of an item at the end of the items list. + * \param parent: Add the item inside the parent panel. If parent is null the item is made a + * child of the root panel. + */ + bNodeTreeInterfaceItem *add_item_copy(const bNodeTreeInterfaceItem &item, + bNodeTreeInterfacePanel *parent); + /** + * Insert a copy of an item. + * \param parent: Add the item inside the parent panel. If parent is null the item is made a + * child of the root panel. + * \param position: Position of the item within the parent panel. + */ + bNodeTreeInterfaceItem *insert_item_copy(const bNodeTreeInterfaceItem &item, + bNodeTreeInterfacePanel *parent, + int position); + + /** + * Remove an item from the interface. + * \param move_content_to_parent: If the item is a panel, move the contents to the parent instead + * of deleting it. + * \return True if the item was found and successfully removed. + */ + bool remove_item(bNodeTreeInterfaceItem &item, bool move_content_to_parent = true); + void clear_items(); + + /** + * Move an item to a new position. + * \param new_position: New position of the item in the parent panel. + */ + bool move_item(bNodeTreeInterfaceItem &item, int new_position); + /** + * Move an item to a new panel and/or position. + * \param new_parent: Panel that the item is moved to. If null the item is added to the root + * panel. + * \param new_position: New position of the item in the parent panel. + */ + bool move_item_to_parent(bNodeTreeInterfaceItem &item, + bNodeTreeInterfacePanel *new_parent, + int new_position); + + /** + * Apply a function to every item in the interface. + * \note: The items are visited in drawing order from top to bottom. + * + * \param fn: Function to execute for each item, iterations stops if false is returned. + * \param include_root: Include the root panel in the iteration. + */ + void foreach_item(blender::FunctionRef fn, + bool include_root = false) + { + root_panel.foreach_item(fn, /*include_self=*/include_root); + } + /** + * Apply a function to every item in the interface. + * \note: The items are visited in drawing order from top to bottom. + * + * \param fn: Function to execute for each item, iterations stops if false is returned. + * \param include_root: Include the root panel in the iteration. + */ + void foreach_item(blender::FunctionRef fn, + bool include_root = false) const + { + root_panel.foreach_item(fn, /*include_self=*/include_root); + } + + void foreach_id(LibraryForeachIDData *cb); + +#endif +} bNodeTreeInterface; + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 7499a9e8520..6c3d5d137bf 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -10,6 +10,7 @@ #include "DNA_ID.h" #include "DNA_listBase.h" +#include "DNA_node_tree_interface_types.h" #include "DNA_scene_types.h" /* for #ImageFormatData */ #include "DNA_vec_types.h" /* for #rctf */ @@ -637,6 +638,8 @@ typedef struct bNodeTree { */ ListBase inputs, outputs; + bNodeTreeInterface tree_interface; + /** * Node preview hash table. * Only available in base node trees (e.g. scene->node_tree).