diff options
47 files changed, 1691 insertions, 113 deletions
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 2038842ee59..a2959556810 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -34,6 +34,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 ¶ms); + #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) \ { \ diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index a4de6730f8f..be458ed036e 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -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; } } diff --git a/source/blender/editors/space_node/CMakeLists.txt b/source/blender/editors/space_node/CMakeLists.txt index e88d61fe880..94b67e43651 100644 --- a/source/blender/editors/space_node/CMakeLists.txt +++ b/source/blender/editors/space_node/CMakeLists.txt @@ -39,6 +39,7 @@ set(INC set(SRC drawnode.cc + link_drag_search.cc node_add.cc node_context_path.cc node_draw.cc diff --git a/source/blender/editors/space_node/link_drag_search.cc b/source/blender/editors/space_node/link_drag_search.cc new file mode 100644 index 00000000000..e1ba36e81c0 --- /dev/null +++ b/source/blender/editors/space_node/link_drag_search.cc @@ -0,0 +1,291 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_listbase.h" +#include "BLI_string_search.h" + +#include "DNA_space_types.h" + +#include "BKE_context.h" + +#include "NOD_socket_search_link.hh" + +#include "BLT_translation.h" + +#include "RNA_access.h" + +#include "WM_api.h" + +#include "node_intern.hh" + +using blender::nodes::SocketLinkOperation; + +namespace blender::ed::space_node { + +struct LinkDragSearchStorage { + bNode &from_node; + bNodeSocket &from_socket; + float2 cursor; + Vector<SocketLinkOperation> search_link_ops; + char search[256]; + + eNodeSocketInOut in_out() const + { + return static_cast<eNodeSocketInOut>(from_socket.in_out); + } +}; + +static void add_reroute_node_fn(nodes::LinkSearchOpParams ¶ms) +{ + bNode &reroute = params.add_node("NodeReroute"); + if (params.socket.in_out == SOCK_IN) { + nodeAddLink(¶ms.node_tree, + &reroute, + static_cast<bNodeSocket *>(reroute.outputs.first), + ¶ms.node, + ¶ms.socket); + } + else { + nodeAddLink(¶ms.node_tree, + ¶ms.node, + ¶ms.socket, + &reroute, + static_cast<bNodeSocket *>(reroute.inputs.first)); + } +} + +static void add_group_input_node_fn(nodes::LinkSearchOpParams ¶ms) +{ + /* Add a group input based on the connected socket, and add a new group input node. */ + bNodeSocket *interface_socket = ntreeAddSocketInterfaceFromSocket( + ¶ms.node_tree, ¶ms.node, ¶ms.socket); + const int group_input_index = BLI_findindex(¶ms.node_tree.inputs, interface_socket); + + bNode &group_input = params.add_node("NodeGroupInput"); + + /* This is necessary to create the new sockets in the other input nodes. */ + ntreeUpdateTree(CTX_data_main(¶ms.C), ¶ms.node_tree); + + /* Hide the new input in all other group input nodes, to avoid making them taller. */ + LISTBASE_FOREACH (bNode *, node, ¶ms.node_tree.nodes) { + if (node->type == NODE_GROUP_INPUT) { + bNodeSocket *new_group_input_socket = (bNodeSocket *)BLI_findlink(&node->outputs, + group_input_index); + new_group_input_socket->flag |= SOCK_HIDDEN; + } + } + + /* Hide all existing inputs in the new group input node, to only display the new one. */ + LISTBASE_FOREACH (bNodeSocket *, socket, &group_input.outputs) { + socket->flag |= SOCK_HIDDEN; + } + + bNodeSocket *socket = (bNodeSocket *)BLI_findlink(&group_input.outputs, group_input_index); + if (socket == nullptr) { + /* Adding sockets can fail in some cases. There's no good reason not to be safe here. */ + return; + } + /* Unhide the socket for the new input in the new node and make a connection to it. */ + socket->flag &= ~SOCK_HIDDEN; + nodeAddLink(¶ms.node_tree, &group_input, socket, ¶ms.node, ¶ms.socket); +} + +/** + * Call the callback to gather compatible socket connections for all node types, and the operations + * that will actually make the connections. Also add some custom operations like connecting a group + * output node. + */ +static void gather_socket_link_operations(bNodeTree &node_tree, + const bNodeSocket &socket, + Vector<SocketLinkOperation> &search_link_ops) +{ + NODE_TYPES_BEGIN (node_type) { + if (StringRef(node_type->idname).find("Legacy") != StringRef::not_found) { + continue; + } + const char *disabled_hint; + if (!(node_type->poll && node_type->poll(node_type, &node_tree, &disabled_hint))) { + continue; + } + + if (node_type->gather_link_search_ops) { + nodes::GatherLinkSearchOpParams params{*node_type, node_tree, socket, search_link_ops}; + node_type->gather_link_search_ops(params); + } + } + NODE_TYPES_END; + + search_link_ops.append({IFACE_("Reroute"), add_reroute_node_fn}); + + const bool is_node_group = !(node_tree.id.flag & LIB_EMBEDDED_DATA); + + if (is_node_group && socket.in_out == SOCK_IN) { + search_link_ops.append({IFACE_("Group Input"), add_group_input_node_fn}); + } +} + +static void link_drag_search_update_fn(const bContext *UNUSED(C), + void *arg, + const char *str, + uiSearchItems *items, + const bool is_first) +{ + LinkDragSearchStorage &storage = *static_cast<LinkDragSearchStorage *>(arg); + + StringSearch *search = BLI_string_search_new(); + + for (SocketLinkOperation &op : storage.search_link_ops) { + BLI_string_search_add(search, op.name.c_str(), &op, op.weight); + } + + /* Don't filter when the menu is first opened, but still run the search + * so the items are in the same order they will appear in while searching. */ + const char *string = is_first ? "" : str; + SocketLinkOperation **filtered_items; + const int filtered_amount = BLI_string_search_query(search, string, (void ***)&filtered_items); + + for (const int i : IndexRange(filtered_amount)) { + SocketLinkOperation &item = *filtered_items[i]; + if (!UI_search_item_add(items, item.name.c_str(), &item, ICON_NONE, 0, 0)) { + break; + } + } + + MEM_freeN(filtered_items); + BLI_string_search_free(search); +} + +static void link_drag_search_exec_fn(bContext *C, void *arg1, void *arg2) +{ + Main &bmain = *CTX_data_main(C); + SpaceNode &snode = *CTX_wm_space_node(C); + LinkDragSearchStorage &storage = *static_cast<LinkDragSearchStorage *>(arg1); + SocketLinkOperation *item = static_cast<SocketLinkOperation *>(arg2); + if (item == nullptr) { + return; + } + + node_deselect_all(snode); + + Vector<bNode *> new_nodes; + nodes::LinkSearchOpParams params{ + *C, *snode.edittree, storage.from_node, storage.from_socket, new_nodes}; + item->fn(params); + if (new_nodes.is_empty()) { + return; + } + + /* For now, assume that only one node is created by the callback. */ + BLI_assert(new_nodes.size() == 1); + bNode *new_node = new_nodes.first(); + + new_node->locx = storage.cursor.x / UI_DPI_FAC; + new_node->locy = storage.cursor.y / UI_DPI_FAC + 20 * UI_DPI_FAC; + if (storage.in_out() == SOCK_IN) { + new_node->locx -= new_node->width; + } + + nodeSetSelected(new_node, true); + nodeSetActive(snode.edittree, new_node); + + /* Ideally it would be possible to tag the node tree in some way so it updates only after the + * translate operation is finished, but normally moving nodes around doesn't cause updates. */ + ntreeUpdateTree(&bmain, snode.edittree); + snode_notify(*C, snode); + snode_dag_update(*C, snode); + + /* Start translation operator with the new node. */ + wmOperatorType *ot = WM_operatortype_find("TRANSFORM_OT_translate", true); + BLI_assert(ot); + PointerRNA ptr; + WM_operator_properties_create_ptr(&ptr, ot); + RNA_boolean_set(&ptr, "view2d_edge_pan", true); + WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr); + WM_operator_properties_free(&ptr); +} + +static void link_drag_search_free_fn(void *arg) +{ + LinkDragSearchStorage *storage = static_cast<LinkDragSearchStorage *>(arg); + delete storage; +} + +static uiBlock *create_search_popup_block(bContext *C, ARegion *region, void *arg_op) +{ + LinkDragSearchStorage &storage = *(LinkDragSearchStorage *)arg_op; + + bNodeTree *node_tree = CTX_wm_space_node(C)->nodetree; + gather_socket_link_operations(*node_tree, storage.from_socket, storage.search_link_ops); + + uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS); + UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_SEARCH_MENU); + UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + + uiBut *but = uiDefSearchBut(block, + storage.search, + 0, + ICON_VIEWZOOM, + sizeof(storage.search), + storage.in_out() == SOCK_OUT ? 10 : 10 - UI_searchbox_size_x(), + 10, + UI_searchbox_size_x(), + UI_UNIT_Y, + 0, + 0, + ""); + UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP); + UI_but_func_search_set(but, + nullptr, + link_drag_search_update_fn, + &storage, + false, + link_drag_search_free_fn, + link_drag_search_exec_fn, + nullptr); + UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT); + + /* Fake button to hold space for the search items. */ + uiDefBut(block, + UI_BTYPE_LABEL, + 0, + "", + storage.in_out() == SOCK_OUT ? 10 : 10 - UI_searchbox_size_x(), + 10 - UI_searchbox_size_y(), + UI_searchbox_size_x(), + UI_searchbox_size_y(), + nullptr, + 0, + 0, + 0, + 0, + nullptr); + + const int offset[2] = {0, -UI_UNIT_Y}; + UI_block_bounds_set_popup(block, 0.3f * U.widget_unit, offset); + return block; +} + +void invoke_node_link_drag_add_menu(bContext &C, + bNode &node, + bNodeSocket &socket, + const float2 &cursor) +{ + LinkDragSearchStorage *storage = new LinkDragSearchStorage{node, socket, cursor}; + /* Use the "_ex" variant with `can_refresh` false to avoid a double free when closing Blender. */ + UI_popup_block_invoke_ex(&C, create_search_popup_block, storage, nullptr, false); +} + +} // namespace blender::ed::space_node
\ No newline at end of file diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh index f3ba405c6d0..2e55bb0cb28 100644 --- a/source/blender/editors/space_node/node_intern.hh +++ b/source/blender/editors/space_node/node_intern.hh @@ -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. + */ + 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. */ - struct 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 diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index ce07496068d..90b53258d5e 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -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 ®ion, 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 ®ion, 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 ®ion = *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(®ion); + 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(®ion->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(); diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index e06a3c98016..5f61d13a3af 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -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 diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index a0bb47daef2..a0b8e237f19 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -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); diff --git a/source/blender/nodes/NOD_node_declaration.hh b/source/blender/nodes/NOD_node_declaration.hh index cccaf93a7d7..af2130ec452 100644 --- a/source/blender/nodes/NOD_node_declaration.hh +++ b/source/blender/nodes/NOD_node_declaration.hh @@ -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 diff --git a/source/blender/nodes/NOD_socket_declarations.hh b/source/blender/nodes/NOD_socket_declarations.hh index 1b78a09d6a8..3fb21a4263d 100644 --- a/source/blender/nodes/NOD_socket_declarations.hh +++ b/source/blender/nodes/NOD_socket_declarations.hh @@ -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> { diff --git a/source/blender/nodes/NOD_socket_declarations_geometry.hh b/source/blender/nodes/NOD_socket_declarations_geometry.hh index 3c919729da9..0ce07da22ff 100644 --- a/source/blender/nodes/NOD_socket_declarations_geometry.hh +++ b/source/blender/nodes/NOD_socket_declarations_geometry.hh @@ -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; diff --git a/source/blender/nodes/NOD_socket_search_link.hh b/source/blender/nodes/NOD_socket_search_link.hh new file mode 100644 index 00000000000..b7594561dc4 --- /dev/null +++ b/source/blender/nodes/NOD_socket_search_link.hh @@ -0,0 +1,151 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <functional> + +#include "BLI_string_ref.hh" +#include "BLI_vector.hh" + +#include "DNA_node_types.h" /* Necessary for eNodeSocketInOut. */ + +#include "NOD_node_declaration.hh" + +struct bContext; + +namespace blender::nodes { + +/** + * Parameters for the operation operation of adding a node after the link drag search menu closes. + */ +class LinkSearchOpParams { + private: + /** + * Keep track of the nodes added by the callback, so they can be selected or moved afterwards. + */ + Vector<bNode *> &added_nodes_; + + public: + const bContext &C; + bNodeTree &node_tree; + /** + * The node that contains the #socket. + */ + bNode &node; + /** + * The existing socket to connect any added nodes to. Might be an input or output socket. + */ + bNodeSocket &socket; + + LinkSearchOpParams(const bContext &C, + bNodeTree &node_tree, + bNode &node, + bNodeSocket &socket, + Vector<bNode *> &added_nodes) + : added_nodes_(added_nodes), C(C), node_tree(node_tree), node(node), socket(socket) + { + } + + bNode &add_node(StringRef idname); + bNode &add_node(const bNodeType &type); + /** + * Find a socket with the given name (correctly checks for inputs and outputs) + * and connect it to the socket the link drag started from (#socket). + */ + void connect_available_socket(bNode &new_node, StringRef socket_name); + /** + * Like #connect_available_socket, but also calls the node's update function. + */ + void update_and_connect_available_socket(bNode &new_node, StringRef socket_name); +}; + +struct SocketLinkOperation { + using LinkSocketFn = std::function<void(LinkSearchOpParams &link_params)>; + + std::string name; + LinkSocketFn fn; + int weight = 0; +}; + +class GatherLinkSearchOpParams { + /** The current node type. */ + const bNodeType &node_type_; + + const bNodeTree &node_tree_; + + const bNodeSocket &other_socket_; + + /* The operations currently being built. Owned by the caller. */ + Vector<SocketLinkOperation> &items_; + + public: + GatherLinkSearchOpParams(const bNodeType &node_type, + const bNodeTree &node_tree, + const bNodeSocket &other_socket, + Vector<SocketLinkOperation> &items) + : node_type_(node_type), node_tree_(node_tree), other_socket_(other_socket), items_(items) + { + } + + /** + * The node on the other side of the dragged link. + */ + const bNodeSocket &other_socket() const; + + /** + * The node tree the user is editing when the search menu is created. + */ + const bNodeTree &node_tree() const; + + /** + * The type of the node in the current callback. + */ + const bNodeType &node_type() const; + + /** + * Whether to list the input or output sockets of the node. + */ + eNodeSocketInOut in_out() const; + + /** + * \param weight: Used to customize the order when multiple search items match. + * + * \warning When creating lambdas for the #fn argument, be careful not to capture this class + * itself, since it is temporary. That is why we tend to use the same variable name for this + * class (`params`) that we do for the argument to `LinkSocketFn`. + */ + void add_item(std::string socket_name, SocketLinkOperation::LinkSocketFn fn, int weight = 0); +}; + +/** + * This callback can be used for a node type when a few things are true about its inputs. + * To avoid creating more boilerplate, it is the default callback for node types. + * - Either all declared sockets are visible in the default state of the node, *OR* the node's + * type's declaration has been extended with #make_available functions for those sockets. + * + * If a node type does not meet these criteria, the function will do nothing in a release build. + * In a debug build, an assert will most likely be hit. + * + * \note For nodes with the deprecated #bNodeSocketTemplate instead of a declaration, + * these criteria do not apply and the function just tries its best without asserting. + */ +void search_link_ops_for_basic_node(GatherLinkSearchOpParams ¶ms); + +void search_link_ops_for_declarations(GatherLinkSearchOpParams ¶ms, + Span<SocketDeclarationPtr> declarations); + +} // namespace blender::nodes diff --git a/source/blender/nodes/composite/node_composite_util.cc b/source/blender/nodes/composite/node_composite_util.cc index 9ea98d2a867..1262dfad11f 100644 --- a/source/blender/nodes/composite/node_composite_util.cc +++ b/source/blender/nodes/composite/node_composite_util.cc @@ -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; } diff --git a/source/blender/nodes/function/node_function_util.cc b/source/blender/nodes/function/node_function_util.cc index ac48322c269..83f5b571695 100644 --- a/source/blender/nodes/function/node_function_util.cc +++ b/source/blender/nodes/function/node_function_util.cc @@ -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; } diff --git a/source/blender/nodes/function/nodes/node_fn_random_value.cc b/source/blender/nodes/function/nodes/node_fn_random_value.cc index e2efae68001..b053482c99d 100644 --- a/source/blender/nodes/function/nodes/node_fn_random_value.cc +++ b/source/blender/nodes/function/nodes/node_fn_random_value.cc @@ -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 ¶ms) +{ + 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 ¶ms) { + 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 ¶ms) { + 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 ¶ms) { + 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); diff --git a/source/blender/nodes/geometry/node_geometry_tree.cc b/source/blender/nodes/geometry/node_geometry_tree.cc index 3f4089807bc..89ab4d9961e 100644 --- a/source/blender/nodes/geometry/node_geometry_tree.cc +++ b/source/blender/nodes/geometry/node_geometry_tree.cc @@ -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), diff --git a/source/blender/nodes/geometry/node_geometry_util.cc b/source/blender/nodes/geometry/node_geometry_util.cc index ebe678c6de8..49991a40c1b 100644 --- a/source/blender/nodes/geometry/node_geometry_util.cc +++ b/source/blender/nodes/geometry/node_geometry_util.cc @@ -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; } diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index cf731427841..3376b75d05b 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -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 diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc index cf20ddacca7..22a9e4a0c33 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc @@ -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 ¶ms) +{ + const bNodeType &node_type = params.node_type(); + if (params.other_socket().type == SOCK_GEOMETRY) { + params.add_item(IFACE_("Geometry"), [node_type](LinkSearchOpParams ¶ms) { + 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 ¶ms) { + 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 ¶ms) { + 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); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc index 082ea87853e..d6662e4e637 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc @@ -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) diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc index c754f70d323..b79125d43d1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc @@ -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 ¶ms) +{ + 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 ¶ms) { + bNode &node = params.add_node(node_type); + params.connect_available_socket(node, "Geometry"); + }); + } + if (type) { + params.add_item(IFACE_("Attribute"), [&](LinkSearchOpParams ¶ms) { + 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 ¶ms) { + 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); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc index 7fbcc47c708..a438c1d6086 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc @@ -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) diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc index b08fcd86059..70abf4c64a7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_circle.cc @@ -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) diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc index 41f92c7d0c8..07bd7cec766 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc @@ -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 ¶ms) + { + 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 ¶ms) +{ + if (params.in_out() == SOCK_OUT) { + if (params.other_socket().type == SOCK_GEOMETRY) { + params.add_item(IFACE_("Curve"), [](LinkSearchOpParams ¶ms) { + 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); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc index 57e08a91211..eff760266f5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc @@ -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(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc index 2f9dfa8158b..0e9425246cb 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc @@ -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); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc index 449d0d14092..c6908cb8ed0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc @@ -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 ¶ms) +{ + class SocketSearchOp { + public: + StringRef socket_name; + GeometryNodeCurveSampleMode mode; + void operator()(LinkSearchOpParams ¶ms) + { + 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); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_line.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_line.cc index 05830098cc2..389dc278197 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_line.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_line.cc @@ -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 ¶ms) +{ + 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 ¶ms) { + 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 ¶ms) { + 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 ¶ms) { + bNode &node = params.add_node("GeometryNodeMeshLine"); + params.connect_available_socket(node, "Start Location"); + }); + params.add_item(IFACE_("Offset"), [](LinkSearchOpParams ¶ms) { + 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 ¶ms) { + 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); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc index c356e1cf22b..744cce6d445 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc @@ -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) diff --git a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc index c33654b3f20..8968675c1ed 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc @@ -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 ¶ms) +{ + 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 ¶ms) { + 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); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc index 912552c7ce9..e30b11907e8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc @@ -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) diff --git a/source/blender/nodes/geometry/nodes/node_geo_switch.cc b/source/blender/nodes/geometry/nodes/node_geo_switch.cc index 146407e0c33..d22522fe087 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_switch.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_switch.cc @@ -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 ¶ms) +{ + if (params.in_out() == SOCK_OUT) { + params.add_item(IFACE_("Output"), [](LinkSearchOpParams ¶ms) { + 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 ¶ms) { + bNode &node = params.add_node("GeometryNodeSwitch"); + params.connect_available_socket(node, "Start"); + }); + } + params.add_item(IFACE_("False"), [](LinkSearchOpParams ¶ms) { + 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 ¶ms) { + 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); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc index cfa6b752993..007373929f4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc @@ -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 ¶ms) +{ + if (params.other_socket().type == SOCK_GEOMETRY) { + params.add_item(IFACE_("Target"), [](LinkSearchOpParams ¶ms) { + 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 ¶ms) { + 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); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_viewer.cc b/source/blender/nodes/geometry/nodes/node_geo_viewer.cc index ad9737ac24b..18f2fa4cd36 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_viewer.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_viewer.cc @@ -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 ¶ms) +{ + auto set_active_fn = [](LinkSearchOpParams ¶ms, bNode &viewer_node) { + /* Set this new viewer node active in spreadsheet editors. */ + SpaceNode *snode = CTX_wm_space_node(¶ms.C); + Main *bmain = CTX_data_main(¶ms.C); + ED_node_set_active(bmain, snode, ¶ms.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 ¶ms) { + 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 ¶ms) { + 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, ¶ms.node.outputs) { + if (socket->type == SOCK_GEOMETRY && !(socket->flag & (SOCK_UNAVAIL | SOCK_HIDDEN))) { + nodeAddLink(¶ms.node_tree, + ¶ms.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); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc index 04a1a8e04b8..0819b401941 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc @@ -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")); diff --git a/source/blender/nodes/intern/node_declaration.cc b/source/blender/nodes/intern/node_declaration.cc index 7a19acd2510..75d47cfd386 100644 --- a/source/blender/nodes/intern/node_declaration.cc +++ b/source/blender/nodes/intern/node_declaration.cc @@ -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 diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc index dbc4f489a88..d83c05b38a1 100644 --- a/source/blender/nodes/intern/node_socket.cc +++ b/source/blender/nodes/intern/node_socket.cc @@ -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) diff --git a/source/blender/nodes/intern/node_socket_declarations.cc b/source/blender/nodes/intern/node_socket_declarations.cc index 1795cc339e7..4fef5b96e9f 100644 --- a/source/blender/nodes/intern/node_socket_declarations.cc +++ b/source/blender/nodes/intern/node_socket_declarations.cc @@ -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 diff --git a/source/blender/nodes/intern/socket_search_link.cc b/source/blender/nodes/intern/socket_search_link.cc new file mode 100644 index 00000000000..48f8106e58b --- /dev/null +++ b/source/blender/nodes/intern/socket_search_link.cc @@ -0,0 +1,199 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_set.hh" + +#include "BKE_node.h" + +#include "UI_interface.h" + +#include "NOD_node_declaration.hh" +#include "NOD_socket_search_link.hh" + +namespace blender::nodes { + +void GatherLinkSearchOpParams::add_item(std::string socket_name, + SocketLinkOperation::LinkSocketFn fn, + const int weight) +{ + + std::string name = std::string(node_type_.ui_name) + " " + UI_MENU_ARROW_SEP + socket_name; + + items_.append({std::move(name), std::move(fn), weight}); +} + +const bNodeSocket &GatherLinkSearchOpParams::other_socket() const +{ + return other_socket_; +} + +const bNodeTree &GatherLinkSearchOpParams::node_tree() const +{ + return node_tree_; +} + +const bNodeType &GatherLinkSearchOpParams::node_type() const +{ + return node_type_; +} + +eNodeSocketInOut GatherLinkSearchOpParams::in_out() const +{ + return other_socket_.in_out == SOCK_IN ? SOCK_OUT : SOCK_IN; +} + +void LinkSearchOpParams::connect_available_socket(bNode &new_node, StringRef socket_name) +{ + const eNodeSocketInOut in_out = socket.in_out == SOCK_IN ? SOCK_OUT : SOCK_IN; + bNodeSocket *new_node_socket = bke::node_find_enabled_socket(new_node, in_out, socket_name); + if (new_node_socket == nullptr) { + /* If the socket isn't found, some node's search gather functions probably aren't configured + * properly. It's likely enough that it's worth avoiding a crash in a release build though. */ + BLI_assert_unreachable(); + return; + } + nodeAddLink(&node_tree, &new_node, new_node_socket, &node, &socket); +} + +bNode &LinkSearchOpParams::add_node(StringRef idname) +{ + std::string idname_str = idname; + bNode *node = nodeAddNode(&C, &node_tree, idname_str.c_str()); + BLI_assert(node != nullptr); + added_nodes_.append(node); + return *node; +} + +bNode &LinkSearchOpParams::add_node(const bNodeType &node_type) +{ + return this->add_node(node_type.idname); +} + +void LinkSearchOpParams::update_and_connect_available_socket(bNode &new_node, + StringRef socket_name) +{ + if (new_node.typeinfo->updatefunc) { + new_node.typeinfo->updatefunc(&node_tree, &new_node); + } + this->connect_available_socket(new_node, socket_name); +} + +void search_link_ops_for_declarations(GatherLinkSearchOpParams ¶ms, + Span<SocketDeclarationPtr> declarations) +{ + const bNodeType &node_type = params.node_type(); + + const SocketDeclaration *main_socket = nullptr; + Vector<const SocketDeclaration *> connectable_sockets; + + Set<StringRef> socket_names; + for (const int i : declarations.index_range()) { + const SocketDeclaration &socket = *declarations[i]; + if (!socket_names.add(socket.name())) { + /* Don't add sockets with the same name to the search. Needed to support being called from + * #search_link_ops_for_basic_node, which should have "okay" behavior for nodes with + * duplicate socket names. */ + continue; + } + if (!socket.can_connect(params.other_socket())) { + continue; + } + if (socket.is_default_link_socket() || main_socket == nullptr) { + /* Either the first connectable or explicitly tagged socket is the main socket. */ + main_socket = &socket; + } + connectable_sockets.append(&socket); + } + for (const int i : connectable_sockets.index_range()) { + const SocketDeclaration &socket = *connectable_sockets[i]; + /* Give non-main sockets a lower weight so that they don't show up at the top of the search + * when they are not explicitly searched for. The -1 is used to make sure that the first socket + * has a smaller weight than zero so that it does not have the same weight as the main socket. + * Negative weights are used to avoid making the heighest weight dependent on the number of + * sockets. */ + const int weight = (&socket == main_socket) ? 0 : -1 - i; + params.add_item( + socket.name(), + [&node_type, &socket](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node(node_type); + socket.make_available(node); + params.update_and_connect_available_socket(node, socket.name()); + }, + weight); + } +} + +static void search_link_ops_for_socket_templates(GatherLinkSearchOpParams ¶ms, + const bNodeSocketTemplate *templates, + const eNodeSocketInOut in_out) +{ + const bNodeType &node_type = params.node_type(); + const bNodeTreeType &node_tree_type = *params.node_tree().typeinfo; + + Set<StringRef> socket_names; + for (const bNodeSocketTemplate *socket_template = templates; socket_template->type != -1; + socket_template++) { + eNodeSocketDatatype from = (eNodeSocketDatatype)socket_template->type; + eNodeSocketDatatype to = (eNodeSocketDatatype)params.other_socket().type; + if (in_out == SOCK_IN) { + std::swap(from, to); + } + if (node_tree_type.validate_link && !node_tree_type.validate_link(from, to)) { + continue; + } + if (!socket_names.add(socket_template->name)) { + /* See comment in #search_link_ops_for_declarations. */ + continue; + } + + params.add_item( + socket_template->name, [socket_template, node_type, in_out](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node(node_type); + bNodeSocket *new_node_socket = bke::node_find_enabled_socket( + node, in_out, socket_template->name); + if (new_node_socket != nullptr) { + /* Rely on the way #nodeAddLink switches in/out if necessary. */ + nodeAddLink(¶ms.node_tree, ¶ms.node, ¶ms.socket, &node, new_node_socket); + } + }); + } +} + +void search_link_ops_for_basic_node(GatherLinkSearchOpParams ¶ms) +{ + const bNodeType &node_type = params.node_type(); + + if (node_type.declare) { + if (node_type.declaration_is_dynamic) { + /* Dynamic declarations (whatever they end up being) aren't supported + * by this function, but still avoid a crash in release builds. */ + BLI_assert_unreachable(); + return; + } + + const NodeDeclaration &declaration = *node_type.fixed_declaration; + + search_link_ops_for_declarations(params, declaration.sockets(params.in_out())); + } + else if (node_type.inputs && params.in_out() == SOCK_IN) { + search_link_ops_for_socket_templates(params, node_type.inputs, SOCK_IN); + } + else if (node_type.outputs && params.in_out() == SOCK_OUT) { + search_link_ops_for_socket_templates(params, node_type.outputs, SOCK_OUT); + } +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/shader/node_shader_tree.c b/source/blender/nodes/shader/node_shader_tree.c index 40023ca80d8..c3b5236373c 100644 --- a/source/blender/nodes/shader/node_shader_tree.c +++ b/source/blender/nodes/shader/node_shader_tree.c @@ -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; } diff --git a/source/blender/nodes/shader/node_shader_util.cc b/source/blender/nodes/shader/node_shader_util.cc index 8b7e72fbcc9..f2464d4c1b4 100644 --- a/source/blender/nodes/shader/node_shader_util.cc +++ b/source/blender/nodes/shader/node_shader_util.cc @@ -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; } /* ****** */ diff --git a/source/blender/nodes/shader/nodes/node_shader_math.cc b/source/blender/nodes/shader/nodes/node_shader_math.cc index a10124460ff..51c94b10c8b 100644 --- a/source/blender/nodes/shader/nodes/node_shader_math.cc +++ b/source/blender/nodes/shader/nodes/node_shader_math.cc @@ -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 ¶ms) +{ + /* For now, do something very basic (only exposing "Add", and a single "Value" socket). */ + params.add_item(IFACE_("Value"), [](LinkSearchOpParams ¶ms) { + 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); } diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc b/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc index 9504b0dc9f0..97ac199bc03 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc +++ b/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc @@ -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); diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc b/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc index e3764120663..d7f21853a01 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc +++ b/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc @@ -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")) diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc b/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc index 6968a7483b6..8a1170de304 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc +++ b/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc @@ -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 diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc b/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc index f2dfd2e6968..0e72cee38e7 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc +++ b/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc @@ -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")); }; diff --git a/source/blender/nodes/shader/nodes/node_shader_vector_math.cc b/source/blender/nodes/shader/nodes/node_shader_vector_math.cc index 7f87332ba1b..70a0d47e1b9 100644 --- a/source/blender/nodes/shader/nodes/node_shader_vector_math.cc +++ b/source/blender/nodes/shader/nodes/node_shader_vector_math.cc @@ -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 ¶ms) +{ + /* For now, do something very basic (only exposing "Add", and a single "Vector" socket). */ + params.add_item(IFACE_("Vector"), [](LinkSearchOpParams ¶ms) { + 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); } |