Previously, it was not possible to add any of the existing zones using link-drag-search, but only using normal search of the Add menu. For most zones the intended behavior was fairly straight forward. Just for For Each Element zone is a bit special, because it has two output geometries which have different meaning. I also had to update the link-drag-search a bit to remove the limitation that only a single node can be added. Pull Request: https://projects.blender.org/blender/blender/pulls/137684
310 lines
9.7 KiB
C++
310 lines
9.7 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#pragma once
|
|
|
|
/**
|
|
* Some nodes have a dynamic number of sockets (e.g. simulation input/output). These nodes store an
|
|
* array of items in their `bNode->storage` (e.g. `NodeSimulationItem`). Different nodes have
|
|
* slightly different storage requirements, but a lot of the logic is still the same between nodes.
|
|
* This file implements various shared functionality that can be used by different nodes to deal
|
|
* with these item arrays.
|
|
*
|
|
* In order to use the functions, one has to implement an "accessor" which tells the shared code
|
|
* how to deal with specific item arrays. Different functions have different requirements for the
|
|
* accessor. It's easiest to just look at existing accessors like #SimulationItemsAccessor and
|
|
* #RepeatItemsAccessor and to implement the same methods.
|
|
*/
|
|
|
|
#include "BLI_string.h"
|
|
#include "BLI_string_utils.hh"
|
|
|
|
#include "BKE_node.hh"
|
|
#include "BKE_node_runtime.hh"
|
|
|
|
#include "DNA_array_utils.hh"
|
|
|
|
#include "NOD_socket.hh"
|
|
|
|
namespace blender::nodes::socket_items {
|
|
|
|
/**
|
|
* References a "C-Array" that is stored elsewhere. This is different from a MutableSpan, because
|
|
* one can even resize the array through this reference.
|
|
*/
|
|
template<typename T> struct SocketItemsRef {
|
|
T **items;
|
|
int *items_num;
|
|
int *active_index;
|
|
};
|
|
|
|
/**
|
|
* Iterates over the node tree to find the node that this item belongs to.
|
|
*/
|
|
template<typename Accessor>
|
|
inline bNode *find_node_by_item(bNodeTree &ntree, const typename Accessor::ItemT &item)
|
|
{
|
|
ntree.ensure_topology_cache();
|
|
for (bNode *node : ntree.nodes_by_type(Accessor::node_idname)) {
|
|
SocketItemsRef array = Accessor::get_items_from_node(*node);
|
|
if (&item >= *array.items && &item < *array.items + *array.items_num) {
|
|
return node;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Destruct all the items and the free the array itself.
|
|
*/
|
|
template<typename Accessor> inline void destruct_array(bNode &node)
|
|
{
|
|
using ItemT = typename Accessor::ItemT;
|
|
SocketItemsRef ref = Accessor::get_items_from_node(node);
|
|
for (const int i : IndexRange(*ref.items_num)) {
|
|
ItemT &item = (*ref.items)[i];
|
|
Accessor::destruct_item(&item);
|
|
}
|
|
MEM_SAFE_FREE(*ref.items);
|
|
}
|
|
|
|
/**
|
|
* Removes all items from the node.
|
|
*/
|
|
template<typename Accessor> inline void clear(bNode &node)
|
|
{
|
|
destruct_array<Accessor>(node);
|
|
SocketItemsRef ref = Accessor::get_items_from_node(node);
|
|
*ref.items_num = 0;
|
|
*ref.active_index = 0;
|
|
}
|
|
|
|
/**
|
|
* Copy the items from the storage of the source node to the storage of the destination node.
|
|
*/
|
|
template<typename Accessor> inline void copy_array(const bNode &src_node, bNode &dst_node)
|
|
{
|
|
using ItemT = typename Accessor::ItemT;
|
|
SocketItemsRef src_ref = Accessor::get_items_from_node(const_cast<bNode &>(src_node));
|
|
SocketItemsRef dst_ref = Accessor::get_items_from_node(dst_node);
|
|
const int items_num = *src_ref.items_num;
|
|
*dst_ref.items = MEM_calloc_arrayN<ItemT>(items_num, __func__);
|
|
for (const int i : IndexRange(items_num)) {
|
|
Accessor::copy_item((*src_ref.items)[i], (*dst_ref.items)[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Changes the name of an existing item and makes sure that the name is unique among other the
|
|
* other items in the same array.
|
|
*/
|
|
template<typename Accessor>
|
|
inline void set_item_name_and_make_unique(bNode &node,
|
|
typename Accessor::ItemT &item,
|
|
const char *value)
|
|
{
|
|
using ItemT = typename Accessor::ItemT;
|
|
SocketItemsRef array = Accessor::get_items_from_node(node);
|
|
StringRefNull default_name = "Item";
|
|
if constexpr (Accessor::has_type) {
|
|
default_name = *bke::node_static_socket_label(Accessor::get_socket_type(item), 0);
|
|
}
|
|
|
|
char unique_name[MAX_NAME + 4];
|
|
STRNCPY(unique_name, value);
|
|
|
|
BLI_uniquename_cb(
|
|
[&](const StringRef name) {
|
|
for (ItemT &item_iter : blender::MutableSpan(*array.items, *array.items_num)) {
|
|
if (&item_iter != &item) {
|
|
if (*Accessor::get_name(item_iter) == name) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
default_name.c_str(),
|
|
'.',
|
|
unique_name,
|
|
ARRAY_SIZE(unique_name));
|
|
|
|
char **item_name = Accessor::get_name(item);
|
|
MEM_SAFE_FREE(*item_name);
|
|
*item_name = BLI_strdup(unique_name);
|
|
}
|
|
|
|
namespace detail {
|
|
|
|
template<typename Accessor> inline typename Accessor::ItemT &add_item_to_array(bNode &node)
|
|
{
|
|
using ItemT = typename Accessor::ItemT;
|
|
SocketItemsRef array = Accessor::get_items_from_node(node);
|
|
|
|
ItemT *old_items = *array.items;
|
|
const int old_items_num = *array.items_num;
|
|
const int new_items_num = old_items_num + 1;
|
|
|
|
ItemT *new_items = MEM_calloc_arrayN<ItemT>(new_items_num, __func__);
|
|
std::copy_n(old_items, old_items_num, new_items);
|
|
ItemT &new_item = new_items[old_items_num];
|
|
|
|
MEM_SAFE_FREE(old_items);
|
|
*array.items = new_items;
|
|
*array.items_num = new_items_num;
|
|
if (array.active_index) {
|
|
*array.active_index = old_items_num;
|
|
}
|
|
|
|
return new_item;
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
/**
|
|
* Add a new item at the end with the given socket type and name.
|
|
*/
|
|
template<typename Accessor>
|
|
inline typename Accessor::ItemT *add_item_with_socket_type_and_name(
|
|
bNode &node, const eNodeSocketDatatype socket_type, const char *name)
|
|
{
|
|
using ItemT = typename Accessor::ItemT;
|
|
BLI_assert(Accessor::supports_socket_type(socket_type));
|
|
ItemT &new_item = detail::add_item_to_array<Accessor>(node);
|
|
Accessor::init_with_socket_type_and_name(node, new_item, socket_type, name);
|
|
return &new_item;
|
|
}
|
|
|
|
/**
|
|
* Add a new item at the end with the given name.
|
|
*/
|
|
template<typename Accessor>
|
|
inline typename Accessor::ItemT *add_item_with_name(bNode &node, const char *name)
|
|
{
|
|
using ItemT = typename Accessor::ItemT;
|
|
ItemT &new_item = detail::add_item_to_array<Accessor>(node);
|
|
Accessor::init_with_name(node, new_item, name);
|
|
return &new_item;
|
|
}
|
|
|
|
/**
|
|
* Add a new item at the end.
|
|
*/
|
|
template<typename Accessor> inline typename Accessor::ItemT *add_item(bNode &node)
|
|
{
|
|
using ItemT = typename Accessor::ItemT;
|
|
ItemT &new_item = detail::add_item_to_array<Accessor>(node);
|
|
Accessor::init(node, new_item);
|
|
return &new_item;
|
|
}
|
|
|
|
template<typename Accessor>
|
|
inline std::string get_socket_identifier(const typename Accessor::ItemT &item,
|
|
const eNodeSocketInOut in_out)
|
|
{
|
|
if constexpr (Accessor::has_single_identifier_str) {
|
|
return Accessor::socket_identifier_for_item(item);
|
|
}
|
|
else {
|
|
if (in_out == SOCK_IN) {
|
|
return Accessor::input_socket_identifier_for_item(item);
|
|
}
|
|
return Accessor::output_socket_identifier_for_item(item);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the link connects to the `extend_socket`. If yes, create a new item for the linked
|
|
* socket, update the node and then change the link to point to the new socket.
|
|
* \return False if the link should be removed.
|
|
*/
|
|
template<typename Accessor>
|
|
[[nodiscard]] inline bool try_add_item_via_extend_socket(bNodeTree &ntree,
|
|
bNode &extend_node,
|
|
bNodeSocket &extend_socket,
|
|
bNode &storage_node,
|
|
bNodeLink &link)
|
|
{
|
|
using ItemT = typename Accessor::ItemT;
|
|
bNodeSocket *src_socket = nullptr;
|
|
if (link.tosock == &extend_socket) {
|
|
src_socket = link.fromsock;
|
|
}
|
|
else if (link.fromsock == &extend_socket) {
|
|
src_socket = link.tosock;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
|
|
const ItemT *item = nullptr;
|
|
if constexpr (Accessor::has_name && Accessor::has_type) {
|
|
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(src_socket->type);
|
|
if (!Accessor::supports_socket_type(socket_type)) {
|
|
return false;
|
|
}
|
|
item = add_item_with_socket_type_and_name<Accessor>(
|
|
storage_node, socket_type, src_socket->name);
|
|
}
|
|
else if constexpr (Accessor::has_name && !Accessor::has_type) {
|
|
item = add_item_with_name<Accessor>(storage_node, src_socket->name);
|
|
}
|
|
else {
|
|
item = add_item<Accessor>(storage_node);
|
|
}
|
|
if (item == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
update_node_declaration_and_sockets(ntree, extend_node);
|
|
if (extend_socket.is_input()) {
|
|
const std::string item_identifier = get_socket_identifier<Accessor>(*item, SOCK_IN);
|
|
bNodeSocket *new_socket = bke::node_find_socket(extend_node, SOCK_IN, item_identifier.c_str());
|
|
link.tosock = new_socket;
|
|
}
|
|
else {
|
|
const std::string item_identifier = get_socket_identifier<Accessor>(*item, SOCK_OUT);
|
|
bNodeSocket *new_socket = bke::node_find_socket(
|
|
extend_node, SOCK_OUT, item_identifier.c_str());
|
|
link.fromsock = new_socket;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Allow the item array to be extended from any extend-socket in the node.
|
|
* \return False if the link should be removed.
|
|
*/
|
|
template<typename Accessor>
|
|
[[nodiscard]] inline bool try_add_item_via_any_extend_socket(
|
|
bNodeTree &ntree,
|
|
bNode &extend_node,
|
|
bNode &storage_node,
|
|
bNodeLink &link,
|
|
const std::optional<StringRef> socket_identifier = std::nullopt)
|
|
{
|
|
bNodeSocket *possible_extend_socket = nullptr;
|
|
if (link.fromnode == &extend_node) {
|
|
possible_extend_socket = link.fromsock;
|
|
}
|
|
if (link.tonode == &extend_node) {
|
|
possible_extend_socket = link.tosock;
|
|
}
|
|
if (possible_extend_socket == nullptr) {
|
|
return true;
|
|
}
|
|
if (!STREQ(possible_extend_socket->idname, "NodeSocketVirtual")) {
|
|
return true;
|
|
}
|
|
if (socket_identifier.has_value()) {
|
|
if (possible_extend_socket->identifier != socket_identifier) {
|
|
return true;
|
|
}
|
|
}
|
|
return try_add_item_via_extend_socket<Accessor>(
|
|
ntree, extend_node, *possible_extend_socket, storage_node, link);
|
|
}
|
|
|
|
} // namespace blender::nodes::socket_items
|