Blender V5.0
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
8
9#include <algorithm>
10
11#include "MEM_guardedalloc.h"
12
13#include "DNA_array_utils.hh"
14#include "DNA_node_types.h"
15
16#include "BLI_easing.h"
17#include "BLI_listbase.h"
18#include "BLI_math_geom.h"
19#include "BLI_stack.hh"
20#include "BLI_vector.hh"
21
22#include "BKE_context.hh"
24#include "BKE_node.hh"
26#include "BKE_node_runtime.hh"
28#include "BKE_screen.hh"
29
30#include "ED_node.hh" /* own include */
31#include "ED_render.hh"
32#include "ED_screen.hh"
33#include "ED_space_api.hh"
34#include "ED_viewer_path.hh"
35
36#include "RNA_access.hh"
37#include "RNA_define.hh"
38#include "RNA_prototypes.hh"
39
40#include "WM_api.hh"
41#include "WM_types.hh"
42
43#include "UI_interface_icons.hh"
44#include "UI_resources.hh"
45#include "UI_view2d.hh"
46
47#include "NOD_geo_viewer.hh"
49#include "NOD_socket.hh"
50#include "NOD_socket_items.hh"
51
52#include "node_intern.hh" /* own include */
53
56 bNode *insert; /* Inserted node. */
57 bNode *prev, *next; /* Previous/next node in the chain. */
58
60
61 float offset_x; /* Offset to apply to node chain. */
62};
63
65
67{
68 LISTBASE_FOREACH (bNodeLink *, link, links) {
69 link->flag &= ~NODE_LINK_TEMP_HIGHLIGHT;
70 }
71}
72
73/* -------------------------------------------------------------------- */
76
78{
79 bNodeLink oplink{};
80 if (socket.in_out == SOCK_OUT) {
81 oplink.fromnode = &node;
82 oplink.fromsock = &socket;
83 }
84 else {
85 oplink.tonode = &node;
86 oplink.tosock = &socket;
87 }
88 oplink.flag |= NODE_LINK_VALID;
89 return oplink;
90}
91
92static void pick_link(bNodeLinkDrag &nldrag,
93 SpaceNode &snode,
94 bNode *node,
95 bNodeLink &link_to_pick)
96{
98
99 bNodeLink link = create_drag_link(*link_to_pick.fromnode, *link_to_pick.fromsock);
100
101 nldrag.links.append(link);
102 bke::node_remove_link(snode.edittree, link_to_pick);
103 snode.edittree->ensure_topology_cache();
106
107 /* Send changed event to original link->tonode. */
108 if (node) {
110 }
111}
112
114 wmOperator &op,
115 bNodeLinkDrag &nldrag,
116 const float2 &cursor)
117{
118 SpaceNode *snode = CTX_wm_space_node(&C);
119 ARegion *region = CTX_wm_region(&C);
120 bNodeTree &node_tree = *snode->edittree;
121
122 float2 drag_start;
123 RNA_float_get_array(op.ptr, "drag_start", drag_start);
124 bNodeSocket *socket = node_find_indicated_socket(*snode, *region, drag_start, SOCK_IN);
125 bNode &node = socket->owner_node();
126
127 /* Distance to test overlapping of cursor on link. */
128 const float cursor_link_touch_distance = 12.5f * UI_SCALE_FAC;
129
130 bNodeLink *link_to_pick = nullptr;
131 clear_picking_highlight(&node_tree.links);
132 for (bNodeLink *link : socket->directly_linked_links()) {
133 /* Test if the cursor is near a link. */
134 std::array<float2, NODE_LINK_RESOL + 1> coords;
136
137 for (const int i : IndexRange(coords.size() - 1)) {
138 const float distance = dist_squared_to_line_segment_v2(cursor, coords[i], coords[i + 1]);
139 if (distance < cursor_link_touch_distance) {
140 link_to_pick = link;
141 nldrag.last_picked_multi_input_socket_link = link_to_pick;
142 }
143 }
144 }
145
146 /* If no linked was picked in this call, try using the one picked in the previous call.
147 * Not essential for the basic behavior, but can make interaction feel a bit better if
148 * the mouse moves to the right and loses the "selection." */
149 if (!link_to_pick) {
150 bNodeLink *last_picked_link = nldrag.last_picked_multi_input_socket_link;
151 if (last_picked_link) {
152 link_to_pick = last_picked_link;
153 }
154 }
155
156 if (link_to_pick) {
157 /* Highlight is set here and cleared in the next iteration or if the operation finishes. */
158 link_to_pick->flag |= NODE_LINK_TEMP_HIGHLIGHT;
160
161 if (!node_find_indicated_socket(*snode, *region, cursor, SOCK_IN)) {
162 pick_link(nldrag, *snode, &node, *link_to_pick);
163 }
164 }
165}
166
167static bool socket_is_available(const bNodeTree *ntree, bNodeSocket *sock, const bool allow_used)
168{
169 ntree->ensure_topology_cache();
170 if (!sock->is_visible()) {
171 return false;
172 }
173
174 if (!allow_used && (sock->flag & SOCK_IS_LINKED)) {
175 /* Multi input sockets are available (even if used). */
176 if (!(sock->flag & SOCK_MULTI_INPUT)) {
177 return false;
178 }
179 }
180
181 return true;
182}
183
185 bNode *node,
186 bNodeSocket *sock_target,
187 const bool allow_multiple)
188{
189 /* First look for selected output. */
190 LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) {
191 if (!socket_is_available(ntree, sock, allow_multiple)) {
192 continue;
193 }
194
195 if (sock->flag & SELECT) {
196 return sock;
197 }
198 }
199
200 /* Try to find a socket with a matching name. */
201 LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) {
202 if (!socket_is_available(ntree, sock, allow_multiple)) {
203 continue;
204 }
205
206 /* Check for same types. */
207 if (sock->type == sock_target->type) {
208 if (STREQ(sock->name, sock_target->name)) {
209 return sock;
210 }
211 }
212 }
213
214 /* Otherwise settle for the first available socket of the right type. */
215 LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) {
216 if (!socket_is_available(ntree, sock, allow_multiple)) {
217 continue;
218 }
219
220 /* Check for same types. */
221 if (sock->type == sock_target->type) {
222 return sock;
223 }
224 }
225
226 /* If the target is an extend socket, then connect the first available socket that is not
227 * already linked to the target node. */
228 ntree->ensure_topology_cache();
229 if (STREQ(sock_target->idname, "NodeSocketVirtual")) {
231 if (!output->is_icon_visible()) {
232 continue;
233 }
234
235 /* Find out if the socket is already linked to the target node. */
236 const Span<bNodeSocket *> directly_linked_sockets = output->directly_linked_sockets();
237 bool is_output_linked_to_target_node = false;
238 for (bNodeSocket *socket : directly_linked_sockets) {
239 if (&socket->owner_node() == &sock_target->owner_node()) {
240 is_output_linked_to_target_node = true;
241 break;
242 }
243 }
244
245 /* Already linked, ignore it. */
246 if (is_output_linked_to_target_node) {
247 continue;
248 }
249
250 return output;
251 }
252 }
253
254 /* Always allow linking to an reroute node. The socket type of the reroute sockets might change
255 * after the link has been created. */
256 if (node->is_reroute()) {
257 return (bNodeSocket *)node->outputs.first;
258 }
259
260 return nullptr;
261}
262
263/* Returns the list of available inputs sorted by their order of importance, where the order of
264 * importance is assumed to be the numerical value of the socket type, such that a higher value
265 * corresponds to a higher importance. If only_unlinked is true, only input sockets that are
266 * unlinked will be considered. */
268 const bNode *node,
269 const bool only_unlinked)
270{
273 if (socket_is_available(ntree, input, !only_unlinked)) {
274 inputs.append(input);
275 }
276 }
277
278 std::sort(inputs.begin(), inputs.end(), [](const bNodeSocket *a, const bNodeSocket *b) {
279 return a->type > b->type;
280 });
281
282 return inputs;
283}
284
286 SpaceNode &snode,
287 bNode *node_fr,
288 bNodeSocket *sock_fr,
289 bNode *node_to,
290 bNodeSocket *sock_to,
291 int replace)
292{
293 bNodeTree *ntree = snode.edittree;
294
295 if (replace) {
296 bke::node_remove_socket_links(*ntree, *sock_to);
297 }
298
299 bNodeLink &link = bke::node_add_link(*ntree, *node_fr, *sock_fr, *node_to, *sock_to);
300
301 if (link.fromnode->typeinfo->insert_link) {
302 bke::NodeInsertLinkParams params{*ntree, *link.fromnode, link, &C};
303 if (!link.fromnode->typeinfo->insert_link(params)) {
304 bke::node_remove_link(ntree, link);
305 return false;
306 }
307 }
308 if (link.tonode->typeinfo->insert_link) {
309 bke::NodeInsertLinkParams params{*ntree, *link.tonode, link, &C};
310 if (!link.tonode->typeinfo->insert_link(params)) {
311 bke::node_remove_link(ntree, link);
312 return false;
313 }
314 }
315
316 return true;
317}
318
323
325 bNodeLink &drag_link,
326 const float2 &cursor)
327{
328 const float2 &socket_location = socket.runtime->location;
329
331 for (bNodeLink *link : socket.directly_linked_links()) {
333 socket_location, link->multi_input_sort_id, link->tosock->runtime->total_inputs);
334 links.append({link, location});
335 };
336
337 links.append({&drag_link, cursor});
338
339 std::sort(links.begin(), links.end(), [](const LinkAndPosition a, const LinkAndPosition b) {
340 return a.multi_socket_position.y < b.multi_socket_position.y;
341 });
342
343 for (const int i : links.index_range()) {
344 links[i].link->multi_input_sort_id = i;
345 }
346}
347
349{
350 for (bNodeSocket *socket : node.input_sockets()) {
351 if (!socket->is_multi_input()) {
352 continue;
353 }
354 Vector<bNodeLink *, 8> links = socket->directly_linked_links();
355 std::sort(links.begin(), links.end(), [](const bNodeLink *a, const bNodeLink *b) {
356 return a->multi_input_sort_id < b->multi_input_sort_id;
357 });
358
359 for (const int i : links.index_range()) {
360 links[i]->multi_input_sort_id = i;
361 }
362 }
363}
364
366 SpaceNode &snode,
367 const bool allow_multiple,
368 const bool replace)
369{
370 bNodeTree *ntree = snode.edittree;
371 Vector<bNode *> sorted_nodes = get_selected_nodes(*ntree).extract_vector();
372
373 /* Sort nodes left to right. */
374 std::sort(sorted_nodes.begin(), sorted_nodes.end(), [](const bNode *a, const bNode *b) {
375 return a->location[0] < b->location[0];
376 });
377
378 // int numlinks = 0; /* UNUSED */
379 for (const int i : sorted_nodes.as_mutable_span().drop_back(1).index_range()) {
380 bool has_selected_inputs = false;
381
382 bNode *node_fr = sorted_nodes[i];
383 bNode *node_to = sorted_nodes[i + 1];
384 /* Corner case: input/output node aligned the wrong way around (#47729). */
385 if (BLI_listbase_is_empty(&node_to->inputs) || BLI_listbase_is_empty(&node_fr->outputs)) {
386 std::swap(node_fr, node_to);
387 }
388
389 /* If there are selected sockets, connect those. */
390 LISTBASE_FOREACH (bNodeSocket *, sock_to, &node_to->inputs) {
391 if (sock_to->flag & SELECT) {
392 has_selected_inputs = true;
393
394 if (!socket_is_available(ntree, sock_to, replace)) {
395 continue;
396 }
397
398 /* Check for an appropriate output socket to connect from. */
399 bNodeSocket *sock_fr = best_socket_output(ntree, node_fr, sock_to, allow_multiple);
400 if (!sock_fr) {
401 continue;
402 }
403
404 if (snode_autoconnect_input(C, snode, node_fr, sock_fr, node_to, sock_to, replace)) {
405 // numlinks++;
406 }
407 }
408 }
409
410 if (!has_selected_inputs) {
411 Vector<bNodeSocket *> inputs = get_available_sorted_inputs(ntree, node_to, !replace);
412 for (bNodeSocket *input : inputs) {
413 /* Check for an appropriate output socket to connect from. */
414 bNodeSocket *sock_fr = best_socket_output(ntree, node_fr, input, allow_multiple);
415 if (!sock_fr) {
416 continue;
417 }
418
419 if (snode_autoconnect_input(C, snode, node_fr, sock_fr, node_to, input, replace)) {
420 // numlinks++;
421 break;
422 }
423 }
424 }
425 }
426}
427
429
430namespace viewer_linking {
431
432/* -------------------------------------------------------------------- */
435
436/* Depending on the node tree type, different socket types are supported by viewer nodes. */
437static bool socket_can_be_viewed(const bNodeSocket &socket)
438{
439 if (!socket.is_icon_visible()) {
440 return false;
441 }
442 if (STREQ(socket.idname, "NodeSocketVirtual")) {
443 return false;
444 }
445 return true;
446}
447
449 bNode &viewer_node)
450{
451 const auto &storage = *static_cast<const NodeGeometryViewer *>(viewer_node.storage);
452 if (storage.items_num >= 1) {
453 const NodeGeometryViewerItem &first_item = storage.items[0];
454 if (first_item.socket_type == SOCK_GEOMETRY) {
455 return;
456 }
457 }
458 std::optional<int> existing_geometry_index;
459 for (const int i : IndexRange(storage.items_num)) {
460 const NodeGeometryViewerItem &item = storage.items[i];
461 if (item.socket_type == SOCK_GEOMETRY) {
462 existing_geometry_index = i;
463 break;
464 }
465 }
466 if (existing_geometry_index) {
467 BLI_assert(*existing_geometry_index >= 1);
468 dna::array::move_index(storage.items, storage.items_num, *existing_geometry_index, 0);
469 return;
470 }
472 tree, viewer_node, SOCK_GEOMETRY, "Geometry");
473 dna::array::move_index(storage.items, storage.items_num, storage.items_num - 1, 0);
474}
475
477 bNodeTree &ntree, bNode &viewer_node, const eNodeSocketDatatype socket_type)
478{
479 const auto &storage = *static_cast<const NodeGeometryViewer *>(viewer_node.storage);
480 if (storage.items_num == 0) {
482 ntree, viewer_node, socket_type, IFACE_("Value"));
483 return 0;
484 }
485 if (storage.items_num == 1 && storage.items[0].socket_type != SOCK_GEOMETRY) {
486 storage.items[0].socket_type = socket_type;
487 return 0;
488 }
489 if (storage.items_num == 1 && storage.items[0].socket_type == SOCK_GEOMETRY) {
491 ntree, viewer_node, socket_type, IFACE_("Value"));
492 return 1;
493 }
494 if (storage.items_num == 2 && storage.items[0].socket_type == SOCK_GEOMETRY &&
495 storage.items[1].socket_type != SOCK_GEOMETRY)
496 {
497 storage.items[1].socket_type = socket_type;
498 return 1;
499 }
500 std::optional<int> existing_geometry_index;
501 for (const int i : IndexRange(storage.items_num)) {
502 if (storage.items[i].socket_type == SOCK_GEOMETRY) {
503 existing_geometry_index = i;
504 break;
505 }
506 }
507 if (existing_geometry_index) {
508 dna::array::move_index(storage.items, storage.items_num, *existing_geometry_index, 0);
509 storage.items[1].socket_type = socket_type;
510 MEM_SAFE_FREE(storage.items[1].name);
511 storage.items[1].name = BLI_strdup(IFACE_("Value"));
512 return 1;
513 }
514 storage.items[0].socket_type = socket_type;
515 MEM_SAFE_FREE(storage.items[0].name);
516 storage.items[0].name = BLI_strdup(IFACE_("Value"));
517 return 0;
518}
519
520static std::string get_viewer_source_name(const bNodeSocket &socket)
521{
522 const bNode &node = socket.owner_node();
523 if (node.is_reroute()) {
524 const bNodeSocket &reroute_input = node.input_socket(0);
525 if (!reroute_input.is_logically_linked()) {
526 return IFACE_(socket.typeinfo->label);
527 }
528 return reroute_input.logically_linked_sockets()[0]->name;
529 }
530 return socket.name;
531}
532
536 bNode &viewer_node,
537 bNodeSocket &src_socket)
538{
539 if (viewer_node.type_legacy != GEO_NODE_VIEWER) {
540 /* In viewer nodes in the compositor, only the first input should be linked to. */
541 return (bNodeSocket *)viewer_node.inputs.first;
542 }
544 {
545 return nullptr;
546 }
547
548 /* For the geometry nodes viewer, find the socket with the correct type. */
549 const std::string name = get_viewer_source_name(src_socket);
550
551 int item_index;
552 if (src_socket.type == SOCK_GEOMETRY) {
554 item_index = 0;
555 }
556 else {
558 ntree, viewer_node, src_socket.typeinfo->type);
559 }
560
561 auto &storage = *static_cast<NodeGeometryViewer *>(viewer_node.storage);
562 NodeGeometryViewerItem &item = storage.items[item_index];
564 viewer_node, item, name.c_str());
567 return static_cast<bNodeSocket *>(BLI_findlink(&viewer_node.inputs, item_index));
568}
569
570static bool is_viewer_node(const bNode &node)
571{
573}
574
575static bool is_viewer_socket_in_viewer(const bNodeSocket &socket)
576{
577 const bNode &node = socket.owner_node();
579 if (node.typeinfo->type_legacy == GEO_NODE_VIEWER) {
580 return true;
581 }
582 return socket.index() == 0;
583}
584
585static bool is_viewer_socket(const bNodeSocket &socket)
586{
587 if (is_viewer_node(socket.owner_node())) {
588 return is_viewer_socket_in_viewer(socket);
589 }
590 return false;
591}
592
594{
595 SpaceNode *snode = CTX_wm_space_node(C);
597}
598
600{
601 LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &btree.links) {
602 if (link->tonode == &viewer_node) {
603 if (link->tosock->flag & SOCK_UNAVAIL) {
604 bke::node_remove_link(&btree, *link);
605 }
606 }
607 }
608}
609
611{
612 int last_linked_data_socket_index = -1;
613 bool has_linked_geometry_socket = false;
614 for (bNodeSocket *socket : node_to_view.output_sockets()) {
615 if (!socket_can_be_viewed(*socket)) {
616 continue;
617 }
618 for (bNodeLink *link : socket->directly_linked_links()) {
619 bNodeSocket &target_socket = *link->tosock;
620 bNode &target_node = *link->tonode;
621 if (is_viewer_socket(target_socket)) {
622 if (link->is_muted() || !(target_node.flag & NODE_DO_OUTPUT)) {
623 /* This socket is linked to a deactivated viewer, the viewer should be activated. */
624 return socket;
625 }
626 if (socket->type == SOCK_GEOMETRY) {
627 has_linked_geometry_socket = true;
628 }
629 else {
630 last_linked_data_socket_index = socket->index();
631 }
632 }
633 }
634 }
635
636 if (last_linked_data_socket_index == -1 && !has_linked_geometry_socket) {
637 /* Return the first socket that can be viewed. */
638 for (bNodeSocket *socket : node_to_view.output_sockets()) {
639 if (socket_can_be_viewed(*socket)) {
640 return socket;
641 }
642 }
643 return nullptr;
644 }
645
646 bNodeSocket *already_viewed_socket = nullptr;
647
648 /* Pick the next socket to be linked to the viewer. */
649 const int tot_outputs = node_to_view.output_sockets().size();
650 for (const int offset : IndexRange(1, tot_outputs)) {
651 const int index = (last_linked_data_socket_index + offset) % tot_outputs;
652 bNodeSocket &output_socket = node_to_view.output_socket(index);
653 if (!socket_can_be_viewed(output_socket)) {
654 continue;
655 }
656 if (has_linked_geometry_socket && output_socket.type == SOCK_GEOMETRY) {
657 /* Skip geometry sockets when cycling if one is already viewed. */
658 already_viewed_socket = &output_socket;
659 continue;
660 }
661
662 bool is_currently_viewed = false;
663 for (const bNodeLink *link : output_socket.directly_linked_links()) {
664 bNodeSocket &target_socket = *link->tosock;
665 bNode &target_node = *link->tonode;
666 if (!is_viewer_socket(target_socket)) {
667 continue;
668 }
669 if (link->is_muted()) {
670 continue;
671 }
672 if (!(target_node.flag & NODE_DO_OUTPUT)) {
673 continue;
674 }
675 is_currently_viewed = true;
676 break;
677 }
678 if (is_currently_viewed) {
679 already_viewed_socket = &output_socket;
680 continue;
681 }
682 return &output_socket;
683 }
684 return already_viewed_socket;
685}
686
687static void finalize_viewer_link(const bContext &C,
688 SpaceNode &snode,
689 bNode &viewer_node,
690 bNodeLink &viewer_link)
691{
692 Main *bmain = CTX_data_main(&C);
694 viewer_link.flag &= ~NODE_LINK_MUTED;
695 viewer_node.flag &= ~NODE_MUTED;
696 viewer_node.flag |= NODE_DO_OUTPUT;
697
698 if (snode.edittree->type == NTREE_GEOMETRY) {
699
700 std::optional<int> item_identifier;
701 const NodeGeometryViewerItem *item =
703 viewer_node, viewer_link.tosock->identifier);
704 BLI_assert(item);
705 if (item) {
706 item_identifier = item->identifier;
707 }
708
709 viewer_path::activate_geometry_node(*bmain, snode, viewer_node, item_identifier);
710 }
711 else if (snode.edittree->type == NTREE_COMPOSIT) {
712 for (bNode *node : snode.nodetree->all_nodes()) {
713 if (node->is_type("CompositorNodeViewer") && node != &viewer_node) {
714 node->flag &= ~NODE_DO_OUTPUT;
715 }
716 }
717 }
719 BKE_main_ensure_invariants(*bmain, snode.edittree->id);
720}
721
723 const rctf &rect,
724 const Span<const bNode *> ignored_nodes)
725{
726 for (const bNode *node : tree.all_nodes()) {
727 if (node->is_frame()) {
728 continue;
729 }
730 if (ignored_nodes.contains(node)) {
731 continue;
732 }
733 if (BLI_rctf_isect(&rect, &node->runtime->draw_bounds, nullptr)) {
734 return node;
735 }
736 }
737 return nullptr;
738}
739
745 const float step_distance,
746 const float max_distance)
747{
748 /* Prefer moving viewer a bit further horizontally than vertically. */
749 const float y_scale = 0.5f;
750
751 Vector<float2> candidates;
752 candidates.append(initial);
753 for (float distance = step_distance; distance <= max_distance; distance += step_distance) {
754 const float arc_length = distance * M_PI;
755 const int checks = std::max<int>(2, ceilf(arc_length / step_distance));
756 for (const int i : IndexRange(checks)) {
757 const float angle = i / float(checks - 1) * M_PI;
758 const float candidate_x = initial.x + distance * std::sin(angle);
759 const float candidate_y = initial.y + distance * std::cos(angle) * y_scale;
760 candidates.append({candidate_x, candidate_y});
761 }
762 }
763 return candidates;
764}
765
771static void position_viewer_node(const bContext &C,
773 bNode &viewer_node,
774 const bNode &node_to_view)
775{
776 ScrArea &area = *CTX_wm_area(&C);
777 ARegion &region = *CTX_wm_region(&C);
779
780 tree.ensure_topology_cache();
781
782 const View2D &v2d = region.v2d;
783 rctf region_rect;
784 region_rect.xmin = 0;
785 region_rect.xmax = region.winx;
786 region_rect.ymin = 0;
787 region_rect.ymax = region.winy;
788 if (U.uiflag2 & USER_REGION_OVERLAP) {
789 region_rect.xmax -= sidebar.winx;
790 }
791
792 rctf region_bounds;
793 UI_view2d_region_to_view_rctf(&v2d, &region_rect, &region_bounds);
794
795 viewer_node.ui_order = tree.all_nodes().size();
797
798 const bool is_new_viewer_node = BLI_rctf_size_x(&viewer_node.runtime->draw_bounds) == 0;
799 if (!is_new_viewer_node &&
800 BLI_rctf_inside_rctf(&region_bounds, &viewer_node.runtime->draw_bounds) &&
801 viewer_node.runtime->draw_bounds.xmin > node_to_view.runtime->draw_bounds.xmax)
802 {
803 /* Stay at the old viewer position when the viewer node is still in view and on the right side
804 * of the node-to-view. */
805 return;
806 }
807
808 const float default_padding_x = U.node_margin;
809 const float default_padding_y = 10;
810 const float viewer_width = is_new_viewer_node ?
811 viewer_node.width * UI_SCALE_FAC :
812 BLI_rctf_size_x(&viewer_node.runtime->draw_bounds);
813 const float viewer_height = is_new_viewer_node ?
814 100 * UI_SCALE_FAC :
815 BLI_rctf_size_y(&viewer_node.runtime->draw_bounds);
816
817 const float2 main_candidate{node_to_view.runtime->draw_bounds.xmax + default_padding_x,
818 node_to_view.runtime->draw_bounds.ymax + viewer_height +
819 default_padding_y};
820
821 std::optional<float2> new_viewer_position;
822
823 const Vector<float2> position_candidates = get_viewer_node_position_candidates(
824 main_candidate, 50 * UI_SCALE_FAC, 800 * UI_SCALE_FAC);
825 for (const float2 &candidate_pos : position_candidates) {
826 rctf candidate;
827 candidate.xmin = candidate_pos.x;
828 candidate.xmax = candidate_pos.x + viewer_width;
829 candidate.ymin = candidate_pos.y - viewer_height;
830 candidate.ymax = candidate_pos.y;
831
832 if (!BLI_rctf_inside_rctf(&region_bounds, &candidate)) {
833 /* Avoid moving viewer outside of visible region. */
834 continue;
835 }
836
837 rctf padded_candidate = candidate;
838 BLI_rctf_pad(&padded_candidate, default_padding_x - 1, default_padding_y - 1);
839
840 const bNode *overlapping_node = find_overlapping_node(
841 tree, padded_candidate, {&viewer_node, &node_to_view});
842 if (!overlapping_node) {
843 new_viewer_position = candidate_pos;
844 break;
845 }
846 }
847
848 if (!new_viewer_position) {
849 new_viewer_position = main_candidate;
850 }
851
852 viewer_node.location[0] = new_viewer_position->x / UI_SCALE_FAC;
853 viewer_node.location[1] = new_viewer_position->y / UI_SCALE_FAC;
854 viewer_node.parent = nullptr;
855}
856
857static int view_socket(const bContext &C,
858 SpaceNode &snode,
859 bNodeTree &btree,
860 bNode &bnode_to_view,
861 bNodeSocket &bsocket_to_view)
862{
863 bNode *viewer_node = nullptr;
864 /* Try to find a viewer that is already active. */
865 for (bNode *node : btree.all_nodes()) {
866 if (is_viewer_node(*node)) {
867 if (node->flag & NODE_DO_OUTPUT && node->custom1 == NODE_VIEWER_SHORTCUT_NONE) {
868 viewer_node = node;
869 break;
870 }
871 }
872 }
873
874 /* Try to reactivate existing viewer connection. */
875 for (bNodeLink *link : bsocket_to_view.directly_linked_links()) {
876 bNodeSocket &target_socket = *link->tosock;
877 bNode &target_node = *link->tonode;
878 if (is_viewer_socket(target_socket) && ELEM(viewer_node, nullptr, &target_node)) {
879 finalize_viewer_link(C, snode, target_node, *link);
880 position_viewer_node(C, btree, target_node, bnode_to_view);
881 return OPERATOR_FINISHED;
882 }
883 }
884
885 if (viewer_node == nullptr) {
886 for (bNode *node : btree.all_nodes()) {
887 if (is_viewer_node(*node) && node->custom1 == NODE_VIEWER_SHORTCUT_NONE) {
888 viewer_node = node;
889 break;
890 }
891 }
892 }
893 if (viewer_node == nullptr) {
894 const float2 socket_location = bsocket_to_view.runtime->location;
895 const int viewer_type = get_default_viewer_type(&C);
896 const float2 location{socket_location.x / UI_SCALE_FAC + 100,
897 socket_location.y / UI_SCALE_FAC};
898 viewer_node = add_static_node(C, viewer_type, location);
899 }
900
901 bNodeSocket *viewer_bsocket = node_link_viewer_get_socket(btree, *viewer_node, bsocket_to_view);
902 if (viewer_bsocket == nullptr) {
903 return OPERATOR_CANCELLED;
904 }
905 viewer_bsocket->flag &= ~SOCK_HIDDEN;
906
907 bNodeLink *viewer_link = nullptr;
908 LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &btree.links) {
909 if (link->tosock == viewer_bsocket) {
910 viewer_link = link;
911 break;
912 }
913 }
914 if (viewer_link == nullptr) {
915 viewer_link = &bke::node_add_link(
916 btree, bnode_to_view, bsocket_to_view, *viewer_node, *viewer_bsocket);
917 }
918 else {
919 viewer_link->fromnode = &bnode_to_view;
920 viewer_link->fromsock = &bsocket_to_view;
922 }
923 finalize_viewer_link(C, snode, *viewer_node, *viewer_link);
924 position_viewer_node(C, btree, *viewer_node, bnode_to_view);
925 return OPERATOR_CANCELLED;
926}
927
928static int node_link_viewer(const bContext &C, bNode &bnode_to_view, bNodeSocket *bsocket_to_view)
929{
930 SpaceNode &snode = *CTX_wm_space_node(&C);
931 bNodeTree *btree = snode.edittree;
932 btree->ensure_topology_cache();
933
934 if (bsocket_to_view == nullptr) {
935 bsocket_to_view = determine_socket_to_view(bnode_to_view);
936 }
937
938 if (bsocket_to_view == nullptr) {
939 return OPERATOR_CANCELLED;
940 }
941
942 return view_socket(C, snode, *btree, bnode_to_view, *bsocket_to_view);
943}
944
946
947} // namespace viewer_linking
948
949/* -------------------------------------------------------------------- */
952
954{
955 SpaceNode &snode = *CTX_wm_space_node(C);
956 bNode *node = bke::node_get_active(*snode.edittree);
957
958 if (!node) {
959 return OPERATOR_CANCELLED;
960 }
961
963
964 bNodeSocket *socket_to_view = nullptr;
965 LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) {
966 if (socket->flag & SELECT) {
967 socket_to_view = socket;
968 break;
969 }
970 }
971
972 if (viewer_linking::node_link_viewer(*C, *node, socket_to_view) == OPERATOR_CANCELLED) {
973 return OPERATOR_CANCELLED;
974 }
975
977
978 return OPERATOR_FINISHED;
979}
980
982{
984 return false;
985 }
986 SpaceNode *snode = CTX_wm_space_node(C);
987 if (ED_node_is_compositor(snode)) {
988 return true;
989 }
990 if (ED_node_is_geometry(snode)) {
992 /* The viewer node is not supported in the "Tool" context. */
993 return false;
994 }
995 return true;
996 }
997 return false;
998}
999
1001{
1002 /* identifiers */
1003 ot->name = "Link to Viewer Node";
1004 ot->description = "Link to viewer node";
1005 ot->idname = "NODE_OT_link_viewer";
1006
1007 /* API callbacks. */
1010
1011 /* flags */
1012 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1013}
1014
1016
1017/* -------------------------------------------------------------------- */
1020
1026{
1027 if (nldrag.in_out == SOCK_OUT) {
1028 for (const bNodeLink &link : nldrag.links) {
1029 if (link.tonode && link.tosock) {
1030 return false;
1031 }
1032 }
1033 }
1034 else {
1035 for (const bNodeLink &link : nldrag.links) {
1036 if (link.fromnode && link.fromsock) {
1037 return false;
1038 }
1039 }
1040 }
1041 return true;
1042}
1043
1045 const bNodeLinkDrag &nldrag)
1046{
1047 /* Custom node trees aren't supported yet. */
1048 if (node_tree.type == NTREE_CUSTOM) {
1049 return false;
1050 }
1051 /* Only create the search menu when the drag has not already connected the links to a socket. */
1052 if (!dragged_links_are_detached(nldrag)) {
1053 return false;
1054 }
1055 if (nldrag.swap_links) {
1056 return false;
1057 }
1058 /* Don't create the search menu if the drag is disconnecting a link from an input node. */
1059 if (nldrag.start_socket->in_out == SOCK_IN && nldrag.start_link_count > 0) {
1060 return false;
1061 }
1062 /* Don't allow a drag from the "new socket" (group input node or simulation nodes currently).
1063 * Handling these properly in node callbacks increases the complexity too much for now. */
1064 if (nldrag.start_socket->type == SOCK_CUSTOM) {
1065 return false;
1066 }
1067 if (nldrag.start_socket->type == SOCK_TEXTURE) {
1068 /* This socket types is not used anymore, but can currently still exists in files. */
1069 return false;
1070 }
1071 return true;
1072}
1073
1074static bool need_drag_link_tooltip(const bNodeTree &node_tree, const bNodeLinkDrag &nldrag)
1075{
1076 return nldrag.swap_links || should_create_drag_link_search_menu(node_tree, nldrag);
1077}
1078
1079static void draw_draglink_tooltip(const bContext * /*C*/, ARegion * /*region*/, void *arg)
1080{
1081 bNodeLinkDrag *nldrag = static_cast<bNodeLinkDrag *>(arg);
1082
1083 uchar text_col[4];
1084 UI_GetThemeColor4ubv(TH_TEXT, text_col);
1085
1086 const int padding = 4 * UI_SCALE_FAC;
1087 const float x = nldrag->in_out == SOCK_IN ? nldrag->cursor[0] - 3.3f * padding :
1088 nldrag->cursor[0];
1089 const float y = nldrag->cursor[1] - 2.0f * UI_SCALE_FAC;
1090
1091 const bool new_link = nldrag->in_out == nldrag->start_socket->in_out;
1092 const bool swap_links = nldrag->swap_links;
1093
1094 const int icon = !swap_links ? ICON_ADD : (new_link ? ICON_ANIM : ICON_UV_SYNC_SELECT);
1095
1097 x, y, icon, UI_INV_SCALE_FAC, 1.0f, 0.0f, text_col, false, UI_NO_ICON_OVERLAY_TEXT);
1098}
1099
1100static void draw_draglink_tooltip_activate(const ARegion &region, bNodeLinkDrag &nldrag)
1101{
1102 if (nldrag.draw_handle == nullptr) {
1104 region.runtime->type, draw_draglink_tooltip, &nldrag, REGION_DRAW_POST_PIXEL);
1105 }
1106}
1107
1108static void draw_draglink_tooltip_deactivate(const ARegion &region, bNodeLinkDrag &nldrag)
1109{
1110 if (nldrag.draw_handle) {
1111 ED_region_draw_cb_exit(region.runtime->type, nldrag.draw_handle);
1112 nldrag.draw_handle = nullptr;
1113 }
1114}
1115
1116static int node_socket_count_links(const bNodeTree &ntree, const bNodeSocket &socket)
1117{
1118 int count = 0;
1119 LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
1120 if (ELEM(&socket, link->fromsock, link->tosock)) {
1121 count++;
1122 }
1123 }
1124 return count;
1125}
1126
1128 const bNode *node,
1129 bNodeSocket *socket_to_match)
1130{
1131 bNodeSocket *first_socket = socket_to_match->in_out == SOCK_IN ?
1132 static_cast<bNodeSocket *>(node->inputs.first) :
1133 static_cast<bNodeSocket *>(node->outputs.first);
1134
1135 bNodeSocket *socket = socket_to_match->next ? socket_to_match->next : first_socket;
1136 while (socket != socket_to_match) {
1137 if (socket->is_visible()) {
1138 const bool sockets_are_compatible = socket->typeinfo == socket_to_match->typeinfo;
1139 if (sockets_are_compatible) {
1140 const int link_count = node_socket_count_links(ntree, *socket);
1141 const bool socket_has_capacity = link_count < bke::node_socket_link_limit(*socket);
1142 if (socket_has_capacity) {
1143 /* Found a valid free socket we can swap to. */
1144 return socket;
1145 }
1146 }
1147 }
1148 /* Wrap around the list end. */
1149 socket = socket->next ? socket->next : first_socket;
1150 }
1151
1152 return nullptr;
1153}
1154
1155static void displace_links(bNodeTree *ntree, const bNode *node, bNodeLink *inserted_link)
1156{
1157 bNodeSocket *linked_socket = node == inserted_link->tonode ? inserted_link->tosock :
1158 inserted_link->fromsock;
1159 bNodeSocket *replacement_socket = node_find_linkable_socket(*ntree, node, linked_socket);
1160
1161 if (linked_socket->is_input()) {
1162 BLI_assert(!linked_socket->is_multi_input());
1163 ntree->ensure_topology_cache();
1164
1165 if (linked_socket->directly_linked_links().is_empty()) {
1166 return;
1167 }
1168 bNodeLink *displaced_link = linked_socket->directly_linked_links().first();
1169
1170 if (!replacement_socket) {
1171 bke::node_remove_link(ntree, *displaced_link);
1172 return;
1173 }
1174
1175 displaced_link->tosock = replacement_socket;
1176
1177 if (replacement_socket->is_multi_input()) {
1178 /* Check for duplicate links when linking to multi input sockets. */
1179 for (bNodeLink *existing_link : replacement_socket->runtime->directly_linked_links) {
1180 if (existing_link->fromsock == displaced_link->fromsock) {
1181 bke::node_remove_link(ntree, *displaced_link);
1182 return;
1183 }
1184 }
1185 const int multi_input_sort_id = node_socket_count_links(*ntree, *replacement_socket) - 1;
1186 displaced_link->multi_input_sort_id = multi_input_sort_id;
1187 }
1188
1190 return;
1191 }
1192
1193 LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree->links) {
1194 if (link->fromsock == linked_socket) {
1195 if (replacement_socket) {
1196 link->fromsock = replacement_socket;
1198 }
1199 else {
1200 bke::node_remove_link(ntree, *link);
1202 }
1203 }
1204 }
1205}
1206
1208{
1209 bNodeLink &link = nldrag.links.first();
1210 if (!link.fromsock || !link.tosock) {
1211 return;
1212 }
1213 if (nldrag.start_socket->is_input()) {
1214 displace_links(&ntree, link.fromnode, &link);
1215 }
1216 else {
1217 displace_links(&ntree, link.tonode, &link);
1218 }
1219}
1220
1221static void node_swap_links(bNodeLinkDrag &nldrag, bNodeTree &ntree)
1222{
1223 bNodeSocket &linked_socket = *nldrag.hovered_socket;
1224 bNodeSocket *start_socket = nldrag.start_socket;
1225 bNode *start_node = nldrag.start_node;
1226
1227 if (linked_socket.is_input()) {
1228 LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
1229 if (link->tosock != &linked_socket) {
1230 continue;
1231 }
1232 if (link->fromnode == start_node) {
1233 /* Don't link a node to itself. */
1234 bke::node_remove_link(&ntree, *link);
1235 continue;
1236 }
1237
1238 link->tosock = start_socket;
1239 link->tonode = start_node;
1240 }
1241 }
1242 else {
1243 LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
1244 if (link->fromsock != &linked_socket) {
1245 continue;
1246 }
1247 if (link->tonode == start_node) {
1248 /* Don't link a node to itself. */
1249 bke::node_remove_link(&ntree, *link);
1250 continue;
1251 }
1252 link->fromsock = start_socket;
1253 link->fromnode = start_node;
1254 }
1255 }
1256
1258}
1259
1261{
1262 bNodeSocket &linked_socket = *nldrag.hovered_socket;
1263
1264 int link_count = node_socket_count_links(ntree, linked_socket);
1265 const int link_limit = bke::node_socket_link_limit(linked_socket);
1266 Set<bNodeLink *> links_to_remove;
1267
1268 ntree.ensure_topology_cache();
1269
1270 /* Remove duplicate links first. */
1271 for (const bNodeLink dragged_link : nldrag.links) {
1272 if (linked_socket.is_input()) {
1273 for (bNodeLink *link : linked_socket.runtime->directly_linked_links) {
1274 const bool duplicate_link = link->fromsock == dragged_link.fromsock;
1275 if (duplicate_link) {
1276 links_to_remove.add(link);
1277 link_count--;
1278 }
1279 }
1280 }
1281 else {
1282 for (bNodeLink *link : linked_socket.runtime->directly_linked_links) {
1283 const bool duplicate_link = link->tosock == dragged_link.tosock;
1284 if (duplicate_link) {
1285 links_to_remove.add(link);
1286 link_count--;
1287 }
1288 }
1289 }
1290 }
1291
1292 for (bNodeLink *link : linked_socket.runtime->directly_linked_links) {
1293 const bool link_limit_exceeded = !(link_count < link_limit);
1294 if (link_limit_exceeded) {
1295 if (links_to_remove.add(link)) {
1296 link_count--;
1297 }
1298 }
1299 }
1300
1301 for (bNodeLink *link : links_to_remove) {
1302 bke::node_remove_link(&ntree, *link);
1303 }
1304}
1305
1307{
1308 Main *bmain = CTX_data_main(&C);
1309 ARegion &region = *CTX_wm_region(&C);
1310 SpaceNode &snode = *CTX_wm_space_node(&C);
1311 bNodeTree &ntree = *snode.edittree;
1312
1313 /* Handle node links already occupying the socket. */
1314 if (const bNodeSocket *linked_socket = nldrag.hovered_socket) {
1315 /* Swapping existing links out of multi input sockets is not supported. */
1316 const bool connecting_to_multi_input = linked_socket->is_multi_input() ||
1317 nldrag.start_socket->is_multi_input();
1318 if (nldrag.swap_links && !connecting_to_multi_input) {
1319 const bool is_new_link = nldrag.in_out == nldrag.start_socket->in_out;
1320 if (is_new_link) {
1321 node_displace_existing_links(nldrag, ntree);
1322 }
1323 else {
1324 node_swap_links(nldrag, ntree);
1325 }
1326 }
1327 else {
1329 }
1330 }
1331
1332 for (const bNodeLink &link : nldrag.links) {
1333 if (!link.tosock || !link.fromsock) {
1334 continue;
1335 }
1336
1337 /* Before actually adding the link let nodes perform special link insertion handling. */
1338
1339 bNodeLink *new_link = MEM_mallocN<bNodeLink>(__func__);
1340 *new_link = link;
1341 if (link.fromnode->typeinfo->insert_link) {
1342 bke::NodeInsertLinkParams params{ntree, *link.fromnode, *new_link, &C};
1343 if (!link.fromnode->typeinfo->insert_link(params)) {
1344 MEM_freeN(new_link);
1345 continue;
1346 }
1347 }
1348 if (link.tonode->typeinfo->insert_link) {
1349 bke::NodeInsertLinkParams params{ntree, *link.tonode, *new_link, &C};
1350 if (!link.tonode->typeinfo->insert_link(params)) {
1351 MEM_freeN(new_link);
1352 continue;
1353 }
1354 }
1355
1356 BLI_addtail(&ntree.links, new_link);
1357 BKE_ntree_update_tag_link_added(&ntree, new_link);
1358 }
1359
1360 BKE_main_ensure_invariants(*bmain, ntree.id);
1361
1362 /* Ensure drag-link tool-tip is disabled. */
1363 draw_draglink_tooltip_deactivate(region, nldrag);
1364
1365 ED_workspace_status_text(&C, nullptr);
1366 ED_region_tag_redraw(&region);
1368
1369 snode.runtime->linkdrag.reset();
1370}
1371
1383
1384static void node_link_find_socket(bContext &C, wmOperator &op, const float2 &cursor)
1385{
1386 SpaceNode &snode = *CTX_wm_space_node(&C);
1387 ARegion &region = *CTX_wm_region(&C);
1388 bNodeLinkDrag &nldrag = *static_cast<bNodeLinkDrag *>(op.customdata);
1389
1390 if (nldrag.in_out == SOCK_OUT) {
1391 if (bNodeSocket *tsock = node_find_indicated_socket(snode, region, cursor, SOCK_IN)) {
1392 nldrag.hovered_socket = tsock;
1393 bNode &tnode = tsock->owner_node();
1394 for (bNodeLink &link : nldrag.links) {
1395 /* Skip if socket is on the same node as the fromsock. */
1396 if (link.fromnode == &tnode) {
1397 continue;
1398 }
1399
1400 /* Skip if tsock is already linked with this output. */
1401 bNodeLink *existing_link_connected_to_fromsock = nullptr;
1402 LISTBASE_FOREACH (bNodeLink *, existing_link, &snode.edittree->links) {
1403 if (existing_link->fromsock == link.fromsock && existing_link->tosock == tsock) {
1404 existing_link_connected_to_fromsock = existing_link;
1405 break;
1406 }
1407 }
1408
1409 /* Attach links to the socket. */
1410 link.tonode = &tnode;
1411 link.tosock = tsock;
1413 if (existing_link_connected_to_fromsock) {
1414 link.multi_input_sort_id = existing_link_connected_to_fromsock->multi_input_sort_id;
1415 continue;
1416 }
1417 if (tsock && tsock->is_multi_input()) {
1418 sort_multi_input_socket_links_with_drag(*tsock, link, cursor);
1419 }
1420 }
1421 }
1422 else {
1423 nldrag.hovered_socket = nullptr;
1424 for (bNodeLink &link : nldrag.links) {
1425 link.tonode = nullptr;
1426 link.tosock = nullptr;
1427 }
1431 }
1432 }
1433 }
1434 else {
1435 if (bNodeSocket *tsock = node_find_indicated_socket(snode, region, cursor, SOCK_OUT)) {
1436 nldrag.hovered_socket = tsock;
1437 bNode &node = tsock->owner_node();
1438 for (bNodeLink &link : nldrag.links) {
1439 /* Skip if this is already the target socket. */
1440 if (link.fromsock == tsock) {
1441 continue;
1442 }
1443 /* Skip if socket is on the same node as the `fromsock`. */
1444 if (link.tonode == &node) {
1445 continue;
1446 }
1447
1448 /* Attach links to the socket. */
1449 link.fromnode = &node;
1450 link.fromsock = tsock;
1451 }
1452 }
1453 else {
1454 nldrag.hovered_socket = nullptr;
1455 for (bNodeLink &link : nldrag.links) {
1456 link.fromnode = nullptr;
1457 link.fromsock = nullptr;
1458 }
1459 }
1460 }
1461}
1462
1463enum class NodeLinkAction : int {
1466 Swap = 2,
1468};
1469
1471{
1472 static const EnumPropertyItem modal_items[] = {
1473 {int(NodeLinkAction::Begin), "BEGIN", 0, "Drag Node-link", ""},
1474 {int(NodeLinkAction::Confirm), "CONFIRM", 0, "Confirm Link", ""},
1475 {int(NodeLinkAction::Cancel), "CANCEL", 0, "Cancel", ""},
1476 {int(NodeLinkAction::Swap), "SWAP", 0, "Swap Links", ""},
1477 {0, nullptr, 0, nullptr, nullptr},
1478 };
1479
1480 wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Node Link Modal Map");
1481
1482 /* This function is called for each space-type, only needs to add map once. */
1483 if (keymap && keymap->modal_items) {
1484 return nullptr;
1485 }
1486
1487 keymap = WM_modalkeymap_ensure(keyconf, "Node Link Modal Map", modal_items);
1488
1489 WM_modalkeymap_assign(keymap, "NODE_OT_link");
1490
1491 return keymap;
1492}
1493
1495{
1496 bNodeLinkDrag &nldrag = *static_cast<bNodeLinkDrag *>(op->customdata);
1497 SpaceNode &snode = *CTX_wm_space_node(C);
1498 ARegion *region = CTX_wm_region(C);
1499
1500 UI_view2d_edge_pan_apply_event(C, &nldrag.pan_data, event);
1501
1502 float2 cursor;
1503 UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &cursor.x, &cursor.y);
1504 nldrag.cursor[0] = event->mval[0];
1505 nldrag.cursor[1] = event->mval[1];
1506
1507 if (event->type == EVT_MODAL_MAP) {
1508 switch (event->val) {
1509 case int(NodeLinkAction::Begin): {
1511 }
1512 case int(NodeLinkAction::Confirm): {
1513 /* Add a search menu for compatible sockets if the drag released on empty space. */
1514 if (should_create_drag_link_search_menu(*snode.edittree, nldrag)) {
1515 bNodeLink &link = nldrag.links.first();
1516 if (nldrag.in_out == SOCK_OUT) {
1517 invoke_node_link_drag_add_menu(*C, *link.fromnode, *link.fromsock, cursor);
1518 }
1519 else {
1520 invoke_node_link_drag_add_menu(*C, *link.tonode, *link.tosock, cursor);
1521 }
1522 }
1523 add_dragged_links_to_tree(*C, nldrag);
1524 return OPERATOR_FINISHED;
1525 }
1526 case int(NodeLinkAction::Cancel): {
1527 node_link_cancel(C, op);
1528 return OPERATOR_CANCELLED;
1529 }
1530 case int(NodeLinkAction::Swap):
1531 if (event->prev_val == KM_PRESS) {
1532 nldrag.swap_links = true;
1533 }
1534 else if (event->prev_val == KM_RELEASE) {
1535 nldrag.swap_links = false;
1536 }
1538 }
1539 }
1540 else if (event->type == MOUSEMOVE) {
1541 if (nldrag.start_socket->is_multi_input() && nldrag.links.is_empty()) {
1542 pick_input_link_by_link_intersect(*C, *op, nldrag, cursor);
1543 }
1544 else {
1545 node_link_find_socket(*C, *op, cursor);
1546 ED_region_tag_redraw(region);
1547 }
1548
1549 if (need_drag_link_tooltip(*snode.edittree, nldrag)) {
1550 draw_draglink_tooltip_activate(*region, nldrag);
1551 }
1552 else {
1553 draw_draglink_tooltip_deactivate(*region, nldrag);
1554 }
1555 }
1556
1558}
1559
1561{
1562 tree.ensure_topology_cache();
1563 Vector<bNodeLink *> links = socket.directly_linked_links();
1564 for (bNodeLink *link : links) {
1565 if (!link->is_available()) {
1566 bke::node_remove_link(&tree, *link);
1567 }
1568 }
1569}
1570
1571static std::unique_ptr<bNodeLinkDrag> node_link_init(ARegion &region,
1572 SpaceNode &snode,
1573 const float2 cursor,
1574 const bool detach)
1575{
1576 if (bNodeSocket *sock = node_find_indicated_socket(snode, region, cursor, SOCK_OUT)) {
1577 remove_unavailable_links(*snode.edittree, *sock);
1578 snode.edittree->ensure_topology_cache();
1579 bNode &node = sock->owner_node();
1580
1581 std::unique_ptr<bNodeLinkDrag> nldrag = std::make_unique<bNodeLinkDrag>();
1582 nldrag->start_node = &node;
1583 nldrag->start_socket = sock;
1584 nldrag->start_link_count = bke::node_count_socket_links(*snode.edittree, *sock);
1585 int link_limit = bke::node_socket_link_limit(*sock);
1586 if (nldrag->start_link_count > 0 && (nldrag->start_link_count >= link_limit || detach)) {
1587 /* Dragged links are fixed on input side. */
1588 nldrag->in_out = SOCK_IN;
1589 /* Detach current links and store them in the operator data. */
1591 if (link->fromsock == sock) {
1592 bNodeLink oplink = *link;
1593 oplink.next = oplink.prev = nullptr;
1594 oplink.flag |= NODE_LINK_VALID;
1595
1596 nldrag->links.append(oplink);
1597 bke::node_remove_link(snode.edittree, *link);
1598 }
1599 }
1600 }
1601 else {
1602 /* Dragged links are fixed on output side. */
1603 nldrag->in_out = SOCK_OUT;
1604 nldrag->links.append(create_drag_link(node, *sock));
1605 }
1606 return nldrag;
1607 }
1608
1609 if (bNodeSocket *sock = node_find_indicated_socket(snode, region, cursor, SOCK_IN)) {
1610 remove_unavailable_links(*snode.edittree, *sock);
1611 snode.edittree->ensure_topology_cache();
1612 bNode &node = sock->owner_node();
1613 std::unique_ptr<bNodeLinkDrag> nldrag = std::make_unique<bNodeLinkDrag>();
1614 nldrag->last_node_hovered_while_dragging_a_link = &node;
1615 nldrag->start_node = &node;
1616 nldrag->start_socket = sock;
1617
1618 nldrag->start_link_count = bke::node_count_socket_links(*snode.edittree, *sock);
1619 if (nldrag->start_link_count > 0) {
1620 /* Dragged links are fixed on output side. */
1621 nldrag->in_out = SOCK_OUT;
1622 /* Detach current links and store them in the operator data. */
1623 bNodeLink *link_to_pick;
1625 if (link->tosock == sock) {
1626 link_to_pick = link;
1627 }
1628 }
1629
1630 if (link_to_pick != nullptr && !nldrag->start_socket->is_multi_input()) {
1631 bNodeLink oplink = *link_to_pick;
1632 oplink.next = oplink.prev = nullptr;
1633 oplink.flag |= NODE_LINK_VALID;
1634
1635 nldrag->links.append(oplink);
1636 bke::node_remove_link(snode.edittree, *link_to_pick);
1637
1638 /* Send changed event to original link->tonode. */
1640 }
1641 }
1642 else {
1643 /* Dragged links are fixed on input side. */
1644 nldrag->in_out = SOCK_IN;
1645 nldrag->links.append(create_drag_link(node, *sock));
1646 }
1647 return nldrag;
1648 }
1649
1650 return {};
1651}
1652
1654{
1655 Main &bmain = *CTX_data_main(C);
1656 SpaceNode &snode = *CTX_wm_space_node(C);
1657 ARegion &region = *CTX_wm_region(C);
1658
1659 bool detach = RNA_boolean_get(op->ptr, "detach");
1660
1661 int2 mval;
1662 WM_event_drag_start_mval(event, &region, mval);
1663
1664 float2 cursor;
1665 UI_view2d_region_to_view(&region.v2d, mval[0], mval[1], &cursor[0], &cursor[1]);
1666 RNA_float_set_array(op->ptr, "drag_start", cursor);
1667
1669
1670 std::unique_ptr<bNodeLinkDrag> nldrag = node_link_init(region, snode, cursor, detach);
1671 if (!nldrag) {
1673 }
1674
1675 UI_view2d_edge_pan_operator_init(C, &nldrag->pan_data, op);
1676
1677 /* Add icons at the cursor when the link is dragged in empty space. */
1678 if (need_drag_link_tooltip(*snode.edittree, *nldrag)) {
1680 }
1681 snode.runtime->linkdrag = std::move(nldrag);
1682 op->customdata = snode.runtime->linkdrag.get();
1683
1685
1687}
1688
1690{
1691 /* identifiers */
1692 ot->name = "Link Nodes";
1693 ot->idname = "NODE_OT_link";
1694 ot->description = "Use the mouse to create a link between two nodes";
1695
1696 /* API callbacks. */
1697 ot->invoke = node_link_invoke;
1698 ot->modal = node_link_modal;
1700 ot->cancel = node_link_cancel;
1701
1702 /* flags */
1704
1705 RNA_def_boolean(ot->srna, "detach", false, "Detach", "Detach and redirect existing links");
1706 RNA_def_float_array(ot->srna,
1707 "drag_start",
1708 2,
1709 nullptr,
1712 "Drag Start",
1713 "The position of the mouse cursor at the start of the operation",
1716
1724}
1725
1727
1728/* -------------------------------------------------------------------- */
1731
1732/* Makes a link between selected output and input sockets. */
1734{
1735 Main &bmain = *CTX_data_main(C);
1736 SpaceNode &snode = *CTX_wm_space_node(C);
1737 bNodeTree &node_tree = *snode.edittree;
1738 const bool replace = RNA_boolean_get(op->ptr, "replace");
1739
1741
1742 snode_autoconnect(*C, snode, true, replace);
1743
1744 /* Deselect sockets after linking. */
1745 node_deselect_all_input_sockets(node_tree, false);
1746 node_deselect_all_output_sockets(node_tree, false);
1747
1748 BKE_main_ensure_invariants(bmain, node_tree.id);
1749
1750 return OPERATOR_FINISHED;
1751}
1752
1754{
1755 /* identifiers */
1756 ot->name = "Make Links";
1757 ot->description = "Make a link between selected output and input sockets";
1758 ot->idname = "NODE_OT_link_make";
1759
1760 /* callbacks */
1761 ot->exec = node_make_link_exec;
1762 /* XXX we need a special poll which checks that there are selected input/output sockets. */
1764
1765 /* flags */
1766 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
1767
1769 ot->srna, "replace", false, "Replace", "Replace socket connections with the new links");
1770}
1771
1773
1774/* -------------------------------------------------------------------- */
1777
1779{
1780 Main &bmain = *CTX_data_main(C);
1781 SpaceNode &snode = *CTX_wm_space_node(C);
1782 const ARegion &region = *CTX_wm_region(C);
1783
1784 Vector<float2> path;
1785 RNA_BEGIN (op->ptr, itemptr, "path") {
1786 float2 loc_region;
1787 RNA_float_get_array(&itemptr, "loc", loc_region);
1788 float2 loc_view;
1789 UI_view2d_region_to_view(&region.v2d, loc_region.x, loc_region.y, &loc_view.x, &loc_view.y);
1790 path.append(loc_view);
1791 if (path.size() >= 256) {
1792 break;
1793 }
1794 }
1795 RNA_END;
1796
1797 if (path.is_empty()) {
1799 }
1800
1801 bool found = false;
1802
1804
1805 bNodeTree &node_tree = *snode.edittree;
1806 node_tree.ensure_topology_cache();
1807
1808 Set<bNodeLink *> links_to_remove;
1809 LISTBASE_FOREACH (bNodeLink *, link, &node_tree.links) {
1810 if (node_link_is_hidden_or_dimmed(region.v2d, *link)) {
1811 continue;
1812 }
1813
1814 if (link_path_intersection(*link, path)) {
1815
1816 if (!found) {
1817 /* TODO(sergey): Why did we kill jobs twice? */
1819 found = true;
1820 }
1821 links_to_remove.add(link);
1822 }
1823 }
1824
1825 Set<bNode *> affected_nodes;
1826 for (bNodeLink *link : links_to_remove) {
1827 bNode *to_node = link->tonode;
1828 bke::node_remove_link(snode.edittree, *link);
1829 affected_nodes.add(to_node);
1830 }
1831
1832 node_tree.ensure_topology_cache();
1833 for (bNode *node : affected_nodes) {
1835 }
1836
1838 if (found) {
1839 return OPERATOR_FINISHED;
1840 }
1841
1842 return OPERATOR_CANCELLED;
1843}
1844
1846{
1847 ot->name = "Cut Links";
1848 ot->idname = "NODE_OT_links_cut";
1849 ot->description = "Use the mouse to cut (remove) some links";
1850
1851 ot->invoke = WM_gesture_lines_invoke;
1852 ot->modal = WM_gesture_lines_modal;
1853 ot->exec = cut_links_exec;
1854 ot->cancel = WM_gesture_lines_cancel;
1855
1857
1858 /* flags */
1860
1861 /* properties */
1862 PropertyRNA *prop;
1863 prop = RNA_def_collection_runtime(ot->srna, "path", &RNA_OperatorMousePath, "Path", "");
1865
1866 /* internal */
1867 RNA_def_int(ot->srna, "cursor", WM_CURSOR_KNIFE, 0, INT_MAX, "Cursor", "", 0, INT_MAX);
1868}
1869
1871
1872/* -------------------------------------------------------------------- */
1875
1876bool all_links_muted(const bNodeSocket &socket)
1877{
1878 for (const bNodeLink *link : socket.directly_linked_links()) {
1879 if (!(link->flag & NODE_LINK_MUTED)) {
1880 return false;
1881 }
1882 }
1883 return true;
1884}
1885
1887{
1888 Main &bmain = *CTX_data_main(C);
1889 SpaceNode &snode = *CTX_wm_space_node(C);
1890 const ARegion &region = *CTX_wm_region(C);
1891 bNodeTree &ntree = *snode.edittree;
1892
1893 Vector<float2> path;
1894 RNA_BEGIN (op->ptr, itemptr, "path") {
1895 float2 loc_region;
1896 RNA_float_get_array(&itemptr, "loc", loc_region);
1897 float2 loc_view;
1898 UI_view2d_region_to_view(&region.v2d, loc_region.x, loc_region.y, &loc_view.x, &loc_view.y);
1899 path.append(loc_view);
1900 if (path.size() >= 256) {
1901 break;
1902 }
1903 }
1904 RNA_END;
1905
1906 if (path.is_empty()) {
1908 }
1909
1911
1912 ntree.ensure_topology_cache();
1913
1914 Set<bNodeLink *> affected_links;
1915 LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
1916 if (node_link_is_hidden_or_dimmed(region.v2d, *link)) {
1917 continue;
1918 }
1919 if (!link_path_intersection(*link, path)) {
1920 continue;
1921 }
1922 affected_links.add(link);
1923 }
1924
1925 if (affected_links.is_empty()) {
1926 return OPERATOR_CANCELLED;
1927 }
1928
1929 bke::node_tree_runtime::AllowUsingOutdatedInfo allow_outdated_info{ntree};
1930
1931 for (bNodeLink *link : affected_links) {
1932 bke::node_link_set_mute(ntree, *link, !(link->flag & NODE_LINK_MUTED));
1933 const bool muted = link->flag & NODE_LINK_MUTED;
1934
1935 /* Propagate mute status downstream past reroute nodes. */
1936 if (link->tonode->is_reroute()) {
1937 Stack<bNodeLink *> links;
1938 links.push_multiple(link->tonode->output_socket(0).directly_linked_links());
1939 while (!links.is_empty()) {
1940 bNodeLink *link = links.pop();
1941 bke::node_link_set_mute(ntree, *link, muted);
1942 if (!link->tonode->is_reroute()) {
1943 continue;
1944 }
1945 links.push_multiple(link->tonode->output_socket(0).directly_linked_links());
1946 }
1947 }
1948 /* Propagate mute status upstream past reroutes, but only if all outputs are muted. */
1949 if (link->fromnode->is_reroute()) {
1950 if (!muted || all_links_muted(*link->fromsock)) {
1951 Stack<bNodeLink *> links;
1952 links.push_multiple(link->fromnode->input_socket(0).directly_linked_links());
1953 while (!links.is_empty()) {
1954 bNodeLink *link = links.pop();
1955 bke::node_link_set_mute(ntree, *link, muted);
1956 if (!link->fromnode->is_reroute()) {
1957 continue;
1958 }
1959 if (!muted || all_links_muted(*link->fromsock)) {
1960 links.push_multiple(link->fromnode->input_socket(0).directly_linked_links());
1961 }
1962 }
1963 }
1964 }
1965 }
1966
1968 return OPERATOR_FINISHED;
1969}
1970
1972{
1973 ot->name = "Mute Links";
1974 ot->idname = "NODE_OT_links_mute";
1975 ot->description = "Use the mouse to mute links";
1976
1977 ot->invoke = WM_gesture_lines_invoke;
1978 ot->modal = WM_gesture_lines_modal;
1979 ot->exec = mute_links_exec;
1980 ot->cancel = WM_gesture_lines_cancel;
1981
1983
1984 /* flags */
1986
1987 /* properties */
1988 PropertyRNA *prop;
1989 prop = RNA_def_collection_runtime(ot->srna, "path", &RNA_OperatorMousePath, "Path", "");
1991
1992 /* internal */
1993 RNA_def_int(ot->srna, "cursor", WM_CURSOR_MUTE, 0, INT_MAX, "Cursor", "", 0, INT_MAX);
1994}
1995
1997
1998/* -------------------------------------------------------------------- */
2001
2003{
2004 SpaceNode &snode = *CTX_wm_space_node(C);
2005 bNodeTree &ntree = *snode.edittree;
2006
2008
2009 for (bNode *node : ntree.all_nodes()) {
2010 if (node->flag & SELECT) {
2011 bke::node_internal_relink(ntree, *node);
2012 }
2013 }
2014
2016 return OPERATOR_FINISHED;
2017}
2018
2020{
2021 ot->name = "Detach Links";
2022 ot->idname = "NODE_OT_links_detach";
2023 ot->description =
2024 "Remove all links to selected nodes, and try to connect neighbor nodes together";
2025
2026 ot->exec = detach_links_exec;
2028
2029 /* flags */
2030 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2031}
2032
2034
2035/* -------------------------------------------------------------------- */
2038
2040{
2041 SpaceNode &snode = *CTX_wm_space_node(C);
2042 bNodeTree &ntree = *snode.edittree;
2043 bNode *frame = bke::node_get_active(ntree);
2044 if (!frame || !frame->is_frame()) {
2045 return OPERATOR_CANCELLED;
2046 }
2047
2048 for (bNode *node : ntree.all_nodes()) {
2049 if (node == frame) {
2050 continue;
2051 }
2052 if (node->flag & NODE_SELECT) {
2053 bke::node_detach_node(ntree, *node);
2054 bke::node_attach_node(ntree, *node, *frame);
2055 }
2056 }
2057
2060
2061 return OPERATOR_FINISHED;
2062}
2063
2065{
2066 /* identifiers */
2067 ot->name = "Make Parent";
2068 ot->description = "Attach selected nodes";
2069 ot->idname = "NODE_OT_parent_set";
2070
2071 /* API callbacks. */
2072 ot->exec = node_parent_set_exec;
2074
2075 /* flags */
2076 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2077}
2078
2080
2081/* -------------------------------------------------------------------- */
2084
2086 bool done;
2088};
2089
2091 MutableSpan<NodeJoinState> join_states,
2092 bNode *node,
2093 bNode *frame,
2094 const VectorSet<bNode *> &selected_nodes)
2095{
2096 join_states[node->index()].done = true;
2097
2098 if (node == frame) {
2099 join_states[node->index()].descendent = true;
2100 }
2101 else if (node->parent) {
2102 /* call recursively */
2103 if (!join_states[node->parent->index()].done) {
2104 node_join_attach_recursive(ntree, join_states, node->parent, frame, selected_nodes);
2105 }
2106
2107 /* in any case: if the parent is a descendant, so is the child */
2108 if (join_states[node->parent->index()].descendent) {
2109 join_states[node->index()].descendent = true;
2110 }
2111 else if (selected_nodes.contains(node)) {
2112 /* if parent is not an descendant of the frame, reattach the node */
2113 bke::node_detach_node(ntree, *node);
2114 bke::node_attach_node(ntree, *node, *frame);
2115 join_states[node->index()].descendent = true;
2116 }
2117 }
2118 else if (selected_nodes.contains(node)) {
2119 bke::node_attach_node(ntree, *node, *frame);
2120 join_states[node->index()].descendent = true;
2121 }
2122}
2123
2125{
2126 Vector<const bNode *> parents;
2127 for (const bNode *parent = node.parent; parent; parent = parent->parent) {
2128 parents.append(parent);
2129 }
2130 /* Reverse so that the root frame is the first element (if there is any). */
2131 std::reverse(parents.begin(), parents.end());
2132 return parents;
2133}
2134
2136{
2137 if (nodes.is_empty()) {
2138 return nullptr;
2139 }
2140 /* The common parent node also has to be a parent of the first node. */
2142 for (const bNode *node : nodes.drop_front(1)) {
2143 const Vector<const bNode *> parents = get_sorted_node_parents(*node);
2144 /* Possibly shrink set of candidates so that it only contains the parents common with the
2145 * current node. */
2146 candidates.resize(std::min(candidates.size(), parents.size()));
2147 for (const int i : candidates.index_range()) {
2148 if (candidates[i] != parents[i]) {
2149 candidates.resize(i);
2150 break;
2151 }
2152 }
2153 if (candidates.is_empty()) {
2154 break;
2155 }
2156 }
2157 if (candidates.is_empty()) {
2158 return nullptr;
2159 }
2160 return candidates.last();
2161}
2162
2164{
2165 Main &bmain = *CTX_data_main(C);
2166 SpaceNode &snode = *CTX_wm_space_node(C);
2167 bNodeTree &ntree = *snode.edittree;
2168
2169 const VectorSet<bNode *> selected_nodes = get_selected_nodes(ntree);
2170
2171 bNode *frame_node = add_static_node(*C, NODE_FRAME, snode.runtime->cursor);
2172 bke::node_set_active(ntree, *frame_node);
2173 frame_node->parent = const_cast<bNode *>(find_common_parent_node(selected_nodes.as_span()));
2174
2175 ntree.ensure_topology_cache();
2176
2177 Array<NodeJoinState> join_states(ntree.all_nodes().size(), NodeJoinState{false, false});
2178
2179 for (bNode *node : ntree.all_nodes()) {
2180 if (!join_states[node->index()].done) {
2181 node_join_attach_recursive(ntree, join_states, node, frame_node, selected_nodes);
2182 }
2183 }
2184
2186 BKE_main_ensure_invariants(bmain, snode.edittree->id);
2188
2189 return OPERATOR_FINISHED;
2190}
2191
2193 wmOperator *op,
2194 const wmEvent *event)
2195{
2196 ARegion *region = CTX_wm_region(C);
2197 SpaceNode *snode = CTX_wm_space_node(C);
2198
2199 /* Convert mouse coordinates to v2d space. */
2201 event->mval[0],
2202 event->mval[1],
2203 &snode->runtime->cursor[0],
2204 &snode->runtime->cursor[1]);
2205
2206 snode->runtime->cursor[0] /= UI_SCALE_FAC;
2207 snode->runtime->cursor[1] /= UI_SCALE_FAC;
2208
2209 return node_join_in_frame_exec(C, op);
2210}
2211
2213{
2214 /* identifiers */
2215 ot->name = "Join Nodes in Frame";
2216 ot->description = "Attach selected nodes to a new common frame";
2217 ot->idname = "NODE_OT_join";
2218
2219 /* API callbacks. */
2221 ot->invoke = node_join_in_frame_invoke;
2223
2224 /* flags */
2225 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2226}
2227
2229
2230/* -------------------------------------------------------------------- */
2233
2234static void join_group_inputs(bNodeTree &tree, VectorSet<bNode *> group_inputs, bNode *active_node)
2235{
2236 bNode *main_node = nullptr;
2237 if (group_inputs.contains(active_node)) {
2238 main_node = active_node;
2239 }
2240 else {
2241 main_node = group_inputs[0];
2242 /* Move main node to average of all group inputs. */
2243 float2 location{};
2244 for (const bNode *node : group_inputs) {
2245 location += node->location;
2246 }
2247 location /= float(group_inputs.size());
2248 copy_v2_v2(main_node->location, location);
2249 }
2250 tree.ensure_topology_cache();
2252 for (bNode *node : group_inputs) {
2253 for (bNodeSocket *socket : node->output_sockets().drop_back(1)) {
2254 old_link_map.add_multiple(socket, socket->directly_linked_links());
2255 }
2256 }
2258 for (bNodeSocket *socket : main_node->output_sockets()) {
2259 used_link_targets.add_multiple(socket, socket->directly_linked_sockets());
2260 }
2261 for (bNode *node : group_inputs) {
2262 if (node == main_node) {
2263 continue;
2264 }
2265 bool keep_node = false;
2266
2267 /* Using runtime data directly because we know the parts that are used are still valid. */
2268 for (const int group_input_i : node->runtime->outputs.index_range().drop_back(1)) {
2269 bool keep_socket = false;
2270 bNodeSocket &new_socket = *main_node->runtime->outputs[group_input_i];
2271 bNodeSocket &old_socket = *node->runtime->outputs[group_input_i];
2272 for (bNodeLink *link : old_link_map.lookup(&old_socket)) {
2273 bNodeSocket &to_socket = *link->tosock;
2274 if (used_link_targets.lookup(&new_socket).contains(&to_socket)) {
2275 keep_node = true;
2276 keep_socket = true;
2277 continue;
2278 }
2279 used_link_targets.add(&new_socket, &to_socket);
2280 link->fromsock = &new_socket;
2281 link->fromnode = main_node;
2282 new_socket.flag &= ~SOCK_HIDDEN;
2284 }
2285 if (!keep_socket) {
2286 old_socket.flag |= SOCK_HIDDEN;
2287 }
2288 }
2289 if (!keep_node) {
2290 bke::node_free_node(&tree, *node);
2291 }
2292 }
2293}
2294
2296{
2297 Main &bmain = *CTX_data_main(C);
2298 SpaceNode &snode = *CTX_wm_space_node(C);
2299 bNodeTree &ntree = *snode.edittree;
2300
2301 bNode *active_node = bke::node_get_active(ntree);
2302 VectorSet<bNode *> selected_nodes = get_selected_nodes(ntree);
2303 if (selected_nodes.size() <= 1) {
2304 return OPERATOR_CANCELLED;
2305 }
2306
2307 if (std::all_of(selected_nodes.begin(), selected_nodes.end(), [](const bNode *node) {
2308 return node->is_group_input();
2309 }))
2310 {
2311 join_group_inputs(ntree, std::move(selected_nodes), active_node);
2312 }
2313 else {
2314 BKE_report(op->reports, RPT_ERROR, "Selected nodes can't be joined");
2315 return OPERATOR_CANCELLED;
2316 }
2317
2318 BKE_main_ensure_invariants(bmain, snode.edittree->id);
2320
2321 return OPERATOR_FINISHED;
2322}
2323
2325{
2326 ot->name = "Join Nodes";
2327 ot->description = "Merge selected group input nodes into one if possible";
2328 ot->idname = "NODE_OT_join_nodes";
2329
2330 ot->exec = node_join_nodes_exec;
2332
2333 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2334}
2335
2337
2338/* -------------------------------------------------------------------- */
2341
2342static bNode *node_find_frame_to_attach(ARegion &region, bNodeTree &ntree, const int2 mouse_xy)
2343{
2344 /* convert mouse coordinates to v2d space */
2345 float2 cursor;
2346 UI_view2d_region_to_view(&region.v2d, mouse_xy.x, mouse_xy.y, &cursor.x, &cursor.y);
2347
2348 for (bNode *frame : tree_draw_order_calc_nodes_reversed(ntree)) {
2349 /* skip selected, those are the nodes we want to attach */
2350 if (!frame->is_frame() || (frame->flag & NODE_SELECT)) {
2351 continue;
2352 }
2353 if (BLI_rctf_isect_pt_v(&frame->runtime->draw_bounds, cursor)) {
2354 return frame;
2355 }
2356 }
2357
2358 return nullptr;
2359}
2360
2361static bool can_attach_node_to_frame(const bNode &node, const bNode &frame)
2362{
2363 /* Disallow moving a parent into its child. */
2364 if (node.is_frame() && bke::node_is_parent_and_child(node, frame)) {
2365 return false;
2366 }
2367 if (node.parent == nullptr) {
2368 return true;
2369 }
2370 if (node.parent == &frame) {
2371 return false;
2372 }
2373 /* Attach nodes which share parent with the frame. */
2374 const bool share_parent = bke::node_is_parent_and_child(*node.parent, frame);
2375 if (!share_parent) {
2376 return false;
2377 }
2378 return true;
2379}
2380
2382{
2383 ARegion &region = *CTX_wm_region(C);
2384 SpaceNode &snode = *CTX_wm_space_node(C);
2385 bNodeTree &ntree = *snode.edittree;
2386 bNode *frame = node_find_frame_to_attach(region, ntree, event->mval);
2387 if (frame == nullptr) {
2388 /* Return "finished" so that auto offset operator macros can work. */
2389 return OPERATOR_FINISHED;
2390 }
2391
2392 bool changed = false;
2393
2394 for (bNode *node : tree_draw_order_calc_nodes_reversed(*snode.edittree)) {
2395 if (!(node->flag & NODE_SELECT)) {
2396 continue;
2397 }
2398 if (!can_attach_node_to_frame(*node, *frame)) {
2399 continue;
2400 }
2401 bke::node_detach_node(ntree, *node);
2402 bke::node_attach_node(ntree, *node, *frame);
2403 changed = true;
2404 }
2405
2406 if (changed) {
2409 }
2410
2411 return OPERATOR_FINISHED;
2412}
2413
2415{
2416 ot->name = "Attach Nodes";
2417 ot->description = "Attach active node to a frame";
2418 ot->idname = "NODE_OT_attach";
2419
2420 ot->invoke = node_attach_invoke;
2422
2423 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2424}
2425
2427
2428/* -------------------------------------------------------------------- */
2431
2433 bool done;
2435};
2436
2438 MutableSpan<NodeDetachstate> detach_states,
2439 bNode *node)
2440{
2441 detach_states[node->index()].done = true;
2442
2443 if (node->parent) {
2444 /* Call recursively. */
2445 if (!detach_states[node->parent->index()].done) {
2446 node_detach_recursive(ntree, detach_states, node->parent);
2447 }
2448
2449 /* In any case: if the parent is a descendant, so is the child. */
2450 if (detach_states[node->parent->index()].descendent) {
2451 detach_states[node->index()].descendent = true;
2452 }
2453 else if (node->flag & NODE_SELECT) {
2454 /* If parent is not a descendant of a selected node, detach. */
2455 bke::node_detach_node(ntree, *node);
2456 detach_states[node->index()].descendent = true;
2457 }
2458 }
2459 else if (node->flag & NODE_SELECT) {
2460 detach_states[node->index()].descendent = true;
2461 }
2462}
2463
2464/* Detach the root nodes in the current selection. */
2466{
2467 SpaceNode &snode = *CTX_wm_space_node(C);
2468 bNodeTree &ntree = *snode.edittree;
2469
2470 Array<NodeDetachstate> detach_states(ntree.all_nodes().size(), NodeDetachstate{false, false});
2471
2472 /* Detach nodes recursively. Relative order is preserved here. */
2473 for (bNode *node : ntree.all_nodes()) {
2474 if (!detach_states[node->index()].done) {
2475 node_detach_recursive(ntree, detach_states, node);
2476 }
2477 }
2478
2481
2482 return OPERATOR_FINISHED;
2483}
2484
2486{
2487 /* identifiers */
2488 ot->name = "Detach Nodes";
2489 ot->description = "Detach selected nodes from parents";
2490 ot->idname = "NODE_OT_detach";
2491
2492 /* API callbacks. */
2493 ot->exec = node_detach_exec;
2495
2496 /* flags */
2497 ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
2498}
2499
2501
2502/* -------------------------------------------------------------------- */
2505
2507{
2508 bNode *selected_node = nullptr;
2509 int selected_node_count = 0;
2510 for (bNode *node : node_tree.all_nodes()) {
2511 if (node->flag & SELECT) {
2512 selected_node = node;
2513 selected_node_count++;
2514 }
2515 if (selected_node_count > 1) {
2516 return nullptr;
2517 }
2518 }
2519 if (!selected_node) {
2520 return nullptr;
2521 }
2522 if (selected_node->input_sockets().is_empty() || selected_node->output_sockets().is_empty()) {
2523 return nullptr;
2524 }
2525 return selected_node;
2526}
2527
2529{
2530 const bNodeSocket *main_input = get_main_socket(tree, node, SOCK_IN);
2531 const bNodeSocket *main_output = get_main_socket(tree, node, SOCK_IN);
2532 if (ELEM(nullptr, main_input, main_output)) {
2533 return false;
2534 }
2535 if (node.is_reroute()) {
2536 return true;
2537 }
2538 if (!tree.typeinfo->validate_link) {
2539 return true;
2540 }
2541 if (!tree.typeinfo->validate_link(eNodeSocketDatatype(link.fromsock->type),
2542 eNodeSocketDatatype(main_input->type)))
2543 {
2544 return false;
2545 }
2546 if (!tree.typeinfo->validate_link(eNodeSocketDatatype(main_output->type),
2548 {
2549 return false;
2550 }
2551 return true;
2552}
2553
2555 const ARegion &region,
2556 const bool attach_enabled,
2557 const bool is_new_node)
2558{
2559 bNodeTree &node_tree = *snode.edittree;
2560 node_tree.ensure_topology_cache();
2561
2563
2564 bNode *node_to_insert = get_selected_node_for_insertion(node_tree);
2565 if (!node_to_insert) {
2566 return;
2567 }
2568 Vector<bNodeSocket *> already_linked_sockets;
2569 for (bNodeSocket *socket : node_to_insert->input_sockets()) {
2570 already_linked_sockets.extend(socket->directly_linked_sockets());
2571 }
2572 for (bNodeSocket *socket : node_to_insert->output_sockets()) {
2573 already_linked_sockets.extend(socket->directly_linked_sockets());
2574 }
2575 if (!is_new_node && !already_linked_sockets.is_empty()) {
2576 return;
2577 }
2578
2579 /* Find link to select/highlight. */
2580 bNodeLink *selink = nullptr;
2581 float dist_best = FLT_MAX;
2582 LISTBASE_FOREACH (bNodeLink *, link, &node_tree.links) {
2583 if (node_link_is_hidden_or_dimmed(region.v2d, *link)) {
2584 continue;
2585 }
2586 if (ELEM(node_to_insert, link->fromnode, link->tonode)) {
2587 /* Don't insert on a link that is connected to the node already. */
2588 continue;
2589 }
2590 if (is_new_node && !already_linked_sockets.is_empty()) {
2591 /* Only allow links coming from or going to the already linked socket after
2592 * link-drag-search. */
2593 bool is_linked_to_linked = false;
2594 for (const bNodeSocket *socket : already_linked_sockets) {
2595 if (ELEM(socket, link->fromsock, link->tosock)) {
2596 is_linked_to_linked = true;
2597 break;
2598 }
2599 }
2600 if (!is_linked_to_linked) {
2601 continue;
2602 }
2603 }
2604
2605 std::array<float2, NODE_LINK_RESOL + 1> coords;
2606 node_link_bezier_points_evaluated(*link, coords);
2607 float dist = FLT_MAX;
2608
2609 /* Loop over link coords to find shortest dist to upper left node edge of a intersected line
2610 * segment. */
2611 for (int i = 0; i < NODE_LINK_RESOL; i++) {
2612 /* Check if the node rectangle intersects the line from this point to next one. */
2613 if (BLI_rctf_isect_segment(&node_to_insert->runtime->draw_bounds, coords[i], coords[i + 1]))
2614 {
2615 /* Store the shortest distance to the upper left edge of all intersections found so far. */
2616 const float node_xy[] = {node_to_insert->runtime->draw_bounds.xmin,
2617 node_to_insert->runtime->draw_bounds.ymax};
2618
2619 /* To be precise coords should be clipped by `select->draw_bounds`, but not done since
2620 * there's no real noticeable difference. */
2621 dist = min_ff(dist_squared_to_line_segment_v2(node_xy, coords[i], coords[i + 1]), dist);
2622 }
2623 }
2624
2625 /* We want the link with the shortest distance to node center. */
2626 if (dist < dist_best) {
2627 dist_best = dist;
2628 selink = link;
2629 }
2630 }
2631
2632 if (selink) {
2633 selink->flag |= NODE_LINK_INSERT_TARGET;
2634 if (!attach_enabled || !node_can_be_inserted_on_link(node_tree, *node_to_insert, *selink)) {
2636 }
2637 }
2638}
2639
2641{
2643
2644 ARegion &region = *CTX_wm_region(&C);
2645
2646 snode.edittree->ensure_topology_cache();
2647 const bNode *frame = node_find_frame_to_attach(region, *snode.edittree, cursor);
2648 if (!frame) {
2649 return;
2650 }
2651 for (const bNode *node : snode.edittree->all_nodes()) {
2652 if (!(node->flag & NODE_SELECT)) {
2653 continue;
2654 }
2655 if (!can_attach_node_to_frame(*node, *frame)) {
2656 continue;
2657 }
2658 /* We detected that a node can be attached to the frame, so highlight it. */
2660 return;
2661 }
2662}
2663
2668
2670{
2671 LISTBASE_FOREACH (bNodeLink *, link, &node_tree.links) {
2673 }
2674}
2675
2676void node_insert_on_link_flags(Main &bmain, SpaceNode &snode, bool is_new_node)
2677{
2678 bNodeTree &node_tree = *snode.edittree;
2679 node_tree.ensure_topology_cache();
2680 bNode *node_to_insert = get_selected_node_for_insertion(node_tree);
2681 if (!node_to_insert) {
2682 return;
2683 }
2684
2685 /* Find link to insert on. */
2686 bNodeTree &ntree = *snode.edittree;
2687 bNodeLink *old_link = nullptr;
2688 LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
2689 if (link->flag & NODE_LINK_INSERT_TARGET) {
2690 if (!(link->flag & NODE_LINK_INSERT_TARGET_INVALID)) {
2691 old_link = link;
2692 }
2693 break;
2694 }
2695 }
2697 if (old_link == nullptr) {
2698 return;
2699 }
2700
2701 bNodeSocket *best_input = nullptr;
2702 if (is_new_node) {
2703 for (bNodeSocket *socket : node_to_insert->input_sockets()) {
2704 if (!socket->directly_linked_sockets().is_empty()) {
2705 best_input = socket;
2706 break;
2707 }
2708 }
2709 }
2710 if (!best_input) {
2711 best_input = get_main_socket(ntree, *node_to_insert, SOCK_IN);
2712 }
2713 bNodeSocket *best_output = nullptr;
2714 if (is_new_node) {
2715 for (bNodeSocket *socket : node_to_insert->output_sockets()) {
2716 if (!socket->directly_linked_sockets().is_empty()) {
2717 best_output = socket;
2718 break;
2719 }
2720 }
2721 }
2722 if (!best_output) {
2723 best_output = get_main_socket(ntree, *node_to_insert, SOCK_OUT);
2724 }
2725
2726 if (!node_to_insert->is_reroute()) {
2727 /* Ignore main sockets when the types don't match. */
2728 if (best_input != nullptr && ntree.typeinfo->validate_link != nullptr &&
2729 !ntree.typeinfo->validate_link(eNodeSocketDatatype(old_link->fromsock->type),
2730 eNodeSocketDatatype(best_input->type)))
2731 {
2732 best_input = nullptr;
2733 }
2734 if (best_output != nullptr && ntree.typeinfo->validate_link != nullptr &&
2735 !ntree.typeinfo->validate_link(eNodeSocketDatatype(best_output->type),
2736 eNodeSocketDatatype(old_link->tosock->type)))
2737 {
2738 best_output = nullptr;
2739 }
2740 }
2741
2742 bNode *from_node = old_link->fromnode;
2743 bNodeSocket *from_socket = old_link->fromsock;
2744 bNode *to_node = old_link->tonode;
2745
2746 const bool best_input_is_linked = best_input && best_input->is_directly_linked();
2747
2748 if (best_output != nullptr) {
2749 /* Relink the "start" of the existing link to the newly inserted node. */
2750 old_link->fromnode = node_to_insert;
2751 old_link->fromsock = best_output;
2753 }
2754 else {
2755 bke::node_remove_link(&ntree, *old_link);
2756 }
2757
2758 if (best_input != nullptr) {
2759 /* Don't change an existing link. */
2760 if (!best_input_is_linked) {
2761 /* Add a new link that connects the node on the left to the newly inserted node. */
2762 bke::node_add_link(ntree, *from_node, *from_socket, *node_to_insert, *best_input);
2763 }
2764 }
2765
2766 /* Set up insert offset data, it needs stuff from here. */
2767 if (U.uiflag & USER_NODE_AUTO_OFFSET) {
2768 BLI_assert(snode.runtime->iofsd == nullptr);
2770
2771 iofsd->insert = node_to_insert;
2772 iofsd->prev = from_node;
2773 iofsd->next = to_node;
2774
2775 snode.runtime->iofsd = iofsd;
2776 }
2777
2778 BKE_main_ensure_invariants(bmain, ntree.id);
2779}
2780
2782
2783/* -------------------------------------------------------------------- */
2786
2787static int get_main_socket_priority(const bNodeSocket *socket)
2788{
2789 switch (eNodeSocketDatatype(socket->type)) {
2790 case SOCK_CUSTOM:
2791 return 0;
2792 case SOCK_MENU:
2793 return 1;
2794 case SOCK_BOOLEAN:
2795 return 2;
2796 case SOCK_INT:
2797 return 3;
2798 case SOCK_FLOAT:
2799 return 4;
2800 case SOCK_VECTOR:
2801 return 5;
2802 case SOCK_RGBA:
2803 return 6;
2804 case SOCK_STRING:
2805 case SOCK_SHADER:
2806 case SOCK_OBJECT:
2807 case SOCK_IMAGE:
2808 case SOCK_ROTATION:
2809 case SOCK_MATRIX:
2810 case SOCK_GEOMETRY:
2811 case SOCK_COLLECTION:
2812 case SOCK_TEXTURE:
2813 case SOCK_MATERIAL:
2814 case SOCK_BUNDLE:
2815 case SOCK_CLOSURE:
2816 return 7;
2817 }
2818 return -1;
2819}
2820
2822{
2823 ListBase *sockets = (in_out == SOCK_IN) ? &node.inputs : &node.outputs;
2824
2825 /* Try to get the main socket based on the socket declaration. */
2826 bke::node_declaration_ensure(ntree, node);
2827 const nodes::NodeDeclaration *node_decl = node.declaration();
2828 if (node_decl != nullptr) {
2829 Span<nodes::SocketDeclaration *> socket_decls = (in_out == SOCK_IN) ? node_decl->inputs :
2830 node_decl->outputs;
2831 for (const nodes::SocketDeclaration *socket_decl : socket_decls) {
2832 if (!socket_decl->is_default_link_socket) {
2833 continue;
2834 }
2835 bNodeSocket *socket = static_cast<bNodeSocket *>(BLI_findlink(sockets, socket_decl->index));
2836 if (socket && socket->is_visible()) {
2837 return socket;
2838 }
2839 }
2840 }
2841
2842 /* Find priority range. */
2843 int maxpriority = -1;
2844 LISTBASE_FOREACH (bNodeSocket *, sock, sockets) {
2845 if (sock->flag & SOCK_UNAVAIL) {
2846 continue;
2847 }
2848 maxpriority = max_ii(get_main_socket_priority(sock), maxpriority);
2849 }
2850
2851 /* Try all priorities, starting from 'highest'. */
2852 for (int priority = maxpriority; priority >= 0; priority--) {
2853 LISTBASE_FOREACH (bNodeSocket *, sock, sockets) {
2854 if (!!sock->is_visible() && priority == get_main_socket_priority(sock)) {
2855 return sock;
2856 }
2857 }
2858 }
2859
2860 return nullptr;
2861}
2862
2863static bool node_parents_offset_flag_enable_cb(bNode *parent, void * /*userdata*/)
2864{
2865 /* NODE_TEST is used to flag nodes that shouldn't be offset (again) */
2866 parent->flag |= NODE_TEST;
2867
2868 return true;
2869}
2870
2871static void node_offset_apply(bNode &node, const float offset_x)
2872{
2873 /* NODE_TEST is used to flag nodes that shouldn't be offset (again) */
2874 if ((node.flag & NODE_TEST) == 0) {
2875 node.runtime->anim_ofsx = (offset_x / UI_SCALE_FAC);
2876 node.flag |= NODE_TEST;
2877 }
2878}
2879
2880#define NODE_INSOFS_ANIM_DURATION 0.25f
2881
2887 bNode *tonode,
2888 void *userdata,
2889 const bool reversed)
2890{
2892 bNode *ofs_node = reversed ? fromnode : tonode;
2893
2894 node_offset_apply(*ofs_node, data->offset_x);
2895
2896 return true;
2897}
2898
2900 ARegion *region,
2901 const int mouse_xy[2],
2902 const bool right_alignment)
2903{
2904 bNodeTree *ntree = iofsd->ntree;
2905 bNode &insert = *iofsd->insert;
2906 bNode *prev = iofsd->prev, *next = iofsd->next;
2907 bNode *init_parent = insert.parent; /* store old insert.parent for restoring later */
2908
2909 const float min_margin = U.node_margin * UI_SCALE_FAC;
2910 const float width = NODE_WIDTH(insert);
2911 const bool needs_alignment = (next->runtime->draw_bounds.xmin -
2912 prev->runtime->draw_bounds.xmax) < (width + (min_margin * 2.0f));
2913
2914 float margin = width;
2915
2916 /* NODE_TEST will be used later, so disable for all nodes */
2918
2919 /* `insert.draw_bounds` isn't updated yet,
2920 * so `totr_insert` is used to get the correct world-space coords. */
2921 rctf totr_insert;
2922 node_to_updated_rect(insert, totr_insert);
2923
2924 /* Frame attachment wasn't handled yet so we search the frame that the node will be attached to
2925 * later. */
2926 insert.parent = node_find_frame_to_attach(*region, *ntree, mouse_xy);
2927
2928 /* This makes sure nodes are also correctly offset when inserting a node on top of a frame
2929 * without actually making it a part of the frame (because mouse isn't intersecting it)
2930 * - logic here is similar to node_find_frame_to_attach. */
2931 if (!insert.parent ||
2932 (prev->parent && (prev->parent == next->parent) && (prev->parent != insert.parent)))
2933 {
2934 rctf totr_frame;
2935
2936 /* check nodes front to back */
2937 for (bNode *frame : tree_draw_order_calc_nodes_reversed(*ntree)) {
2938 /* skip selected, those are the nodes we want to attach */
2939 if (!frame->is_frame() || (frame->flag & NODE_SELECT)) {
2940 continue;
2941 }
2942
2943 /* for some reason frame y coords aren't correct yet */
2944 node_to_updated_rect(*frame, totr_frame);
2945
2946 if (BLI_rctf_isect_x(&totr_frame, totr_insert.xmin) &&
2947 BLI_rctf_isect_x(&totr_frame, totr_insert.xmax))
2948 {
2949 if (BLI_rctf_isect_y(&totr_frame, totr_insert.ymin) ||
2950 BLI_rctf_isect_y(&totr_frame, totr_insert.ymax))
2951 {
2952 /* frame isn't insert.parent actually, but this is needed to make offsetting
2953 * nodes work correctly for above checked cases (it is restored later) */
2954 insert.parent = frame;
2955 break;
2956 }
2957 }
2958 }
2959 }
2960
2961 /* *** ensure offset at the left (or right for right_alignment case) of insert_node *** */
2962
2963 float dist = right_alignment ? totr_insert.xmin - prev->runtime->draw_bounds.xmax :
2964 next->runtime->draw_bounds.xmin - totr_insert.xmax;
2965 /* distance between insert_node and prev is smaller than min margin */
2966 if (dist < min_margin) {
2967 const float addval = (min_margin - dist) * (right_alignment ? 1.0f : -1.0f);
2968
2969 node_offset_apply(insert, addval);
2970
2971 totr_insert.xmin += addval;
2972 totr_insert.xmax += addval;
2973 margin += min_margin;
2974 }
2975
2976 /* *** ensure offset at the right (or left for right_alignment case) of insert_node *** */
2977
2978 dist = right_alignment ? next->runtime->draw_bounds.xmin - totr_insert.xmax :
2979 totr_insert.xmin - prev->runtime->draw_bounds.xmax;
2980 /* distance between insert_node and next is smaller than min margin */
2981 if (dist < min_margin) {
2982 const float addval = (min_margin - dist) * (right_alignment ? 1.0f : -1.0f);
2983 if (needs_alignment) {
2984 bNode *offs_node = right_alignment ? next : prev;
2985 node_offset_apply(*offs_node, addval);
2986 margin = addval;
2987 }
2988 /* enough room is available, but we want to ensure the min margin at the right */
2989 else {
2990 /* offset inserted node so that min margin is kept at the right */
2991 node_offset_apply(insert, -addval);
2992 }
2993 }
2994
2995 if (needs_alignment) {
2996 iofsd->offset_x = margin;
2997
2998 /* flag all parents of insert as offset to prevent them from being offset */
3000 /* iterate over entire chain and apply offsets */
3002 right_alignment ? next : prev,
3004 iofsd,
3005 !right_alignment);
3006 }
3007
3008 insert.parent = init_parent;
3009}
3010
3015{
3016 SpaceNode *snode = CTX_wm_space_node(C);
3017 NodeInsertOfsData *iofsd = static_cast<NodeInsertOfsData *>(op->customdata);
3018 bool redraw = false;
3019
3020 if (!snode || event->type != TIMER || iofsd == nullptr || iofsd->anim_timer != event->customdata)
3021 {
3022 return OPERATOR_PASS_THROUGH;
3023 }
3024
3025 const float duration = float(iofsd->anim_timer->time_duration);
3026
3027 /* handle animation - do this before possibly aborting due to duration, since
3028 * main thread might be so busy that node hasn't reached final position yet */
3029 for (bNode *node : snode->edittree->all_nodes()) {
3030 if (UNLIKELY(node->runtime->anim_ofsx)) {
3031 const float prev_duration = duration - float(iofsd->anim_timer->time_delta);
3032 /* Clamp duration to not overshoot. */
3033 const float clamped_duration = math::min(duration, NODE_INSOFS_ANIM_DURATION);
3034 if (prev_duration < clamped_duration) {
3035 const float offset_step = node->runtime->anim_ofsx *
3037 clamped_duration, 0.0f, 1.0f, NODE_INSOFS_ANIM_DURATION) -
3039 prev_duration, 0.0f, 1.0f, NODE_INSOFS_ANIM_DURATION));
3040 node->location[0] += offset_step;
3041 redraw = true;
3042 }
3043 }
3044 }
3045 if (redraw) {
3047 }
3048
3049 /* end timer + free insert offset data */
3050 if (duration > NODE_INSOFS_ANIM_DURATION) {
3052
3053 for (bNode *node : snode->edittree->all_nodes()) {
3054 node->runtime->anim_ofsx = 0.0f;
3055 }
3056
3057 MEM_freeN(iofsd);
3058
3060 }
3061
3063}
3064
3065#undef NODE_INSOFS_ANIM_DURATION
3066
3068 wmOperator *op,
3069 const wmEvent *event)
3070{
3071 const SpaceNode *snode = CTX_wm_space_node(C);
3072 NodeInsertOfsData *iofsd = snode->runtime->iofsd;
3073 snode->runtime->iofsd = nullptr;
3074 op->customdata = iofsd;
3075
3076 if (!iofsd || !iofsd->insert) {
3077 return OPERATOR_CANCELLED;
3078 }
3079
3081
3082 iofsd->ntree = snode->edittree;
3084
3086 iofsd, CTX_wm_region(C), event->mval, (snode->insert_ofs_dir == SNODE_INSERTOFS_DIR_RIGHT));
3087
3088 /* add temp handler */
3090
3092}
3093
3095{
3096 /* identifiers */
3097 ot->name = "Insert Offset";
3098 ot->description = "Automatically offset nodes on insertion";
3099 ot->idname = "NODE_OT_insert_offset";
3100
3101 /* callbacks */
3102 ot->invoke = node_insert_offset_invoke;
3105
3106 /* flags */
3108}
3109
3111
3112} // 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)
void BKE_main_ensure_invariants(Main &bmain, std::optional< blender::Span< ID * > > modified_ids=std::nullopt)
#define NODE_FRAME
Definition BKE_node.hh:812
#define CMP_NODE_VIEWER
#define GEO_NODE_VIEWER
void BKE_ntree_update_tag_active_output_changed(bNodeTree *ntree)
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)
@ RPT_ERROR
Definition BKE_report.hh:39
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
ARegion * BKE_area_find_region_type(const ScrArea *area, int region_type)
Definition screen.cc:846
#define BLI_assert(a)
Definition BLI_assert.h:46
float BLI_easing_cubic_ease_in_out(float time, float begin, float change, float duration)
Definition easing.cc:108
void BLI_kdtree_nd_ insert(KDTree *tree, int index, const float co[KD_DIMS]) ATTR_NONNULL(1
void * BLI_findlink(const ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:534
#define LISTBASE_FOREACH(type, var, list)
BLI_INLINE bool BLI_listbase_is_empty(const ListBase *lb)
#define LISTBASE_FOREACH_MUTABLE(type, var, list)
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
MINLINE float min_ff(float a, float b)
MINLINE int max_ii(int a, int b)
#define M_PI
float dist_squared_to_line_segment_v2(const float p[2], const float l1[2], const float l2[2])
Definition math_geom.cc:291
MINLINE void copy_v2_v2(float r[2], const float a[2])
bool BLI_rctf_isect_x(const rctf *rect, float x)
Definition rct.cc: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.cc: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.cc:637
BLI_INLINE float BLI_rctf_size_x(const struct rctf *rct)
Definition BLI_rect.h:202
bool BLI_rctf_inside_rctf(const rctf *rct_a, const rctf *rct_b)
Definition rct.cc:193
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition BLI_rect.h:206
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.cc:41
unsigned char uchar
#define UNLIKELY(x)
#define ELEM(...)
#define STREQ(a, b)
#define IFACE_(msgid)
@ NODE_TEST
@ NODE_DO_OUTPUT
@ NODE_MUTED
@ NODE_SELECT
@ NTREE_CUSTOM
@ NTREE_GEOMETRY
@ NTREE_COMPOSIT
eNodeSocketInOut
@ SOCK_OUT
@ SOCK_IN
@ NODE_VIEWER_SHORTCUT_NONE
@ NODE_LINK_TEMP_HIGHLIGHT
@ NODE_LINK_MUTED
@ NODE_LINK_INSERT_TARGET
@ NODE_LINK_INSERT_TARGET_INVALID
@ NODE_LINK_VALID
@ SOCK_IS_LINKED
@ SOCK_MULTI_INPUT
@ SOCK_HIDDEN
@ SOCK_UNAVAIL
eNodeSocketDatatype
@ SOCK_INT
@ SOCK_TEXTURE
@ SOCK_VECTOR
@ SOCK_CLOSURE
@ SOCK_BOOLEAN
@ SOCK_MATERIAL
@ SOCK_SHADER
@ SOCK_MATRIX
@ SOCK_FLOAT
@ SOCK_IMAGE
@ SOCK_COLLECTION
@ SOCK_CUSTOM
@ SOCK_BUNDLE
@ SOCK_GEOMETRY
@ SOCK_ROTATION
@ SOCK_OBJECT
@ SOCK_STRING
@ SOCK_RGBA
@ SOCK_MENU
@ NODE_GEO_VIEWER_ITEM_FLAG_AUTO_REMOVE
@ RGN_TYPE_UI
@ SNODE_GEOMETRY_TOOL
@ SNODE_INSERTOFS_DIR_RIGHT
#define UI_SCALE_FAC
#define UI_INV_SCALE_FAC
@ USER_NODE_AUTO_OFFSET
@ USER_REGION_OVERLAP
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
@ OPERATOR_RUNNING_MODAL
@ OPERATOR_PASS_THROUGH
bool ED_node_is_geometry(const SpaceNode *snode)
Definition node_edit.cc:502
bool ED_node_is_compositor(const SpaceNode *snode)
Definition node_edit.cc:487
#define NODE_EDGE_PAN_OUTSIDE_PAD
Definition ED_node_c.hh:29
#define NODE_EDGE_PAN_INSIDE_PAD
Definition ED_node_c.hh:28
#define NODE_EDGE_PAN_MAX_SPEED
Definition ED_node_c.hh:31
#define NODE_EDGE_PAN_DELAY
Definition ED_node_c.hh:32
#define NODE_EDGE_PAN_ZOOM_INFLUENCE
Definition ED_node_c.hh:33
#define NODE_EDGE_PAN_SPEED_RAMP
Definition ED_node_c.hh:30
void ED_preview_kill_jobs(wmWindowManager *wm, Main *bmain)
void ED_area_tag_redraw(ScrArea *area)
Definition area.cc:693
bool ED_operator_node_editable(bContext *C)
void ED_workspace_status_text(bContext *C, const char *str)
Definition area.cc:1024
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:618
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
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:117
Read Guarded memory(de)allocation.
#define MEM_SAFE_FREE(v)
#define RNA_BEGIN(sptr, itemptr, propname)
#define RNA_END
@ PROP_SKIP_SAVE
Definition RNA_types.hh:344
@ PROP_HIDDEN
Definition RNA_types.hh:338
#define C
Definition RandGen.cpp:29
#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:1668
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:1675
#define NC_NODE
Definition WM_types.hh:394
@ KM_PRESS
Definition WM_types.hh:311
@ KM_RELEASE
Definition WM_types.hh:312
#define ND_DISPLAY
Definition WM_types.hh:491
@ OPTYPE_BLOCKING
Definition WM_types.hh:184
@ OPTYPE_DEPENDS_ON_CURSOR
Definition WM_types.hh:218
@ OPTYPE_UNDO
Definition WM_types.hh:182
@ OPTYPE_REGISTER
Definition WM_types.hh:180
#define U
ccl_device_inline float arc_length(const float e2, const float gamma)
void append(const T &value)
bool is_empty() const
const T & first() const
Span< Value > lookup(const Key &key) const
void add_multiple(const Key &key, Span< Value > values)
void add(const Key &key, const Value &value)
bool add(const Key &key)
Definition BLI_set.hh:248
bool is_empty() const
Definition BLI_set.hh:595
constexpr bool contains(const T &value) const
Definition BLI_span.hh:277
bool is_empty() const
Definition BLI_stack.hh:308
void push_multiple(Span< T > values)
Definition BLI_stack.hh:281
const Key * begin() const
int64_t size() const
const Key * end() const
bool contains(const Key &key) const
Span< Key > as_span() 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)
Vector< SocketDeclaration * > inputs
Vector< SocketDeclaration * > outputs
nullptr float
#define SELECT
KDTree_3d * tree
#define input
#define output
float distance(VecOp< float, D >, VecOp< float, D >) RET
uint padding(uint offset, uint alignment)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void * MEM_mallocN(size_t len, const char *str)
Definition mallocn.cc:128
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
static ulong * next
bool node_is_parent_and_child(const bNode &parent, const bNode &child)
Definition node.cc:3330
void node_attach_node(bNodeTree &ntree, bNode &node, bNode &parent)
Definition node.cc:3978
bNode * node_get_active(bNodeTree &ntree)
Definition node.cc:4685
void node_remove_link(bNodeTree *ntree, bNodeLink &link)
Definition node.cc:3847
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:3340
void node_parents_iterator(bNode *node, bool(*callback)(bNode *, void *), void *userdata)
Definition node.cc:3443
int node_count_socket_links(const bNodeTree &ntree, const bNodeSocket &sock)
Definition node.cc:4674
void node_internal_relink(bNodeTree &ntree, bNode &node)
Definition node.cc:3908
void node_free_node(bNodeTree *tree, bNode &node)
Definition node.cc:4302
void node_detach_node(bNodeTree &ntree, bNode &node)
Definition node.cc:3986
bNodeLink & node_add_link(bNodeTree &ntree, bNode &fromnode, bNodeSocket &fromsock, bNode &tonode, bNodeSocket &tosock)
Definition node.cc:3810
void node_link_set_mute(bNodeTree &ntree, bNodeLink &link, const bool muted)
Definition node.cc:3864
void node_remove_socket_links(bNodeTree &ntree, bNodeSocket &sock)
Definition node.cc:3873
bool node_declaration_ensure(bNodeTree &ntree, bNode &node)
Definition node.cc:4818
void node_set_active(bNodeTree &ntree, bNode &node)
Definition node.cc:4724
int node_socket_link_limit(const bNodeSocket &sock)
Definition node.cc:4753
void node_tree_node_flag_set(bNodeTree &ntree, int flag, bool enable)
Definition node.cc:4574
void move_index(T *items, const int items_num, const int from_index, const int to_index)
static int view_socket(const bContext &C, SpaceNode &snode, bNodeTree &btree, bNode &bnode_to_view, bNodeSocket &bsocket_to_view)
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 std::string get_viewer_source_name(const bNodeSocket &socket)
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 socket_can_be_viewed(const bNodeSocket &socket)
static bool is_viewer_socket_in_viewer(const bNodeSocket &socket)
static int ensure_geometry_nodes_viewer_has_non_geometry_socket(bNodeTree &ntree, bNode &viewer_node, const eNodeSocketDatatype socket_type)
static Vector< float2 > get_viewer_node_position_candidates(const float2 initial, const float step_distance, const float max_distance)
static int get_default_viewer_type(const bContext *C)
static void position_viewer_node(const bContext &C, bNodeTree &tree, bNode &viewer_node, const bNode &node_to_view)
static void ensure_geometry_nodes_viewer_starts_with_geometry_socket(bNodeTree &tree, bNode &viewer_node)
void NODE_OT_parent_set(wmOperatorType *ot)
static void join_group_inputs(bNodeTree &tree, VectorSet< bNode * > group_inputs, bNode *active_node)
static wmOperatorStatus node_link_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static void draw_draglink_tooltip_activate(const ARegion &region, bNodeLinkDrag &nldrag)
void node_deselect_all_input_sockets(bNodeTree &node_tree, bool deselect_nodes)
NodeLinkData data[NODELINK_GROUP_SIZE]
Definition drawnode.cc:1863
void node_insert_on_link_flags_set(SpaceNode &snode, const ARegion &region, bool attach_enabled, bool is_new_node)
static wmOperatorStatus mute_links_exec(bContext *C, wmOperator *op)
static void node_swap_links(bNodeLinkDrag &nldrag, bNodeTree &ntree)
void tree_draw_order_update(bNodeTree &ntree)
Definition node_draw.cc:316
static bool need_drag_link_tooltip(const bNodeTree &node_tree, const bNodeLinkDrag &nldrag)
static wmOperatorStatus node_join_in_frame_invoke(bContext *C, wmOperator *op, const wmEvent *event)
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:97
static std::unique_ptr< bNodeLinkDrag > node_link_init(ARegion &region, SpaceNode &snode, const float2 cursor, const bool detach)
void invoke_node_link_drag_add_menu(bContext &C, bNode &node, bNodeSocket &socket, const float2 &cursor)
static wmOperatorStatus node_active_link_viewer_exec(bContext *C, wmOperator *)
void NODE_OT_detach(wmOperatorType *ot)
static Vector< const bNode * > get_sorted_node_parents(const bNode &node)
bool all_links_muted(const bNodeSocket &socket)
static bool can_attach_node_to_frame(const bNode &node, const bNode &frame)
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 wmOperatorStatus node_insert_offset_modal(bContext *C, wmOperator *op, const wmEvent *event)
static wmOperatorStatus node_link_modal(bContext *C, wmOperator *op, const wmEvent *event)
void NODE_OT_insert_offset(wmOperatorType *ot)
Array< bNode * > tree_draw_order_calc_nodes_reversed(bNodeTree &ntree)
Definition node_draw.cc:340
static int get_main_socket_priority(const bNodeSocket *socket)
static bool snode_autoconnect_input(bContext &C, SpaceNode &snode, bNode *node_fr, bNodeSocket *sock_fr, bNode *node_to, bNodeSocket *sock_to, int replace)
static Vector< bNodeSocket * > get_available_sorted_inputs(const bNodeTree *ntree, const bNode *node, const bool only_unlinked)
float2 node_link_calculate_multi_input_position(const float2 &socket_position, const int index, const int total_inputs)
Definition node_edit.cc:124
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 wmOperatorStatus node_attach_invoke(bContext *C, wmOperator *, const wmEvent *event)
static void node_detach_recursive(bNodeTree &ntree, MutableSpan< NodeDetachstate > detach_states, bNode *node)
static void node_displace_existing_links(bNodeLinkDrag &nldrag, bNodeTree &ntree)
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)
static wmOperatorStatus node_join_nodes_exec(bContext *C, wmOperator *op)
static wmOperatorStatus node_join_in_frame_exec(bContext *C, wmOperator *)
void NODE_OT_join(wmOperatorType *ot)
static void snode_autoconnect(bContext &C, SpaceNode &snode, const bool allow_multiple, const bool replace)
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 wmOperatorStatus node_detach_exec(bContext *C, wmOperator *)
static bool socket_is_available(const bNodeTree *ntree, bNodeSocket *sock, const bool allow_used)
static wmOperatorStatus detach_links_exec(bContext *C, wmOperator *)
static bool node_can_be_inserted_on_link(bNodeTree &tree, bNode &node, const bNodeLink &link)
void NODE_OT_attach(wmOperatorType *ot)
void node_insert_on_frame_flag_set(bContext &C, SpaceNode &snode, const int2 &cursor)
bool node_link_is_hidden_or_dimmed(const View2D &v2d, const bNodeLink &link)
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:156
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 wmOperatorStatus node_insert_offset_invoke(bContext *C, wmOperator *op, const wmEvent *event)
float2 node_to_view(const float2 &co)
Definition node_draw.cc:373
void node_insert_on_link_flags_clear(bNodeTree &node_tree)
static void add_dragged_links_to_tree(bContext &C, bNodeLinkDrag &nldrag)
static void remove_unavailable_links(bNodeTree &tree, bNodeSocket &socket)
static wmOperatorStatus node_parent_set_exec(bContext *C, wmOperator *)
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:1683
wmKeyMap * node_link_modal_keymap(wmKeyConfig *keyconf)
static wmOperatorStatus node_make_link_exec(bContext *C, wmOperator *op)
void NODE_OT_join_nodes(wmOperatorType *ot)
static void node_link_insert_offset_ntree(NodeInsertOfsData *iofsd, ARegion *region, const int mouse_xy[2], const bool right_alignment)
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:388
static bool should_create_drag_link_search_menu(const bNodeTree &node_tree, const bNodeLinkDrag &nldrag)
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)
void node_insert_on_frame_flag_clear(SpaceNode &snode)
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)
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 wmOperatorStatus cut_links_exec(bContext *C, wmOperator *op)
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)
void activate_geometry_node(Main &bmain, SpaceNode &snode, bNode &node, std::optional< int > item_identifier=std::nullopt)
T min(const T &a, const T &b)
void set_item_name_and_make_unique(bNode &node, typename Accessor::ItemT &item, const char *value)
Accessor::ItemT * find_item_by_identifier(bNode &node, const StringRef identifier)
Accessor::ItemT * add_item_with_socket_type_and_name(bNodeTree &ntree, bNode &node, const eNodeSocketDatatype socket_type, const char *name, std::optional< int > dimensions=std::nullopt)
void update_node_declaration_and_sockets(bNodeTree &ntree, bNode &node)
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
#define NODE_LINK_RESOL
#define NODE_WIDTH(node)
#define NODE_INSOFS_ANIM_DURATION
static blender::bke::bNodeSocketTemplate inputs[]
const char * name
#define ceilf
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
ARegionRuntimeHandle * runtime
void * first
NodeGeometryViewerItem * items
SpaceNode_Runtime * runtime
char node_tree_sub_type
struct bNodeTree * edittree
struct bNodeTree * nodetree
bNodeSocketRuntimeHandle * runtime
bNodeSocketTypeHandle * typeinfo
struct bNodeSocket * next
char identifier[64]
char idname[64]
bNodeTreeTypeHandle * typeinfo
ListBase links
float location[2]
bNodeTypeHandle * typeinfo
float width
ListBase inputs
struct bNode * parent
int16_t type_legacy
bNodeRuntimeHandle * runtime
void * storage
int16_t ui_order
ListBase outputs
int32_t identifier
std::optional< int > frame_identifier_to_highlight
std::unique_ptr< bNodeLinkDrag > linkdrag
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int)
float xmax
float xmin
float ymax
float ymin
wmEventType type
Definition WM_types.hh:757
short val
Definition WM_types.hh:759
int mval[2]
Definition WM_types.hh:763
short prev_val
Definition WM_types.hh:814
void * customdata
Definition WM_types.hh:807
const void * modal_items
struct ReportList * reports
struct PointerRNA * ptr
double time_delta
Definition WM_types.hh:970
double time_duration
Definition WM_types.hh:968
i
Definition text_draw.cc:230
@ WM_CURSOR_KNIFE
Definition wm_cursors.hh:31
@ WM_CURSOR_MUTE
Definition wm_cursors.hh:60
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:4237
void WM_gesture_lines_cancel(bContext *C, wmOperator *op)
wmOperatorStatus WM_gesture_lines_modal(bContext *C, wmOperator *op, const wmEvent *event)
wmOperatorStatus 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:932
void WM_modalkeymap_assign(wmKeyMap *km, const char *opname)
wmKeyMap * WM_modalkeymap_find(wmKeyConfig *keyconf, const char *idname)
Definition wm_keymap.cc:959
wmTimer * WM_event_timer_add(wmWindowManager *wm, wmWindow *win, const wmEventType event_type, const double time_step)
void WM_event_timer_remove(wmWindowManager *wm, wmWindow *, wmTimer *timer)