This helps solving the problem encountered in #113553. The problem is that we currently can't support link-drag-search for nodes which have a dynamic declaration. With this patch, there is only a single `declare` function per node type, instead of the separate `declare` and `declare_dynamic` functions. The new `declare` function has access to the node and tree. However, both are allowed to be null. The final node declaration has a flag for whether it depends on the node context or not. Nodes that previously had a dynamic declaration should now create as much of the declaration as possible that does not depend on the node. This allows code like for link-drag-search to take those sockets into account even if the other sockets are dynamic. For node declarations that have dynamic types (e.g. Switch node), we can also add extra information to the static node declaration, like the identifier of the socket with the dynamic type. This is not part of this patch though. I can think of two main alternatives to the approach implemented here: * Define two separate functions for dynamic nodes. One that creates the "static declaration" without node context, and on that creates the actual declaration with node context. * Have a single declare function that generates "build instructions" for the actual node declaration. So instead of building the final declaration directly, one can for example add a socket whose type depends on a specific rna path in the node. The actual node declaration is then automatically generated based on the build instructions. This becomes quite a bit more tricky with dynamic amounts of sockets and introduces another indirection between declarations and what sockets the node actually has. I found the approach implemented in this patch to lead to the least amount of boilerplate (doesn't require a seperate "build instructions" data structure) and code duplication (socket properties are still only defined in one place). At the same time, it offers more flexibility to how nodes can be dynamic. Pull Request: https://projects.blender.org/blender/blender/pulls/113742
152 lines
5.0 KiB
C++
152 lines
5.0 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "BLI_set.hh"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_node.hh"
|
|
|
|
#include "UI_interface.hh"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "NOD_node_declaration.hh"
|
|
#include "NOD_socket_search_link.hh"
|
|
|
|
namespace blender::nodes {
|
|
|
|
void GatherLinkSearchOpParams::add_item(std::string socket_name,
|
|
SocketLinkOperation::LinkSocketFn fn,
|
|
const int weight)
|
|
{
|
|
|
|
std::string name = std::string(IFACE_(node_type_.ui_name)) + " " + UI_MENU_ARROW_SEP +
|
|
socket_name;
|
|
|
|
items_.append({std::move(name), std::move(fn), weight});
|
|
}
|
|
|
|
const bNodeSocket &GatherLinkSearchOpParams::other_socket() const
|
|
{
|
|
return other_socket_;
|
|
}
|
|
|
|
const SpaceNode &GatherLinkSearchOpParams::space_node() const
|
|
{
|
|
return snode_;
|
|
}
|
|
|
|
const bNodeTree &GatherLinkSearchOpParams::node_tree() const
|
|
{
|
|
return node_tree_;
|
|
}
|
|
|
|
const bNodeType &GatherLinkSearchOpParams::node_type() const
|
|
{
|
|
return node_type_;
|
|
}
|
|
|
|
eNodeSocketInOut GatherLinkSearchOpParams::in_out() const
|
|
{
|
|
return other_socket_.in_out == SOCK_IN ? SOCK_OUT : SOCK_IN;
|
|
}
|
|
|
|
void LinkSearchOpParams::connect_available_socket(bNode &new_node, StringRef socket_name)
|
|
{
|
|
const eNodeSocketInOut in_out = socket.in_out == SOCK_IN ? SOCK_OUT : SOCK_IN;
|
|
bNodeSocket *new_node_socket = bke::node_find_enabled_socket(new_node, in_out, socket_name);
|
|
if (new_node_socket == nullptr) {
|
|
/* If the socket isn't found, some node's search gather functions probably aren't configured
|
|
* properly. It's likely enough that it's worth avoiding a crash in a release build though. */
|
|
BLI_assert_unreachable();
|
|
return;
|
|
}
|
|
nodeAddLink(&node_tree, &new_node, new_node_socket, &node, &socket);
|
|
if (in_out == SOCK_OUT) {
|
|
/* If the old socket already contained a value, then transfer it to a new one, from
|
|
* which this value will get there. */
|
|
bke::node_socket_move_default_value(*CTX_data_main(&C), node_tree, socket, *new_node_socket);
|
|
}
|
|
}
|
|
|
|
bNode &LinkSearchOpParams::add_node(StringRef idname)
|
|
{
|
|
std::string idname_str = idname;
|
|
bNode *node = nodeAddNode(&C, &node_tree, idname_str.c_str());
|
|
BLI_assert(node != nullptr);
|
|
added_nodes_.append(node);
|
|
return *node;
|
|
}
|
|
|
|
bNode &LinkSearchOpParams::add_node(const bNodeType &node_type)
|
|
{
|
|
return this->add_node(node_type.idname);
|
|
}
|
|
|
|
void LinkSearchOpParams::update_and_connect_available_socket(bNode &new_node,
|
|
StringRef socket_name)
|
|
{
|
|
if (new_node.typeinfo->updatefunc) {
|
|
new_node.typeinfo->updatefunc(&node_tree, &new_node);
|
|
}
|
|
this->connect_available_socket(new_node, socket_name);
|
|
}
|
|
|
|
void search_link_ops_for_declarations(GatherLinkSearchOpParams ¶ms,
|
|
Span<SocketDeclaration *> declarations)
|
|
{
|
|
const bNodeType &node_type = params.node_type();
|
|
|
|
const SocketDeclaration *main_socket = nullptr;
|
|
Vector<const SocketDeclaration *> connectable_sockets;
|
|
|
|
Set<StringRef> socket_names;
|
|
for (const int i : declarations.index_range()) {
|
|
const SocketDeclaration &socket = *declarations[i];
|
|
if (!socket_names.add(socket.name)) {
|
|
/* Don't add sockets with the same name to the search. Needed to support being called from
|
|
* #search_link_ops_for_basic_node, which should have "okay" behavior for nodes with
|
|
* duplicate socket names. */
|
|
continue;
|
|
}
|
|
if (!socket.can_connect(params.other_socket())) {
|
|
continue;
|
|
}
|
|
if (socket.is_default_link_socket || main_socket == nullptr) {
|
|
/* Either the first connectable or explicitly tagged socket is the main socket. */
|
|
main_socket = &socket;
|
|
}
|
|
connectable_sockets.append(&socket);
|
|
}
|
|
for (const int i : connectable_sockets.index_range()) {
|
|
const SocketDeclaration &socket = *connectable_sockets[i];
|
|
/* Give non-main sockets a lower weight so that they don't show up at the top of the search
|
|
* when they are not explicitly searched for. The -1 is used to make sure that the first socket
|
|
* has a smaller weight than zero so that it does not have the same weight as the main socket.
|
|
* Negative weights are used to avoid making the highest weight dependent on the number of
|
|
* sockets. */
|
|
const int weight = (&socket == main_socket) ? 0 : -1 - i;
|
|
params.add_item(
|
|
IFACE_(socket.name.c_str()),
|
|
[&node_type, &socket](LinkSearchOpParams ¶ms) {
|
|
bNode &node = params.add_node(node_type);
|
|
socket.make_available(node);
|
|
params.update_and_connect_available_socket(node, socket.name);
|
|
},
|
|
weight);
|
|
}
|
|
}
|
|
|
|
void search_link_ops_for_basic_node(GatherLinkSearchOpParams ¶ms)
|
|
{
|
|
const bNodeType &node_type = params.node_type();
|
|
if (!node_type.static_declaration) {
|
|
return;
|
|
}
|
|
const NodeDeclaration &declaration = *node_type.static_declaration;
|
|
search_link_ops_for_declarations(params, declaration.sockets(params.in_out()));
|
|
}
|
|
|
|
} // namespace blender::nodes
|