Blender V4.3
node_relationships.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2005 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include "MEM_guardedalloc.h"
10
11#include "DNA_node_types.h"
12
13#include "BLI_easing.h"
14#include "BLI_math_geom.h"
15#include "BLI_stack.hh"
16
17#include "BKE_context.hh"
18#include "BKE_node.hh"
19#include "BKE_node_runtime.hh"
21
22#include "ED_node.hh" /* own include */
23#include "ED_render.hh"
24#include "ED_screen.hh"
25#include "ED_space_api.hh"
26#include "ED_viewer_path.hh"
27
28#include "RNA_access.hh"
29#include "RNA_define.hh"
30#include "RNA_prototypes.hh"
31
32#include "WM_api.hh"
33#include "WM_types.hh"
34
35#include "UI_interface_icons.hh"
36#include "UI_resources.hh"
37#include "UI_view2d.hh"
38
39#include "BLT_translation.hh"
40
42#include "NOD_socket.hh"
44
45#include "node_intern.hh" /* own include */
46
49 bNode *insert; /* Inserted node. */
50 bNode *prev, *next; /* Previous/next node in the chain. */
52
54
55 float offset_x; /* Offset to apply to node chain. */
56};
57
59
61{
62 LISTBASE_FOREACH (bNodeLink *, link, links) {
63 link->flag &= ~NODE_LINK_TEMP_HIGHLIGHT;
64 }
65}
66
67/* -------------------------------------------------------------------- */
72{
73 bNodeLink oplink{};
74 if (socket.in_out == SOCK_OUT) {
75 oplink.fromnode = &node;
76 oplink.fromsock = &socket;
77 }
78 else {
79 oplink.tonode = &node;
80 oplink.tosock = &socket;
81 }
82 oplink.flag |= NODE_LINK_VALID;
83 return oplink;
84}
85
86static void pick_link(bNodeLinkDrag &nldrag,
87 SpaceNode &snode,
88 bNode *node,
89 bNodeLink &link_to_pick)
90{
92
93 bNodeLink link = create_drag_link(*link_to_pick.fromnode, *link_to_pick.fromsock);
94
95 nldrag.links.append(link);
96 bke::node_remove_link(snode.edittree, &link_to_pick);
97 snode.edittree->ensure_topology_cache();
100
101 /* Send changed event to original link->tonode. */
102 if (node) {
104 }
105}
106
108 wmOperator &op,
109 bNodeLinkDrag &nldrag,
110 const float2 &cursor)
111{
112 SpaceNode *snode = CTX_wm_space_node(&C);
113 ARegion *region = CTX_wm_region(&C);
114 bNodeTree &node_tree = *snode->edittree;
115
116 float2 drag_start;
117 RNA_float_get_array(op.ptr, "drag_start", drag_start);
118 bNodeSocket *socket = node_find_indicated_socket(*snode, *region, drag_start, SOCK_IN);
119 bNode &node = socket->owner_node();
120
121 /* Distance to test overlapping of cursor on link. */
122 const float cursor_link_touch_distance = 12.5f * UI_SCALE_FAC;
123
124 bNodeLink *link_to_pick = nullptr;
126 for (bNodeLink *link : socket->directly_linked_links()) {
127 /* Test if the cursor is near a link. */
128 std::array<float2, NODE_LINK_RESOL + 1> coords;
130
131 for (const int i : IndexRange(coords.size() - 1)) {
132 const float distance = dist_squared_to_line_segment_v2(cursor, coords[i], coords[i + 1]);
133 if (distance < cursor_link_touch_distance) {
134 link_to_pick = link;
135 nldrag.last_picked_multi_input_socket_link = link_to_pick;
136 }
137 }
138 }
139
140 /* If no linked was picked in this call, try using the one picked in the previous call.
141 * Not essential for the basic behavior, but can make interaction feel a bit better if
142 * the mouse moves to the right and loses the "selection." */
143 if (!link_to_pick) {
144 bNodeLink *last_picked_link = nldrag.last_picked_multi_input_socket_link;
145 if (last_picked_link) {
146 link_to_pick = last_picked_link;
147 }
148 }
149
150 if (link_to_pick) {
151 /* Highlight is set here and cleared in the next iteration or if the operation finishes. */
152 link_to_pick->flag |= NODE_LINK_TEMP_HIGHLIGHT;
154
155 if (!node_find_indicated_socket(*snode, *region, cursor, SOCK_IN)) {
156 pick_link(nldrag, *snode, &node, *link_to_pick);
157 }
158 }
159}
160
161static bool socket_is_available(bNodeTree * /*ntree*/, bNodeSocket *sock, const bool allow_used)
162{
163 if (!sock->is_visible()) {
164 return false;
165 }
166
167 if (!allow_used && (sock->flag & SOCK_IS_LINKED)) {
168 /* Multi input sockets are available (even if used). */
169 if (!(sock->flag & SOCK_MULTI_INPUT)) {
170 return false;
171 }
172 }
173
174 return true;
175}
176
178 bNode *node,
179 bNodeSocket *sock_target,
180 const bool allow_multiple)
181{
182 /* First look for selected output. */
183 LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) {
184 if (!socket_is_available(ntree, sock, allow_multiple)) {
185 continue;
186 }
187
188 if (sock->flag & SELECT) {
189 return sock;
190 }
191 }
192
193 /* Try to find a socket with a matching name. */
194 LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) {
195 if (!socket_is_available(ntree, sock, allow_multiple)) {
196 continue;
197 }
198
199 /* Check for same types. */
200 if (sock->type == sock_target->type) {
201 if (STREQ(sock->name, sock_target->name)) {
202 return sock;
203 }
204 }
205 }
206
207 /* Otherwise settle for the first available socket of the right type. */
208 LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) {
209 if (!socket_is_available(ntree, sock, allow_multiple)) {
210 continue;
211 }
212
213 /* Check for same types. */
214 if (sock->type == sock_target->type) {
215 return sock;
216 }
217 }
218
219 /* Always allow linking to an reroute node. The socket type of the reroute sockets might change
220 * after the link has been created. */
221 if (node->type == NODE_REROUTE) {
222 return (bNodeSocket *)node->outputs.first;
223 }
224
225 return nullptr;
226}
227
228/* This is a bit complicated, but designed to prioritize finding
229 * sockets of higher types, such as image, first. */
230static bNodeSocket *best_socket_input(bNodeTree *ntree, bNode *node, int num, int replace)
231{
232 int maxtype = 0;
233 LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
234 maxtype = max_ii(sock->type, maxtype);
235 }
236
237 /* Find sockets of higher 'types' first (i.e. image). */
238 int a = 0;
239 for (int socktype = maxtype; socktype >= 0; socktype--) {
240 LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
241 if (!socket_is_available(ntree, sock, replace)) {
242 a++;
243 continue;
244 }
245
246 if (sock->type == socktype) {
247 /* Increment to make sure we don't keep finding the same socket on every attempt running
248 * this function. */
249 a++;
250 if (a > num) {
251 return sock;
252 }
253 }
254 }
255 }
256
257 return nullptr;
258}
259
261 bNode *node_fr,
262 bNodeSocket *sock_fr,
263 bNode *node_to,
264 bNodeSocket *sock_to,
265 int replace)
266{
267 bNodeTree *ntree = snode.edittree;
268
269 if (replace) {
270 bke::node_remove_socket_links(ntree, sock_to);
271 }
272
273 bke::node_add_link(ntree, node_fr, sock_fr, node_to, sock_to);
274 return true;
275}
276
281
283 bNodeLink &drag_link,
284 const float2 &cursor)
285{
286 const float2 &socket_location = socket.runtime->location;
287
289 for (bNodeLink *link : socket.directly_linked_links()) {
291 socket_location, link->multi_input_sort_id, link->tosock->runtime->total_inputs);
292 links.append({link, location});
293 };
294
295 links.append({&drag_link, cursor});
296
297 std::sort(links.begin(), links.end(), [](const LinkAndPosition a, const LinkAndPosition b) {
298 return a.multi_socket_position.y < b.multi_socket_position.y;
299 });
300
301 for (const int i : links.index_range()) {
302 links[i].link->multi_input_sort_id = i;
303 }
304}
305
307{
308 for (bNodeSocket *socket : node.input_sockets()) {
309 if (!socket->is_multi_input()) {
310 continue;
311 }
312 Vector<bNodeLink *, 8> links = socket->directly_linked_links();
313 std::sort(links.begin(), links.end(), [](const bNodeLink *a, const bNodeLink *b) {
314 return a->multi_input_sort_id < b->multi_input_sort_id;
315 });
316
317 for (const int i : links.index_range()) {
318 links[i]->multi_input_sort_id = i;
319 }
320 }
321}
322
323static void snode_autoconnect(SpaceNode &snode, const bool allow_multiple, const bool replace)
324{
325 bNodeTree *ntree = snode.edittree;
326 Vector<bNode *> sorted_nodes;
327
328 for (bNode *node : ntree->all_nodes()) {
329 if (node->flag & NODE_SELECT) {
330 sorted_nodes.append(node);
331 }
332 }
333
334 /* Sort nodes left to right. */
335 std::sort(sorted_nodes.begin(), sorted_nodes.end(), [](const bNode *a, const bNode *b) {
336 return a->locx < b->locx;
337 });
338
339 // int numlinks = 0; /* UNUSED */
340 for (const int i : sorted_nodes.as_mutable_span().drop_back(1).index_range()) {
341 bool has_selected_inputs = false;
342
343 bNode *node_fr = sorted_nodes[i];
344 bNode *node_to = sorted_nodes[i + 1];
345 /* Corner case: input/output node aligned the wrong way around (#47729). */
346 if (BLI_listbase_is_empty(&node_to->inputs) || BLI_listbase_is_empty(&node_fr->outputs)) {
347 std::swap(node_fr, node_to);
348 }
349
350 /* If there are selected sockets, connect those. */
351 LISTBASE_FOREACH (bNodeSocket *, sock_to, &node_to->inputs) {
352 if (sock_to->flag & SELECT) {
353 has_selected_inputs = true;
354
355 if (!socket_is_available(ntree, sock_to, replace)) {
356 continue;
357 }
358
359 /* Check for an appropriate output socket to connect from. */
360 bNodeSocket *sock_fr = best_socket_output(ntree, node_fr, sock_to, allow_multiple);
361 if (!sock_fr) {
362 continue;
363 }
364
365 if (snode_autoconnect_input(snode, node_fr, sock_fr, node_to, sock_to, replace)) {
366 // numlinks++;
367 }
368 }
369 }
370
371 if (!has_selected_inputs) {
372 /* No selected inputs, connect by finding suitable match. */
373 int num_inputs = BLI_listbase_count(&node_to->inputs);
374
375 for (int i = 0; i < num_inputs; i++) {
376
377 /* Find the best guess input socket. */
378 bNodeSocket *sock_to = best_socket_input(ntree, node_to, i, replace);
379 if (!sock_to) {
380 continue;
381 }
382
383 /* Check for an appropriate output socket to connect from. */
384 bNodeSocket *sock_fr = best_socket_output(ntree, node_fr, sock_to, allow_multiple);
385 if (!sock_fr) {
386 continue;
387 }
388
389 if (snode_autoconnect_input(snode, node_fr, sock_fr, node_to, sock_to, replace)) {
390 // numlinks++;
391 break;
392 }
393 }
394 }
395 }
396}
397
400namespace viewer_linking {
401
402/* -------------------------------------------------------------------- */
406/* Depending on the node tree type, different socket types are supported by viewer nodes. */
407static bool socket_can_be_viewed(const bNode &node, const bNodeSocket &socket)
408{
409 if (!node.is_socket_icon_drawn(socket)) {
410 return false;
411 }
412 if (STREQ(socket.idname, "NodeSocketVirtual")) {
413 return false;
414 }
415 if (socket.owner_tree().type != NTREE_GEOMETRY) {
416 return true;
417 }
418 return ELEM(socket.typeinfo->type,
422 SOCK_INT,
426 SOCK_RGBA,
427 SOCK_MENU);
428}
429
434 bNode &viewer_node,
435 bNodeSocket &src_socket)
436{
437 if (viewer_node.type != GEO_NODE_VIEWER) {
438 /* In viewer nodes in the compositor, only the first input should be linked to. */
439 return (bNodeSocket *)viewer_node.inputs.first;
440 }
441 /* For the geometry nodes viewer, find the socket with the correct type. */
442
443 if (src_socket.type == SOCK_GEOMETRY) {
444 return static_cast<bNodeSocket *>(viewer_node.inputs.first);
445 }
446
447 ntree.ensure_topology_cache();
448 if (!socket_can_be_viewed(src_socket.owner_node(), src_socket)) {
449 return nullptr;
450 }
451
452 NodeGeometryViewer &storage = *static_cast<NodeGeometryViewer *>(viewer_node.storage);
454 eNodeSocketDatatype(src_socket.type));
455 BLI_assert(data_type != CD_AUTO_FROM_NAME);
456 storage.data_type = data_type;
458
459 return static_cast<bNodeSocket *>(viewer_node.inputs.last);
460}
461
462static bool is_viewer_node(const bNode &node)
463{
464 return ELEM(node.type, CMP_NODE_VIEWER, GEO_NODE_VIEWER);
465}
466
467static bool is_viewer_socket_in_viewer(const bNodeSocket &socket)
468{
469 const bNode &node = socket.owner_node();
471 if (node.typeinfo->type == GEO_NODE_VIEWER) {
472 return true;
473 }
474 return socket.index() == 0;
475}
476
477static bool is_viewer_socket(const bNodeSocket &socket)
478{
479 if (is_viewer_node(socket.owner_node())) {
480 return is_viewer_socket_in_viewer(socket);
481 }
482 return false;
483}
484
486{
487 SpaceNode *snode = CTX_wm_space_node(C);
489}
490
492{
493 LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &btree.links) {
494 if (link->tonode == &viewer_node) {
495 if (link->tosock->flag & SOCK_UNAVAIL) {
496 bke::node_remove_link(&btree, link);
497 }
498 }
499 }
500}
501
503{
504 int last_linked_data_socket_index = -1;
505 bool has_linked_geometry_socket = false;
506 for (bNodeSocket *socket : node_to_view.output_sockets()) {
507 if (!socket_can_be_viewed(node_to_view, *socket)) {
508 continue;
509 }
510 for (bNodeLink *link : socket->directly_linked_links()) {
511 bNodeSocket &target_socket = *link->tosock;
512 bNode &target_node = *link->tonode;
513 if (is_viewer_socket(target_socket)) {
514 if (link->is_muted() || !(target_node.flag & NODE_DO_OUTPUT)) {
515 /* This socket is linked to a deactivated viewer, the viewer should be activated. */
516 return socket;
517 }
518 if (socket->type == SOCK_GEOMETRY) {
519 has_linked_geometry_socket = true;
520 }
521 else {
522 last_linked_data_socket_index = socket->index();
523 }
524 }
525 }
526 }
527
528 if (last_linked_data_socket_index == -1 && !has_linked_geometry_socket) {
529 /* Return the first socket that can be viewed. */
530 for (bNodeSocket *socket : node_to_view.output_sockets()) {
531 if (socket_can_be_viewed(node_to_view, *socket)) {
532 return socket;
533 }
534 }
535 return nullptr;
536 }
537
538 bNodeSocket *already_viewed_socket = nullptr;
539
540 /* Pick the next socket to be linked to the viewer. */
541 const int tot_outputs = node_to_view.output_sockets().size();
542 for (const int offset : IndexRange(1, tot_outputs)) {
543 const int index = (last_linked_data_socket_index + offset) % tot_outputs;
544 bNodeSocket &output_socket = node_to_view.output_socket(index);
545 if (!socket_can_be_viewed(node_to_view, output_socket)) {
546 continue;
547 }
548 if (has_linked_geometry_socket && output_socket.type == SOCK_GEOMETRY) {
549 /* Skip geometry sockets when cycling if one is already viewed. */
550 already_viewed_socket = &output_socket;
551 continue;
552 }
553
554 bool is_currently_viewed = false;
555 for (const bNodeLink *link : output_socket.directly_linked_links()) {
556 bNodeSocket &target_socket = *link->tosock;
557 bNode &target_node = *link->tonode;
558 if (!is_viewer_socket(target_socket)) {
559 continue;
560 }
561 if (link->is_muted()) {
562 continue;
563 }
564 if (!(target_node.flag & NODE_DO_OUTPUT)) {
565 continue;
566 }
567 is_currently_viewed = true;
568 break;
569 }
570 if (is_currently_viewed) {
571 already_viewed_socket = &output_socket;
572 continue;
573 }
574 return &output_socket;
575 }
576 return already_viewed_socket;
577}
578
579static void finalize_viewer_link(const bContext &C,
580 SpaceNode &snode,
581 bNode &viewer_node,
582 bNodeLink &viewer_link)
583{
584 Main *bmain = CTX_data_main(&C);
586 viewer_link.flag &= ~NODE_LINK_MUTED;
587 viewer_node.flag &= ~NODE_MUTED;
588 viewer_node.flag |= NODE_DO_OUTPUT;
589 if (snode.edittree->type == NTREE_GEOMETRY) {
590 viewer_path::activate_geometry_node(*bmain, snode, viewer_node);
591 }
592 ED_node_tree_propagate_change(&C, bmain, snode.edittree);
593}
594
596 const rctf &rect,
597 const Span<const bNode *> ignored_nodes)
598{
599 for (const bNode *node : tree.all_nodes()) {
600 if (node->is_frame()) {
601 continue;
602 }
603 if (ignored_nodes.contains(node)) {
604 continue;
605 }
606 if (BLI_rctf_isect(&rect, &node->runtime->totr, nullptr)) {
607 return node;
608 }
609 }
610 return nullptr;
611}
612
618 const float step_distance,
619 const float max_distance)
620{
621 /* Prefer moving viewer a bit further horizontally than vertically. */
622 const float y_scale = 0.5f;
623
624 Vector<float2> candidates;
625 candidates.append(initial);
626 for (float distance = step_distance; distance <= max_distance; distance += step_distance) {
627 const float arc_length = distance * M_PI;
628 const int checks = std::max<int>(2, ceilf(arc_length / step_distance));
629 for (const int i : IndexRange(checks)) {
630 const float angle = i / float(checks - 1) * M_PI;
631 const float candidate_x = initial.x + distance * std::sin(angle);
632 const float candidate_y = initial.y + distance * std::cos(angle) * y_scale;
633 candidates.append({candidate_x, candidate_y});
634 }
635 }
636 return candidates;
637}
638
645 bNode &viewer_node,
646 const bNode &node_to_view,
647 const ARegion &region)
648{
649 tree.ensure_topology_cache();
650
651 const View2D &v2d = region.v2d;
652 rctf region_rect;
653 region_rect.xmin = 0;
654 region_rect.xmax = region.winx;
655 region_rect.ymin = 0;
656 region_rect.ymax = region.winy;
657 rctf region_bounds;
658 UI_view2d_region_to_view_rctf(&v2d, &region_rect, &region_bounds);
659
660 viewer_node.ui_order = tree.all_nodes().size();
662
663 const float default_padding_x = U.node_margin;
664 const float default_padding_y = 10;
665 const float viewer_width = BLI_rctf_size_x(&viewer_node.runtime->totr);
666 float viewer_height = BLI_rctf_size_y(&viewer_node.runtime->totr);
667 if (viewer_height == 0) {
668 /* Can't use if the viewer node has only just been added and the actual height is not yet
669 * known. */
670 viewer_height = 100;
671 }
672
673 const float2 main_candidate{node_to_view.runtime->totr.xmax + default_padding_x,
674 node_to_view.runtime->totr.ymax + viewer_height + default_padding_y};
675
676 std::optional<float2> new_viewer_position;
677
678 const Vector<float2> position_candidates = get_viewer_node_position_candidates(
679 main_candidate, 50 * UI_SCALE_FAC, 800 * UI_SCALE_FAC);
680 for (const float2 &candidate_pos : position_candidates) {
681 rctf candidate;
682 candidate.xmin = candidate_pos.x;
683 candidate.xmax = candidate_pos.x + viewer_width;
684 candidate.ymin = candidate_pos.y - viewer_height;
685 candidate.ymax = candidate_pos.y;
686
687 if (!BLI_rctf_inside_rctf(&region_bounds, &candidate)) {
688 /* Avoid moving viewer outside of visible region. */
689 continue;
690 }
691
692 rctf padded_candidate = candidate;
693 BLI_rctf_pad(&padded_candidate, default_padding_x - 1, default_padding_y - 1);
694
695 const bNode *overlapping_node = find_overlapping_node(
696 tree, padded_candidate, {&viewer_node, &node_to_view});
697 if (!overlapping_node) {
698 new_viewer_position = candidate_pos;
699 break;
700 }
701 }
702
703 if (!new_viewer_position) {
704 new_viewer_position = main_candidate;
705 }
706
707 const float2 old_position = float2(viewer_node.locx, viewer_node.locy) * UI_SCALE_FAC;
708 if (old_position.x > node_to_view.runtime->totr.xmax) {
709 if (BLI_rctf_inside_rctf(&region_bounds, &viewer_node.runtime->totr)) {
710 /* Measure distance from right edge of the node to view and the left edge of the
711 * viewer node. */
712 const float2 node_to_view_top_right{node_to_view.runtime->totr.xmax,
713 node_to_view.runtime->totr.ymax};
714 const float2 node_to_view_bottom_right{node_to_view.runtime->totr.xmax,
715 node_to_view.runtime->totr.ymin};
716 const float old_distance = dist_seg_seg_v2(old_position,
717 old_position + float2(0, viewer_height),
718 node_to_view_top_right,
719 node_to_view_bottom_right);
720 const float new_distance = dist_seg_seg_v2(*new_viewer_position,
721 *new_viewer_position + float2(0, viewer_height),
722 node_to_view_top_right,
723 node_to_view_bottom_right);
724 if (old_distance <= new_distance) {
725 new_viewer_position = old_position;
726 }
727 }
728 }
729
730 viewer_node.locx = new_viewer_position->x / UI_SCALE_FAC;
731 viewer_node.locy = new_viewer_position->y / UI_SCALE_FAC;
732 viewer_node.parent = nullptr;
733}
734
735static int view_socket(const bContext &C,
736 SpaceNode &snode,
737 bNodeTree &btree,
738 bNode &bnode_to_view,
739 bNodeSocket &bsocket_to_view)
740{
741 ARegion &region = *CTX_wm_region(&C);
742
743 bNode *viewer_node = nullptr;
744 /* Try to find a viewer that is already active. */
745 for (bNode *node : btree.all_nodes()) {
746 if (is_viewer_node(*node)) {
747 if (node->flag & NODE_DO_OUTPUT) {
748 viewer_node = node;
749 break;
750 }
751 }
752 }
753
754 /* Try to reactivate existing viewer connection. */
755 for (bNodeLink *link : bsocket_to_view.directly_linked_links()) {
756 bNodeSocket &target_socket = *link->tosock;
757 bNode &target_node = *link->tonode;
758 if (is_viewer_socket(target_socket) && ELEM(viewer_node, nullptr, &target_node)) {
759 finalize_viewer_link(C, snode, target_node, *link);
760 position_viewer_node(btree, target_node, bnode_to_view, region);
761 return OPERATOR_FINISHED;
762 }
763 }
764
765 if (viewer_node == nullptr) {
766 for (bNode *node : btree.all_nodes()) {
767 if (is_viewer_node(*node)) {
768 viewer_node = node;
769 break;
770 }
771 }
772 }
773 if (viewer_node == nullptr) {
774 const float2 socket_location = bsocket_to_view.runtime->location;
775 const int viewer_type = get_default_viewer_type(&C);
776 const float2 location{socket_location.x / UI_SCALE_FAC + 100,
777 socket_location.y / UI_SCALE_FAC};
778 viewer_node = add_static_node(C, viewer_type, location);
779 }
780
781 bNodeSocket *viewer_bsocket = node_link_viewer_get_socket(btree, *viewer_node, bsocket_to_view);
782 if (viewer_bsocket == nullptr) {
783 return OPERATOR_CANCELLED;
784 }
785 viewer_bsocket->flag &= ~SOCK_HIDDEN;
786
787 bNodeLink *viewer_link = nullptr;
788 LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &btree.links) {
789 if (link->tosock == viewer_bsocket) {
790 viewer_link = link;
791 break;
792 }
793 }
794 if (viewer_link == nullptr) {
795 viewer_link = bke::node_add_link(
796 &btree, &bnode_to_view, &bsocket_to_view, viewer_node, viewer_bsocket);
797 }
798 else {
799 viewer_link->fromnode = &bnode_to_view;
800 viewer_link->fromsock = &bsocket_to_view;
802 }
803 finalize_viewer_link(C, snode, *viewer_node, *viewer_link);
804 position_viewer_node(btree, *viewer_node, bnode_to_view, region);
805 return OPERATOR_CANCELLED;
806}
807
808static int node_link_viewer(const bContext &C, bNode &bnode_to_view, bNodeSocket *bsocket_to_view)
809{
810 SpaceNode &snode = *CTX_wm_space_node(&C);
811 bNodeTree *btree = snode.edittree;
812 btree->ensure_topology_cache();
813
814 if (bsocket_to_view == nullptr) {
815 bsocket_to_view = determine_socket_to_view(bnode_to_view);
816 }
817
818 if (bsocket_to_view == nullptr) {
819 return OPERATOR_CANCELLED;
820 }
821
822 return view_socket(C, snode, *btree, bnode_to_view, *bsocket_to_view);
823}
824
827} // namespace viewer_linking
828
829/* -------------------------------------------------------------------- */
834{
835 SpaceNode &snode = *CTX_wm_space_node(C);
836 bNode *node = bke::node_get_active(snode.edittree);
837
838 if (!node) {
839 return OPERATOR_CANCELLED;
840 }
841
843
844 bNodeSocket *socket_to_view = nullptr;
845 LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) {
846 if (socket->flag & SELECT) {
847 socket_to_view = socket;
848 break;
849 }
850 }
851
852 if (viewer_linking::node_link_viewer(*C, *node, socket_to_view) == OPERATOR_CANCELLED) {
853 return OPERATOR_CANCELLED;
854 }
855
857
858 return OPERATOR_FINISHED;
859}
860
862{
864 return false;
865 }
866 SpaceNode *snode = CTX_wm_space_node(C);
867 if (ED_node_is_compositor(snode)) {
868 return true;
869 }
870 if (ED_node_is_geometry(snode)) {
872 /* The viewer node is not supported in the "Tool" context. */
873 return false;
874 }
875 return true;
876 }
877 return false;
878}
879
881{
882 /* identifiers */
883 ot->name = "Link to Viewer Node";
884 ot->description = "Link to viewer node";
885 ot->idname = "NODE_OT_link_viewer";
886
887 /* api callbacks */
890
891 /* flags */
893}
894
897/* -------------------------------------------------------------------- */
906{
907 if (nldrag.in_out == SOCK_OUT) {
908 for (const bNodeLink &link : nldrag.links) {
909 if (link.tonode && link.tosock) {
910 return false;
911 }
912 }
913 }
914 else {
915 for (const bNodeLink &link : nldrag.links) {
916 if (link.fromnode && link.fromsock) {
917 return false;
918 }
919 }
920 }
921 return true;
922}
923
925 const bNodeLinkDrag &nldrag)
926{
927 /* Custom node trees aren't supported yet. */
928 if (node_tree.type == NTREE_CUSTOM) {
929 return false;
930 }
931 /* Only create the search menu when the drag has not already connected the links to a socket. */
932 if (!dragged_links_are_detached(nldrag)) {
933 return false;
934 }
935 if (nldrag.swap_links) {
936 return false;
937 }
938 /* Don't create the search menu if the drag is disconnecting a link from an input node. */
939 if (nldrag.start_socket->in_out == SOCK_IN && nldrag.start_link_count > 0) {
940 return false;
941 }
942 /* Don't allow a drag from the "new socket" (group input node or simulation nodes currently).
943 * Handling these properly in node callbacks increases the complexity too much for now. */
944 if (nldrag.start_socket->type == SOCK_CUSTOM) {
945 return false;
946 }
947 if (nldrag.start_socket->type == SOCK_TEXTURE) {
948 /* This socket types is not used anymore, but can currently still exists in files. */
949 return false;
950 }
951 return true;
952}
953
954static bool need_drag_link_tooltip(const bNodeTree &node_tree, const bNodeLinkDrag &nldrag)
955{
957}
958
959static void draw_draglink_tooltip(const bContext * /*C*/, ARegion * /*region*/, void *arg)
960{
961 bNodeLinkDrag *nldrag = static_cast<bNodeLinkDrag *>(arg);
962
963 uchar text_col[4];
964 UI_GetThemeColor4ubv(TH_TEXT, text_col);
965
966 const int padding = 4 * UI_SCALE_FAC;
967 const float x = nldrag->in_out == SOCK_IN ? nldrag->cursor[0] - 3.3f * padding :
968 nldrag->cursor[0];
969 const float y = nldrag->cursor[1] - 2.0f * UI_SCALE_FAC;
970
971 const bool new_link = nldrag->in_out == nldrag->start_socket->in_out;
972 const bool swap_links = nldrag->swap_links;
973
974 const int icon = !swap_links ? ICON_ADD : (new_link ? ICON_ANIM : ICON_UV_SYNC_SELECT);
975
977 x, y, icon, UI_INV_SCALE_FAC, 1.0f, 0.0f, text_col, false, UI_NO_ICON_OVERLAY_TEXT);
978}
979
980static void draw_draglink_tooltip_activate(const ARegion &region, bNodeLinkDrag &nldrag)
981{
982 if (nldrag.draw_handle == nullptr) {
984 region.type, draw_draglink_tooltip, &nldrag, REGION_DRAW_POST_PIXEL);
985 }
986}
987
988static void draw_draglink_tooltip_deactivate(const ARegion &region, bNodeLinkDrag &nldrag)
989{
990 if (nldrag.draw_handle) {
991 ED_region_draw_cb_exit(region.type, nldrag.draw_handle);
992 nldrag.draw_handle = nullptr;
993 }
994}
995
996static int node_socket_count_links(const bNodeTree &ntree, const bNodeSocket &socket)
997{
998 int count = 0;
999 LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
1000 if (ELEM(&socket, link->fromsock, link->tosock)) {
1001 count++;
1002 }
1003 }
1004 return count;
1005}
1006
1008 const bNode *node,
1009 bNodeSocket *socket_to_match)
1010{
1011 bNodeSocket *first_socket = socket_to_match->in_out == SOCK_IN ?
1012 static_cast<bNodeSocket *>(node->inputs.first) :
1013 static_cast<bNodeSocket *>(node->outputs.first);
1014
1015 bNodeSocket *socket = socket_to_match->next ? socket_to_match->next : first_socket;
1016 while (socket != socket_to_match) {
1017 if (!socket->is_hidden() && socket->is_available()) {
1018 const bool sockets_are_compatible = socket->typeinfo == socket_to_match->typeinfo;
1019 if (sockets_are_compatible) {
1020 const int link_count = node_socket_count_links(ntree, *socket);
1021 const bool socket_has_capacity = link_count < bke::node_socket_link_limit(socket);
1022 if (socket_has_capacity) {
1023 /* Found a valid free socket we can swap to. */
1024 return socket;
1025 }
1026 }
1027 }
1028 /* Wrap around the list end. */
1029 socket = socket->next ? socket->next : first_socket;
1030 }
1031
1032 return nullptr;
1033}
1034
1035static void displace_links(bNodeTree *ntree, const bNode *node, bNodeLink *inserted_link)
1036{
1037 bNodeSocket *linked_socket = node == inserted_link->tonode ? inserted_link->tosock :
1038 inserted_link->fromsock;
1039 bNodeSocket *replacement_socket = node_find_linkable_socket(*ntree, node, linked_socket);
1040
1041 if (linked_socket->is_input()) {
1042 BLI_assert(!linked_socket->is_multi_input());
1043 ntree->ensure_topology_cache();
1044
1045 if (linked_socket->directly_linked_links().is_empty()) {
1046 return;
1047 }
1048 bNodeLink *displaced_link = linked_socket->directly_linked_links().first();
1049
1050 if (!replacement_socket) {
1051 bke::node_remove_link(ntree, displaced_link);
1052 return;
1053 }
1054
1055 displaced_link->tosock = replacement_socket;
1056
1057 if (replacement_socket->is_multi_input()) {
1058 /* Check for duplicate links when linking to multi input sockets. */
1059 for (bNodeLink *existing_link : replacement_socket->runtime->directly_linked_links) {
1060 if (existing_link->fromsock == displaced_link->fromsock) {
1061 bke::node_remove_link(ntree, displaced_link);
1062 return;
1063 }
1064 }
1065 const int multi_input_sort_id = node_socket_count_links(*ntree, *replacement_socket) - 1;
1066 displaced_link->multi_input_sort_id = multi_input_sort_id;
1067 }
1068
1070 return;
1071 }
1072
1073 LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree->links) {
1074 if (link->fromsock == linked_socket) {
1075 if (replacement_socket) {
1076 link->fromsock = replacement_socket;
1078 }
1079 else {
1080 bke::node_remove_link(ntree, link);
1082 }
1083 }
1084 }
1085}
1086
1088{
1089 bNodeLink &link = nldrag.links.first();
1090 if (!link.fromsock || !link.tosock) {
1091 return;
1092 }
1093 if (nldrag.start_socket->is_input()) {
1094 displace_links(&ntree, link.fromnode, &link);
1095 }
1096 else {
1097 displace_links(&ntree, link.tonode, &link);
1098 }
1099}
1100
1101static void node_swap_links(bNodeLinkDrag &nldrag, bNodeTree &ntree)
1102{
1103 bNodeSocket &linked_socket = *nldrag.hovered_socket;
1104 bNodeSocket *start_socket = nldrag.start_socket;
1105 bNode *start_node = nldrag.start_node;
1106
1107 if (linked_socket.is_input()) {
1108 LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
1109 if (link->tosock != &linked_socket) {
1110 continue;
1111 }
1112 if (link->fromnode == start_node) {
1113 /* Don't link a node to itself. */
1114 bke::node_remove_link(&ntree, link);
1115 continue;
1116 }
1117
1118 link->tosock = start_socket;
1119 link->tonode = start_node;
1120 }
1121 }
1122 else {
1123 LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
1124 if (link->fromsock != &linked_socket) {
1125 continue;
1126 }
1127 if (link->tonode == start_node) {
1128 /* Don't link a node to itself. */
1129 bke::node_remove_link(&ntree, link);
1130 continue;
1131 }
1132 link->fromsock = start_socket;
1133 link->fromnode = start_node;
1134 }
1135 }
1136
1138}
1139
1141{
1142 bNodeSocket &linked_socket = *nldrag.hovered_socket;
1143
1144 int link_count = node_socket_count_links(ntree, linked_socket);
1145 const int link_limit = bke::node_socket_link_limit(&linked_socket);
1146 Set<bNodeLink *> links_to_remove;
1147
1148 ntree.ensure_topology_cache();
1149
1150 /* Remove duplicate links first. */
1151 for (const bNodeLink dragged_link : nldrag.links) {
1152 if (linked_socket.is_input()) {
1153 for (bNodeLink *link : linked_socket.runtime->directly_linked_links) {
1154 const bool duplicate_link = link->fromsock == dragged_link.fromsock;
1155 if (duplicate_link) {
1156 links_to_remove.add(link);
1157 link_count--;
1158 }
1159 }
1160 }
1161 else {
1162 for (bNodeLink *link : linked_socket.runtime->directly_linked_links) {
1163 const bool duplicate_link = link->tosock == dragged_link.tosock;
1164 if (duplicate_link) {
1165 links_to_remove.add(link);
1166 link_count--;
1167 }
1168 }
1169 }
1170 }
1171
1172 for (bNodeLink *link : linked_socket.runtime->directly_linked_links) {
1173 const bool link_limit_exceeded = !(link_count < link_limit);
1174 if (link_limit_exceeded) {
1175 if (links_to_remove.add(link)) {
1176 link_count--;
1177 }
1178 }
1179 }
1180
1181 for (bNodeLink *link : links_to_remove) {
1182 bke::node_remove_link(&ntree, link);
1183 }
1184}
1185
1187{
1188 Main *bmain = CTX_data_main(&C);
1189 ARegion &region = *CTX_wm_region(&C);
1190 SpaceNode &snode = *CTX_wm_space_node(&C);
1191 bNodeTree &ntree = *snode.edittree;
1192
1193 /* Handle node links already occupying the socket. */
1194 if (const bNodeSocket *linked_socket = nldrag.hovered_socket) {
1195 /* Swapping existing links out of multi input sockets is not supported. */
1196 const bool connecting_to_multi_input = linked_socket->is_multi_input() ||
1197 nldrag.start_socket->is_multi_input();
1198 if (nldrag.swap_links && !connecting_to_multi_input) {
1199 const bool is_new_link = nldrag.in_out == nldrag.start_socket->in_out;
1200 if (is_new_link) {
1201 node_displace_existing_links(nldrag, ntree);
1202 }
1203 else {
1204 node_swap_links(nldrag, ntree);
1205 }
1206 }
1207 else {
1209 }
1210 }
1211
1212 for (const bNodeLink &link : nldrag.links) {
1213 if (!link.tosock || !link.fromsock) {
1214 continue;
1215 }
1216
1217 /* Before actually adding the link let nodes perform special link insertion handling. */
1218
1219 bNodeLink *new_link = static_cast<bNodeLink *>(MEM_mallocN(sizeof(bNodeLink), __func__));
1220 *new_link = link;
1221 if (link.fromnode->typeinfo->insert_link) {
1222 if (!link.fromnode->typeinfo->insert_link(&ntree, link.fromnode, new_link)) {
1223 MEM_freeN(new_link);
1224 continue;
1225 }
1226 }
1227 if (link.tonode->typeinfo->insert_link) {
1228 if (!link.tonode->typeinfo->insert_link(&ntree, link.tonode, new_link)) {
1229 MEM_freeN(new_link);
1230 continue;
1231 }
1232 }
1233
1234 BLI_addtail(&ntree.links, new_link);
1235 BKE_ntree_update_tag_link_added(&ntree, new_link);
1236 }
1237
1238 ED_node_tree_propagate_change(&C, bmain, &ntree);
1239
1240 /* Ensure drag-link tool-tip is disabled. */
1241 draw_draglink_tooltip_deactivate(region, nldrag);
1242
1243 ED_workspace_status_text(&C, nullptr);
1244 ED_region_tag_redraw(&region);
1246
1247 snode.runtime->linkdrag.reset();
1248}
1249
1261
1262static void node_link_find_socket(bContext &C, wmOperator &op, const float2 &cursor)
1263{
1264 SpaceNode &snode = *CTX_wm_space_node(&C);
1265 ARegion &region = *CTX_wm_region(&C);
1266 bNodeLinkDrag &nldrag = *static_cast<bNodeLinkDrag *>(op.customdata);
1267
1268 if (nldrag.in_out == SOCK_OUT) {
1269 if (bNodeSocket *tsock = node_find_indicated_socket(snode, region, cursor, SOCK_IN)) {
1270 nldrag.hovered_socket = tsock;
1271 bNode &tnode = tsock->owner_node();
1272 for (bNodeLink &link : nldrag.links) {
1273 /* Skip if socket is on the same node as the fromsock. */
1274 if (link.fromnode == &tnode) {
1275 continue;
1276 }
1277
1278 /* Skip if tsock is already linked with this output. */
1279 bNodeLink *existing_link_connected_to_fromsock = nullptr;
1280 LISTBASE_FOREACH (bNodeLink *, existing_link, &snode.edittree->links) {
1281 if (existing_link->fromsock == link.fromsock && existing_link->tosock == tsock) {
1282 existing_link_connected_to_fromsock = existing_link;
1283 break;
1284 }
1285 }
1286
1287 /* Attach links to the socket. */
1288 link.tonode = &tnode;
1289 link.tosock = tsock;
1291 if (existing_link_connected_to_fromsock) {
1292 link.multi_input_sort_id = existing_link_connected_to_fromsock->multi_input_sort_id;
1293 continue;
1294 }
1295 if (tsock && tsock->is_multi_input()) {
1296 sort_multi_input_socket_links_with_drag(*tsock, link, cursor);
1297 }
1298 }
1299 }
1300 else {
1301 nldrag.hovered_socket = nullptr;
1302 for (bNodeLink &link : nldrag.links) {
1303 link.tonode = nullptr;
1304 link.tosock = nullptr;
1305 }
1309 }
1310 }
1311 }
1312 else {
1313 if (bNodeSocket *tsock = node_find_indicated_socket(snode, region, cursor, SOCK_OUT)) {
1314 nldrag.hovered_socket = tsock;
1315 bNode &node = tsock->owner_node();
1316 for (bNodeLink &link : nldrag.links) {
1317 /* Skip if this is already the target socket. */
1318 if (link.fromsock == tsock) {
1319 continue;
1320 }
1321 /* Skip if socket is on the same node as the `fromsock`. */
1322 if (link.tonode == &node) {
1323 continue;
1324 }
1325
1326 /* Attach links to the socket. */
1327 link.fromnode = &node;
1328 link.fromsock = tsock;
1329 }
1330 }
1331 else {
1332 nldrag.hovered_socket = nullptr;
1333 for (bNodeLink &link : nldrag.links) {
1334 link.fromnode = nullptr;
1335 link.fromsock = nullptr;
1336 }
1337 }
1338 }
1339}
1340
1341enum class NodeLinkAction : int {
1342 Begin = 0,
1343 Cancel = 1,
1344 Swap = 2,
1345 Confirm = 3,
1346};
1347
1349{
1350 static const EnumPropertyItem modal_items[] = {
1351 {int(NodeLinkAction::Begin), "BEGIN", 0, "Drag Node-link", ""},
1352 {int(NodeLinkAction::Confirm), "CONFIRM", 0, "Confirm Link", ""},
1353 {int(NodeLinkAction::Cancel), "CANCEL", 0, "Cancel", ""},
1354 {int(NodeLinkAction::Swap), "SWAP", 0, "Swap Links", ""},
1355 {0, nullptr, 0, nullptr, nullptr},
1356 };
1357
1358 wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Node Link Modal Map");
1359
1360 /* This function is called for each space-type, only needs to add map once. */
1361 if (keymap && keymap->modal_items) {
1362 return nullptr;
1363 }
1364
1365 keymap = WM_modalkeymap_ensure(keyconf, "Node Link Modal Map", modal_items);
1366
1367 WM_modalkeymap_assign(keymap, "NODE_OT_link");
1368
1369 return keymap;
1370}
1371
1372static int node_link_modal(bContext *C, wmOperator *op, const wmEvent *event)
1373{
1374 bNodeLinkDrag &nldrag = *static_cast<bNodeLinkDrag *>(op->customdata);
1375 SpaceNode &snode = *CTX_wm_space_node(C);
1376 ARegion *region = CTX_wm_region(C);
1377
1378 UI_view2d_edge_pan_apply_event(C, &nldrag.pan_data, event);
1379
1380 float2 cursor;
1381 UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &cursor.x, &cursor.y);
1382 nldrag.cursor[0] = event->mval[0];
1383 nldrag.cursor[1] = event->mval[1];
1384
1385 if (event->type == EVT_MODAL_MAP) {
1386 switch (event->val) {
1387 case int(NodeLinkAction::Begin): {
1389 }
1391 /* Add a search menu for compatible sockets if the drag released on empty space. */
1392 if (should_create_drag_link_search_menu(*snode.edittree, nldrag)) {
1393 bNodeLink &link = nldrag.links.first();
1394 if (nldrag.in_out == SOCK_OUT) {
1395 invoke_node_link_drag_add_menu(*C, *link.fromnode, *link.fromsock, cursor);
1396 }
1397 else {
1398 invoke_node_link_drag_add_menu(*C, *link.tonode, *link.tosock, cursor);
1399 }
1400 }
1401 add_dragged_links_to_tree(*C, nldrag);
1402 return OPERATOR_FINISHED;
1403 }
1405 node_link_cancel(C, op);
1406 return OPERATOR_CANCELLED;
1407 }
1409 if (event->prev_val == KM_PRESS) {
1410 nldrag.swap_links = true;
1411 }
1412 else if (event->prev_val == KM_RELEASE) {
1413 nldrag.swap_links = false;
1414 }
1416 }
1417 }
1418 else if (event->type == MOUSEMOVE) {
1419 if (nldrag.start_socket->is_multi_input() && nldrag.links.is_empty()) {
1420 pick_input_link_by_link_intersect(*C, *op, nldrag, cursor);
1421 }
1422 else {
1423 node_link_find_socket(*C, *op, cursor);
1424 ED_region_tag_redraw(region);
1425 }
1426
1427 if (need_drag_link_tooltip(*snode.edittree, nldrag)) {
1428 draw_draglink_tooltip_activate(*region, nldrag);
1429 }
1430 else {
1431 draw_draglink_tooltip_deactivate(*region, nldrag);
1432 }
1433 }
1434
1436}
1437
1438static std::unique_ptr<bNodeLinkDrag> node_link_init(ARegion &region,
1439 SpaceNode &snode,
1440 const float2 cursor,
1441 const bool detach)
1442{
1443 if (bNodeSocket *sock = node_find_indicated_socket(snode, region, cursor, SOCK_OUT)) {
1444 bNode &node = sock->owner_node();
1445
1446 std::unique_ptr<bNodeLinkDrag> nldrag = std::make_unique<bNodeLinkDrag>();
1447 nldrag->start_node = &node;
1448 nldrag->start_socket = sock;
1449 nldrag->start_link_count = bke::node_count_socket_links(snode.edittree, sock);
1450 int link_limit = bke::node_socket_link_limit(sock);
1451 if (nldrag->start_link_count > 0 && (nldrag->start_link_count >= link_limit || detach)) {
1452 /* Dragged links are fixed on input side. */
1453 nldrag->in_out = SOCK_IN;
1454 /* Detach current links and store them in the operator data. */
1456 if (link->fromsock == sock) {
1457 bNodeLink oplink = *link;
1458 oplink.next = oplink.prev = nullptr;
1459 oplink.flag |= NODE_LINK_VALID;
1460
1461 nldrag->links.append(oplink);
1462 bke::node_remove_link(snode.edittree, link);
1463 }
1464 }
1465 }
1466 else {
1467 /* Dragged links are fixed on output side. */
1468 nldrag->in_out = SOCK_OUT;
1469 nldrag->links.append(create_drag_link(node, *sock));
1470 }
1471 return nldrag;
1472 }
1473
1474 if (bNodeSocket *sock = node_find_indicated_socket(snode, region, cursor, SOCK_IN)) {
1475 bNode &node = sock->owner_node();
1476 std::unique_ptr<bNodeLinkDrag> nldrag = std::make_unique<bNodeLinkDrag>();
1477 nldrag->last_node_hovered_while_dragging_a_link = &node;
1478 nldrag->start_node = &node;
1479 nldrag->start_socket = sock;
1480
1481 nldrag->start_link_count = bke::node_count_socket_links(snode.edittree, sock);
1482 if (nldrag->start_link_count > 0) {
1483 /* Dragged links are fixed on output side. */
1484 nldrag->in_out = SOCK_OUT;
1485 /* Detach current links and store them in the operator data. */
1486 bNodeLink *link_to_pick;
1488 if (link->tosock == sock) {
1489 link_to_pick = link;
1490 }
1491 }
1492
1493 if (link_to_pick != nullptr && !nldrag->start_socket->is_multi_input()) {
1494 bNodeLink oplink = *link_to_pick;
1495 oplink.next = oplink.prev = nullptr;
1496 oplink.flag |= NODE_LINK_VALID;
1497
1498 nldrag->links.append(oplink);
1499 bke::node_remove_link(snode.edittree, link_to_pick);
1500
1501 /* Send changed event to original link->tonode. */
1503 }
1504 }
1505 else {
1506 /* Dragged links are fixed on input side. */
1507 nldrag->in_out = SOCK_IN;
1508 nldrag->links.append(create_drag_link(node, *sock));
1509 }
1510 return nldrag;
1511 }
1512
1513 return {};
1514}
1515
1516static int node_link_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1517{
1518 Main &bmain = *CTX_data_main(C);
1519 SpaceNode &snode = *CTX_wm_space_node(C);
1520 ARegion &region = *CTX_wm_region(C);
1521
1522 bool detach = RNA_boolean_get(op->ptr, "detach");
1523
1524 int2 mval;
1525 WM_event_drag_start_mval(event, &region, mval);
1526
1527 float2 cursor;
1528 UI_view2d_region_to_view(&region.v2d, mval[0], mval[1], &cursor[0], &cursor[1]);
1529 RNA_float_set_array(op->ptr, "drag_start", cursor);
1530
1532
1533 std::unique_ptr<bNodeLinkDrag> nldrag = node_link_init(region, snode, cursor, detach);
1534 if (!nldrag) {
1536 }
1537
1538 UI_view2d_edge_pan_operator_init(C, &nldrag->pan_data, op);
1539
1540 /* Add icons at the cursor when the link is dragged in empty space. */
1541 if (need_drag_link_tooltip(*snode.edittree, *nldrag)) {
1543 }
1544 snode.runtime->linkdrag = std::move(nldrag);
1545 op->customdata = snode.runtime->linkdrag.get();
1546
1548
1550}
1551
1553{
1554 /* identifiers */
1555 ot->name = "Link Nodes";
1556 ot->idname = "NODE_OT_link";
1557 ot->description = "Use the mouse to create a link between two nodes";
1558
1559 /* api callbacks */
1564
1565 /* flags */
1567
1568 RNA_def_boolean(ot->srna, "detach", false, "Detach", "Detach and redirect existing links");
1570 "drag_start",
1571 2,
1572 nullptr,
1575 "Drag Start",
1576 "The position of the mouse cursor at the start of the operation",
1579
1587}
1588
1591/* -------------------------------------------------------------------- */
1595/* Makes a link between selected output and input sockets. */
1597{
1598 Main &bmain = *CTX_data_main(C);
1599 SpaceNode &snode = *CTX_wm_space_node(C);
1600 bNodeTree &node_tree = *snode.edittree;
1601 const bool replace = RNA_boolean_get(op->ptr, "replace");
1602
1604
1605 snode_autoconnect(snode, true, replace);
1606
1607 /* Deselect sockets after linking. */
1610
1612
1613 return OPERATOR_FINISHED;
1614}
1615
1617{
1618 /* identifiers */
1619 ot->name = "Make Links";
1620 ot->description = "Make a link between selected output and input sockets";
1621 ot->idname = "NODE_OT_link_make";
1622
1623 /* callbacks */
1625 /* XXX we need a special poll which checks that there are selected input/output sockets. */
1627
1628 /* flags */
1630
1632 ot->srna, "replace", false, "Replace", "Replace socket connections with the new links");
1633}
1634
1637/* -------------------------------------------------------------------- */
1642{
1643 Main &bmain = *CTX_data_main(C);
1644 SpaceNode &snode = *CTX_wm_space_node(C);
1645 const ARegion &region = *CTX_wm_region(C);
1646
1647 Vector<float2> path;
1648 RNA_BEGIN (op->ptr, itemptr, "path") {
1649 float2 loc_region;
1650 RNA_float_get_array(&itemptr, "loc", loc_region);
1651 float2 loc_view;
1652 UI_view2d_region_to_view(&region.v2d, loc_region.x, loc_region.y, &loc_view.x, &loc_view.y);
1653 path.append(loc_view);
1654 if (path.size() >= 256) {
1655 break;
1656 }
1657 }
1658 RNA_END;
1659
1660 if (path.is_empty()) {
1662 }
1663
1664 bool found = false;
1665
1667
1668 bNodeTree &node_tree = *snode.edittree;
1669 node_tree.ensure_topology_cache();
1670
1671 Set<bNodeLink *> links_to_remove;
1672 LISTBASE_FOREACH (bNodeLink *, link, &node_tree.links) {
1673 if (node_link_is_hidden_or_dimmed(region.v2d, *link)) {
1674 continue;
1675 }
1676
1677 if (link_path_intersection(*link, path)) {
1678
1679 if (!found) {
1680 /* TODO(sergey): Why did we kill jobs twice? */
1682 found = true;
1683 }
1684 links_to_remove.add(link);
1685 }
1686 }
1687
1688 Set<bNode *> affected_nodes;
1689 for (bNodeLink *link : links_to_remove) {
1690 bNode *to_node = link->tonode;
1691 bke::node_remove_link(snode.edittree, link);
1692 affected_nodes.add(to_node);
1693 }
1694
1695 node_tree.ensure_topology_cache();
1696 for (bNode *node : affected_nodes) {
1698 }
1699
1701 if (found) {
1702 return OPERATOR_FINISHED;
1703 }
1704
1705 return OPERATOR_CANCELLED;
1706}
1707
1709{
1710 ot->name = "Cut Links";
1711 ot->idname = "NODE_OT_links_cut";
1712 ot->description = "Use the mouse to cut (remove) some links";
1713
1718
1720
1721 /* flags */
1723
1724 /* properties */
1725 PropertyRNA *prop;
1726 prop = RNA_def_collection_runtime(ot->srna, "path", &RNA_OperatorMousePath, "Path", "");
1728
1729 /* internal */
1730 RNA_def_int(ot->srna, "cursor", WM_CURSOR_KNIFE, 0, INT_MAX, "Cursor", "", 0, INT_MAX);
1731}
1732
1735/* -------------------------------------------------------------------- */
1739bool all_links_muted(const bNodeSocket &socket)
1740{
1741 for (const bNodeLink *link : socket.directly_linked_links()) {
1742 if (!(link->flag & NODE_LINK_MUTED)) {
1743 return false;
1744 }
1745 }
1746 return true;
1747}
1748
1750{
1751 Main &bmain = *CTX_data_main(C);
1752 SpaceNode &snode = *CTX_wm_space_node(C);
1753 const ARegion &region = *CTX_wm_region(C);
1754 bNodeTree &ntree = *snode.edittree;
1755
1756 Vector<float2> path;
1757 RNA_BEGIN (op->ptr, itemptr, "path") {
1758 float2 loc_region;
1759 RNA_float_get_array(&itemptr, "loc", loc_region);
1760 float2 loc_view;
1761 UI_view2d_region_to_view(&region.v2d, loc_region.x, loc_region.y, &loc_view.x, &loc_view.y);
1762 path.append(loc_view);
1763 if (path.size() >= 256) {
1764 break;
1765 }
1766 }
1767 RNA_END;
1768
1769 if (path.is_empty()) {
1771 }
1772
1774
1775 ntree.ensure_topology_cache();
1776
1777 Set<bNodeLink *> affected_links;
1778 LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
1779 if (node_link_is_hidden_or_dimmed(region.v2d, *link)) {
1780 continue;
1781 }
1782 if (!link_path_intersection(*link, path)) {
1783 continue;
1784 }
1785 affected_links.add(link);
1786 }
1787
1788 if (affected_links.is_empty()) {
1789 return OPERATOR_CANCELLED;
1790 }
1791
1792 bke::node_tree_runtime::AllowUsingOutdatedInfo allow_outdated_info{ntree};
1793
1794 for (bNodeLink *link : affected_links) {
1795 bke::node_link_set_mute(&ntree, link, !(link->flag & NODE_LINK_MUTED));
1796 const bool muted = link->flag & NODE_LINK_MUTED;
1797
1798 /* Propagate mute status downstream past reroute nodes. */
1799 if (link->tonode->is_reroute()) {
1800 Stack<bNodeLink *> links;
1801 links.push_multiple(link->tonode->output_socket(0).directly_linked_links());
1802 while (!links.is_empty()) {
1803 bNodeLink *link = links.pop();
1804 bke::node_link_set_mute(&ntree, link, muted);
1805 if (!link->tonode->is_reroute()) {
1806 continue;
1807 }
1808 links.push_multiple(link->tonode->output_socket(0).directly_linked_links());
1809 }
1810 }
1811 /* Propagate mute status upstream past reroutes, but only if all outputs are muted. */
1812 if (link->fromnode->is_reroute()) {
1813 if (!muted || all_links_muted(*link->fromsock)) {
1814 Stack<bNodeLink *> links;
1815 links.push_multiple(link->fromnode->input_socket(0).directly_linked_links());
1816 while (!links.is_empty()) {
1817 bNodeLink *link = links.pop();
1818 bke::node_link_set_mute(&ntree, link, muted);
1819 if (!link->fromnode->is_reroute()) {
1820 continue;
1821 }
1822 if (!muted || all_links_muted(*link->fromsock)) {
1823 links.push_multiple(link->fromnode->input_socket(0).directly_linked_links());
1824 }
1825 }
1826 }
1827 }
1828 }
1829
1831 return OPERATOR_FINISHED;
1832}
1833
1835{
1836 ot->name = "Mute Links";
1837 ot->idname = "NODE_OT_links_mute";
1838 ot->description = "Use the mouse to mute links";
1839
1844
1846
1847 /* flags */
1849
1850 /* properties */
1851 PropertyRNA *prop;
1852 prop = RNA_def_collection_runtime(ot->srna, "path", &RNA_OperatorMousePath, "Path", "");
1854
1855 /* internal */
1856 RNA_def_int(ot->srna, "cursor", WM_CURSOR_MUTE, 0, INT_MAX, "Cursor", "", 0, INT_MAX);
1857}
1858
1861/* -------------------------------------------------------------------- */
1865static int detach_links_exec(bContext *C, wmOperator * /*op*/)
1866{
1867 SpaceNode &snode = *CTX_wm_space_node(C);
1868 bNodeTree &ntree = *snode.edittree;
1869
1871
1872 for (bNode *node : ntree.all_nodes()) {
1873 if (node->flag & SELECT) {
1874 bke::node_internal_relink(&ntree, node);
1875 }
1876 }
1877
1879 return OPERATOR_FINISHED;
1880}
1881
1883{
1884 ot->name = "Detach Links";
1885 ot->idname = "NODE_OT_links_detach";
1886 ot->description =
1887 "Remove all links to selected nodes, and try to connect neighbor nodes together";
1888
1891
1892 /* flags */
1894}
1895
1898/* -------------------------------------------------------------------- */
1903{
1904 SpaceNode &snode = *CTX_wm_space_node(C);
1905 bNodeTree &ntree = *snode.edittree;
1906 bNode *frame = bke::node_get_active(&ntree);
1907 if (!frame || frame->type != NODE_FRAME) {
1908 return OPERATOR_CANCELLED;
1909 }
1910
1911 for (bNode *node : ntree.all_nodes()) {
1912 if (node == frame) {
1913 continue;
1914 }
1915 if (node->flag & NODE_SELECT) {
1916 bke::node_detach_node(&ntree, node);
1917 bke::node_attach_node(&ntree, node, frame);
1918 }
1919 }
1920
1923
1924 return OPERATOR_FINISHED;
1925}
1926
1928{
1929 /* identifiers */
1930 ot->name = "Make Parent";
1931 ot->description = "Attach selected nodes";
1932 ot->idname = "NODE_OT_parent_set";
1933
1934 /* api callbacks */
1937
1938 /* flags */
1940}
1941
1944/* -------------------------------------------------------------------- */
1949 bool done;
1951};
1952
1954 MutableSpan<NodeJoinState> join_states,
1955 bNode *node,
1956 bNode *frame,
1957 const VectorSet<bNode *> &selected_nodes)
1958{
1959 join_states[node->index()].done = true;
1960
1961 if (node == frame) {
1962 join_states[node->index()].descendent = true;
1963 }
1964 else if (node->parent) {
1965 /* call recursively */
1966 if (!join_states[node->parent->index()].done) {
1967 node_join_attach_recursive(ntree, join_states, node->parent, frame, selected_nodes);
1968 }
1969
1970 /* in any case: if the parent is a descendant, so is the child */
1971 if (join_states[node->parent->index()].descendent) {
1972 join_states[node->index()].descendent = true;
1973 }
1974 else if (selected_nodes.contains(node)) {
1975 /* if parent is not an descendant of the frame, reattach the node */
1976 bke::node_detach_node(&ntree, node);
1977 bke::node_attach_node(&ntree, node, frame);
1978 join_states[node->index()].descendent = true;
1979 }
1980 }
1981 else if (selected_nodes.contains(node)) {
1982 bke::node_attach_node(&ntree, node, frame);
1983 join_states[node->index()].descendent = true;
1984 }
1985}
1986
1988{
1989 Vector<const bNode *> parents;
1990 for (const bNode *parent = node.parent; parent; parent = parent->parent) {
1991 parents.append(parent);
1992 }
1993 /* Reverse so that the root frame is the first element (if there is any). */
1994 std::reverse(parents.begin(), parents.end());
1995 return parents;
1996}
1997
1999{
2000 if (nodes.is_empty()) {
2001 return nullptr;
2002 }
2003 /* The common parent node also has to be a parent of the first node. */
2004 Vector<const bNode *> candidates = get_sorted_node_parents(*nodes[0]);
2005 for (const bNode *node : nodes.drop_front(1)) {
2006 const Vector<const bNode *> parents = get_sorted_node_parents(*node);
2007 /* Possibly shrink set of candidates so that it only contains the parents common with the
2008 * current node. */
2009 candidates.resize(std::min(candidates.size(), parents.size()));
2010 for (const int i : candidates.index_range()) {
2011 if (candidates[i] != parents[i]) {
2012 candidates.resize(i);
2013 break;
2014 }
2015 }
2016 if (candidates.is_empty()) {
2017 break;
2018 }
2019 }
2020 if (candidates.is_empty()) {
2021 return nullptr;
2022 }
2023 return candidates.last();
2024}
2025
2026static int node_join_exec(bContext *C, wmOperator * /*op*/)
2027{
2028 Main &bmain = *CTX_data_main(C);
2029 SpaceNode &snode = *CTX_wm_space_node(C);
2030 bNodeTree &ntree = *snode.edittree;
2031
2032 const VectorSet<bNode *> selected_nodes = get_selected_nodes(ntree);
2033
2034 bNode *frame_node = bke::node_add_static_node(C, &ntree, NODE_FRAME);
2035 bke::node_set_active(&ntree, frame_node);
2036 frame_node->parent = const_cast<bNode *>(find_common_parent_node(selected_nodes.as_span()));
2037
2038 ntree.ensure_topology_cache();
2039
2040 Array<NodeJoinState> join_states(ntree.all_nodes().size(), NodeJoinState{false, false});
2041
2042 for (bNode *node : ntree.all_nodes()) {
2043 if (!join_states[node->index()].done) {
2044 node_join_attach_recursive(ntree, join_states, node, frame_node, selected_nodes);
2045 }
2046 }
2047
2049 ED_node_tree_propagate_change(C, &bmain, snode.edittree);
2051
2052 return OPERATOR_FINISHED;
2053}
2054
2056{
2057 /* identifiers */
2058 ot->name = "Join Nodes";
2059 ot->description = "Attach selected nodes to a new common frame";
2060 ot->idname = "NODE_OT_join";
2061
2062 /* api callbacks */
2065
2066 /* flags */
2068}
2069
2072/* -------------------------------------------------------------------- */
2076static bNode *node_find_frame_to_attach(ARegion &region, bNodeTree &ntree, const int2 mouse_xy)
2077{
2078 /* convert mouse coordinates to v2d space */
2079 float2 cursor;
2080 UI_view2d_region_to_view(&region.v2d, mouse_xy.x, mouse_xy.y, &cursor.x, &cursor.y);
2081
2082 for (bNode *frame : tree_draw_order_calc_nodes_reversed(ntree)) {
2083 /* skip selected, those are the nodes we want to attach */
2084 if (!frame->is_frame() || (frame->flag & NODE_SELECT)) {
2085 continue;
2086 }
2087 if (BLI_rctf_isect_pt_v(&frame->runtime->totr, cursor)) {
2088 return frame;
2089 }
2090 }
2091
2092 return nullptr;
2093}
2094
2095static int node_attach_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *event)
2096{
2097 ARegion &region = *CTX_wm_region(C);
2098 SpaceNode &snode = *CTX_wm_space_node(C);
2099 bNodeTree &ntree = *snode.edittree;
2100 bNode *frame = node_find_frame_to_attach(region, ntree, event->mval);
2101 if (frame == nullptr) {
2102 /* Return "finished" so that auto offset operator macros can work. */
2103 return OPERATOR_FINISHED;
2104 }
2105
2106 bool changed = false;
2107
2108 for (bNode *node : tree_draw_order_calc_nodes_reversed(*snode.edittree)) {
2109 if (!(node->flag & NODE_SELECT)) {
2110 continue;
2111 }
2112
2113 /* Disallow moving a parent into its child. */
2114 if (node->is_frame() && bke::node_is_parent_and_child(node, frame)) {
2115 continue;
2116 }
2117
2118 if (node->parent == nullptr) {
2119 bke::node_attach_node(&ntree, node, frame);
2120 changed = true;
2121 continue;
2122 }
2123
2124 if (node->parent == frame) {
2125 continue;
2126 }
2127
2128 /* Attach nodes which share parent with the frame. */
2129 const bool share_parent = bke::node_is_parent_and_child(node->parent, frame);
2130 if (!share_parent) {
2131 continue;
2132 }
2133
2134 bke::node_detach_node(&ntree, node);
2135 bke::node_attach_node(&ntree, node, frame);
2136 changed = true;
2137 }
2138
2139 if (changed) {
2142 }
2143
2144 return OPERATOR_FINISHED;
2145}
2146
2148{
2149 ot->name = "Attach Nodes";
2150 ot->description = "Attach active node to a frame";
2151 ot->idname = "NODE_OT_attach";
2152
2155
2157}
2158
2161/* -------------------------------------------------------------------- */
2166 bool done;
2168};
2169
2171 MutableSpan<NodeDetachstate> detach_states,
2172 bNode *node)
2173{
2174 detach_states[node->index()].done = true;
2175
2176 if (node->parent) {
2177 /* Call recursively. */
2178 if (!detach_states[node->parent->index()].done) {
2179 node_detach_recursive(ntree, detach_states, node->parent);
2180 }
2181
2182 /* In any case: if the parent is a descendant, so is the child. */
2183 if (detach_states[node->parent->index()].descendent) {
2184 detach_states[node->index()].descendent = true;
2185 }
2186 else if (node->flag & NODE_SELECT) {
2187 /* If parent is not a descendant of a selected node, detach. */
2188 bke::node_detach_node(&ntree, node);
2189 detach_states[node->index()].descendent = true;
2190 }
2191 }
2192 else if (node->flag & NODE_SELECT) {
2193 detach_states[node->index()].descendent = true;
2194 }
2195}
2196
2197/* Detach the root nodes in the current selection. */
2198static int node_detach_exec(bContext *C, wmOperator * /*op*/)
2199{
2200 SpaceNode &snode = *CTX_wm_space_node(C);
2201 bNodeTree &ntree = *snode.edittree;
2202
2203 Array<NodeDetachstate> detach_states(ntree.all_nodes().size(), NodeDetachstate{false, false});
2204
2205 /* Detach nodes recursively. Relative order is preserved here. */
2206 for (bNode *node : ntree.all_nodes()) {
2207 if (!detach_states[node->index()].done) {
2208 node_detach_recursive(ntree, detach_states, node);
2209 }
2210 }
2211
2214
2215 return OPERATOR_FINISHED;
2216}
2217
2219{
2220 /* identifiers */
2221 ot->name = "Detach Nodes";
2222 ot->description = "Detach selected nodes from parents";
2223 ot->idname = "NODE_OT_detach";
2224
2225 /* api callbacks */
2228
2229 /* flags */
2231}
2232
2235/* -------------------------------------------------------------------- */
2240{
2241 bNode *selected_node = nullptr;
2242 int selected_node_count = 0;
2243 for (bNode *node : node_tree.all_nodes()) {
2244 if (node->flag & SELECT) {
2245 selected_node = node;
2246 selected_node_count++;
2247 }
2248 if (selected_node_count > 1) {
2249 return nullptr;
2250 }
2251 }
2252 if (!selected_node) {
2253 return nullptr;
2254 }
2255 if (selected_node->input_sockets().is_empty() || selected_node->output_sockets().is_empty()) {
2256 return nullptr;
2257 }
2258 return selected_node;
2259}
2260
2262{
2263 const bNodeSocket *main_input = get_main_socket(tree, node, SOCK_IN);
2264 const bNodeSocket *main_output = get_main_socket(tree, node, SOCK_IN);
2265 if (ELEM(nullptr, main_input, main_output)) {
2266 return false;
2267 }
2268 if (node.is_reroute()) {
2269 return true;
2270 }
2271 if (!tree.typeinfo->validate_link) {
2272 return true;
2273 }
2274 if (!tree.typeinfo->validate_link(eNodeSocketDatatype(link.fromsock->type),
2275 eNodeSocketDatatype(main_input->type)))
2276 {
2277 return false;
2278 }
2279 if (!tree.typeinfo->validate_link(eNodeSocketDatatype(main_output->type),
2281 {
2282 return false;
2283 }
2284 return true;
2285}
2286
2288 const ARegion &region,
2289 const bool attach_enabled,
2290 const bool is_new_node)
2291{
2292 bNodeTree &node_tree = *snode.edittree;
2293 node_tree.ensure_topology_cache();
2294
2296
2298 if (!node_to_insert) {
2299 return;
2300 }
2301 Vector<bNodeSocket *> already_linked_sockets;
2302 for (bNodeSocket *socket : node_to_insert->input_sockets()) {
2303 already_linked_sockets.extend(socket->directly_linked_sockets());
2304 }
2305 for (bNodeSocket *socket : node_to_insert->output_sockets()) {
2306 already_linked_sockets.extend(socket->directly_linked_sockets());
2307 }
2308 if (!is_new_node && !already_linked_sockets.is_empty()) {
2309 return;
2310 }
2311
2312 /* Find link to select/highlight. */
2313 bNodeLink *selink = nullptr;
2314 float dist_best = FLT_MAX;
2315 LISTBASE_FOREACH (bNodeLink *, link, &node_tree.links) {
2316 if (node_link_is_hidden_or_dimmed(region.v2d, *link)) {
2317 continue;
2318 }
2319 if (ELEM(node_to_insert, link->fromnode, link->tonode)) {
2320 /* Don't insert on a link that is connected to the node already. */
2321 continue;
2322 }
2323 if (is_new_node && !already_linked_sockets.is_empty()) {
2324 /* Only allow links coming from or going to the already linked socket after
2325 * link-drag-search. */
2326 bool is_linked_to_linked = false;
2327 for (const bNodeSocket *socket : already_linked_sockets) {
2328 if (ELEM(socket, link->fromsock, link->tosock)) {
2329 is_linked_to_linked = true;
2330 break;
2331 }
2332 }
2333 if (!is_linked_to_linked) {
2334 continue;
2335 }
2336 }
2337
2338 std::array<float2, NODE_LINK_RESOL + 1> coords;
2339 node_link_bezier_points_evaluated(*link, coords);
2340 float dist = FLT_MAX;
2341
2342 /* Loop over link coords to find shortest dist to upper left node edge of a intersected line
2343 * segment. */
2344 for (int i = 0; i < NODE_LINK_RESOL; i++) {
2345 /* Check if the node rectangle intersects the line from this point to next one. */
2346 if (BLI_rctf_isect_segment(&node_to_insert->runtime->totr, coords[i], coords[i + 1])) {
2347 /* Store the shortest distance to the upper left edge of all intersections found so far. */
2348 const float node_xy[] = {node_to_insert->runtime->totr.xmin,
2349 node_to_insert->runtime->totr.ymax};
2350
2351 /* To be precise coords should be clipped by `select->totr`, but not done since there's no
2352 * real noticeable difference. */
2353 dist = min_ff(dist_squared_to_line_segment_v2(node_xy, coords[i], coords[i + 1]), dist);
2354 }
2355 }
2356
2357 /* We want the link with the shortest distance to node center. */
2358 if (dist < dist_best) {
2359 dist_best = dist;
2360 selink = link;
2361 }
2362 }
2363
2364 if (selink) {
2365 selink->flag |= NODE_LINK_INSERT_TARGET;
2366 if (!attach_enabled || !node_can_be_inserted_on_link(node_tree, *node_to_insert, *selink)) {
2368 }
2369 }
2370}
2371
2378
2379void node_insert_on_link_flags(Main &bmain, SpaceNode &snode, bool is_new_node)
2380{
2381 bNodeTree &node_tree = *snode.edittree;
2382 node_tree.ensure_topology_cache();
2384 if (!node_to_insert) {
2385 return;
2386 }
2387
2388 /* Find link to insert on. */
2389 bNodeTree &ntree = *snode.edittree;
2390 bNodeLink *old_link = nullptr;
2391 LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
2392 if (link->flag & NODE_LINK_INSERT_TARGET) {
2393 if (!(link->flag & NODE_LINK_INSERT_TARGET_INVALID)) {
2394 old_link = link;
2395 }
2396 break;
2397 }
2398 }
2400 if (old_link == nullptr) {
2401 return;
2402 }
2403
2404 bNodeSocket *best_input = nullptr;
2405 if (is_new_node) {
2406 for (bNodeSocket *socket : node_to_insert->input_sockets()) {
2407 if (!socket->directly_linked_sockets().is_empty()) {
2408 best_input = socket;
2409 break;
2410 }
2411 }
2412 }
2413 if (!best_input) {
2414 best_input = get_main_socket(ntree, *node_to_insert, SOCK_IN);
2415 }
2416 bNodeSocket *best_output = nullptr;
2417 if (is_new_node) {
2418 for (bNodeSocket *socket : node_to_insert->output_sockets()) {
2419 if (!socket->directly_linked_sockets().is_empty()) {
2420 best_output = socket;
2421 break;
2422 }
2423 }
2424 }
2425 if (!best_output) {
2426 best_output = get_main_socket(ntree, *node_to_insert, SOCK_OUT);
2427 }
2428
2429 if (node_to_insert->type != NODE_REROUTE) {
2430 /* Ignore main sockets when the types don't match. */
2431 if (best_input != nullptr && ntree.typeinfo->validate_link != nullptr &&
2432 !ntree.typeinfo->validate_link(static_cast<eNodeSocketDatatype>(old_link->fromsock->type),
2433 static_cast<eNodeSocketDatatype>(best_input->type)))
2434 {
2435 best_input = nullptr;
2436 }
2437 if (best_output != nullptr && ntree.typeinfo->validate_link != nullptr &&
2438 !ntree.typeinfo->validate_link(static_cast<eNodeSocketDatatype>(best_output->type),
2439 static_cast<eNodeSocketDatatype>(old_link->tosock->type)))
2440 {
2441 best_output = nullptr;
2442 }
2443 }
2444
2445 bNode *from_node = old_link->fromnode;
2446 bNodeSocket *from_socket = old_link->fromsock;
2447 bNode *to_node = old_link->tonode;
2448
2449 const bool best_input_is_linked = best_input && best_input->is_directly_linked();
2450
2451 if (best_output != nullptr) {
2452 /* Relink the "start" of the existing link to the newly inserted node. */
2453 old_link->fromnode = node_to_insert;
2454 old_link->fromsock = best_output;
2456 }
2457 else {
2458 bke::node_remove_link(&ntree, old_link);
2459 }
2460
2461 if (best_input != nullptr) {
2462 /* Don't change an existing link. */
2463 if (!best_input_is_linked) {
2464 /* Add a new link that connects the node on the left to the newly inserted node. */
2465 bke::node_add_link(&ntree, from_node, from_socket, node_to_insert, best_input);
2466 }
2467 }
2468
2469 /* Set up insert offset data, it needs stuff from here. */
2470 if (U.uiflag & USER_NODE_AUTO_OFFSET) {
2471 BLI_assert(snode.runtime->iofsd == nullptr);
2472 NodeInsertOfsData *iofsd = MEM_cnew<NodeInsertOfsData>(__func__);
2473
2474 iofsd->insert = node_to_insert;
2475 iofsd->prev = from_node;
2476 iofsd->next = to_node;
2477
2478 snode.runtime->iofsd = iofsd;
2479 }
2480
2481 ED_node_tree_propagate_change(nullptr, &bmain, &ntree);
2482}
2483
2486/* -------------------------------------------------------------------- */
2490static int get_main_socket_priority(const bNodeSocket *socket)
2491{
2492 switch ((eNodeSocketDatatype)socket->type) {
2493 case SOCK_CUSTOM:
2494 return 0;
2495 case SOCK_BOOLEAN:
2496 return 1;
2497 case SOCK_INT:
2498 return 2;
2499 case SOCK_FLOAT:
2500 return 3;
2501 case SOCK_VECTOR:
2502 return 4;
2503 case SOCK_RGBA:
2504 return 5;
2505 case SOCK_STRING:
2506 case SOCK_SHADER:
2507 case SOCK_OBJECT:
2508 case SOCK_IMAGE:
2509 case SOCK_ROTATION:
2510 case SOCK_MATRIX:
2511 case SOCK_GEOMETRY:
2512 case SOCK_COLLECTION:
2513 case SOCK_TEXTURE:
2514 case SOCK_MATERIAL:
2515 case SOCK_MENU:
2516 return 6;
2517 }
2518 return -1;
2519}
2520
2522{
2523 ListBase *sockets = (in_out == SOCK_IN) ? &node.inputs : &node.outputs;
2524
2525 /* Try to get the main socket based on the socket declaration. */
2526 bke::node_declaration_ensure(&ntree, &node);
2527 const nodes::NodeDeclaration *node_decl = node.declaration();
2528 if (node_decl != nullptr) {
2529 Span<nodes::SocketDeclaration *> socket_decls = (in_out == SOCK_IN) ? node_decl->inputs :
2530 node_decl->outputs;
2531 int index;
2532 LISTBASE_FOREACH_INDEX (bNodeSocket *, socket, sockets, index) {
2533 const nodes::SocketDeclaration &socket_decl = *socket_decls[index];
2534 if (!socket->is_visible()) {
2535 continue;
2536 }
2537 if (socket_decl.is_default_link_socket) {
2538 return socket;
2539 }
2540 }
2541 }
2542
2543 /* Find priority range. */
2544 int maxpriority = -1;
2545 LISTBASE_FOREACH (bNodeSocket *, sock, sockets) {
2546 if (sock->flag & SOCK_UNAVAIL) {
2547 continue;
2548 }
2549 maxpriority = max_ii(get_main_socket_priority(sock), maxpriority);
2550 }
2551
2552 /* Try all priorities, starting from 'highest'. */
2553 for (int priority = maxpriority; priority >= 0; priority--) {
2554 LISTBASE_FOREACH (bNodeSocket *, sock, sockets) {
2555 if (!!sock->is_visible() && priority == get_main_socket_priority(sock)) {
2556 return sock;
2557 }
2558 }
2559 }
2560
2561 /* No visible sockets, unhide first of highest priority. */
2562 for (int priority = maxpriority; priority >= 0; priority--) {
2563 LISTBASE_FOREACH (bNodeSocket *, sock, sockets) {
2564 if (sock->flag & SOCK_UNAVAIL) {
2565 continue;
2566 }
2567 if (priority == get_main_socket_priority(sock)) {
2568 sock->flag &= ~SOCK_HIDDEN;
2569 return sock;
2570 }
2571 }
2572 }
2573
2574 return nullptr;
2575}
2576
2577static bool node_parents_offset_flag_enable_cb(bNode *parent, void * /*userdata*/)
2578{
2579 /* NODE_TEST is used to flag nodes that shouldn't be offset (again) */
2580 parent->flag |= NODE_TEST;
2581
2582 return true;
2583}
2584
2585static void node_offset_apply(bNode &node, const float offset_x)
2586{
2587 /* NODE_TEST is used to flag nodes that shouldn't be offset (again) */
2588 if ((node.flag & NODE_TEST) == 0) {
2589 node.runtime->anim_ofsx = (offset_x / UI_SCALE_FAC);
2590 node.flag |= NODE_TEST;
2591 }
2592}
2593
2594static void node_parent_offset_apply(NodeInsertOfsData *data, bNode *parent, const float offset_x)
2595{
2596 node_offset_apply(*parent, offset_x);
2597
2598 /* Flag all children as offset to prevent them from being offset
2599 * separately (they've already moved with the parent). */
2600 for (bNode *node : data->ntree->all_nodes()) {
2601 if (bke::node_is_parent_and_child(parent, node)) {
2602 /* NODE_TEST is used to flag nodes that shouldn't be offset (again) */
2603 node->flag |= NODE_TEST;
2604 }
2605 }
2606}
2607
2608#define NODE_INSOFS_ANIM_DURATION 0.25f
2609
2615 bNode *tonode,
2616 void *userdata,
2617 const bool reversed)
2618{
2619 NodeInsertOfsData *data = (NodeInsertOfsData *)userdata;
2620 bNode *ofs_node = reversed ? fromnode : tonode;
2621
2622 if (ofs_node->parent && ofs_node->parent != data->insert_parent) {
2623 node_offset_apply(*ofs_node->parent, data->offset_x);
2624 }
2625 else {
2626 node_offset_apply(*ofs_node, data->offset_x);
2627 }
2628
2629 return true;
2630}
2631
2636 const bNode *parent,
2637 NodeInsertOfsData *data,
2638 const bool reversed)
2639{
2640 for (bNode *node : ntree->all_nodes()) {
2641 if (bke::node_is_parent_and_child(parent, node)) {
2643 ntree, node, node_link_insert_offset_frame_chain_cb, data, reversed);
2644 }
2645 }
2646}
2647
2653 bNode *tonode,
2654 void *userdata,
2655 const bool reversed)
2656{
2657 NodeInsertOfsData *data = (NodeInsertOfsData *)userdata;
2658 bNode *ofs_node = reversed ? fromnode : tonode;
2659
2660 if (data->insert_parent) {
2661 if (ofs_node->parent && (ofs_node->parent->flag & NODE_TEST) == 0) {
2662 node_parent_offset_apply(data, ofs_node->parent, data->offset_x);
2663 node_link_insert_offset_frame_chains(data->ntree, ofs_node->parent, data, reversed);
2664 }
2665 else {
2666 node_offset_apply(*ofs_node, data->offset_x);
2667 }
2668
2669 if (!bke::node_is_parent_and_child(data->insert_parent, ofs_node)) {
2670 data->insert_parent = nullptr;
2671 }
2672 }
2673 else if (ofs_node->parent) {
2674 bNode *node = bke::node_find_root_parent(ofs_node);
2675 node_offset_apply(*node, data->offset_x);
2676 }
2677 else {
2678 node_offset_apply(*ofs_node, data->offset_x);
2679 }
2680
2681 return true;
2682}
2683
2685 ARegion *region,
2686 const int mouse_xy[2],
2687 const bool right_alignment)
2688{
2689 bNodeTree *ntree = iofsd->ntree;
2690 bNode &insert = *iofsd->insert;
2691 bNode *prev = iofsd->prev, *next = iofsd->next;
2692 bNode *init_parent = insert.parent; /* store old insert.parent for restoring later */
2693
2694 const float min_margin = U.node_margin * UI_SCALE_FAC;
2695 const float width = NODE_WIDTH(insert);
2696 const bool needs_alignment = (next->runtime->totr.xmin - prev->runtime->totr.xmax) <
2697 (width + (min_margin * 2.0f));
2698
2699 float margin = width;
2700
2701 /* NODE_TEST will be used later, so disable for all nodes */
2703
2704 /* `insert.totr` isn't updated yet,
2705 * so `totr_insert` is used to get the correct world-space coords. */
2706 rctf totr_insert;
2707 node_to_updated_rect(insert, totr_insert);
2708
2709 /* Frame attachment wasn't handled yet so we search the frame that the node will be attached to
2710 * later. */
2711 insert.parent = node_find_frame_to_attach(*region, *ntree, mouse_xy);
2712
2713 /* This makes sure nodes are also correctly offset when inserting a node on top of a frame
2714 * without actually making it a part of the frame (because mouse isn't intersecting it)
2715 * - logic here is similar to node_find_frame_to_attach. */
2716 if (!insert.parent ||
2717 (prev->parent && (prev->parent == next->parent) && (prev->parent != insert.parent)))
2718 {
2719 rctf totr_frame;
2720
2721 /* check nodes front to back */
2722 for (bNode *frame : tree_draw_order_calc_nodes_reversed(*ntree)) {
2723 /* skip selected, those are the nodes we want to attach */
2724 if ((frame->type != NODE_FRAME) || (frame->flag & NODE_SELECT)) {
2725 continue;
2726 }
2727
2728 /* for some reason frame y coords aren't correct yet */
2729 node_to_updated_rect(*frame, totr_frame);
2730
2731 if (BLI_rctf_isect_x(&totr_frame, totr_insert.xmin) &&
2732 BLI_rctf_isect_x(&totr_frame, totr_insert.xmax))
2733 {
2734 if (BLI_rctf_isect_y(&totr_frame, totr_insert.ymin) ||
2735 BLI_rctf_isect_y(&totr_frame, totr_insert.ymax))
2736 {
2737 /* frame isn't insert.parent actually, but this is needed to make offsetting
2738 * nodes work correctly for above checked cases (it is restored later) */
2739 insert.parent = frame;
2740 break;
2741 }
2742 }
2743 }
2744 }
2745
2746 /* *** ensure offset at the left (or right for right_alignment case) of insert_node *** */
2747
2748 float dist = right_alignment ? totr_insert.xmin - prev->runtime->totr.xmax :
2749 next->runtime->totr.xmin - totr_insert.xmax;
2750 /* distance between insert_node and prev is smaller than min margin */
2751 if (dist < min_margin) {
2752 const float addval = (min_margin - dist) * (right_alignment ? 1.0f : -1.0f);
2753
2754 node_offset_apply(insert, addval);
2755
2756 totr_insert.xmin += addval;
2757 totr_insert.xmax += addval;
2758 margin += min_margin;
2759 }
2760
2761 /* *** ensure offset at the right (or left for right_alignment case) of insert_node *** */
2762
2763 dist = right_alignment ? next->runtime->totr.xmin - totr_insert.xmax :
2764 totr_insert.xmin - prev->runtime->totr.xmax;
2765 /* distance between insert_node and next is smaller than min margin */
2766 if (dist < min_margin) {
2767 const float addval = (min_margin - dist) * (right_alignment ? 1.0f : -1.0f);
2768 if (needs_alignment) {
2769 bNode *offs_node = right_alignment ? next : prev;
2770 if (!offs_node->parent || offs_node->parent == insert.parent ||
2772 {
2773 node_offset_apply(*offs_node, addval);
2774 }
2775 else if (!insert.parent && offs_node->parent) {
2776 node_offset_apply(*bke::node_find_root_parent(offs_node), addval);
2777 }
2778 margin = addval;
2779 }
2780 /* enough room is available, but we want to ensure the min margin at the right */
2781 else {
2782 /* offset inserted node so that min margin is kept at the right */
2783 node_offset_apply(insert, -addval);
2784 }
2785 }
2786
2787 if (needs_alignment) {
2788 iofsd->insert_parent = insert.parent;
2789 iofsd->offset_x = margin;
2790
2791 /* flag all parents of insert as offset to prevent them from being offset */
2793 /* iterate over entire chain and apply offsets */
2795 right_alignment ? next : prev,
2797 iofsd,
2798 !right_alignment);
2799 }
2800
2801 insert.parent = init_parent;
2802}
2803
2807static int node_insert_offset_modal(bContext *C, wmOperator *op, const wmEvent *event)
2808{
2809 SpaceNode *snode = CTX_wm_space_node(C);
2810 NodeInsertOfsData *iofsd = static_cast<NodeInsertOfsData *>(op->customdata);
2811 bool redraw = false;
2812
2813 if (!snode || event->type != TIMER || iofsd == nullptr || iofsd->anim_timer != event->customdata)
2814 {
2815 return OPERATOR_PASS_THROUGH;
2816 }
2817
2818 const float duration = float(iofsd->anim_timer->time_duration);
2819
2820 /* handle animation - do this before possibly aborting due to duration, since
2821 * main thread might be so busy that node hasn't reached final position yet */
2822 for (bNode *node : snode->edittree->all_nodes()) {
2823 if (UNLIKELY(node->runtime->anim_ofsx)) {
2824 const float prev_duration = duration - float(iofsd->anim_timer->time_delta);
2825 /* Clamp duration to not overshoot. */
2826 const float clamped_duration = math::min(duration, NODE_INSOFS_ANIM_DURATION);
2827 if (prev_duration < clamped_duration) {
2828 const float offset_step = node->runtime->anim_ofsx *
2830 clamped_duration, 0.0f, 1.0f, NODE_INSOFS_ANIM_DURATION) -
2832 prev_duration, 0.0f, 1.0f, NODE_INSOFS_ANIM_DURATION));
2833 node->locx += offset_step;
2834 redraw = true;
2835 }
2836 }
2837 }
2838 if (redraw) {
2840 }
2841
2842 /* end timer + free insert offset data */
2843 if (duration > NODE_INSOFS_ANIM_DURATION) {
2844 WM_event_timer_remove(CTX_wm_manager(C), nullptr, iofsd->anim_timer);
2845
2846 for (bNode *node : snode->edittree->all_nodes()) {
2847 node->runtime->anim_ofsx = 0.0f;
2848 }
2849
2850 MEM_freeN(iofsd);
2851
2853 }
2854
2856}
2857
2858#undef NODE_INSOFS_ANIM_DURATION
2859
2860static int node_insert_offset_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2861{
2862 const SpaceNode *snode = CTX_wm_space_node(C);
2863 NodeInsertOfsData *iofsd = snode->runtime->iofsd;
2864 snode->runtime->iofsd = nullptr;
2865 op->customdata = iofsd;
2866
2867 if (!iofsd || !iofsd->insert) {
2868 return OPERATOR_CANCELLED;
2869 }
2870
2872
2873 iofsd->ntree = snode->edittree;
2875
2877 iofsd, CTX_wm_region(C), event->mval, (snode->insert_ofs_dir == SNODE_INSERTOFS_DIR_RIGHT));
2878
2879 /* add temp handler */
2881
2883}
2884
2886{
2887 /* identifiers */
2888 ot->name = "Insert Offset";
2889 ot->description = "Automatically offset nodes on insertion";
2890 ot->idname = "NODE_OT_insert_offset";
2891
2892 /* callbacks */
2896
2897 /* flags */
2899}
2900
2903} // namespace blender::ed::space_node
SpaceNode * CTX_wm_space_node(const bContext *C)
ScrArea * CTX_wm_area(const bContext *C)
wmWindow * CTX_wm_window(const bContext *C)
Main * CTX_data_main(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
#define NODE_REROUTE
Definition BKE_node.hh:804
#define CMP_NODE_VIEWER
Definition BKE_node.hh:1012
#define GEO_NODE_VIEWER
Definition BKE_node.hh:1201
#define NODE_FRAME
Definition BKE_node.hh:803
void BKE_ntree_update_tag_link_changed(bNodeTree *ntree)
void BKE_ntree_update_tag_link_removed(bNodeTree *ntree)
void BKE_ntree_update_tag_node_property(bNodeTree *ntree, bNode *node)
void BKE_ntree_update_tag_link_added(bNodeTree *ntree, bNodeLink *link)
#define BLI_assert(a)
Definition BLI_assert.h:50
float BLI_easing_cubic_ease_in_out(float time, float begin, float change, float duration)
Definition easing.c:108
void BLI_kdtree_nd_ insert(KDTree *tree, int index, const float co[KD_DIMS]) ATTR_NONNULL(1
BLI_INLINE bool BLI_listbase_is_empty(const struct ListBase *lb)
#define LISTBASE_FOREACH(type, var, list)
#define LISTBASE_FOREACH_MUTABLE(type, var, list)
#define LISTBASE_FOREACH_INDEX(type, var, list, index_var)
void BLI_addtail(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:110
int BLI_listbase_count(const struct ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
MINLINE float min_ff(float a, float b)
MINLINE int max_ii(int a, int b)
#define M_PI
float dist_seg_seg_v2(const float a1[3], const float a2[3], const float b1[3], const float b2[3])
float dist_squared_to_line_segment_v2(const float p[2], const float l1[2], const float l2[2])
Definition math_geom.cc:289
bool BLI_rctf_isect_x(const rctf *rect, float x)
Definition rct.c:93
bool BLI_rctf_isect_pt_v(const struct rctf *rect, const float xy[2])
bool BLI_rctf_isect(const struct rctf *src1, const struct rctf *src2, struct rctf *dest)
bool BLI_rctf_isect_y(const rctf *rect, float y)
Definition rct.c:104
bool BLI_rctf_isect_segment(const struct rctf *rect, const float s1[2], const float s2[2])
void BLI_rctf_pad(struct rctf *rect, float pad_x, float pad_y)
Definition rct.c:631
BLI_INLINE float BLI_rctf_size_x(const struct rctf *rct)
Definition BLI_rect.h:197
bool BLI_rctf_inside_rctf(const rctf *rct_a, const rctf *rct_b)
Definition rct.c:193
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition BLI_rect.h:201
unsigned char uchar
#define UNLIKELY(x)
#define ELEM(...)
#define STREQ(a, b)
@ CD_AUTO_FROM_NAME
@ NTREE_CUSTOM
@ NTREE_GEOMETRY
@ NODE_TEST
@ NODE_DO_OUTPUT
@ NODE_SELECT
@ NODE_LINK_TEMP_HIGHLIGHT
@ NODE_LINK_MUTED
@ NODE_LINK_INSERT_TARGET
@ NODE_LINK_INSERT_TARGET_INVALID
@ NODE_LINK_VALID
eNodeSocketInOut
@ SOCK_OUT
@ SOCK_IN
@ SOCK_IS_LINKED
@ SOCK_MULTI_INPUT
@ SOCK_UNAVAIL
eNodeSocketDatatype
@ SOCK_INT
@ SOCK_TEXTURE
@ SOCK_VECTOR
@ SOCK_BOOLEAN
@ SOCK_MATERIAL
@ SOCK_SHADER
@ SOCK_MATRIX
@ SOCK_FLOAT
@ SOCK_IMAGE
@ SOCK_COLLECTION
@ SOCK_CUSTOM
@ SOCK_GEOMETRY
@ SOCK_ROTATION
@ SOCK_OBJECT
@ SOCK_STRING
@ SOCK_RGBA
@ SOCK_MENU
@ SNODE_INSERTOFS_DIR_RIGHT
@ SNODE_GEOMETRY_TOOL
@ USER_NODE_AUTO_OFFSET
#define UI_SCALE_FAC
#define UI_INV_SCALE_FAC
@ OPERATOR_RUNNING_MODAL
@ OPERATOR_PASS_THROUGH
void ED_node_tree_propagate_change(const bContext *C, Main *bmain, bNodeTree *ntree)
Definition node_edit.cc:492
bool ED_node_is_compositor(const SpaceNode *snode)
Definition node_edit.cc:523
#define NODE_EDGE_PAN_OUTSIDE_PAD
Definition ED_node_c.hh:37
#define NODE_EDGE_PAN_INSIDE_PAD
Definition ED_node_c.hh:36
#define NODE_EDGE_PAN_MAX_SPEED
Definition ED_node_c.hh:39
#define NODE_EDGE_PAN_DELAY
Definition ED_node_c.hh:40
bool ED_node_is_geometry(SpaceNode *snode)
Definition node_edit.cc:538
#define NODE_EDGE_PAN_ZOOM_INFLUENCE
Definition ED_node_c.hh:41
#define NODE_EDGE_PAN_SPEED_RAMP
Definition ED_node_c.hh:38
void ED_preview_kill_jobs(wmWindowManager *wm, Main *bmain)
void ED_area_tag_redraw(ScrArea *area)
Definition area.cc:708
bool ED_operator_node_editable(bContext *C)
void ED_workspace_status_text(bContext *C, const char *str)
Definition area.cc:966
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:634
void * ED_region_draw_cb_activate(ARegionType *art, void(*draw)(const bContext *, ARegion *, void *), void *customdata, int type)
bool ED_region_draw_cb_exit(ARegionType *art, void *handle)
#define REGION_DRAW_POST_PIXEL
Read Guarded memory(de)allocation.
#define RNA_BEGIN(sptr, itemptr, propname)
#define RNA_END
PropertyFlag
Definition RNA_types.hh:201
@ PROP_SKIP_SAVE
Definition RNA_types.hh:245
@ PROP_HIDDEN
Definition RNA_types.hh:239
#define UI_PRECISION_FLOAT_MAX
#define UI_NO_ICON_OVERLAY_TEXT
void UI_icon_draw_ex(float x, float y, int icon_id, float aspect, float alpha, float desaturate, const uchar mono_color[4], bool mono_border, const IconTextOverlay *text_overlay, const bool inverted=false)
@ TH_TEXT
void UI_GetThemeColor4ubv(int colorid, unsigned char col[4])
void UI_view2d_edge_pan_operator_init(bContext *C, View2DEdgePanData *vpd, wmOperator *op)
void UI_view2d_edge_pan_cancel(bContext *C, View2DEdgePanData *vpd)
void UI_view2d_edge_pan_operator_properties_ex(wmOperatorType *ot, float inside_pad, float outside_pad, float speed_ramp, float max_speed, float delay, float zoom_influence)
void UI_view2d_region_to_view(const View2D *v2d, float x, float y, float *r_view_x, float *r_view_y) ATTR_NONNULL()
Definition view2d.cc:1663
void void UI_view2d_edge_pan_apply_event(bContext *C, View2DEdgePanData *vpd, const wmEvent *event)
void UI_view2d_region_to_view_rctf(const View2D *v2d, const rctf *rect_src, rctf *rect_dst) ATTR_NONNULL()
Definition view2d.cc:1670
@ OPTYPE_BLOCKING
Definition WM_types.hh:164
@ OPTYPE_DEPENDS_ON_CURSOR
Definition WM_types.hh:198
@ OPTYPE_UNDO
Definition WM_types.hh:162
@ OPTYPE_REGISTER
Definition WM_types.hh:160
#define NC_NODE
Definition WM_types.hh:361
@ KM_PRESS
Definition WM_types.hh:284
@ KM_RELEASE
Definition WM_types.hh:285
#define ND_DISPLAY
Definition WM_types.hh:458
ccl_device_inline float arc_length(float e2, float gamma)
unsigned int U
Definition btGjkEpa3.h:78
bool add(const Key &key)
Definition BLI_set.hh:248
bool is_empty() const
Definition BLI_set.hh:572
constexpr bool contains(const T &value) const
Definition BLI_span.hh:278
bool is_empty() const
Definition BLI_stack.hh:308
void push_multiple(Span< T > values)
Definition BLI_stack.hh:281
Span< Key > as_span() const
bool contains(const Key &key) const
int64_t size() const
void append(const T &value)
const T & last(const int64_t n=0) const
bool is_empty() const
IndexRange index_range() const
void resize(const int64_t new_size)
MutableSpan< T > as_mutable_span()
void extend(Span< T > array)
const T & first() const
Vector< SocketDeclaration * > inputs
Vector< SocketDeclaration * > outputs
local_group_size(16, 16) .push_constant(Type b
#define SELECT
OperationNode * node
#define ceilf(x)
KDTree_3d * tree
draw_view in_light_buf[] float
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
uint padding(uint offset, uint alignment)
void *(* MEM_mallocN)(size_t len, const char *str)
Definition mallocn.cc:44
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
static ulong * next
bool node_is_parent_and_child(const bNode *parent, const bNode *child)
Definition node.cc:2491
void node_attach_node(bNodeTree *ntree, bNode *node, bNode *parent)
Definition node.cc:3107
void node_set_active(bNodeTree *ntree, bNode *node)
Definition node.cc:3896
void node_detach_node(bNodeTree *ntree, bNode *node)
Definition node.cc:3122
void node_remove_socket_links(bNodeTree *ntree, bNodeSocket *sock)
Definition node.cc:2984
bNode * node_add_static_node(const bContext *C, bNodeTree *ntree, int type)
Definition node.cc:2642
void node_internal_relink(bNodeTree *ntree, bNode *node)
Definition node.cc:3019
void node_link_set_mute(bNodeTree *ntree, bNodeLink *link, const bool muted)
Definition node.cc:2975
bNodeLink * node_add_link(bNodeTree *ntree, bNode *fromnode, bNodeSocket *fromsock, bNode *tonode, bNodeSocket *tosock)
Definition node.cc:2912
bNode * node_find_root_parent(bNode *node)
Definition node.cc:2479
void node_tree_node_flag_set(const bNodeTree *ntree, int flag, bool enable)
Definition node.cc:3738
void node_chain_iterator(const bNodeTree *ntree, const bNode *node_start, bool(*callback)(bNode *, bNode *, void *, const bool), void *userdata, bool reversed)
Definition node.cc:2501
bNode * node_get_active(bNodeTree *ntree)
Definition node.cc:3849
void node_parents_iterator(bNode *node, bool(*callback)(bNode *, void *), void *userdata)
Definition node.cc:2583
void node_remove_link(bNodeTree *ntree, bNodeLink *link)
Definition node.cc:2958
int node_socket_link_limit(const bNodeSocket *sock)
Definition node.cc:3925
bool node_declaration_ensure(bNodeTree *ntree, bNode *node)
Definition node.cc:3992
int node_count_socket_links(const bNodeTree *ntree, const bNodeSocket *sock)
Definition node.cc:3838
std::optional< eCustomDataType > socket_type_to_custom_data_type(eNodeSocketDatatype type)
Definition node.cc:4379
static int view_socket(const bContext &C, SpaceNode &snode, bNodeTree &btree, bNode &bnode_to_view, bNodeSocket &bsocket_to_view)
static void position_viewer_node(bNodeTree &tree, bNode &viewer_node, const bNode &node_to_view, const ARegion &region)
static const bNode * find_overlapping_node(const bNodeTree &tree, const rctf &rect, const Span< const bNode * > ignored_nodes)
static int node_link_viewer(const bContext &C, bNode &bnode_to_view, bNodeSocket *bsocket_to_view)
static bNodeSocket * determine_socket_to_view(bNode &node_to_view)
static void finalize_viewer_link(const bContext &C, SpaceNode &snode, bNode &viewer_node, bNodeLink &viewer_link)
static bNodeSocket * node_link_viewer_get_socket(bNodeTree &ntree, bNode &viewer_node, bNodeSocket &src_socket)
static void remove_links_to_unavailable_viewer_sockets(bNodeTree &btree, bNode &viewer_node)
static bool is_viewer_socket(const bNodeSocket &socket)
static bool is_viewer_node(const bNode &node)
static bool is_viewer_socket_in_viewer(const bNodeSocket &socket)
static Vector< float2 > get_viewer_node_position_candidates(const float2 initial, const float step_distance, const float max_distance)
static bool socket_can_be_viewed(const bNode &node, const bNodeSocket &socket)
static int get_default_viewer_type(const bContext *C)
void NODE_OT_parent_set(wmOperatorType *ot)
static int cut_links_exec(bContext *C, wmOperator *op)
static void draw_draglink_tooltip_activate(const ARegion &region, bNodeLinkDrag &nldrag)
void node_deselect_all_input_sockets(bNodeTree &node_tree, bool deselect_nodes)
static bNodeSocket * best_socket_input(bNodeTree *ntree, bNode *node, int num, int replace)
void node_insert_on_link_flags_set(SpaceNode &snode, const ARegion &region, bool attach_enabled, bool is_new_node)
static void node_swap_links(bNodeLinkDrag &nldrag, bNodeTree &ntree)
void tree_draw_order_update(bNodeTree &ntree)
Definition node_draw.cc:302
static int node_detach_exec(bContext *C, wmOperator *)
static int node_attach_invoke(bContext *C, wmOperator *, const wmEvent *event)
static bool need_drag_link_tooltip(const bNodeTree &node_tree, const bNodeLinkDrag &nldrag)
static bNodeLink create_drag_link(bNode &node, bNodeSocket &socket)
static void clear_picking_highlight(ListBase *links)
static void sort_multi_input_socket_links_with_drag(bNodeSocket &socket, bNodeLink &drag_link, const float2 &cursor)
bNode * add_static_node(const bContext &C, int type, const float2 &location)
Definition node_add.cc:93
static bool snode_autoconnect_input(SpaceNode &snode, bNode *node_fr, bNodeSocket *sock_fr, bNode *node_to, bNodeSocket *sock_to, int replace)
static std::unique_ptr< bNodeLinkDrag > node_link_init(ARegion &region, SpaceNode &snode, const float2 cursor, const bool detach)
static int node_parent_set_exec(bContext *C, wmOperator *)
void invoke_node_link_drag_add_menu(bContext &C, bNode &node, bNodeSocket &socket, const float2 &cursor)
void NODE_OT_detach(wmOperatorType *ot)
static void snode_autoconnect(SpaceNode &snode, const bool allow_multiple, const bool replace)
static Vector< const bNode * > get_sorted_node_parents(const bNode &node)
bool all_links_muted(const bNodeSocket &socket)
static bool socket_is_available(bNodeTree *, bNodeSocket *sock, const bool allow_used)
bNodeSocket * node_find_indicated_socket(SpaceNode &snode, ARegion &region, const float2 &cursor, const eNodeSocketInOut in_out)
static bNodeSocket * node_find_linkable_socket(const bNodeTree &ntree, const bNode *node, bNodeSocket *socket_to_match)
static void node_link_insert_offset_frame_chains(bNodeTree *ntree, const bNode *parent, NodeInsertOfsData *data, const bool reversed)
void NODE_OT_insert_offset(wmOperatorType *ot)
Array< bNode * > tree_draw_order_calc_nodes_reversed(bNodeTree &ntree)
Definition node_draw.cc:326
static int get_main_socket_priority(const bNodeSocket *socket)
float2 node_link_calculate_multi_input_position(const float2 &socket_position, const int index, const int total_inputs)
Definition node_edit.cc:118
void NODE_OT_links_detach(wmOperatorType *ot)
static bool dragged_links_are_detached(const bNodeLinkDrag &nldrag)
void NODE_OT_links_cut(wmOperatorType *ot)
static void pick_input_link_by_link_intersect(const bContext &C, wmOperator &op, bNodeLinkDrag &nldrag, const float2 &cursor)
static void node_detach_recursive(bNodeTree &ntree, MutableSpan< NodeDetachstate > detach_states, bNode *node)
static void node_displace_existing_links(bNodeLinkDrag &nldrag, bNodeTree &ntree)
static int mute_links_exec(bContext *C, wmOperator *op)
static void node_link_cancel(bContext *C, wmOperator *op)
static void pick_link(bNodeLinkDrag &nldrag, SpaceNode &snode, bNode *node, bNodeLink &link_to_pick)
static void displace_links(bNodeTree *ntree, const bNode *node, bNodeLink *inserted_link)
void NODE_OT_join(wmOperatorType *ot)
static bNode * node_find_frame_to_attach(ARegion &region, bNodeTree &ntree, const int2 mouse_xy)
static bool node_active_link_viewer_poll(bContext *C)
static bool node_can_be_inserted_on_link(bNodeTree &tree, bNode &node, const bNodeLink &link)
void NODE_OT_attach(wmOperatorType *ot)
bool node_link_is_hidden_or_dimmed(const View2D &v2d, const bNodeLink &link)
static int node_insert_offset_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static int node_active_link_viewer_exec(bContext *C, wmOperator *)
static void draw_draglink_tooltip_deactivate(const ARegion &region, bNodeLinkDrag &nldrag)
std::optional< float2 > link_path_intersection(const bNodeLink &link, const Span< float2 > path)
Definition node_add.cc:119
void NODE_OT_link_viewer(wmOperatorType *ot)
VectorSet< bNode * > get_selected_nodes(bNodeTree &node_tree)
void node_deselect_all_output_sockets(bNodeTree &node_tree, bool deselect_nodes)
static int detach_links_exec(bContext *C, wmOperator *)
static int node_make_link_exec(bContext *C, wmOperator *op)
void node_insert_on_link_flags_clear(bNodeTree &node_tree)
static void add_dragged_links_to_tree(bContext &C, bNodeLinkDrag &nldrag)
static int node_link_modal(bContext *C, wmOperator *op, const wmEvent *event)
void NODE_OT_link_make(wmOperatorType *ot)
void node_link_bezier_points_evaluated(const bNodeLink &link, std::array< float2, NODE_LINK_RESOL+1 > &coords)
Definition drawnode.cc:1791
wmKeyMap * node_link_modal_keymap(wmKeyConfig *keyconf)
static int node_join_exec(bContext *C, wmOperator *)
float2 node_to_view(const bNode &node, const float2 &co)
Definition node_draw.cc:354
static void node_link_insert_offset_ntree(NodeInsertOfsData *iofsd, ARegion *region, const int mouse_xy[2], const bool right_alignment)
static int node_insert_offset_modal(bContext *C, wmOperator *op, const wmEvent *event)
static bNodeSocket * best_socket_output(bNodeTree *ntree, bNode *node, bNodeSocket *sock_target, const bool allow_multiple)
static int node_socket_count_links(const bNodeTree &ntree, const bNodeSocket &socket)
static bool node_link_insert_offset_chain_cb(bNode *fromnode, bNode *tonode, void *userdata, const bool reversed)
void node_to_updated_rect(const bNode &node, rctf &r_rect)
Definition node_draw.cc:360
static bool should_create_drag_link_search_menu(const bNodeTree &node_tree, const bNodeLinkDrag &nldrag)
static int node_link_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static void node_link_find_socket(bContext &C, wmOperator &op, const float2 &cursor)
void NODE_OT_link(wmOperatorType *ot)
void NODE_OT_links_mute(wmOperatorType *ot)
static const bNode * find_common_parent_node(const Span< const bNode * > nodes)
bNodeSocket * get_main_socket(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_out)
static void node_join_attach_recursive(bNodeTree &ntree, MutableSpan< NodeJoinState > join_states, bNode *node, bNode *frame, const VectorSet< bNode * > &selected_nodes)
void update_multi_input_indices_for_removed_links(bNode &node)
static bool node_link_insert_offset_frame_chain_cb(bNode *fromnode, bNode *tonode, void *userdata, const bool reversed)
void node_insert_on_link_flags(Main &bmain, SpaceNode &snode, bool is_new_node)
static bool node_parents_offset_flag_enable_cb(bNode *parent, void *)
static void node_remove_existing_links_if_needed(bNodeLinkDrag &nldrag, bNodeTree &ntree)
static void node_offset_apply(bNode &node, const float offset_x)
static bNode * get_selected_node_for_insertion(bNodeTree &node_tree)
static void draw_draglink_tooltip(const bContext *, ARegion *, void *arg)
static void node_parent_offset_apply(NodeInsertOfsData *data, bNode *parent, const float offset_x)
void activate_geometry_node(Main &bmain, SpaceNode &snode, bNode &node)
T min(const T &a, const T &b)
void update_node_declaration_and_sockets(bNodeTree &ntree, bNode &node)
VecBase< float, 2 > float2
#define NODE_LINK_RESOL
#define NODE_WIDTH(node)
#define NODE_INSOFS_ANIM_DURATION
void RNA_float_get_array(PointerRNA *ptr, const char *name, float *values)
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
void RNA_float_set_array(PointerRNA *ptr, const char *name, const float *values)
PropertyRNA * RNA_def_collection_runtime(StructOrFunctionRNA *cont_, const char *identifier, StructRNA *type, const char *ui_name, const char *ui_description)
PropertyRNA * RNA_def_float_array(StructOrFunctionRNA *cont_, const char *identifier, const int len, const float *default_value, const float hardmin, const float hardmax, const char *ui_name, const char *ui_description, const float softmin, const float softmax)
PropertyRNA * RNA_def_boolean(StructOrFunctionRNA *cont_, const char *identifier, const bool default_value, const char *ui_name, const char *ui_description)
void RNA_def_property_flag(PropertyRNA *prop, PropertyFlag flag)
PropertyRNA * RNA_def_int(StructOrFunctionRNA *cont_, const char *identifier, const int default_value, const int hardmin, const int hardmax, const char *ui_name, const char *ui_description, const int softmin, const int softmax)
#define FLT_MAX
Definition stdcycles.h:14
void * last
void * first
SpaceNode_Runtime * runtime
struct bNodeTree * edittree
char geometry_nodes_type
bNodeSocketRuntimeHandle * runtime
bNodeSocketTypeHandle * typeinfo
struct bNodeSocket * next
char idname[64]
bNodeTreeTypeHandle * typeinfo
ListBase links
float locy
ListBase inputs
struct bNode * parent
float locx
bNodeRuntimeHandle * runtime
void * storage
int16_t ui_order
ListBase outputs
int16_t type
std::unique_ptr< bNodeLinkDrag > linkdrag
float xmax
float xmin
float ymax
float ymin
short val
Definition WM_types.hh:724
int mval[2]
Definition WM_types.hh:728
short prev_val
Definition WM_types.hh:779
short type
Definition WM_types.hh:722
void * customdata
Definition WM_types.hh:772
const void * modal_items
const char * name
Definition WM_types.hh:990
bool(* poll)(bContext *C) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1042
const char * idname
Definition WM_types.hh:992
int(* modal)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1036
int(* invoke)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1022
int(* exec)(bContext *C, wmOperator *op) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1006
const char * description
Definition WM_types.hh:996
StructRNA * srna
Definition WM_types.hh:1080
void(* cancel)(bContext *C, wmOperator *op)
Definition WM_types.hh:1028
struct PointerRNA * ptr
double time_delta
Definition WM_types.hh:927
double time_duration
Definition WM_types.hh:925
@ WM_CURSOR_KNIFE
Definition wm_cursors.hh:31
@ WM_CURSOR_MUTE
Definition wm_cursors.hh:59
void WM_event_drag_start_mval(const wmEvent *event, const ARegion *region, int r_mval[2])
wmEventHandler_Op * WM_event_add_modal_handler(bContext *C, wmOperator *op)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
@ TIMER
@ EVT_MODAL_MAP
@ MOUSEMOVE
wmOperatorType * ot
Definition wm_files.cc:4125
int WM_gesture_lines_modal(bContext *C, wmOperator *op, const wmEvent *event)
void WM_gesture_lines_cancel(bContext *C, wmOperator *op)
int WM_gesture_lines_invoke(bContext *C, wmOperator *op, const wmEvent *event)
wmKeyMap * WM_modalkeymap_ensure(wmKeyConfig *keyconf, const char *idname, const EnumPropertyItem *items)
Definition wm_keymap.cc:933
void WM_modalkeymap_assign(wmKeyMap *km, const char *opname)
wmKeyMap * WM_modalkeymap_find(wmKeyConfig *keyconf, const char *idname)
Definition wm_keymap.cc:960
void WM_event_timer_remove(wmWindowManager *wm, wmWindow *, wmTimer *timer)
wmTimer * WM_event_timer_add(wmWindowManager *wm, wmWindow *win, const int event_type, const double time_step)