Node Editor: Link Drag Search Menu
This commit adds a search menu when links are dragged above empty space. When releasing the drag, a menu displays all compatible sockets with the source link. The "main" sockets (usually the first) are weighted above other sockets in the search, so they appear first when you type the name of the node. A few special operators for creating a reroute or a group input node are also added to the search. Translation is started after choosing a node so it can be placed quickly, since users would likely adjust the position after anyway. A small "+" is displayed next to the cursor to give a hint about this. Further improvements are possible after this first iteration: - Support custom node trees. - Better drawing of items in the search menu. - Potential tweaks to filtering of items, depending on user feedback. Thanks to Juanfran Matheu for developing an initial patch. Differential Revision: https://developer.blender.org/D8286temp-usd-preview-surf-export
parent
474adc6f88
commit
11be151d58
Notes:
blender-bot
7 hours ago
Referenced by issue #96187: Node link drag search in shader editor doesn't filter by render engine compatibility
@ -0,0 +1,291 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_string_search.h"
|
||||
|
||||
#include "DNA_space_types.h"
|
||||
|
||||
#include "BKE_context.h"
|
||||
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
#include "RNA_access.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
|
||||
#include "node_intern.hh"
|
||||
|
||||
using blender::nodes::SocketLinkOperation;
|
||||
|
||||
namespace blender::ed::space_node {
|
||||
|
||||
struct LinkDragSearchStorage {
|
||||
bNode &from_node;
|
||||
bNodeSocket &from_socket;
|
||||
float2 cursor;
|
||||
Vector<SocketLinkOperation> search_link_ops;
|
||||
char search[256];
|
||||
|
||||
eNodeSocketInOut in_out() const
|
||||
{
|
||||
return static_cast<eNodeSocketInOut>(from_socket.in_out);
|
||||
}
|
||||
};
|
||||
|
||||
static void add_reroute_node_fn(nodes::LinkSearchOpParams ¶ms)
|
||||
{
|
||||
bNode &reroute = params.add_node("NodeReroute");
|
||||
if (params.socket.in_out == SOCK_IN) {
|
||||
nodeAddLink(¶ms.node_tree,
|
||||
&reroute,
|
||||
static_cast<bNodeSocket *>(reroute.outputs.first),
|
||||
¶ms.node,
|
||||
¶ms.socket);
|
||||
}
|
||||
else {
|
||||
nodeAddLink(¶ms.node_tree,
|
||||
¶ms.node,
|
||||
¶ms.socket,
|
||||
&reroute,
|
||||
static_cast<bNodeSocket *>(reroute.inputs.first));
|
||||
}
|
||||
}
|
||||
|
||||
static void add_group_input_node_fn(nodes::LinkSearchOpParams ¶ms)
|
||||
{
|
||||
/* Add a group input based on the connected socket, and add a new group input node. */
|
||||
bNodeSocket *interface_socket = 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. */
|
||||
ntreeUpdateTree(CTX_data_main(¶ms.C), ¶ms.node_tree);
|
||||
|
||||
/* Hide the new input in all other group input nodes, to avoid making them taller. */
|
||||
LISTBASE_FOREACH (bNode *, node, ¶ms.node_tree.nodes) {
|
||||
if (node->type == NODE_GROUP_INPUT) {
|
||||
bNodeSocket *new_group_input_socket = (bNodeSocket *)BLI_findlink(&node->outputs,
|
||||
group_input_index);
|
||||
new_group_input_socket->flag |= SOCK_HIDDEN;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide all existing inputs in the new group input node, to only display the new one. */
|
||||
LISTBASE_FOREACH (bNodeSocket *, socket, &group_input.outputs) {
|
||||
socket->flag |= SOCK_HIDDEN;
|
||||
}
|
||||
|
||||
bNodeSocket *socket = (bNodeSocket *)BLI_findlink(&group_input.outputs, group_input_index);
|
||||
if (socket == nullptr) {
|
||||
/* Adding sockets can fail in some cases. There's no good reason not to be safe here. */
|
||||
return;
|
||||
}
|
||||
/* Unhide the socket for the new input in the new node and make a connection to it. */
|
||||
socket->flag &= ~SOCK_HIDDEN;
|
||||
nodeAddLink(¶ms.node_tree, &group_input, socket, ¶ms.node, ¶ms.socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the callback to gather compatible socket connections for all node types, and the operations
|
||||
* that will actually make the connections. Also add some custom operations like connecting a group
|
||||
* output node.
|
||||
*/
|
||||
static void gather_socket_link_operations(bNodeTree &node_tree,
|
||||
const bNodeSocket &socket,
|
||||
Vector<SocketLinkOperation> &search_link_ops)
|
||||
{
|
||||
NODE_TYPES_BEGIN (node_type) {
|
||||
if (StringRef(node_type->idname).find("Legacy") != StringRef::not_found) {
|
||||
continue;
|
||||
}
|
||||
const char *disabled_hint;
|
||||
if (!(node_type->poll && node_type->poll(node_type, &node_tree, &disabled_hint))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node_type->gather_link_search_ops) {
|
||||
nodes::GatherLinkSearchOpParams params{*node_type, node_tree, socket, search_link_ops};
|
||||
node_type->gather_link_search_ops(params);
|
||||
}
|
||||
}
|
||||
NODE_TYPES_END;
|
||||
|
||||
search_link_ops.append({IFACE_("Reroute"), add_reroute_node_fn});
|
||||
|
||||
const bool is_node_group = !(node_tree.id.flag & LIB_EMBEDDED_DATA);
|
||||
|
||||
if (is_node_group && socket.in_out == SOCK_IN) {
|
||||
search_link_ops.append({IFACE_("Group Input"), add_group_input_node_fn});
|
||||
}
|
||||
}
|
||||
|
||||
static void link_drag_search_update_fn(const bContext *UNUSED(C),
|
||||
void *arg,
|
||||
const char *str,
|
||||
uiSearchItems *items,
|
||||
const bool is_first)
|
||||
{
|
||||
LinkDragSearchStorage &storage = *static_cast<LinkDragSearchStorage *>(arg);
|
||||
|
||||
StringSearch *search = BLI_string_search_new();
|
||||
|
||||
for (SocketLinkOperation &op : storage.search_link_ops) {
|
||||
BLI_string_search_add(search, op.name.c_str(), &op, op.weight);
|
||||
}
|
||||
|
||||
/* Don't filter when the menu is first opened, but still run the search
|
||||
* so the items are in the same order they will appear in while searching. */
|
||||
const char *string = is_first ? "" : str;
|
||||
SocketLinkOperation **filtered_items;
|
||||
const int filtered_amount = BLI_string_search_query(search, string, (void ***)&filtered_items);
|
||||
|
||||
for (const int i : IndexRange(filtered_amount)) {
|
||||
SocketLinkOperation &item = *filtered_items[i];
|
||||
if (!UI_search_item_add(items, item.name.c_str(), &item, ICON_NONE, 0, 0)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MEM_freeN(filtered_items);
|
||||
BLI_string_search_free(search);
|
||||
}
|
||||
|
||||
static void link_drag_search_exec_fn(bContext *C, void *arg1, void *arg2)
|
||||
{
|
||||
Main &bmain = *CTX_data_main(C);
|
||||
SpaceNode &snode = *CTX_wm_space_node(C);
|
||||
LinkDragSearchStorage &storage = *static_cast<LinkDragSearchStorage *>(arg1);
|
||||
SocketLinkOperation *item = static_cast<SocketLinkOperation *>(arg2);
|
||||
if (item == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
node_deselect_all(snode);
|
||||
|
||||
Vector<bNode *> new_nodes;
|
||||
nodes::LinkSearchOpParams params{
|
||||
*C, *snode.edittree, storage.from_node, storage.from_socket, new_nodes};
|
||||
item->fn(params);
|
||||
if (new_nodes.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* For now, assume that only one node is created by the callback. */
|
||||
BLI_assert(new_nodes.size() == 1);
|
||||
bNode *new_node = new_nodes.first();
|
||||
|
||||
new_node->locx = storage.cursor.x / UI_DPI_FAC;
|
||||
new_node->locy = storage.cursor.y / UI_DPI_FAC + 20 * UI_DPI_FAC;
|
||||
if (storage.in_out() == SOCK_IN) {
|
||||
new_node->locx -= new_node->width;
|
||||
}
|
||||
|
||||
nodeSetSelected(new_node, true);
|
||||
nodeSetActive(snode.edittree, new_node);
|
||||
|
||||
/* Ideally it would be possible to tag the node tree in some way so it updates only after the
|
||||
* translate operation is finished, but normally moving nodes around doesn't cause updates. */
|
||||
ntreeUpdateTree(&bmain, snode.edittree);
|
||||
snode_notify(*C, snode);
|
||||
snode_dag_update(*C, snode);
|
||||
|
||||
/* Start translation operator with the new node. */
|
||||
wmOperatorType *ot = WM_operatortype_find("TRANSFORM_OT_translate", true);
|
||||
BLI_assert(ot);
|
||||
PointerRNA ptr;
|
||||
WM_operator_properties_create_ptr(&ptr, ot);
|
||||
RNA_boolean_set(&ptr, "view2d_edge_pan", true);
|
||||
WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr);
|
||||
WM_operator_properties_free(&ptr);
|
||||
}
|
||||
|
||||
static void link_drag_search_free_fn(void *arg)
|
||||
{
|
||||
LinkDragSearchStorage *storage = static_cast<LinkDragSearchStorage *>(arg);
|
||||
delete storage;
|
||||
}
|
||||
|
||||
static uiBlock *create_search_popup_block(bContext *C, ARegion *region, void *arg_op)
|
||||
{
|
||||
LinkDragSearchStorage &storage = *(LinkDragSearchStorage *)arg_op;
|
||||
|
||||
bNodeTree *node_tree = CTX_wm_space_node(C)->nodetree;
|
||||
gather_socket_link_operations(*node_tree, storage.from_socket, storage.search_link_ops);
|
||||
|
||||
uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS);
|
||||
UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_SEARCH_MENU);
|
||||
UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
|
||||
|
||||
uiBut *but = uiDefSearchBut(block,
|
||||
storage.search,
|
||||
0,
|
||||
ICON_VIEWZOOM,
|
||||
sizeof(storage.search),
|
||||
storage.in_out() == SOCK_OUT ? 10 : 10 - UI_searchbox_size_x(),
|
||||
10,
|
||||
UI_searchbox_size_x(),
|
||||
UI_UNIT_Y,
|
||||
0,
|
||||
0,
|
||||
"");
|
||||
UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP);
|
||||
UI_but_func_search_set(but,
|
||||
nullptr,
|
||||
link_drag_search_update_fn,
|
||||
&storage,
|
||||
false,
|
||||
link_drag_search_free_fn,
|
||||
link_drag_search_exec_fn,
|
||||
nullptr);
|
||||
UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT);
|
||||
|
||||
/* Fake button to hold space for the search items. */
|
||||
uiDefBut(block,
|
||||
UI_BTYPE_LABEL,
|
||||
0,
|
||||
"",
|
||||
storage.in_out() == SOCK_OUT ? 10 : 10 - UI_searchbox_size_x(),
|
||||
10 - UI_searchbox_size_y(),
|
||||
UI_searchbox_size_x(),
|
||||
UI_searchbox_size_y(),
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
nullptr);
|
||||
|
||||
const int offset[2] = {0, -UI_UNIT_Y};
|
||||
UI_block_bounds_set_popup(block, 0.3f * U.widget_unit, offset);
|
||||
return block;
|
||||
}
|
||||
|
||||
void invoke_node_link_drag_add_menu(bContext &C,
|
||||
bNode &node,
|
||||
bNodeSocket &socket,
|
||||
const float2 &cursor)
|
||||
{
|
||||
LinkDragSearchStorage *storage = new LinkDragSearchStorage{node, socket, cursor};
|
||||
/* Use the "_ex" variant with `can_refresh` false to avoid a double free when closing Blender. */
|
||||
UI_popup_block_invoke_ex(&C, create_search_popup_block, storage, nullptr, false);
|
||||
}
|
||||
|
||||
} // namespace blender::ed::space_node
|
||||
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "DNA_node_types.h" /* Necessary for eNodeSocketInOut. */
|
||||
|
||||
#include "NOD_node_declaration.hh"
|
||||
|
||||
struct bContext;
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
/**
|
||||
* Parameters for the operation operation of adding a node after the link drag search menu closes.
|
||||
*/
|
||||
class LinkSearchOpParams {
|
||||
private:
|
||||
/**
|
||||
* Keep track of the nodes added by the callback, so they can be selected or moved afterwards.
|
||||
*/
|
||||
Vector<bNode *> &added_nodes_;
|
||||
|
||||
public:
|
||||
const bContext &C;
|
||||
bNodeTree &node_tree;
|
||||
/**
|
||||
* The node that contains the #socket.
|
||||
*/
|
||||
bNode &node;
|
||||
/**
|
||||
* The existing socket to connect any added nodes to. Might be an input or output socket.
|
||||
*/
|
||||
bNodeSocket &socket;
|
||||
|
||||
LinkSearchOpParams(const bContext &C,
|
||||
bNodeTree &node_tree,
|
||||
bNode &node,
|
||||
bNodeSocket &socket,
|
||||
Vector<bNode *> &added_nodes)
|
||||
: added_nodes_(added_nodes), C(C), node_tree(node_tree), node(node), socket(socket)
|
||||
{
|
||||
}
|
||||
|
||||
bNode &add_node(StringRef idname);
|
||||
bNode &add_node(const bNodeType &type);
|
||||
/**
|
||||
* Find a socket with the given name (correctly checks for inputs and outputs)
|
||||
* and connect it to the socket the link drag started from (#socket).
|
||||
*/
|
||||
void connect_available_socket(bNode &new_node, StringRef socket_name);
|
||||
/**
|
||||
* Like #connect_available_socket, but also calls the node's update function.
|
||||
*/
|
||||
void update_and_connect_available_socket(bNode &new_node, StringRef socket_name);
|
||||
};
|
||||
|
||||
struct SocketLinkOperation {
|
||||
using LinkSocketFn = std::function<void(LinkSearchOpParams &link_params)>;
|
||||
|
||||
std::string name;
|
||||
LinkSocketFn fn;
|
||||
int weight = 0;
|
||||
};
|
||||
|
||||
class GatherLinkSearchOpParams {
|
||||
/** The current node type. */
|
||||
const bNodeType &node_type_;
|
||||
|
||||
const bNodeTree &node_tree_;
|
||||
|
||||
const bNodeSocket &other_socket_;
|
||||
|
||||
/* The operations currently being built. Owned by the caller. */
|
||||
Vector<SocketLinkOperation> &items_;
|
||||
|
||||
public:
|
||||
GatherLinkSearchOpParams(const bNodeType &node_type,
|
||||
const bNodeTree &node_tree,
|
||||
const bNodeSocket &other_socket,
|
||||
Vector<SocketLinkOperation> &items)
|
||||
: node_type_(node_type), node_tree_(node_tree), other_socket_(other_socket), items_(items)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* The node on the other side of the dragged link.
|
||||
*/
|
||||
const bNodeSocket &other_socket() const;
|
||||
|
||||
/**
|
||||
* The node tree the user is editing when the search menu is created.
|
||||
*/
|
||||
const bNodeTree &node_tree() const;
|
||||
|
||||
/**
|
||||
* The type of the node in the current callback.
|
||||
*/
|
||||
const bNodeType &node_type() const;
|
||||
|
||||
/**
|
||||
* Whether to list the input or output sockets of the node.
|
||||
*/
|
||||
eNodeSocketInOut in_out() const;
|
||||
|
||||
/**
|
||||
* \param weight: Used to customize the order when multiple search items match.
|
||||
*
|
||||
* \warning When creating lambdas for the #fn argument, be careful not to capture this class
|
||||
* itself, since it is temporary. That is why we tend to use the same variable name for this
|
||||
* class (`params`) that we do for the argument to `LinkSocketFn`.
|
||||
*/
|
||||
void add_item(std::string socket_name, SocketLinkOperation::LinkSocketFn fn, int weight = 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* This callback can be used for a node type when a few things are true about its inputs.
|
||||
* To avoid creating more boilerplate, it is the default callback for node types.
|
||||
* - Either all declared sockets are visible in the default state of the node, *OR* the node's
|
||||
* type's declaration has been extended with #make_available functions for those sockets.
|
||||
*
|
||||
* If a node type does not meet these criteria, the function will do nothing in a release build.
|
||||
* In a debug build, an assert will most likely be hit.
|
||||
*
|
||||
* \note For nodes with the deprecated #bNodeSocketTemplate instead of a declaration,
|
||||
* these criteria do not apply and the function just tries its best without asserting.
|
||||
*/
|
||||
void search_link_ops_for_basic_node(GatherLinkSearchOpParams ¶ms);
|
||||
|
||||
void search_link_ops_for_declarations(GatherLinkSearchOpParams ¶ms,
|
||||
Span<SocketDeclarationPtr> declarations);
|
||||
|
||||
} // namespace blender::nodes
|
||||
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "BLI_set.hh"
|
||||
|
||||
#include "BKE_node.h"
|
||||
|
||||
#include "UI_interface.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(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 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);
|
||||
}
|
||||
|
||||
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<SocketDeclarationPtr> 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 heighest weight dependent on the number of
|
||||
* sockets. */
|
||||
const int weight = (&socket == main_socket) ? 0 : -1 - i;
|
||||
params.add_item(
|
||||
socket.name(),
|
||||
[&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);
|
||||
}
|
||||
}
|
||||
|
||||
static void search_link_ops_for_socket_templates(GatherLinkSearchOpParams ¶ms,
|
||||
const bNodeSocketTemplate *templates,
|
||||
const eNodeSocketInOut in_out)
|
||||
{
|
||||
const bNodeType &node_type = params.node_type();
|
||||
const bNodeTreeType &node_tree_type = *params.node_tree().typeinfo;
|
||||
|
||||
Set<StringRef> socket_names;
|
||||
for (const bNodeSocketTemplate *socket_template = templates; socket_template->type != -1;
|
||||
socket_template++) {
|
||||
eNodeSocketDatatype from = (eNodeSocketDatatype)socket_template->type;
|
||||
eNodeSocketDatatype to = (eNodeSocketDatatype)params.other_socket().type;
|
||||
if (in_out == SOCK_IN) {
|
||||
std::swap(from, to);
|
||||
}
|
||||
if (node_tree_type.validate_link && !node_tree_type.validate_link(from, to)) {
|
||||
continue;
|
||||
}
|
||||
if (!socket_names.add(socket_template->name)) {
|
||||
/* See comment in #search_link_ops_for_declarations. */
|
||||
continue;
|
||||
}
|
||||
|
||||
params.add_item(
|
||||
socket_template->name, [socket_template, node_type, in_out](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node(node_type);
|
||||
bNodeSocket *new_node_socket = bke::node_find_enabled_socket(
|
||||
node, in_out, socket_template->name);
|
||||
if (new_node_socket != nullptr) {
|
||||
/* Rely on the way #nodeAddLink switches in/out if necessary. */
|
||||
nodeAddLink(¶ms.node_tree, ¶ms.node, ¶ms.socket, &node, new_node_socket);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void search_link_ops_for_basic_node(GatherLinkSearchOpParams ¶ms)
|
||||
{
|
||||
const bNodeType &node_type = params.node_type();
|
||||
|
||||
if (node_type.declare) {
|
||||
if (node_type.declaration_is_dynamic) {
|
||||
/* Dynamic declarations (whatever they end up being) aren't supported
|
||||
* by this function, but still avoid a crash in release builds. */
|
||||
BLI_assert_unreachable();
|
||||
return;
|
||||
}
|
||||
|
||||
const NodeDeclaration &declaration = *node_type.fixed_declaration;
|
||||
|
||||
search_link_ops_for_declarations(params, declaration.sockets(params.in_out()));
|
||||
}
|
||||
else if (node_type.inputs && params.in_out() == SOCK_IN) {
|
||||
search_link_ops_for_socket_templates(params, node_type.inputs, SOCK_IN);
|
||||
}
|
||||
else if (node_type.outputs && params.in_out() == SOCK_OUT) {
|
||||
search_link_ops_for_socket_templates(params, node_type.outputs, SOCK_OUT);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::nodes
|
||||
Loading…
Reference in new issue