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/D8286
This commit is contained in:
Hans Goudey 2021-12-15 09:51:57 -06:00
parent 474adc6f88
commit 11be151d58
Notes: blender-bot 2023-02-14 03:59:42 +01:00
Referenced by commit 543ea41569, Cleanup: Remove unused node "add and link node" operator
Referenced by commit 36a830b4d3, Fix: Compare node missing from link drag search
Referenced by issue #96187, Node link drag search in shader editor doesn't filter by render engine compatibility
Referenced by issue #78919, Popover search menu when Drag&Release a NodeLink in node editors
47 changed files with 1691 additions and 113 deletions

View File

@ -33,6 +33,10 @@
#include "RNA_types.h"
#ifdef __cplusplus
# include "BLI_string_ref.hh"
#endif
#ifdef __cplusplus
extern "C" {
#endif
@ -114,6 +118,7 @@ namespace nodes {
class NodeMultiFunctionBuilder;
class GeoNodeExecParams;
class NodeDeclarationBuilder;
class GatherLinkSearchOpParams;
} // namespace nodes
namespace fn {
class CPPType;
@ -129,10 +134,15 @@ using SocketGetCPPValueFunction = void (*)(const struct bNodeSocket &socket, voi
using SocketGetGeometryNodesCPPValueFunction = void (*)(const struct bNodeSocket &socket,
void *r_value);
/* Adds socket link operations that are specific to this node type. */
using NodeGatherSocketLinkOperationsFunction =
void (*)(blender::nodes::GatherLinkSearchOpParams &params);
#else
typedef void *NodeMultiFunctionBuildFunction;
typedef void *NodeGeometryExecFunction;
typedef void *NodeDeclareFunction;
typedef void *NodeGatherSocketLinkOperationsFunction;
typedef void *SocketGetCPPTypeFunction;
typedef void *SocketGetGeometryNodesCPPTypeFunction;
typedef void *SocketGetGeometryNodesCPPValueFunction;
@ -284,7 +294,7 @@ typedef struct bNodeType {
/**
* Can this node type be added to a node tree?
* \param r_disabled_hint: Optional hint to display in the UI when the poll fails.
* \param r_disabled_hint: Hint to display in the UI when the poll fails.
* The callback can set this to a static string without having to
* null-check it (or without setting it to null if it's not used).
* The caller must pass a valid `const char **` and null-initialize it
@ -325,6 +335,13 @@ typedef struct bNodeType {
/* Declaration to be used when it is not dynamic. */
NodeDeclarationHandle *fixed_declaration;
/**
* Add to the list of search names and operations gathered by node link drag searching.
* Usually it isn't necessary to override the default behavior here, but a node type can have
* custom behavior here like adding custom search items.
*/
NodeGatherSocketLinkOperationsFunction gather_link_search_ops;
/** True when the node cannot be muted. */
bool no_muting;
@ -402,7 +419,7 @@ typedef struct bNodeTreeType {
/* Tree update. Overrides `nodetype->updatetreefunc` ! */
void (*update)(struct bNodeTree *ntree);
bool (*validate_link)(struct bNodeTree *ntree, struct bNodeLink *link);
bool (*validate_link)(eNodeSocketDatatype from, eNodeSocketDatatype to);
void (*node_add_init)(struct bNodeTree *ntree, struct bNode *bnode);
@ -1768,6 +1785,18 @@ extern struct bNodeSocketType NodeSocketTypeUndefined;
}
#endif
#ifdef __cplusplus
namespace blender::bke {
bNodeSocket *node_find_enabled_socket(bNode &node, eNodeSocketInOut in_out, StringRef name);
bNodeSocket *node_find_enabled_input_socket(bNode &node, StringRef name);
bNodeSocket *node_find_enabled_output_socket(bNode &node, StringRef name);
} // namespace blender::bke
#endif
#define NODE_STORAGE_FUNCS(StorageT) \
[[maybe_unused]] static StorageT &node_storage(bNode &node) \
{ \

View File

@ -102,6 +102,7 @@ using blender::MutableSpan;
using blender::Set;
using blender::Span;
using blender::Stack;
using blender::StringRef;
using blender::Vector;
using blender::VectorSet;
using blender::nodes::FieldInferencingInterface;
@ -1522,6 +1523,33 @@ struct bNodeSocket *nodeFindSocket(const bNode *node,
return nullptr;
}
namespace blender::bke {
bNodeSocket *node_find_enabled_socket(bNode &node,
const eNodeSocketInOut in_out,
const StringRef name)
{
ListBase *sockets = (in_out == SOCK_IN) ? &node.inputs : &node.outputs;
LISTBASE_FOREACH (bNodeSocket *, socket, sockets) {
if (!(socket->flag & SOCK_UNAVAIL) && socket->name == name) {
return socket;
}
}
return nullptr;
}
bNodeSocket *node_find_enabled_input_socket(bNode &node, StringRef name)
{
return node_find_enabled_socket(node, SOCK_IN, name);
}
bNodeSocket *node_find_enabled_output_socket(bNode &node, StringRef name)
{
return node_find_enabled_socket(node, SOCK_OUT, name);
}
} // namespace blender::bke
/* find unique socket identifier */
static bool unique_identifier_check(void *arg, const char *identifier)
{
@ -4459,7 +4487,8 @@ static void ntree_validate_links(bNodeTree *ntree)
link->flag &= ~NODE_LINK_VALID;
}
else if (ntree->typeinfo->validate_link) {
if (!ntree->typeinfo->validate_link(ntree, link)) {
if (!ntree->typeinfo->validate_link((eNodeSocketDatatype)link->fromsock->type,
(eNodeSocketDatatype)link->tosock->type)) {
link->flag &= ~NODE_LINK_VALID;
}
}

View File

@ -39,6 +39,7 @@ set(INC
set(SRC
drawnode.cc
link_drag_search.cc
node_add.cc
node_context_path.cc
node_draw.cc

View File

@ -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 &params)
{
bNode &reroute = params.add_node("NodeReroute");
if (params.socket.in_out == SOCK_IN) {
nodeAddLink(&params.node_tree,
&reroute,
static_cast<bNodeSocket *>(reroute.outputs.first),
&params.node,
&params.socket);
}
else {
nodeAddLink(&params.node_tree,
&params.node,
&params.socket,
&reroute,
static_cast<bNodeSocket *>(reroute.inputs.first));
}
}
static void add_group_input_node_fn(nodes::LinkSearchOpParams &params)
{
/* Add a group input based on the connected socket, and add a new group input node. */
bNodeSocket *interface_socket = ntreeAddSocketInterfaceFromSocket(
&params.node_tree, &params.node, &params.socket);
const int group_input_index = BLI_findindex(&params.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(&params.C), &params.node_tree);
/* Hide the new input in all other group input nodes, to avoid making them taller. */
LISTBASE_FOREACH (bNode *, node, &params.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(&params.node_tree, &group_input, socket, &params.node, &params.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

View File

@ -43,6 +43,9 @@ struct bNodeLink;
struct bNodeSocket;
struct wmGizmoGroupType;
struct wmKeyConfig;
namespace blender {
struct float2;
}
struct wmWindow;
/** Temporary data used in node link drag modal operator. */
@ -50,14 +53,29 @@ struct bNodeLinkDrag {
/** Links dragged by the operator. */
blender::Vector<bNodeLink *> links;
bool from_multi_input_socket;
int in_out;
eNodeSocketInOut in_out;
/** Draw handler for the "+" icon when dragging a link in empty space. */
void *draw_handle;
/** Temporarily stores the last picked link from multi-input socket operator. */
struct bNodeLink *last_picked_multi_input_socket_link;
bNodeLink *last_picked_multi_input_socket_link;
/** Temporarily stores the last hovered socket for multi-input socket operator.
* Store it to recalculate sorting after it is no longer hovered. */
struct bNode *last_node_hovered_while_dragging_a_link;
/**
* Temporarily stores the last hovered socket for multi-input socket operator.
* Store it to recalculate sorting after it is no longer hovered.
*/
bNode *last_node_hovered_while_dragging_a_link;
/* The cursor position, used for drawing a + icon when dragging a node link. */
std::array<int, 2> cursor;
/** The node the drag started at. */
bNode *start_node;
/** The socket the drag started at. */
bNodeSocket *start_socket;
/** The number of links connected to the #start_socket when the drag started. */
int start_link_count;
/* Data for edge panning */
View2DEdgePanData pan_data;
@ -327,4 +345,9 @@ namespace blender::ed::space_node {
Vector<ui::ContextPathItem> context_path_for_space_node(const bContext &C);
}
void invoke_node_link_drag_add_menu(bContext &C,
bNode &node,
bNodeSocket &socket,
const float2 &cursor);
} // namespace blender::ed::space_node

View File

@ -39,6 +39,7 @@
#include "ED_node.h" /* own include */
#include "ED_render.h"
#include "ED_screen.h"
#include "ED_space_api.h"
#include "ED_spreadsheet.h"
#include "ED_util.h"
@ -50,6 +51,9 @@
#include "WM_api.h"
#include "WM_types.h"
#include "GPU_state.h"
#include "UI_interface_icons.h"
#include "UI_resources.h"
#include "UI_view2d.h"
@ -57,11 +61,15 @@
#include "NOD_node_declaration.hh"
#include "NOD_node_tree_ref.hh"
#include "NOD_socket_declarations.hh"
#include "NOD_socket_declarations_geometry.hh"
#include "node_intern.hh" /* own include */
using namespace blender::nodes::node_tree_ref_types;
using blender::float2;
using blender::StringRef;
using blender::StringRefNull;
using blender::Vector;
/* -------------------------------------------------------------------- */
@ -890,6 +898,83 @@ void NODE_OT_link_viewer(wmOperatorType *ot)
/** \name Add Link Operator
* \{ */
/**
* Check if any of the dragged links are connected to a socket on the side that they are dragged
* from.
*/
static bool dragged_links_are_detached(const bNodeLinkDrag &nldrag)
{
if (nldrag.in_out == SOCK_OUT) {
for (const bNodeLink *link : nldrag.links) {
if (link->tonode && link->tosock) {
return false;
}
}
}
else {
for (const bNodeLink *link : nldrag.links) {
if (link->fromnode && link->fromsock) {
return false;
}
}
}
return true;
}
static bool should_create_drag_link_search_menu(const bNodeTree &node_tree,
const bNodeLinkDrag &nldrag)
{
/* Custom node trees aren't supported yet. */
if (node_tree.type == NTREE_CUSTOM) {
return false;
}
/* Only create the search menu when the drag has not already connected the links to a socket. */
if (!dragged_links_are_detached(nldrag)) {
return false;
}
/* Don't create the search menu if the drag is disconnecting a link from an input node. */
if (nldrag.start_socket->in_out == SOCK_IN && nldrag.start_link_count > 0) {
return false;
}
/* Don't allow a drag from the "new socket" of a group input node. Handling these
* properly in node callbacks increases the complexity too much for now. */
if (ELEM(nldrag.start_node->type, NODE_GROUP_INPUT, NODE_GROUP_OUTPUT)) {
if (nldrag.start_socket->type == SOCK_CUSTOM) {
return false;
}
}
return true;
}
static void draw_draglink_tooltip(const bContext *UNUSED(C), ARegion *UNUSED(region), void *arg)
{
bNodeLinkDrag *nldrag = static_cast<bNodeLinkDrag *>(arg);
const uchar text_col[4] = {255, 255, 255, 255};
const int padding = 4 * UI_DPI_FAC;
const float x = nldrag->in_out == SOCK_IN ? nldrag->cursor[0] - 3.3f * padding :
nldrag->cursor[0];
const float y = nldrag->cursor[1] - 2.0f * UI_DPI_FAC;
UI_icon_draw_ex(x, y, ICON_ADD, U.inv_dpi_fac, 1.0f, 0.0f, text_col, false);
}
static void draw_draglink_tooltip_activate(const ARegion &region, bNodeLinkDrag &nldrag)
{
if (nldrag.draw_handle == nullptr) {
nldrag.draw_handle = ED_region_draw_cb_activate(
region.type, draw_draglink_tooltip, &nldrag, REGION_DRAW_POST_PIXEL);
}
}
static void draw_draglink_tooltip_deactivate(const ARegion &region, bNodeLinkDrag &nldrag)
{
if (nldrag.draw_handle) {
ED_region_draw_cb_exit(region.type, nldrag.draw_handle);
nldrag.draw_handle = nullptr;
}
}
static void node_link_update_header(bContext *C, bNodeLinkDrag *UNUSED(nldrag))
{
char header[UI_MAX_DRAW_STR];
@ -952,12 +1037,11 @@ static void node_remove_extra_links(SpaceNode &snode, bNodeLink &link)
static void node_link_exit(bContext &C, wmOperator &op, const bool apply_links)
{
Main *bmain = CTX_data_main(&C);
ARegion &region = *CTX_wm_region(&C);
SpaceNode &snode = *CTX_wm_space_node(&C);
bNodeTree &ntree = *snode.edittree;
bNodeLinkDrag *nldrag = (bNodeLinkDrag *)op.customdata;
bool do_tag_update = false;
/* View will be reset if no links connect. */
bool reset_view = true;
/* avoid updates while applying links */
ntree.is_updating = true;
@ -995,8 +1079,6 @@ static void node_link_exit(bContext &C, wmOperator &op, const bool apply_links)
if (link->tonode) {
do_tag_update |= (do_tag_update || node_connected_to_output(*bmain, ntree, *link->tonode));
}
reset_view = false;
}
else {
nodeRemLink(&ntree, link);
@ -1010,9 +1092,12 @@ static void node_link_exit(bContext &C, wmOperator &op, const bool apply_links)
snode_dag_update(C, snode);
}
if (reset_view) {
UI_view2d_edge_pan_cancel(&C, &nldrag->pan_data);
}
/* Ensure draglink tooltip is disabled. */
draw_draglink_tooltip_deactivate(*CTX_wm_region(&C), *nldrag);
ED_workspace_status_text(&C, nullptr);
ED_region_tag_redraw(&region);
clear_picking_highlight(&snode.edittree->links);
snode.runtime->linkdrag.reset();
}
@ -1099,12 +1184,15 @@ static void node_link_find_socket(bContext &C, wmOperator &op, const float2 &cur
static int node_link_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
bNodeLinkDrag *nldrag = (bNodeLinkDrag *)op->customdata;
SpaceNode &snode = *CTX_wm_space_node(C);
ARegion *region = CTX_wm_region(C);
UI_view2d_edge_pan_apply_event(C, &nldrag->pan_data, event);
float2 cursor;
UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &cursor.x, &cursor.y);
nldrag->cursor[0] = event->mval[0];
nldrag->cursor[1] = event->mval[1];
switch (event->type) {
case MOUSEMOVE:
@ -1117,22 +1205,46 @@ static int node_link_modal(bContext *C, wmOperator *op, const wmEvent *event)
node_link_update_header(C, nldrag);
ED_region_tag_redraw(region);
}
break;
if (should_create_drag_link_search_menu(*snode.edittree, *nldrag)) {
draw_draglink_tooltip_activate(*region, *nldrag);
}
else {
draw_draglink_tooltip_deactivate(*region, *nldrag);
}
break;
case LEFTMOUSE:
if (event->val == KM_RELEASE) {
/* Add a search menu for compatible sockets if the drag released on empty space. */
if (should_create_drag_link_search_menu(*snode.edittree, *nldrag)) {
bNodeLink &link = *nldrag->links.first();
if (nldrag->in_out == SOCK_OUT) {
blender::ed::space_node::invoke_node_link_drag_add_menu(
*C, *link.fromnode, *link.fromsock, cursor);
}
else {
blender::ed::space_node::invoke_node_link_drag_add_menu(
*C, *link.tonode, *link.tosock, cursor);
}
}
/* Finish link. */
node_link_exit(*C, *op, true);
return OPERATOR_FINISHED;
}
break;
case RIGHTMOUSE:
case MIDDLEMOUSE: {
if (event->val == KM_RELEASE) {
node_link_exit(*C, *op, true);
ED_workspace_status_text(C, nullptr);
ED_region_tag_redraw(region);
SpaceNode &snode = *CTX_wm_space_node(C);
clear_picking_highlight(&snode.edittree->links);
return OPERATOR_FINISHED;
}
break;
}
case EVT_ESCKEY: {
node_link_exit(*C, *op, true);
return OPERATOR_FINISHED;
}
}
return OPERATOR_RUNNING_MODAL;
@ -1148,10 +1260,11 @@ static std::unique_ptr<bNodeLinkDrag> node_link_init(Main &bmain,
bNodeSocket *sock;
if (node_find_indicated_socket(snode, &node, &sock, cursor, SOCK_OUT)) {
std::unique_ptr<bNodeLinkDrag> nldrag = std::make_unique<bNodeLinkDrag>();
const int num_links = nodeCountSocketLinks(snode.edittree, sock);
nldrag->start_node = node;
nldrag->start_socket = sock;
nldrag->start_link_count = nodeCountSocketLinks(snode.edittree, sock);
int link_limit = nodeSocketLinkLimit(sock);
if (num_links > 0 && (num_links >= link_limit || detach)) {
if (nldrag->start_link_count > 0 && (nldrag->start_link_count >= link_limit || detach)) {
/* dragged links are fixed on input side */
nldrag->in_out = SOCK_IN;
/* detach current links and store them in the operator data */
@ -1192,9 +1305,11 @@ static std::unique_ptr<bNodeLinkDrag> node_link_init(Main &bmain,
if (node_find_indicated_socket(snode, &node, &sock, cursor, SOCK_IN)) {
std::unique_ptr<bNodeLinkDrag> nldrag = std::make_unique<bNodeLinkDrag>();
nldrag->last_node_hovered_while_dragging_a_link = node;
nldrag->start_node = node;
nldrag->start_socket = sock;
const int num_links = nodeCountSocketLinks(snode.edittree, sock);
if (num_links > 0) {
nldrag->start_link_count = nodeCountSocketLinks(snode.edittree, sock);
if (nldrag->start_link_count > 0) {
/* dragged links are fixed on output side */
nldrag->in_out = SOCK_OUT;
/* detach current links and store them in the operator data */
@ -1260,6 +1375,10 @@ static int node_link_invoke(bContext *C, wmOperator *op, const wmEvent *event)
if (nldrag) {
UI_view2d_edge_pan_operator_init(C, &nldrag->pan_data, op);
/* Add "+" icon when the link is dragged in empty space. */
if (should_create_drag_link_search_menu(*snode.edittree, *nldrag)) {
draw_draglink_tooltip_activate(*CTX_wm_region(C), *nldrag);
}
snode.runtime->linkdrag = std::move(nldrag);
op->customdata = snode.runtime->linkdrag.get();

View File

@ -193,6 +193,7 @@ set(SRC
intern/node_multi_function.cc
intern/node_socket.cc
intern/node_socket_declarations.cc
intern/socket_search_link.cc
intern/node_tree_ref.cc
intern/node_util.c
@ -214,6 +215,7 @@ set(SRC
NOD_shader.h
NOD_socket.h
NOD_socket_declarations.hh
NOD_socket_search_link.hh
NOD_socket_declarations_geometry.hh
NOD_static_types.h
NOD_texture.h

View File

@ -16,12 +16,12 @@
#pragma once
#include "BKE_node.h"
#ifdef __cplusplus
extern "C" {
#endif
#include "BKE_node.h"
extern struct bNodeTreeType *ntreeType_Geometry;
void register_node_tree_type_geo(void);

View File

@ -16,6 +16,7 @@
#pragma once
#include <functional>
#include <type_traits>
#include "BLI_string_ref.hh"
@ -23,6 +24,8 @@
#include "DNA_node_types.h"
struct bNode;
namespace blender::nodes {
class NodeDeclarationBuilder;
@ -84,6 +87,9 @@ class SocketDeclaration {
std::string name_;
std::string identifier_;
std::string description_;
/** Defined by whether the socket is part of the node's input or
* output socket declaration list. Included here for convenience. */
eNodeSocketInOut in_out_;
bool hide_label_ = false;
bool hide_value_ = false;
bool compact_ = false;
@ -95,19 +101,36 @@ class SocketDeclaration {
InputSocketFieldType input_field_type_ = InputSocketFieldType::None;
OutputFieldDependency output_field_dependency_;
/** Utility method to make the socket available if there is a straightforward way to do so. */
std::function<void(bNode &)> make_available_fn_;
friend NodeDeclarationBuilder;
template<typename SocketDecl> friend class SocketDeclarationBuilder;
public:
virtual ~SocketDeclaration() = default;
virtual bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const = 0;
virtual bNodeSocket &build(bNodeTree &ntree, bNode &node) const = 0;
virtual bool matches(const bNodeSocket &socket) const = 0;
virtual bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const;
/**
* Determine if a new socket described by this declaration could have a valid connection
* the other socket.
*/
virtual bool can_connect(const bNodeSocket &socket) const = 0;
/**
* Change the node such that the socket will become visible. The node type's update method
* should be called afterwards.
* \note Note that this is not necessarily implemented for all node types.
*/
void make_available(bNode &node) const;
StringRefNull name() const;
StringRefNull description() const;
StringRefNull identifier() const;
eNodeSocketInOut in_out() const;
bool is_attribute_name() const;
bool is_default_link_socket() const;
@ -216,6 +239,18 @@ class SocketDeclarationBuilder : public BaseSocketDeclarationBuilder {
std::move(input_dependencies));
return *(Self *)this;
}
/**
* Pass a function that sets properties on the node required to make the corresponding socket
* available, if it is not available on the default state of the node. The function is allowed to
* make other sockets unavailable, since it is meant to be called when the node is first added.
* The node type's update function is called afterwards.
*/
Self &make_available(std::function<void(bNode &)> fn)
{
decl_->make_available_fn_ = std::move(fn);
return *(Self *)this;
}
};
using SocketDeclarationPtr = std::unique_ptr<SocketDeclaration>;
@ -233,6 +268,7 @@ class NodeDeclaration {
Span<SocketDeclarationPtr> inputs() const;
Span<SocketDeclarationPtr> outputs() const;
Span<SocketDeclarationPtr> sockets(eNodeSocketInOut in_out) const;
bool is_function_node() const
{
@ -268,7 +304,7 @@ class NodeDeclarationBuilder {
template<typename DeclType>
typename DeclType::Builder &add_socket(StringRef name,
StringRef identifier,
Vector<SocketDeclarationPtr> &r_decls);
eNodeSocketInOut in_out);
};
/* -------------------------------------------------------------------- */
@ -361,6 +397,11 @@ inline StringRefNull SocketDeclaration::identifier() const
return identifier_;
}
inline eNodeSocketInOut SocketDeclaration::in_out() const
{
return in_out_;
}
inline StringRefNull SocketDeclaration::description() const
{
return description_;
@ -386,6 +427,13 @@ inline const OutputFieldDependency &SocketDeclaration::output_field_dependency()
return output_field_dependency_;
}
inline void SocketDeclaration::make_available(bNode &node) const
{
if (make_available_fn_) {
make_available_fn_(node);
}
}
/** \} */
/* -------------------------------------------------------------------- */
@ -401,28 +449,34 @@ template<typename DeclType>
inline typename DeclType::Builder &NodeDeclarationBuilder::add_input(StringRef name,
StringRef identifier)
{
return this->add_socket<DeclType>(name, identifier, declaration_.inputs_);
return this->add_socket<DeclType>(name, identifier, SOCK_IN);
}
template<typename DeclType>
inline typename DeclType::Builder &NodeDeclarationBuilder::add_output(StringRef name,
StringRef identifier)
{
return this->add_socket<DeclType>(name, identifier, declaration_.outputs_);
return this->add_socket<DeclType>(name, identifier, SOCK_OUT);
}
template<typename DeclType>
inline typename DeclType::Builder &NodeDeclarationBuilder::add_socket(
StringRef name, StringRef identifier, Vector<SocketDeclarationPtr> &r_decls)
inline typename DeclType::Builder &NodeDeclarationBuilder::add_socket(StringRef name,
StringRef identifier,
eNodeSocketInOut in_out)
{
static_assert(std::is_base_of_v<SocketDeclaration, DeclType>);
using Builder = typename DeclType::Builder;
Vector<SocketDeclarationPtr> &declarations = in_out == SOCK_IN ? declaration_.inputs_ :
declaration_.outputs_;
std::unique_ptr<DeclType> socket_decl = std::make_unique<DeclType>();
std::unique_ptr<Builder> socket_decl_builder = std::make_unique<Builder>();
socket_decl_builder->decl_ = &*socket_decl;
socket_decl->name_ = name;
socket_decl->identifier_ = identifier.is_empty() ? name : identifier;
r_decls.append(std::move(socket_decl));
socket_decl->in_out_ = in_out;
declarations.append(std::move(socket_decl));
Builder &socket_decl_builder_ref = *socket_decl_builder;
builders_.append(std::move(socket_decl_builder));
return socket_decl_builder_ref;
@ -444,6 +498,14 @@ inline Span<SocketDeclarationPtr> NodeDeclaration::outputs() const
return outputs_;
}
inline Span<SocketDeclarationPtr> NodeDeclaration::sockets(eNodeSocketInOut in_out) const
{
if (in_out == SOCK_IN) {
return inputs_;
}
return outputs_;
}
/** \} */
} // namespace blender::nodes

View File

@ -39,9 +39,10 @@ class Float : public SocketDeclaration {
public:
using Builder = FloatBuilder;
bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
bool matches(const bNodeSocket &socket) const override;
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
bool can_connect(const bNodeSocket &socket) const override;
};
class FloatBuilder : public SocketDeclarationBuilder<Float> {
@ -66,9 +67,10 @@ class Int : public SocketDeclaration {
public:
using Builder = IntBuilder;
bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
bool matches(const bNodeSocket &socket) const override;
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
bool can_connect(const bNodeSocket &socket) const override;
};
class IntBuilder : public SocketDeclarationBuilder<Int> {
@ -93,9 +95,10 @@ class Vector : public SocketDeclaration {
public:
using Builder = VectorBuilder;
bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
bool matches(const bNodeSocket &socket) const override;
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
bool can_connect(const bNodeSocket &socket) const override;
};
class VectorBuilder : public SocketDeclarationBuilder<Vector> {
@ -117,8 +120,9 @@ class Bool : public SocketDeclaration {
public:
using Builder = BoolBuilder;
bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
bool matches(const bNodeSocket &socket) const override;
bool can_connect(const bNodeSocket &socket) const override;
};
class BoolBuilder : public SocketDeclarationBuilder<Bool> {
@ -137,8 +141,9 @@ class Color : public SocketDeclaration {
public:
using Builder = ColorBuilder;
bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
bool matches(const bNodeSocket &socket) const override;
bool can_connect(const bNodeSocket &socket) const override;
};
class ColorBuilder : public SocketDeclarationBuilder<Color> {
@ -157,8 +162,9 @@ class String : public SocketDeclaration {
public:
using Builder = StringBuilder;
bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
bool matches(const bNodeSocket &socket) const override;
bool can_connect(const bNodeSocket &socket) const override;
};
class StringBuilder : public SocketDeclarationBuilder<String> {
@ -173,9 +179,10 @@ class IDSocketDeclaration : public SocketDeclaration {
public:
IDSocketDeclaration(const char *idname);
bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
bool matches(const bNodeSocket &socket) const override;
bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override;
bool can_connect(const bNodeSocket &socket) const override;
};
class Object : public IDSocketDeclaration {
@ -222,8 +229,9 @@ class Shader : public SocketDeclaration {
public:
using Builder = ShaderBuilder;
bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
bool matches(const bNodeSocket &socket) const override;
bool can_connect(const bNodeSocket &socket) const;
};
class ShaderBuilder : public SocketDeclarationBuilder<Shader> {

View File

@ -35,8 +35,9 @@ class Geometry : public SocketDeclaration {
public:
using Builder = GeometryBuilder;
bNodeSocket &build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const override;
bNodeSocket &build(bNodeTree &ntree, bNode &node) const override;
bool matches(const bNodeSocket &socket) const override;
bool can_connect(const bNodeSocket &socket) const override;
Span<GeometryComponentType> supported_types() const;
bool only_realized_data() const;

View File

@ -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 &params);
void search_link_ops_for_declarations(GatherLinkSearchOpParams &params,
Span<SocketDeclarationPtr> declarations);
} // namespace blender::nodes

View File

@ -21,6 +21,8 @@
* \ingroup nodes
*/
#include "NOD_socket_search_link.hh"
#include "node_composite_util.hh"
bool cmp_node_poll_default(bNodeType *UNUSED(ntype),
@ -52,4 +54,5 @@ void cmp_node_type_base(bNodeType *ntype, int type, const char *name, short ncla
ntype->poll = cmp_node_poll_default;
ntype->updatefunc = cmp_node_update_default;
ntype->insert_link = node_insert_link_default;
ntype->gather_link_search_ops = blender::nodes::search_link_ops_for_basic_node;
}

View File

@ -17,6 +17,8 @@
#include "node_function_util.hh"
#include "node_util.h"
#include "NOD_socket_search_link.hh"
static bool fn_node_poll_default(bNodeType *UNUSED(ntype),
bNodeTree *ntree,
const char **r_disabled_hint)
@ -34,4 +36,5 @@ void fn_node_type_base(bNodeType *ntype, int type, const char *name, short nclas
node_type_base(ntype, type, name, nclass, flag);
ntype->poll = fn_node_poll_default;
ntype->insert_link = node_insert_link_default;
ntype->gather_link_search_ops = blender::nodes::search_link_ops_for_basic_node;
}

View File

@ -19,6 +19,8 @@
#include "node_function_util.hh"
#include "NOD_socket_search_link.hh"
#include "UI_interface.h"
#include "UI_resources.h"
@ -43,7 +45,8 @@ static void fn_node_random_value_declare(NodeDeclarationBuilder &b)
.max(1.0f)
.default_value(0.5f)
.subtype(PROP_FACTOR)
.supports_field();
.supports_field()
.make_available([](bNode &node) { node_storage(node).data_type = CD_PROP_BOOL; });
b.add_input<decl::Int>(N_("ID")).implicit_field();
b.add_input<decl::Int>(N_("Seed")).default_value(0).min(-10000).max(10000).supports_field();
@ -97,6 +100,55 @@ static void fn_node_random_value_update(bNodeTree *ntree, bNode *node)
nodeSetSocketAvailability(ntree, sock_out_bool, data_type == CD_PROP_BOOL);
}
static std::optional<CustomDataType> node_type_from_other_socket(const bNodeSocket &socket)
{
switch (socket.type) {
case SOCK_FLOAT:
return CD_PROP_FLOAT;
case SOCK_BOOLEAN:
return CD_PROP_BOOL;
case SOCK_INT:
return CD_PROP_INT32;
case SOCK_VECTOR:
return CD_PROP_FLOAT3;
case SOCK_RGBA:
return CD_PROP_COLOR;
default:
return {};
}
}
static void fn_node_random_value_gather_link_search(GatherLinkSearchOpParams &params)
{
const NodeDeclaration &declaration = *params.node_type().fixed_declaration;
const std::optional<CustomDataType> type = node_type_from_other_socket(params.other_socket());
if (!type) {
return;
}
if (params.in_out() == SOCK_IN) {
if (ELEM(*type, CD_PROP_INT32, CD_PROP_FLOAT3, CD_PROP_FLOAT)) {
params.add_item(IFACE_("Min"), [type](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeRandomValue");
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Min");
});
params.add_item(IFACE_("Max"), [type](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeRandomValue");
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Max");
});
}
search_link_ops_for_declarations(params, declaration.inputs().take_back(3));
}
else {
params.add_item(IFACE_("Value"), [type](LinkSearchOpParams &params) {
bNode &node = params.add_node("FunctionNodeRandomValue");
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Value");
});
}
}
class RandomVectorFunction : public fn::MultiFunction {
public:
RandomVectorFunction()
@ -297,6 +349,7 @@ void register_node_type_fn_random_value()
ntype.draw_buttons = blender::nodes::fn_node_random_value_layout;
ntype.declare = blender::nodes::fn_node_random_value_declare;
ntype.build_multi_function = blender::nodes::fn_node_random_value_build_multi_function;
ntype.gather_link_search_ops = blender::nodes::fn_node_random_value_gather_link_search;
node_type_storage(
&ntype, "NodeRandomValue", node_free_standard_storage, node_copy_standard_storage);
nodeRegisterType(&ntype);

View File

@ -84,15 +84,16 @@ static void foreach_nodeclass(Scene *UNUSED(scene), void *calldata, bNodeClassCa
func(calldata, NODE_CLASS_LAYOUT, N_("Layout"));
}
static bool geometry_node_tree_validate_link(bNodeTree *UNUSED(ntree), bNodeLink *link)
static bool geometry_node_tree_validate_link(eNodeSocketDatatype type_a,
eNodeSocketDatatype type_b)
{
/* Geometry, string, object, material, texture and collection sockets can only be connected to
* themselves. The other types can be converted between each other. */
if (ELEM(link->fromsock->type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT) &&
ELEM(link->tosock->type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT)) {
if (ELEM(type_a, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT) &&
ELEM(type_b, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT)) {
return true;
}
return (link->tosock->type == link->fromsock->type);
return type_a == type_b;
}
static bool geometry_node_tree_socket_type_valid(bNodeTreeType *UNUSED(ntreetype),

View File

@ -24,6 +24,8 @@
#include "BKE_mesh_runtime.h"
#include "BKE_pointcloud.h"
#include "NOD_socket_search_link.hh"
namespace blender::nodes {
using bke::GeometryInstanceGroup;
@ -49,6 +51,31 @@ void update_attribute_input_socket_availabilities(bNodeTree &ntree,
}
}
std::optional<CustomDataType> node_data_type_to_custom_data_type(const eNodeSocketDatatype type)
{
switch (type) {
case SOCK_FLOAT:
return CD_PROP_FLOAT;
case SOCK_VECTOR:
return CD_PROP_FLOAT3;
case SOCK_RGBA:
return CD_PROP_COLOR;
case SOCK_BOOLEAN:
return CD_PROP_BOOL;
case SOCK_INT:
return CD_PROP_INT32;
case SOCK_STRING:
return CD_PROP_STRING;
default:
return {};
}
}
std::optional<CustomDataType> node_socket_to_custom_data_type(const bNodeSocket &socket)
{
return node_data_type_to_custom_data_type(static_cast<eNodeSocketDatatype>(socket.type));
}
} // namespace blender::nodes
bool geo_node_poll_default(bNodeType *UNUSED(ntype),
@ -67,4 +94,5 @@ void geo_node_type_base(bNodeType *ntype, int type, const char *name, short ncla
node_type_base(ntype, type, name, nclass, flag);
ntype->poll = geo_node_poll_default;
ntype->insert_link = node_insert_link_default;
ntype->gather_link_search_ops = blender::nodes::search_link_ops_for_basic_node;
}

View File

@ -133,4 +133,7 @@ void curve_create_default_rotation_attribute(Span<float3> tangents,
Span<float3> normals,
MutableSpan<float3> rotations);
std::optional<CustomDataType> node_data_type_to_custom_data_type(eNodeSocketDatatype type);
std::optional<CustomDataType> node_socket_to_custom_data_type(const bNodeSocket &socket);
} // namespace blender::nodes

View File

@ -19,6 +19,8 @@
#include "BKE_attribute_math.hh"
#include "NOD_socket_search_link.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_attribute_capture_cc {
@ -92,6 +94,36 @@ static void node_update(bNodeTree *ntree, bNode *node)
nodeSetSocketAvailability(ntree, out_socket_value_int32, data_type == CD_PROP_INT32);
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const bNodeType &node_type = params.node_type();
if (params.other_socket().type == SOCK_GEOMETRY) {
params.add_item(IFACE_("Geometry"), [node_type](LinkSearchOpParams &params) {
bNode &node = params.add_node(node_type);
params.connect_available_socket(node, "Geometry");
});
}
const std::optional<CustomDataType> type = node_data_type_to_custom_data_type(
(eNodeSocketDatatype)params.other_socket().type);
if (type) {
if (params.in_out() == SOCK_OUT) {
params.add_item(IFACE_("Attribute"), [node_type, type](LinkSearchOpParams &params) {
bNode &node = params.add_node(node_type);
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Attribute");
});
}
else {
params.add_item(IFACE_("Value"), [node_type, type](LinkSearchOpParams &params) {
bNode &node = params.add_node(node_type);
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Value");
});
}
}
}
static void try_capture_field_on_geometry(GeometryComponent &component,
const AttributeIDRef &attribute_id,
const AttributeDomain domain,
@ -216,5 +248,6 @@ void register_node_type_geo_attribute_capture()
ntype.declare = file_ns::node_declare;
ntype.geometry_node_execute = file_ns::node_geo_exec;
ntype.draw_buttons = file_ns::node_layout;
ntype.gather_link_search_ops = file_ns::node_gather_link_searches;
nodeRegisterType(&ntype);
}

View File

@ -24,12 +24,24 @@ namespace blender::nodes::node_geo_attribute_domain_size_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry");
b.add_output<decl::Int>("Point Count");
b.add_output<decl::Int>("Edge Count");
b.add_output<decl::Int>("Face Count");
b.add_output<decl::Int>("Face Corner Count");
b.add_output<decl::Int>("Spline Count");
b.add_output<decl::Int>("Instance Count");
b.add_output<decl::Int>("Point Count").make_available([](bNode &node) {
node.custom1 = GEO_COMPONENT_TYPE_MESH;
});
b.add_output<decl::Int>("Edge Count").make_available([](bNode &node) {
node.custom1 = GEO_COMPONENT_TYPE_MESH;
});
b.add_output<decl::Int>("Face Count").make_available([](bNode &node) {
node.custom1 = GEO_COMPONENT_TYPE_MESH;
});
b.add_output<decl::Int>("Face Corner Count").make_available([](bNode &node) {
node.custom1 = GEO_COMPONENT_TYPE_MESH;
});
b.add_output<decl::Int>("Spline Count").make_available([](bNode &node) {
node.custom1 = GEO_COMPONENT_TYPE_CURVE;
});
b.add_output<decl::Int>("Instance Count").make_available([](bNode &node) {
node.custom1 = GEO_COMPONENT_TYPE_INSTANCES;
});
}
static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)

View File

@ -22,6 +22,8 @@
#include "BLI_math_base_safe.h"
#include "NOD_socket_search_link.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_attribute_statistic_cc {
@ -112,6 +114,54 @@ static void node_update(bNodeTree *ntree, bNode *node)
nodeSetSocketAvailability(ntree, socket_vector_variance, data_type == CD_PROP_FLOAT3);
}
static std::optional<CustomDataType> node_type_from_other_socket(const bNodeSocket &socket)
{
switch (socket.type) {
case SOCK_FLOAT:
case SOCK_BOOLEAN:
case SOCK_INT:
return CD_PROP_FLOAT;
case SOCK_VECTOR:
case SOCK_RGBA:
return CD_PROP_FLOAT3;
default:
return {};
}
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const bNodeType &node_type = params.node_type();
const std::optional<CustomDataType> type = node_type_from_other_socket(params.other_socket());
if (params.in_out() == SOCK_IN) {
if (params.other_socket().type == SOCK_GEOMETRY) {
params.add_item(IFACE_("Geometry"), [node_type](LinkSearchOpParams &params) {
bNode &node = params.add_node(node_type);
params.connect_available_socket(node, "Geometry");
});
}
if (type) {
params.add_item(IFACE_("Attribute"), [&](LinkSearchOpParams &params) {
bNode &node = params.add_node(node_type);
node.custom1 = *type;
params.update_and_connect_available_socket(node, "Attribute");
});
}
}
else if (type) {
/* Only use the first 8 declarations since we set the type automatically. */
const NodeDeclaration &declaration = *params.node_type().fixed_declaration;
for (const SocketDeclarationPtr &socket_decl : declaration.outputs().take_front(8)) {
StringRefNull name = socket_decl->name();
params.add_item(IFACE_(name.c_str()), [node_type, name, type](LinkSearchOpParams &params) {
bNode &node = params.add_node(node_type);
node.custom1 = *type;
params.update_and_connect_available_socket(node, name);
});
}
}
}
template<typename T> static T compute_sum(const Span<T> data)
{
return std::accumulate(data.begin(), data.end(), T());
@ -359,5 +409,6 @@ void register_node_type_geo_attribute_statistic()
node_type_update(&ntype, file_ns::node_update);
ntype.geometry_node_execute = file_ns::node_geo_exec;
ntype.draw_buttons = file_ns::node_layout;
ntype.gather_link_search_ops = file_ns::node_gather_link_searches;
nodeRegisterType(&ntype);
}

View File

@ -32,7 +32,12 @@ NODE_STORAGE_FUNCS(NodeGeometryCurveFillet)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE);
b.add_input<decl::Int>(N_("Count")).default_value(1).min(1).max(1000).supports_field();
b.add_input<decl::Int>(N_("Count"))
.default_value(1)
.min(1)
.max(1000)
.supports_field()
.make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_FILLET_POLY; });
b.add_input<decl::Float>(N_("Radius"))
.min(0.0f)
.max(FLT_MAX)

View File

@ -56,7 +56,9 @@ static void node_declare(NodeDeclarationBuilder &b)
.subtype(PROP_DISTANCE)
.description(N_("Distance of the points from the origin"));
b.add_output<decl::Geometry>(N_("Curve"));
b.add_output<decl::Vector>(N_("Center"));
b.add_output<decl::Vector>(N_("Center")).make_available([](bNode &node) {
node_storage(node).mode = GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS;
});
}
static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)

View File

@ -17,6 +17,9 @@
#include "BKE_spline.hh"
#include "UI_interface.h"
#include "UI_resources.h"
#include "NOD_socket_search_link.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_primitive_quadrilaterial_cc {
@ -142,6 +145,44 @@ static void node_update(bNodeTree *ntree, bNode *node)
}
}
class SocketSearchOp {
public:
std::string socket_name;
GeometryNodeCurvePrimitiveQuadMode quad_mode;
void operator()(LinkSearchOpParams &params)
{
bNode &node = params.add_node("GeometryNodeCurvePrimitiveQuadrilateral");
node_storage(node).mode = quad_mode;
params.update_and_connect_available_socket(node, socket_name);
}
};
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
if (params.in_out() == SOCK_OUT) {
if (params.other_socket().type == SOCK_GEOMETRY) {
params.add_item(IFACE_("Curve"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeCurvePrimitiveQuadrilateral");
params.connect_available_socket(node, "Curve");
});
}
}
else {
params.add_item(IFACE_("Width"),
SocketSearchOp{"Width", GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_RECTANGLE});
params.add_item(IFACE_("Height"),
SocketSearchOp{"Height", GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_RECTANGLE});
params.add_item(IFACE_("Bottom Width"),
SocketSearchOp{"Bottom Width", GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_TRAPEZOID});
params.add_item(IFACE_("Top Width"),
SocketSearchOp{"Top Width", GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_TRAPEZOID});
params.add_item(IFACE_("Offset"),
SocketSearchOp{"Offset", GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_PARALLELOGRAM});
params.add_item(IFACE_("Point 1"),
SocketSearchOp{"Point 1", GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_POINTS});
}
}
static void create_rectangle_curve(MutableSpan<float3> positions,
const float height,
const float width)
@ -271,5 +312,6 @@ void register_node_type_geo_curve_primitive_quadrilateral()
"NodeGeometryCurvePrimitiveQuad",
node_free_standard_storage,
node_copy_standard_storage);
ntype.gather_link_search_ops = file_ns::node_gather_link_searches;
nodeRegisterType(&ntype);
}

View File

@ -32,9 +32,17 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_input<decl::Geometry>(N_("Curve"))
.only_realized_data()
.supported_type(GEO_COMPONENT_TYPE_CURVE);
b.add_input<decl::Float>(N_("Factor")).min(0.0f).max(1.0f).subtype(PROP_FACTOR).supports_field();
b.add_input<decl::Float>(N_("Length")).min(0.0f).subtype(PROP_DISTANCE).supports_field();
b.add_input<decl::Float>(N_("Factor"))
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR)
.supports_field()
.make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_FACTOR; });
b.add_input<decl::Float>(N_("Length"))
.min(0.0f)
.subtype(PROP_DISTANCE)
.supports_field()
.make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_LENGTH; });
b.add_output<decl::Vector>(N_("Position")).dependent_field();
b.add_output<decl::Vector>(N_("Tangent")).dependent_field();
b.add_output<decl::Vector>(N_("Normal")).dependent_field();

View File

@ -47,8 +47,18 @@ NODE_STORAGE_FUNCS(NodeGeometryCurveToPoints)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE);
b.add_input<decl::Int>(N_("Count")).default_value(10).min(2).max(100000);
b.add_input<decl::Float>(N_("Length")).default_value(0.1f).min(0.001f).subtype(PROP_DISTANCE);
b.add_input<decl::Int>(N_("Count"))
.default_value(10)
.min(2)
.max(100000)
.make_available(
[](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_RESAMPLE_COUNT; });
b.add_input<decl::Float>(N_("Length"))
.default_value(0.1f)
.min(0.001f)
.subtype(PROP_DISTANCE)
.make_available(
[](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_RESAMPLE_LENGTH; });
b.add_output<decl::Geometry>(N_("Points"));
b.add_output<decl::Vector>(N_("Tangent")).field_source();
b.add_output<decl::Vector>(N_("Normal")).field_source();
@ -401,6 +411,5 @@ void register_node_type_geo_curve_to_points()
&ntype, "NodeGeometryCurveToPoints", node_free_standard_storage, node_copy_standard_storage);
node_type_init(&ntype, file_ns::node_init);
node_type_update(&ntype, file_ns::node_update);
nodeRegisterType(&ntype);
}

View File

@ -20,6 +20,8 @@
#include "UI_interface.h"
#include "UI_resources.h"
#include "NOD_socket_search_link.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curve_trim_cc {
@ -31,21 +33,29 @@ NODE_STORAGE_FUNCS(NodeGeometryCurveTrim)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE);
b.add_input<decl::Float>(N_("Start")).min(0.0f).max(1.0f).subtype(PROP_FACTOR).supports_field();
b.add_input<decl::Float>(N_("Start"))
.min(0.0f)
.max(1.0f)
.subtype(PROP_FACTOR)
.make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_FACTOR; })
.supports_field();
b.add_input<decl::Float>(N_("End"))
.min(0.0f)
.max(1.0f)
.default_value(1.0f)
.subtype(PROP_FACTOR)
.make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_FACTOR; })
.supports_field();
b.add_input<decl::Float>(N_("Start"), "Start_001")
.min(0.0f)
.subtype(PROP_DISTANCE)
.make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_LENGTH; })
.supports_field();
b.add_input<decl::Float>(N_("End"), "End_001")
.min(0.0f)
.default_value(1.0f)
.subtype(PROP_DISTANCE)
.make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_LENGTH; })
.supports_field();
b.add_output<decl::Geometry>(N_("Curve"));
}
@ -80,6 +90,36 @@ static void node_update(bNodeTree *ntree, bNode *node)
nodeSetSocketAvailability(ntree, end_len, mode == GEO_NODE_CURVE_SAMPLE_LENGTH);
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
class SocketSearchOp {
public:
StringRef socket_name;
GeometryNodeCurveSampleMode mode;
void operator()(LinkSearchOpParams &params)
{
bNode &node = params.add_node("GeometryNodeTrimCurve");
node_storage(node).mode = mode;
params.update_and_connect_available_socket(node, socket_name);
}
};
if (params.in_out() == SOCK_OUT) {
params.add_item(IFACE_("Curve"), SocketSearchOp{"Curve", GEO_NODE_CURVE_SAMPLE_FACTOR});
}
else {
params.add_item(IFACE_("Curve"), SocketSearchOp{"Curve", GEO_NODE_CURVE_SAMPLE_FACTOR});
if (params.other_socket().type == SOCK_FLOAT) {
params.add_item(IFACE_("Start (Factor)"),
SocketSearchOp{"Start", GEO_NODE_CURVE_SAMPLE_FACTOR});
params.add_item(IFACE_("End (Factor)"), SocketSearchOp{"End", GEO_NODE_CURVE_SAMPLE_FACTOR});
params.add_item(IFACE_("Start (Length)"),
SocketSearchOp{"Start", GEO_NODE_CURVE_SAMPLE_LENGTH});
params.add_item(IFACE_("End (Length)"), SocketSearchOp{"End", GEO_NODE_CURVE_SAMPLE_LENGTH});
}
}
}
struct TrimLocation {
/* Control point index at the start side of the trim location. */
int left_index;
@ -574,5 +614,6 @@ void register_node_type_geo_curve_trim()
&ntype, "NodeGeometryCurveTrim", node_free_standard_storage, node_copy_standard_storage);
node_type_init(&ntype, file_ns::node_init);
node_type_update(&ntype, file_ns::node_update);
ntype.gather_link_search_ops = file_ns::node_gather_link_searches;
nodeRegisterType(&ntype);
}

View File

@ -23,6 +23,8 @@
#include "UI_interface.h"
#include "UI_resources.h"
#include "NOD_socket_search_link.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_mesh_primitive_line_cc {
@ -100,6 +102,40 @@ static void node_update(bNodeTree *ntree, bNode *node)
count_mode == GEO_NODE_MESH_LINE_COUNT_TOTAL);
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const NodeDeclaration &declaration = *params.node_type().fixed_declaration;
if (params.in_out() == SOCK_OUT) {
search_link_ops_for_declarations(params, declaration.outputs());
return;
}
params.add_item(IFACE_("Count"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeMeshLine");
node_storage(node).mode = GEO_NODE_MESH_LINE_MODE_OFFSET;
params.connect_available_socket(node, "Count");
});
params.add_item(IFACE_("Resolution"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeMeshLine");
node_storage(node).mode = GEO_NODE_MESH_LINE_MODE_OFFSET;
node_storage(node).count_mode = GEO_NODE_MESH_LINE_COUNT_RESOLUTION;
params.connect_available_socket(node, "Resolution");
});
params.add_item(IFACE_("Start Location"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeMeshLine");
params.connect_available_socket(node, "Start Location");
});
params.add_item(IFACE_("Offset"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeMeshLine");
params.connect_available_socket(node, "Offset");
});
/* The last socket is reused in end points mode. */
params.add_item(IFACE_("End Location"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeMeshLine");
node_storage(node).mode = GEO_NODE_MESH_LINE_MODE_END_POINTS;
params.connect_available_socket(node, "Offset");
});
}
static void node_geo_exec(GeoNodeExecParams params)
{
const NodeGeometryMeshLine &storage = node_storage(params.node());
@ -194,5 +230,6 @@ void register_node_type_geo_mesh_primitive_line()
&ntype, "NodeGeometryMeshLine", node_free_standard_storage, node_copy_standard_storage);
ntype.geometry_node_execute = file_ns::node_geo_exec;
ntype.draw_buttons = file_ns::node_layout;
ntype.gather_link_search_ops = file_ns::node_gather_link_searches;
nodeRegisterType(&ntype);
}

View File

@ -36,8 +36,19 @@ static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>(N_("Points"));
b.add_input<decl::Float>(N_("Density")).default_value(1.0f).min(0.0f);
b.add_input<decl::Float>(N_("Voxel Size")).default_value(0.3f).min(0.01f).subtype(PROP_DISTANCE);
b.add_input<decl::Float>(N_("Voxel Amount")).default_value(64.0f).min(0.0f);
b.add_input<decl::Float>(N_("Voxel Size"))
.default_value(0.3f)
.min(0.01f)
.subtype(PROP_DISTANCE)
.make_available([](bNode &node) {
node_storage(node).resolution_mode = GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE;
});
b.add_input<decl::Float>(N_("Voxel Amount"))
.default_value(64.0f)
.min(0.0f)
.make_available([](bNode &node) {
node_storage(node).resolution_mode = GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT;
});
b.add_input<decl::Float>(N_("Radius"))
.default_value(0.5f)
.min(0.0f)

View File

@ -23,6 +23,8 @@
#include "UI_interface.h"
#include "UI_resources.h"
#include "NOD_socket_search_link.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_raycast_cc {
@ -110,6 +112,25 @@ static void node_update(bNodeTree *ntree, bNode *node)
nodeSetSocketAvailability(ntree, out_socket_int32, data_type == CD_PROP_INT32);
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
const NodeDeclaration &declaration = *params.node_type().fixed_declaration;
search_link_ops_for_declarations(params, declaration.inputs().take_front(1));
search_link_ops_for_declarations(params, declaration.inputs().take_back(3));
search_link_ops_for_declarations(params, declaration.outputs().take_front(4));
const std::optional<CustomDataType> type = node_data_type_to_custom_data_type(
(eNodeSocketDatatype)params.other_socket().type);
if (type) {
/* The input and output sockets have the same name. */
params.add_item(IFACE_("Attribute"), [type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeRaycast");
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Attribute");
});
}
}
static eAttributeMapMode get_map_mode(GeometryNodeRaycastMapMode map_mode)
{
switch (map_mode) {
@ -437,5 +458,6 @@ void register_node_type_geo_raycast()
ntype.declare = file_ns::node_declare;
ntype.geometry_node_execute = file_ns::node_geo_exec;
ntype.draw_buttons = file_ns::node_layout;
ntype.gather_link_search_ops = file_ns::node_gather_link_searches;
nodeRegisterType(&ntype);
}

View File

@ -57,9 +57,14 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_input<decl::Float>(N_("Text Box Height"))
.default_value(0.0f)
.min(0.0f)
.subtype(PROP_DISTANCE);
.subtype(PROP_DISTANCE)
.make_available([](bNode &node) {
node_storage(node).overflow = GEO_NODE_STRING_TO_CURVES_MODE_SCALE_TO_FIT;
});
b.add_output<decl::Geometry>(N_("Curves"));
b.add_output<decl::String>(N_("Remainder"));
b.add_output<decl::String>(N_("Remainder")).make_available([](bNode &node) {
node_storage(node).overflow = GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW;
});
}
static void node_layout(uiLayout *layout, struct bContext *C, PointerRNA *ptr)

View File

@ -24,6 +24,8 @@
#include "BKE_material.h"
#include "NOD_socket_search_link.hh"
#include "FN_multi_function_signature.hh"
namespace blender::nodes::node_geo_switch_cc {
@ -122,6 +124,35 @@ static void node_update(bNodeTree *ntree, bNode *node)
}
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
if (params.in_out() == SOCK_OUT) {
params.add_item(IFACE_("Output"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeSwitch");
node_storage(node).input_type = params.socket.type;
params.update_and_connect_available_socket(node, "Output");
});
}
else {
if (params.other_socket().type == SOCK_BOOLEAN) {
params.add_item(IFACE_("Switch"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeSwitch");
params.connect_available_socket(node, "Start");
});
}
params.add_item(IFACE_("False"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeSwitch");
node_storage(node).input_type = params.socket.type;
params.update_and_connect_available_socket(node, "False");
});
params.add_item(IFACE_("True"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeSwitch");
node_storage(node).input_type = params.socket.type;
params.update_and_connect_available_socket(node, "True");
});
}
}
template<typename T> class SwitchFieldsFunction : public fn::MultiFunction {
public:
SwitchFieldsFunction()
@ -303,6 +334,7 @@ void register_node_type_geo_switch()
node_type_storage(&ntype, "NodeSwitch", node_free_standard_storage, node_copy_standard_storage);
ntype.geometry_node_execute = file_ns::node_geo_exec;
ntype.geometry_node_execute_supports_laziness = true;
ntype.gather_link_search_ops = file_ns::node_gather_link_searches;
ntype.draw_buttons = file_ns::node_layout;
nodeRegisterType(&ntype);
}

View File

@ -31,6 +31,8 @@
#include "UI_interface.h"
#include "UI_resources.h"
#include "NOD_socket_search_link.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_transfer_attribute_cc {
@ -54,8 +56,14 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_input<decl::Bool>(N_("Attribute"), "Attribute_003").hide_value().supports_field();
b.add_input<decl::Int>(N_("Attribute"), "Attribute_004").hide_value().supports_field();
b.add_input<decl::Vector>(N_("Source Position")).implicit_field();
b.add_input<decl::Int>(N_("Index")).implicit_field();
b.add_input<decl::Vector>(N_("Source Position"))
.implicit_field()
.make_available([](bNode &node) {
node_storage(node).mode = GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED;
});
b.add_input<decl::Int>(N_("Index")).implicit_field().make_available([](bNode &node) {
node_storage(node).mode = GEO_NODE_ATTRIBUTE_TRANSFER_INDEX;
});
b.add_output<decl::Vector>(N_("Attribute")).dependent_field({6, 7});
b.add_output<decl::Float>(N_("Attribute"), "Attribute_001").dependent_field({6, 7});
@ -126,6 +134,30 @@ static void node_update(bNodeTree *ntree, bNode *node)
nodeSetSocketAvailability(ntree, out_socket_int32, data_type == CD_PROP_INT32);
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
if (params.other_socket().type == SOCK_GEOMETRY) {
params.add_item(IFACE_("Target"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeAttributeTransfer");
params.connect_available_socket(node, "Target");
});
}
const NodeDeclaration &declaration = *params.node_type().fixed_declaration;
search_link_ops_for_declarations(params, declaration.inputs().take_back(2));
const std::optional<CustomDataType> type = node_data_type_to_custom_data_type(
(eNodeSocketDatatype)params.other_socket().type);
if (type) {
/* The input and output sockets have the same name. */
params.add_item(IFACE_("Attribute"), [type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeAttributeTransfer");
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Attribute");
});
}
}
static void get_closest_in_bvhtree(BVHTreeFromMesh &tree_data,
const VArray<float3> &positions,
const IndexMask mask,
@ -812,5 +844,6 @@ void register_node_type_geo_transfer_attribute()
ntype.declare = file_ns::node_declare;
ntype.geometry_node_execute = file_ns::node_geo_exec;
ntype.draw_buttons = file_ns::node_layout;
ntype.gather_link_search_ops = file_ns::node_gather_link_searches;
nodeRegisterType(&ntype);
}

View File

@ -14,9 +14,16 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "BKE_context.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "ED_node.h"
#include "ED_spreadsheet.h"
#include "NOD_socket_search_link.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_viewer_cc {
@ -80,6 +87,52 @@ static void node_update(bNodeTree *ntree, bNode *node)
}
}
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
{
auto set_active_fn = [](LinkSearchOpParams &params, bNode &viewer_node) {
/* Set this new viewer node active in spreadsheet editors. */
SpaceNode *snode = CTX_wm_space_node(&params.C);
Main *bmain = CTX_data_main(&params.C);
ED_node_set_active(bmain, snode, &params.node_tree, &viewer_node, nullptr);
ED_spreadsheet_context_paths_set_geometry_node(bmain, snode, &viewer_node);
};
const std::optional<CustomDataType> type = node_socket_to_custom_data_type(
params.other_socket());
if (params.in_out() == SOCK_OUT) {
/* The viewer node only has inputs. */
return;
}
if (params.other_socket().type == SOCK_GEOMETRY) {
params.add_item(IFACE_("Geometry"), [set_active_fn](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeViewer");
params.connect_available_socket(node, "Geometry");
set_active_fn(params, node);
});
}
if (type &&
ELEM(type, CD_PROP_FLOAT, CD_PROP_BOOL, CD_PROP_INT32, CD_PROP_FLOAT3, CD_PROP_COLOR)) {
params.add_item(IFACE_("Value"), [type, set_active_fn](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeViewer");
node_storage(node).data_type = *type;
params.update_and_connect_available_socket(node, "Value");
/* If the source node has a geometry socket, connect it to the new viewer node as well. */
LISTBASE_FOREACH (bNodeSocket *, socket, &params.node.outputs) {
if (socket->type == SOCK_GEOMETRY && !(socket->flag & (SOCK_UNAVAIL | SOCK_HIDDEN))) {
nodeAddLink(&params.node_tree,
&params.node,
socket,
&node,
static_cast<bNodeSocket *>(node.inputs.first));
}
}
set_active_fn(params, node);
});
}
}
} // namespace blender::nodes::node_geo_viewer_cc
void register_node_type_geo_viewer()
@ -95,5 +148,6 @@ void register_node_type_geo_viewer()
node_type_init(&ntype, file_ns::node_init);
ntype.declare = file_ns::node_declare;
ntype.draw_buttons_ex = file_ns::node_layout;
ntype.gather_link_search_ops = file_ns::node_gather_link_searches;
nodeRegisterType(&ntype);
}

View File

@ -42,8 +42,19 @@ NODE_STORAGE_FUNCS(NodeGeometryVolumeToMesh)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>(N_("Volume")).supported_type(GEO_COMPONENT_TYPE_VOLUME);
b.add_input<decl::Float>(N_("Voxel Size")).default_value(0.3f).min(0.01f).subtype(PROP_DISTANCE);
b.add_input<decl::Float>(N_("Voxel Amount")).default_value(64.0f).min(0.0f);
b.add_input<decl::Float>(N_("Voxel Size"))
.default_value(0.3f)
.min(0.01f)
.subtype(PROP_DISTANCE)
.make_available([](bNode &node) {
node_storage(node).resolution_mode = VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_SIZE;
});
b.add_input<decl::Float>(N_("Voxel Amount"))
.default_value(64.0f)
.min(0.0f)
.make_available([](bNode &node) {
node_storage(node).resolution_mode = VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_AMOUNT;
});
b.add_input<decl::Float>(N_("Threshold")).default_value(0.1f).min(0.0f);
b.add_input<decl::Float>(N_("Adaptivity")).min(0.0f).max(1.0f).subtype(PROP_FACTOR);
b.add_output<decl::Geometry>(N_("Mesh"));

View File

@ -51,7 +51,9 @@ bNodeSocket &SocketDeclaration::update_or_build(bNodeTree &ntree,
bNodeSocket &socket) const
{
/* By default just rebuild. */
return this->build(ntree, node, (eNodeSocketInOut)socket.in_out);
BLI_assert(socket.in_out == in_out_);
UNUSED_VARS_NDEBUG(socket);
return this->build(ntree, node);
}
void SocketDeclaration::set_common_flags(bNodeSocket &socket) const

View File

@ -191,7 +191,6 @@ static void refresh_socket_list(bNodeTree &ntree,
bNode &node,
ListBase &sockets,
Span<SocketDeclarationPtr> socket_decls,
const eNodeSocketInOut in_out,
const bool do_id_user)
{
Vector<bNodeSocket *> old_sockets = sockets;
@ -210,7 +209,7 @@ static void refresh_socket_list(bNodeTree &ntree,
bNodeSocket *new_socket = nullptr;
if (old_socket_with_same_identifier == nullptr) {
/* Create a completely new socket. */
new_socket = &socket_decl->build(ntree, node, in_out);
new_socket = &socket_decl->build(ntree, node);
}
else {
STRNCPY(old_socket_with_same_identifier->name, socket_decl->name().c_str());
@ -258,8 +257,8 @@ static void refresh_node(bNodeTree &ntree,
blender::nodes::NodeDeclaration &node_decl,
bool do_id_user)
{
refresh_socket_list(ntree, node, node.inputs, node_decl.inputs(), SOCK_IN, do_id_user);
refresh_socket_list(ntree, node, node.outputs, node_decl.outputs(), SOCK_OUT, do_id_user);
refresh_socket_list(ntree, node, node.inputs, node_decl.inputs(), do_id_user);
refresh_socket_list(ntree, node, node.outputs, node_decl.outputs(), do_id_user);
}
void node_verify_sockets(bNodeTree *ntree, bNode *node, bool do_id_user)

View File

@ -23,6 +23,52 @@
namespace blender::nodes::decl {
/**
* \note This function only deals with declarations, not the field status of existing nodes. If the
* field status of existing nodes was stored on the sockets, an improvement would be to check the
* existing socket's current status instead of the declaration.
*/
static bool field_types_are_compatible(const SocketDeclaration &input,
const SocketDeclaration &output)
{
if (output.output_field_dependency().field_type() == OutputSocketFieldType::FieldSource) {
if (input.input_field_type() == InputSocketFieldType::None) {
return false;
}
}
return true;
}
static bool sockets_can_connect(const SocketDeclaration &socket_decl,
const bNodeSocket &other_socket)
{
/* Input sockets cannot connect to input sockets, outputs cannot connect to outputs. */
if (socket_decl.in_out() == other_socket.in_out) {
return false;
}
if (other_socket.declaration) {
if (socket_decl.in_out() == SOCK_IN) {
if (!field_types_are_compatible(socket_decl, *other_socket.declaration)) {
return false;
}
}
else {
if (!field_types_are_compatible(*other_socket.declaration, socket_decl)) {
return false;
}
}
}
return true;
}
static bool basic_types_can_connect(const SocketDeclaration &UNUSED(socket_decl),
const bNodeSocket &other_socket)
{
return ELEM(other_socket.type, SOCK_FLOAT, SOCK_INT, SOCK_BOOLEAN, SOCK_VECTOR, SOCK_RGBA);
}
static void modify_subtype_except_for_storage(bNodeSocket &socket, int new_subtype)
{
const char *idname = nodeStaticSocketType(socket.type, new_subtype);
@ -35,10 +81,10 @@ static void modify_subtype_except_for_storage(bNodeSocket &socket, int new_subty
/** \name #Float
* \{ */
bNodeSocket &Float::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const
bNodeSocket &Float::build(bNodeTree &ntree, bNode &node) const
{
bNodeSocket &socket = *nodeAddStaticSocket(
&ntree, &node, in_out, SOCK_FLOAT, subtype_, identifier_.c_str(), name_.c_str());
&ntree, &node, in_out_, SOCK_FLOAT, subtype_, identifier_.c_str(), name_.c_str());
this->set_common_flags(socket);
bNodeSocketValueFloat &value = *(bNodeSocketValueFloat *)socket.default_value;
value.min = soft_min_value_;
@ -68,10 +114,19 @@ bool Float::matches(const bNodeSocket &socket) const
return true;
}
bool Float::can_connect(const bNodeSocket &socket) const
{
if (!sockets_can_connect(*this, socket)) {
return false;
}
return basic_types_can_connect(*this, socket);
}
bNodeSocket &Float::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const
{
if (socket.type != SOCK_FLOAT) {
return this->build(ntree, node, (eNodeSocketInOut)socket.in_out);
BLI_assert(socket.in_out == in_out_);
return this->build(ntree, node);
}
if (socket.typeinfo->subtype != subtype_) {
modify_subtype_except_for_storage(socket, subtype_);
@ -90,10 +145,10 @@ bNodeSocket &Float::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &
/** \name #Int
* \{ */
bNodeSocket &Int::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const
bNodeSocket &Int::build(bNodeTree &ntree, bNode &node) const
{
bNodeSocket &socket = *nodeAddStaticSocket(
&ntree, &node, in_out, SOCK_INT, subtype_, identifier_.c_str(), name_.c_str());
&ntree, &node, in_out_, SOCK_INT, subtype_, identifier_.c_str(), name_.c_str());
this->set_common_flags(socket);
bNodeSocketValueInt &value = *(bNodeSocketValueInt *)socket.default_value;
value.min = soft_min_value_;
@ -123,10 +178,19 @@ bool Int::matches(const bNodeSocket &socket) const
return true;
}
bool Int::can_connect(const bNodeSocket &socket) const
{
if (!sockets_can_connect(*this, socket)) {
return false;
}
return basic_types_can_connect(*this, socket);
}
bNodeSocket &Int::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const
{
if (socket.type != SOCK_INT) {
return this->build(ntree, node, (eNodeSocketInOut)socket.in_out);
BLI_assert(socket.in_out == in_out_);
return this->build(ntree, node);
}
if (socket.typeinfo->subtype != subtype_) {
modify_subtype_except_for_storage(socket, subtype_);
@ -145,10 +209,10 @@ bNodeSocket &Int::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &so
/** \name #Vector
* \{ */
bNodeSocket &Vector::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const
bNodeSocket &Vector::build(bNodeTree &ntree, bNode &node) const
{
bNodeSocket &socket = *nodeAddStaticSocket(
&ntree, &node, in_out, SOCK_VECTOR, subtype_, identifier_.c_str(), name_.c_str());
&ntree, &node, in_out_, SOCK_VECTOR, subtype_, identifier_.c_str(), name_.c_str());
this->set_common_flags(socket);
bNodeSocketValueVector &value = *(bNodeSocketValueVector *)socket.default_value;
copy_v3_v3(value.value, default_value_);
@ -171,10 +235,19 @@ bool Vector::matches(const bNodeSocket &socket) const
return true;
}
bool Vector::can_connect(const bNodeSocket &socket) const
{
if (!sockets_can_connect(*this, socket)) {
return false;
}
return basic_types_can_connect(*this, socket);
}
bNodeSocket &Vector::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const
{
if (socket.type != SOCK_VECTOR) {
return this->build(ntree, node, (eNodeSocketInOut)socket.in_out);
BLI_assert(socket.in_out == in_out_);
return this->build(ntree, node);
}
if (socket.typeinfo->subtype != subtype_) {
modify_subtype_except_for_storage(socket, subtype_);
@ -192,10 +265,10 @@ bNodeSocket &Vector::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket
/** \name #Bool
* \{ */
bNodeSocket &Bool::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const
bNodeSocket &Bool::build(bNodeTree &ntree, bNode &node) const
{
bNodeSocket &socket = *nodeAddStaticSocket(
&ntree, &node, in_out, SOCK_BOOLEAN, PROP_NONE, identifier_.c_str(), name_.c_str());
&ntree, &node, in_out_, SOCK_BOOLEAN, PROP_NONE, identifier_.c_str(), name_.c_str());
this->set_common_flags(socket);
bNodeSocketValueBoolean &value = *(bNodeSocketValueBoolean *)socket.default_value;
value.value = default_value_;
@ -213,16 +286,24 @@ bool Bool::matches(const bNodeSocket &socket) const
return true;
}
bool Bool::can_connect(const bNodeSocket &socket) const
{
if (!sockets_can_connect(*this, socket)) {
return false;
}
return basic_types_can_connect(*this, socket);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #Color
* \{ */
bNodeSocket &Color::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const
bNodeSocket &Color::build(bNodeTree &ntree, bNode &node) const
{
bNodeSocket &socket = *nodeAddStaticSocket(
&ntree, &node, in_out, SOCK_RGBA, PROP_NONE, identifier_.c_str(), name_.c_str());
&ntree, &node, in_out_, SOCK_RGBA, PROP_NONE, identifier_.c_str(), name_.c_str());
this->set_common_flags(socket);
bNodeSocketValueRGBA &value = *(bNodeSocketValueRGBA *)socket.default_value;
copy_v4_v4(value.value, default_value_);
@ -245,16 +326,24 @@ bool Color::matches(const bNodeSocket &socket) const
return true;
}
bool Color::can_connect(const bNodeSocket &socket) const
{
if (!sockets_can_connect(*this, socket)) {
return false;
}
return basic_types_can_connect(*this, socket);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #String
* \{ */
bNodeSocket &String::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const
bNodeSocket &String::build(bNodeTree &ntree, bNode &node) const
{
bNodeSocket &socket = *nodeAddStaticSocket(
&ntree, &node, in_out, SOCK_STRING, PROP_NONE, identifier_.c_str(), name_.c_str());
&ntree, &node, in_out_, SOCK_STRING, PROP_NONE, identifier_.c_str(), name_.c_str());
STRNCPY(((bNodeSocketValueString *)socket.default_value)->value, default_value_.c_str());
this->set_common_flags(socket);
return socket;
@ -271,18 +360,21 @@ bool String::matches(const bNodeSocket &socket) const
return true;
}
bool String::can_connect(const bNodeSocket &socket) const
{
return sockets_can_connect(*this, socket) && socket.type == SOCK_STRING;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #IDSocketDeclaration
* \{ */
bNodeSocket &IDSocketDeclaration::build(bNodeTree &ntree,
bNode &node,
eNodeSocketInOut in_out) const
bNodeSocket &IDSocketDeclaration::build(bNodeTree &ntree, bNode &node) const
{
bNodeSocket &socket = *nodeAddSocket(
&ntree, &node, in_out, idname_, identifier_.c_str(), name_.c_str());
&ntree, &node, in_out_, idname_, identifier_.c_str(), name_.c_str());
this->set_common_flags(socket);
return socket;
}
@ -298,12 +390,18 @@ bool IDSocketDeclaration::matches(const bNodeSocket &socket) const
return true;
}
bool IDSocketDeclaration::can_connect(const bNodeSocket &socket) const
{
return sockets_can_connect(*this, socket) && STREQ(socket.idname, idname_);
}
bNodeSocket &IDSocketDeclaration::update_or_build(bNodeTree &ntree,
bNode &node,
bNodeSocket &socket) const
{
if (StringRef(socket.idname) != idname_) {
return this->build(ntree, node, (eNodeSocketInOut)socket.in_out);
BLI_assert(socket.in_out == in_out_);
return this->build(ntree, node);
}
this->set_common_flags(socket);
return socket;
@ -315,10 +413,10 @@ bNodeSocket &IDSocketDeclaration::update_or_build(bNodeTree &ntree,
/** \name #Geometry
* \{ */
bNodeSocket &Geometry::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const
bNodeSocket &Geometry::build(bNodeTree &ntree, bNode &node) const
{
bNodeSocket &socket = *nodeAddSocket(
&ntree, &node, in_out, "NodeSocketGeometry", identifier_.c_str(), name_.c_str());
&ntree, &node, in_out_, "NodeSocketGeometry", identifier_.c_str(), name_.c_str());
this->set_common_flags(socket);
return socket;
}
@ -334,6 +432,11 @@ bool Geometry::matches(const bNodeSocket &socket) const
return true;
}
bool Geometry::can_connect(const bNodeSocket &socket) const
{
return sockets_can_connect(*this, socket) && socket.type == SOCK_GEOMETRY;
}
Span<GeometryComponentType> Geometry::supported_types() const
{
return supported_types_;
@ -380,10 +483,10 @@ GeometryBuilder &GeometryBuilder::only_instances(bool value)
/** \name #Shader
* \{ */
bNodeSocket &Shader::build(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out) const
bNodeSocket &Shader::build(bNodeTree &ntree, bNode &node) const
{
bNodeSocket &socket = *nodeAddSocket(
&ntree, &node, in_out, "NodeSocketShader", identifier_.c_str(), name_.c_str());
&ntree, &node, in_out_, "NodeSocketShader", identifier_.c_str(), name_.c_str());
this->set_common_flags(socket);
return socket;
}
@ -399,6 +502,18 @@ bool Shader::matches(const bNodeSocket &socket) const
return true;
}
bool Shader::can_connect(const bNodeSocket &socket) const
{
if (!sockets_can_connect(*this, socket)) {
return false;
}
/* Basic types can convert to shaders, but not the other way around. */
if (in_out_ == SOCK_IN) {
return ELEM(socket.type, SOCK_VECTOR, SOCK_RGBA, SOCK_FLOAT, SOCK_INT, SOCK_BOOLEAN);
}
return socket.type == SOCK_SHADER;
}
/** \} */
} // namespace blender::nodes::decl

View File

@ -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 &params,
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 &params) {
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 &params,
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 &params) {
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(&params.node_tree, &params.node, &params.socket, &node, new_node_socket);
}
});
}
}
void search_link_ops_for_basic_node(GatherLinkSearchOpParams &params)
{
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

View File

@ -170,12 +170,12 @@ static void update(bNodeTree *ntree)
}
}
static bool shader_validate_link(bNodeTree *UNUSED(ntree), bNodeLink *link)
static bool shader_validate_link(eNodeSocketDatatype from, eNodeSocketDatatype to)
{
/* Can't connect shader into other socket types, other way around is fine
* since it will be interpreted as emission. */
if (link->fromsock->type == SOCK_SHADER) {
return (link->tosock->type == SOCK_SHADER);
if (from == SOCK_SHADER) {
return to == SOCK_SHADER;
}
return true;
}

View File

@ -25,6 +25,8 @@
#include "node_shader_util.h"
#include "NOD_socket_search_link.hh"
#include "node_exec.h"
bool sh_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree, const char **r_disabled_hint)
@ -54,12 +56,14 @@ void sh_node_type_base(
ntype->poll = sh_node_poll_default;
ntype->insert_link = node_insert_link_default;
ntype->gather_link_search_ops = blender::nodes::search_link_ops_for_basic_node;
}
void sh_fn_node_type_base(bNodeType *ntype, int type, const char *name, short nclass, short flag)
{
sh_node_type_base(ntype, type, name, nclass, flag);
ntype->poll = sh_fn_poll_default;
ntype->gather_link_search_ops = blender::nodes::search_link_ops_for_basic_node;
}
/* ****** */

View File

@ -24,6 +24,7 @@
#include "node_shader_util.h"
#include "NOD_math_functions.hh"
#include "NOD_socket_search_link.hh"
/* **************** SCALAR MATH ******************** */
@ -44,6 +45,15 @@ static void sh_node_math_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Float>(N_("Value"));
};
static void sh_node_math_gather_link_searches(GatherLinkSearchOpParams &params)
{
/* For now, do something very basic (only exposing "Add", and a single "Value" socket). */
params.add_item(IFACE_("Value"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("ShaderNodeMath");
params.update_and_connect_available_socket(node, "Value");
});
}
} // namespace blender::nodes
static const char *gpu_shader_get_name(int mode)
@ -171,6 +181,7 @@ void register_node_type_sh_math()
node_type_gpu(&ntype, gpu_shader_math);
node_type_update(&ntype, node_math_update);
ntype.build_multi_function = sh_node_math_build_multi_function;
ntype.gather_link_search_ops = blender::nodes::sh_node_math_gather_link_searches;
nodeRegisterType(&ntype);
}

View File

@ -29,7 +29,10 @@ static void sh_node_tex_musgrave_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>(N_("Vector")).hide_value().implicit_field();
b.add_input<decl::Float>(N_("W")).min(-1000.0f).max(1000.0f);
b.add_input<decl::Float>(N_("W")).min(-1000.0f).max(1000.0f).make_available([](bNode &node) {
/* Default to 1 instead of 4, because it is much faster. */
node_storage(node).dimensions = 1;
});
b.add_input<decl::Float>(N_("Scale")).min(-1000.0f).max(1000.0f).default_value(5.0f);
b.add_input<decl::Float>(N_("Detail")).min(0.0f).max(15.0f).default_value(2.0f);
b.add_input<decl::Float>(N_("Dimension")).min(0.0f).max(1000.0f).default_value(2.0f);

View File

@ -29,7 +29,10 @@ static void sh_node_tex_noise_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>(N_("Vector")).implicit_field();
b.add_input<decl::Float>(N_("W")).min(-1000.0f).max(1000.0f);
b.add_input<decl::Float>(N_("W")).min(-1000.0f).max(1000.0f).make_available([](bNode &node) {
/* Default to 1 instead of 4, because it is much faster. */
node_storage(node).dimensions = 1;
});
b.add_input<decl::Float>(N_("Scale")).min(-1000.0f).max(1000.0f).default_value(5.0f);
b.add_input<decl::Float>(N_("Detail")).min(0.0f).max(15.0f).default_value(2.0f);
b.add_input<decl::Float>(N_("Roughness"))

View File

@ -29,14 +29,22 @@ static void sh_node_tex_voronoi_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>(N_("Vector")).hide_value().implicit_field();
b.add_input<decl::Float>(N_("W")).min(-1000.0f).max(1000.0f);
b.add_input<decl::Float>(N_("W")).min(-1000.0f).max(1000.0f).make_available([](bNode &node) {
/* Default to 1 instead of 4, because it is much faster. */
node_storage(node).dimensions = 1;
});
b.add_input<decl::Float>(N_("Scale")).min(-1000.0f).max(1000.0f).default_value(5.0f);
b.add_input<decl::Float>(N_("Smoothness"))
.min(0.0f)
.max(1.0f)
.default_value(1.0f)
.subtype(PROP_FACTOR);
b.add_input<decl::Float>(N_("Exponent")).min(0.0f).max(32.0f).default_value(0.5f);
.subtype(PROP_FACTOR)
.make_available([](bNode &node) { node_storage(node).feature = SHD_VORONOI_SMOOTH_F1; });
b.add_input<decl::Float>(N_("Exponent"))
.min(0.0f)
.max(32.0f)
.default_value(0.5f)
.make_available([](bNode &node) { node_storage(node).distance = SHD_VORONOI_MINKOWSKI; });
b.add_input<decl::Float>(N_("Randomness"))
.min(0.0f)
.max(1.0f)
@ -45,8 +53,13 @@ static void sh_node_tex_voronoi_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Float>(N_("Distance")).no_muted_links();
b.add_output<decl::Color>(N_("Color")).no_muted_links();
b.add_output<decl::Vector>(N_("Position")).no_muted_links();
b.add_output<decl::Float>(N_("W")).no_muted_links();
b.add_output<decl::Float>(N_("Radius")).no_muted_links();
b.add_output<decl::Float>(N_("W")).no_muted_links().make_available([](bNode &node) {
/* Default to 1 instead of 4, because it is much faster. */
node_storage(node).dimensions = 1;
});
b.add_output<decl::Float>(N_("Radius")).no_muted_links().make_available([](bNode &node) {
node_storage(node).feature = SHD_VORONOI_N_SPHERE_RADIUS;
});
};
} // namespace blender::nodes

View File

@ -27,7 +27,10 @@ static void sh_node_tex_white_noise_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>(N_("Vector")).min(-10000.0f).max(10000.0f).implicit_field();
b.add_input<decl::Float>(N_("W")).min(-10000.0f).max(10000.0f);
b.add_input<decl::Float>(N_("W")).min(-10000.0f).max(10000.0f).make_available([](bNode &node) {
/* Default to 1 instead of 4, because it is faster. */
node.custom1 = 1;
});
b.add_output<decl::Float>(N_("Value"));
b.add_output<decl::Color>(N_("Color"));
};

View File

@ -24,6 +24,7 @@
#include "node_shader_util.h"
#include "NOD_math_functions.hh"
#include "NOD_socket_search_link.hh"
namespace blender::nodes {
@ -38,6 +39,15 @@ static void sh_node_vector_math_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Float>(N_("Value"));
};
static void sh_node_vector_math_gather_link_searches(GatherLinkSearchOpParams &params)
{
/* For now, do something very basic (only exposing "Add", and a single "Vector" socket). */
params.add_item(IFACE_("Vector"), [](LinkSearchOpParams &params) {
bNode &node = params.add_node("ShaderNodeVectorMath");
params.update_and_connect_available_socket(node, "Vector");
});
}
} // namespace blender::nodes
static const char *gpu_shader_get_name(int mode)
@ -289,6 +299,7 @@ void register_node_type_sh_vect_math()
node_type_gpu(&ntype, gpu_shader_vector_math);
node_type_update(&ntype, node_shader_update_vector_math);
ntype.build_multi_function = sh_node_vector_math_build_multi_function;
ntype.gather_link_search_ops = blender::nodes::sh_node_vector_math_gather_link_searches;
nodeRegisterType(&ntype);
}