Blender V4.5
node_draw.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2008 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9
10#include <iomanip>
11
12#include "MEM_guardedalloc.h"
13
14#include "DNA_light_types.h"
15#include "DNA_linestyle_types.h"
16#include "DNA_material_types.h"
17#include "DNA_modifier_types.h"
18#include "DNA_node_types.h"
19#include "DNA_screen_types.h"
20#include "DNA_space_types.h"
21#include "DNA_text_types.h"
22#include "DNA_texture_types.h"
23#include "DNA_world_types.h"
24
25#include "BLI_array.hh"
26#include "BLI_bounds.hh"
27#include "BLI_convexhull_2d.h"
28#include "BLI_function_ref.hh"
29#include "BLI_listbase.h"
30#include "BLI_map.hh"
31#include "BLI_math_matrix.hh"
33#include "BLI_set.hh"
34#include "BLI_span.hh"
35#include "BLI_string.h"
36#include "BLI_string_ref.hh"
37#include "BLI_vector.hh"
38
39#include "BLT_translation.hh"
40
43#include "BKE_context.hh"
44#include "BKE_curves.hh"
45#include "BKE_global.hh"
46#include "BKE_idtype.hh"
47#include "BKE_lib_id.hh"
48#include "BKE_library.hh"
49#include "BKE_main.hh"
51#include "BKE_node.hh"
52#include "BKE_node_enum.hh"
54#include "BKE_node_runtime.hh"
57#include "BKE_object.hh"
58#include "BKE_scene.hh"
59#include "BKE_scene_runtime.hh"
60#include "BKE_screen.hh"
62
63#include "IMB_imbuf.hh"
64
65#include "DEG_depsgraph.hh"
66
67#include "BLF_api.hh"
68
69#include "BIF_glutil.hh"
70
71#include "GPU_framebuffer.hh"
72#include "GPU_immediate.hh"
73#include "GPU_immediate_util.hh"
74#include "GPU_matrix.hh"
75#include "GPU_state.hh"
76#include "GPU_viewport.hh"
77
78#include "WM_api.hh"
79#include "WM_types.hh"
80
81#include "ED_gpencil_legacy.hh"
82#include "ED_node.hh"
83#include "ED_node_preview.hh"
84#include "ED_screen.hh"
85#include "ED_space_api.hh"
86#include "ED_viewer_path.hh"
87
88#include "UI_interface.hh"
89#include "UI_resources.hh"
90#include "UI_view2d.hh"
91
92#include "RNA_access.hh"
93#include "RNA_prototypes.hh"
94
100
101#include "GEO_fillet_curves.hh"
102
103#include "node_intern.hh" /* own include */
104
105#include <fmt/format.h>
106#include <sstream>
107
113
114namespace blender::ed::space_node {
115
116#define NODE_ZONE_PADDING UI_UNIT_X
117#define ZONE_ZONE_PADDING 0.3f * UI_UNIT_X
118#define EXTRA_INFO_ROW_HEIGHT (20.0f * UI_SCALE_FAC)
119
163
165{
166 return NODE_GRID_STEP_SIZE;
167}
168
170{
171 SpaceNode *snode = CTX_wm_space_node(C);
172 if (snode) {
174
175 if (snode->nodetree) {
176 id_us_ensure_real(&snode->nodetree->id);
177 }
178 }
179}
180
181/* id is supposed to contain a node tree */
183{
184 if (id) {
185 if (GS(id->name) == ID_NT) {
186 return (bNodeTree *)id;
187 }
188 return bke::node_tree_from_id(id);
189 }
190
191 return nullptr;
192}
193
195{
196 bNodeTree *ntree = node_tree_from_ID(id);
197 if (id == nullptr || ntree == nullptr) {
198 return;
199 }
200
201 /* TODO(sergey): With the new dependency graph it should be just enough to only tag ntree itself.
202 * All the users of this tree will have update flushed from the tree. */
203 DEG_id_tag_update(&ntree->id, 0);
204
205 if (ntree->type == NTREE_SHADER) {
206 DEG_id_tag_update(id, 0);
207
208 if (GS(id->name) == ID_MA) {
210 }
211 else if (GS(id->name) == ID_LA) {
213 }
214 else if (GS(id->name) == ID_WO) {
216 }
217 }
218 else if (ntree->type == NTREE_COMPOSIT) {
220 }
221 else if (ntree->type == NTREE_TEXTURE) {
222 DEG_id_tag_update(id, 0);
224 }
225 else if (ntree->type == NTREE_GEOMETRY) {
227 }
228 else if (id == &ntree->id) {
229 /* Node groups. */
230 DEG_id_tag_update(id, 0);
231 }
232}
233
234static std::string node_socket_get_tooltip(const SpaceNode *snode,
235 const bNodeTree &ntree,
236 const bNodeSocket &socket);
237
238static const char *node_socket_get_translation_context(const bNodeSocket &socket)
239{
240 /* The node is not explicitly defined. */
241 if (socket.runtime->declaration == nullptr) {
242 return nullptr;
243 }
244
245 const std::optional<std::string> &translation_context =
246 socket.runtime->declaration->translation_context;
247
248 /* Default context. */
249 if (!translation_context.has_value()) {
250 return nullptr;
251 }
252
253 return translation_context->c_str();
254}
255
256static void node_socket_add_tooltip_in_node_editor(const bNodeSocket &sock, uiLayout &layout);
257
259static bool compare_node_depth(const bNode *a, const bNode *b)
260{
261 /* These tell if either the node or any of the parent nodes is selected.
262 * A selected parent means an unselected node is also in foreground! */
263 bool a_select = (a->flag & NODE_SELECT) != 0, b_select = (b->flag & NODE_SELECT) != 0;
264 bool a_active = (a->flag & NODE_ACTIVE) != 0, b_active = (b->flag & NODE_ACTIVE) != 0;
265
266 /* If one is an ancestor of the other. */
267 /* XXX there might be a better sorting algorithm for stable topological sort,
268 * this is O(n^2) worst case. */
269 for (bNode *parent = a->parent; parent; parent = parent->parent) {
270 /* If B is an ancestor, it is always behind A. */
271 if (parent == b) {
272 return false;
273 }
274 /* Any selected ancestor moves the node forward. */
275 if (parent->flag & NODE_ACTIVE) {
276 a_active = true;
277 }
278 if (parent->flag & NODE_SELECT) {
279 a_select = true;
280 }
281 }
282 for (bNode *parent = b->parent; parent; parent = parent->parent) {
283 /* If A is an ancestor, it is always behind B. */
284 if (parent == a) {
285 return true;
286 }
287 /* Any selected ancestor moves the node forward. */
288 if (parent->flag & NODE_ACTIVE) {
289 b_active = true;
290 }
291 if (parent->flag & NODE_SELECT) {
292 b_select = true;
293 }
294 }
295
296 /* One of the nodes is in the background and the other not. */
297 if ((a->flag & NODE_BACKGROUND) && !(b->flag & NODE_BACKGROUND)) {
298 return true;
299 }
300 if ((b->flag & NODE_BACKGROUND) && !(a->flag & NODE_BACKGROUND)) {
301 return false;
302 }
303
304 /* One has a higher selection state (active > selected > nothing). */
305 if (a_active && !b_active) {
306 return false;
307 }
308 if (b_active && !a_active) {
309 return true;
310 }
311 if (!b_select && (a_active || a_select)) {
312 return false;
313 }
314 if (!a_select && (b_active || b_select)) {
315 return true;
316 }
317
318 return false;
319}
320
322{
323 Array<bNode *> sort_nodes = ntree.all_nodes();
324 std::sort(sort_nodes.begin(), sort_nodes.end(), [](bNode *a, bNode *b) {
325 return a->ui_order < b->ui_order;
326 });
327 std::stable_sort(sort_nodes.begin(), sort_nodes.end(), compare_node_depth);
328 for (const int i : sort_nodes.index_range()) {
329 sort_nodes[i]->ui_order = i;
330 }
331}
332
334{
335 Array<bNode *> nodes = ntree.all_nodes();
336 if (nodes.is_empty()) {
337 return {};
338 }
339 std::sort(nodes.begin(), nodes.end(), [](const bNode *a, const bNode *b) {
340 return a->ui_order < b->ui_order;
341 });
342 return nodes;
343}
344
346{
347 Array<bNode *> nodes = ntree.all_nodes();
348 if (nodes.is_empty()) {
349 return {};
350 }
351 std::sort(nodes.begin(), nodes.end(), [](const bNode *a, const bNode *b) {
352 return a->ui_order > b->ui_order;
353 });
354 return nodes;
355}
356
358{
359 Array<uiBlock *> blocks(nodes.size());
360
361 /* Add node uiBlocks in drawing order - prevents events going to overlapping nodes. */
362 Scene *scene = CTX_data_scene(&C);
363 wmWindow *window = CTX_wm_window(&C);
364 ARegion *region = CTX_wm_region(&C);
365 for (const int i : nodes.index_range()) {
366 const bNode &node = *nodes[i];
367 std::string block_name = "node_" + std::string(node.name);
368 uiBlock *block = UI_block_begin(
369 &C, scene, window, region, std::move(block_name), blender::ui::EmbossType::Emboss);
370 blocks[node.index()] = block;
371 /* This cancels events for background nodes. */
373 }
374
375 return blocks;
376}
377
379{
380 return co * UI_SCALE_FAC;
381}
382
383static rctf node_to_rect(const bNode &node)
384{
385 rctf rect{};
386 rect.xmin = node.location[0];
387 rect.ymin = node.location[1] - node.height;
388 rect.xmax = node.location[0] + node.width;
389 rect.ymax = node.location[1];
390 return rect;
391}
392
393void node_to_updated_rect(const bNode &node, rctf &r_rect)
394{
395 r_rect = node_to_rect(node);
396 BLI_rctf_mul(&r_rect, UI_SCALE_FAC);
397}
398
400{
401 return co / UI_SCALE_FAC;
402}
403
404static bool is_node_panels_supported(const bNode &node)
405{
406 return node.declaration() && node.declaration()->use_custom_socket_order;
407}
408
409/* Draw UI for options, buttons, and previews. */
411 bNodeTree &ntree,
412 bNode &node,
414 uiBlock &block,
415 int &dy)
416{
417 /* Buttons rect? */
418 const bool node_options = draw_buttons && (node.flag & NODE_OPTIONS);
419 if (!node_options) {
420 return false;
421 }
422
423 PointerRNA nodeptr = RNA_pointer_create_discrete(&ntree.id, &RNA_Node, &node);
424
425 /* Round the node origin because text contents are always pixel-aligned. */
426 const float2 loc = math::round(node_to_view(node.location));
427
428 dy -= NODE_DYS / 4;
429
430 uiLayout *layout = UI_block_layout(&block,
433 loc.x + NODE_DYS,
434 dy,
435 NODE_WIDTH(node) - NODE_DY,
436 0,
437 0,
439
440 if (node.is_muted()) {
441 uiLayoutSetActive(layout, false);
442 }
443
444 uiLayoutSetContextPointer(layout, "node", &nodeptr);
445
446 draw_buttons(layout, (bContext *)&C, &nodeptr);
447
448 UI_block_align_end(&block);
449 int buty;
450 UI_block_layout_resolve(&block, nullptr, &buty);
451
452 dy = buty - NODE_DYS / 4;
453 return true;
454}
455
456const char *node_socket_get_label(const bNodeSocket *socket, const char *panel_label)
457{
458 /* Get the short label if possible. This is used when grouping sockets under panels,
459 * to avoid redundancy in the label. */
460 const std::optional<StringRefNull> socket_short_label = bke::node_socket_short_label(*socket);
461 const char *socket_translation_context = node_socket_get_translation_context(*socket);
462
463 if (socket_short_label.has_value()) {
464 return CTX_IFACE_(socket_translation_context, socket_short_label->c_str());
465 }
466
467 const StringRefNull socket_label = bke::node_socket_label(*socket);
468 const char *translated_socket_label = CTX_IFACE_(socket_translation_context,
469 socket_label.c_str());
470
471 /* Shorten socket label if it begins with the panel label. */
472 if (panel_label) {
473 const int len_prefix = strlen(panel_label);
474 if (STREQLEN(translated_socket_label, panel_label, len_prefix) &&
475 translated_socket_label[len_prefix] == ' ')
476 {
477 return translated_socket_label + len_prefix + 1;
478 }
479 }
480
481 /* Full label. */
482 return translated_socket_label;
483}
484
486 bNodeTree &ntree,
487 bNode &node,
488 const char *panel_label,
489 bNodeSocket *input_socket,
490 bNodeSocket *output_socket,
491 uiBlock &block,
492 const int &locx,
493 int &locy)
494{
495 if ((!input_socket || !input_socket->is_visible()) &&
496 (!output_socket || !output_socket->is_visible()))
497 {
498 return false;
499 }
500
501 const int topy = locy;
502
503 /* Add the half the height of a multi-input socket to cursor Y
504 * to account for the increased height of the taller sockets. */
505 const bool is_multi_input = (input_socket ? input_socket->flag & SOCK_MULTI_INPUT : false);
506 const float multi_input_socket_offset = is_multi_input ?
507 std::max(input_socket->runtime->total_inputs - 2,
508 0) *
510 0.0f;
511 locy -= multi_input_socket_offset * 0.5f;
512
513 uiLayout *layout = UI_block_layout(&block,
516 locx + NODE_DYS,
517 locy,
518 NODE_WIDTH(node) - NODE_DY,
519 NODE_DY,
520 0,
522
523 if (node.is_muted()) {
524 uiLayoutSetActive(layout, false);
525 }
526
527 uiLayout *row = &layout->row(true);
528 PointerRNA nodeptr = RNA_pointer_create_discrete(&ntree.id, &RNA_Node, &node);
529 uiLayoutSetContextPointer(row, "node", &nodeptr);
530
531 if (input_socket) {
532 /* Context pointers for current node and socket. */
533 PointerRNA sockptr = RNA_pointer_create_discrete(&ntree.id, &RNA_NodeSocket, input_socket);
534 uiLayoutSetContextPointer(row, "socket", &sockptr);
535
537
538 input_socket->typeinfo->draw(
539 (bContext *)&C, row, &sockptr, &nodeptr, node_socket_get_label(input_socket, panel_label));
540 }
541 else {
542 /* Context pointers for current node and socket. */
543 PointerRNA sockptr = RNA_pointer_create_discrete(&ntree.id, &RNA_NodeSocket, output_socket);
544 uiLayoutSetContextPointer(row, "socket", &sockptr);
545
546 /* Align output buttons to the right. */
548
549 output_socket->typeinfo->draw((bContext *)&C,
550 row,
551 &sockptr,
552 &nodeptr,
553 node_socket_get_label(output_socket, panel_label));
554 }
555
556 if (input_socket) {
557 node_socket_add_tooltip_in_node_editor(*input_socket, *row);
558 /* Round the socket location to stop it from jiggling. */
559 input_socket->runtime->location = float2(round(locx), round(locy - NODE_DYS));
560 }
561 if (output_socket) {
562 node_socket_add_tooltip_in_node_editor(*output_socket, *row);
563 /* Round the socket location to stop it from jiggling. */
564 output_socket->runtime->location = float2(round(locx + NODE_WIDTH(node)),
565 round(locy - NODE_DYS));
566 }
567
568 UI_block_align_end(&block);
569
570 int buty;
571 UI_block_layout_resolve(&block, nullptr, &buty);
572 /* Ensure minimum socket height in case layout is empty. */
573 buty = min_ii(buty, topy - NODE_DY);
574 locy = buty - multi_input_socket_offset * 0.5;
575 return true;
576}
577
578namespace flat_item {
579
588
589struct Socket {
590 static constexpr Type type = Type::Socket;
591 bNodeSocket *input = nullptr;
592 bNodeSocket *output = nullptr;
594};
595struct Separator {
596 static constexpr Type type = Type::Separator;
597};
612struct Layout {
613 static constexpr Type type = Type::Layout;
615};
616
617} // namespace flat_item
618
620 std::variant<flat_item::Socket,
627
629 {
630 return std::visit([](auto &&item) { return item.type; }, this->item);
631 }
632};
633
635 const bNode &node, const nodes::PanelDeclaration &panel_decl, MutableSpan<bool> r_result)
636{
637 bool potentially_visible = false;
638 for (const nodes::ItemDeclaration *item_decl : panel_decl.items) {
639 if (const auto *socket_decl = dynamic_cast<const nodes::SocketDeclaration *>(item_decl)) {
640 const bNodeSocket &socket = node.socket_by_decl(*socket_decl);
641 potentially_visible |= socket.is_visible();
642 }
643 else if (const auto *sub_panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl))
644 {
645 determine_potentially_visible_panels_recursive(node, *sub_panel_decl, r_result);
646 potentially_visible |= r_result[sub_panel_decl->index];
647 }
648 }
649 r_result[panel_decl.index] = potentially_visible;
650}
651
656{
657 for (const nodes::ItemDeclaration *item_decl : node.declaration()->root_items) {
658 if (const auto *panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl)) {
659 determine_potentially_visible_panels_recursive(node, *panel_decl, r_result);
660 }
661 }
662}
663
665 const nodes::PanelDeclaration &panel_decl,
666 const Span<bool> potentially_visible_states,
667 MutableSpan<bool> r_result)
668{
669 if (!potentially_visible_states[panel_decl.index]) {
670 /* This panel does not contain any visible sockets. */
671 return;
672 }
673 r_result[panel_decl.index] = true;
674 const bNodePanelState &panel_state = node.panel_states_array[panel_decl.index];
675 if (panel_state.is_collapsed()) {
676 /* The sub-panels can't be visible if this panel is collapsed. */
677 return;
678 }
679 for (const nodes::ItemDeclaration *item_decl : panel_decl.items) {
680 if (const auto *sub_panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl)) {
682 node, *sub_panel_decl, potentially_visible_states, r_result);
683 }
684 }
685}
686
687static void determine_visible_panels_impl(const bNode &node,
688 const Span<bool> potentially_visible_states,
689 MutableSpan<bool> r_result)
690{
691 for (const nodes::ItemDeclaration *item_decl : node.declaration()->root_items) {
692 if (const auto *panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl)) {
694 node, *panel_decl, potentially_visible_states, r_result);
695 }
696 }
697}
698
704static void determine_visible_panels(const bNode &node, MutableSpan<bool> r_visibility_states)
705{
706 Array<bool> potentially_visible_states(r_visibility_states.size(), false);
707 determine_potentially_visible_panels(node, potentially_visible_states);
708 determine_visible_panels_impl(node, potentially_visible_states, r_visibility_states);
709}
710
712 const nodes::SocketDeclaration &socket_decl,
713 const nodes::PanelDeclaration *panel_decl,
714 const nodes::SocketDeclaration *prev_socket_decl,
715 Vector<FlatNodeItem> &r_items)
716{
717 bNodeSocket &socket = node.socket_by_decl(socket_decl);
718 if (!socket.is_visible()) {
719 return;
720 }
721 if (socket_decl.align_with_previous_socket) {
722 if (!prev_socket_decl || !node.socket_by_decl(*prev_socket_decl).is_visible()) {
723 r_items.append({flat_item::Socket()});
724 }
725 }
726 else {
727 r_items.append({flat_item::Socket()});
728 }
729 flat_item::Socket &item = std::get<flat_item::Socket>(r_items.last().item);
730 if (socket_decl.in_out == SOCK_IN) {
731 BLI_assert(!item.input);
732 item.input = &socket;
733 }
734 else {
735 BLI_assert(!item.output);
736 item.output = &socket;
737 }
738 item.panel_decl = panel_decl;
739}
740
742{
743 r_items.append({flat_item::Separator()});
744}
745
746static void add_flat_items_for_layout(const bNode &node,
747 const nodes::LayoutDeclaration &layout_decl,
748 Vector<FlatNodeItem> &r_items)
749{
750 if (!(node.flag & NODE_OPTIONS)) {
751 return;
752 }
753 r_items.append({flat_item::Layout{&layout_decl}});
754}
755
757 const nodes::PanelDeclaration &panel_decl,
758 const Span<bool> panel_visibility,
759 Vector<FlatNodeItem> &r_items)
760{
761 if (!panel_visibility[panel_decl.index]) {
762 return;
763 }
764 flat_item::PanelHeader header_item;
765 header_item.decl = &panel_decl;
766 const nodes::SocketDeclaration *panel_input_decl = panel_decl.panel_input_decl();
767 if (panel_input_decl) {
768 header_item.input = &node.socket_by_decl(*panel_input_decl);
769 }
770 r_items.append({header_item});
771
772 const bNodePanelState &panel_state = node.panel_states_array[panel_decl.index];
773 if (panel_state.is_collapsed()) {
774 return;
775 }
776 r_items.append({flat_item::PanelContentBegin{&panel_decl}});
777 const nodes::SocketDeclaration *prev_socket_decl = nullptr;
778 for (const nodes::ItemDeclaration *item_decl : panel_decl.items) {
779 if (item_decl == panel_input_decl) {
780 continue;
781 }
782 if (const auto *socket_decl = dynamic_cast<const nodes::SocketDeclaration *>(item_decl)) {
783 add_flat_items_for_socket(node, *socket_decl, &panel_decl, prev_socket_decl, r_items);
784 prev_socket_decl = socket_decl;
785 }
786 else {
787 if (const auto *sub_panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl)) {
788 add_flat_items_for_panel(node, *sub_panel_decl, panel_visibility, r_items);
789 }
790 else if (dynamic_cast<const nodes::SeparatorDeclaration *>(item_decl)) {
792 }
793 else if (const auto *layout_decl = dynamic_cast<const nodes::LayoutDeclaration *>(item_decl))
794 {
795 add_flat_items_for_layout(node, *layout_decl, r_items);
796 }
797 prev_socket_decl = nullptr;
798 }
799 }
800 r_items.append({flat_item::PanelContentEnd{&panel_decl}});
801}
802
807{
809 BLI_assert(node.runtime->panels.size() == node.num_panel_states);
810
811 const int panels_num = node.num_panel_states;
812 Array<bool> panel_visibility(panels_num, false);
813 determine_visible_panels(node, panel_visibility);
814
815 const nodes::SocketDeclaration *prev_socket_decl = nullptr;
816
818 for (const nodes::ItemDeclaration *item_decl : node.declaration()->root_items) {
819 if (const auto *socket_decl = dynamic_cast<const nodes::SocketDeclaration *>(item_decl)) {
820 add_flat_items_for_socket(node, *socket_decl, nullptr, prev_socket_decl, items);
821 prev_socket_decl = socket_decl;
822 }
823 else {
824 if (const auto *panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl)) {
825 add_flat_items_for_panel(node, *panel_decl, panel_visibility, items);
826 }
827 else if (dynamic_cast<const nodes::SeparatorDeclaration *>(item_decl)) {
829 }
830 else if (const auto *layout_decl = dynamic_cast<const nodes::LayoutDeclaration *>(item_decl))
831 {
832 add_flat_items_for_layout(node, *layout_decl, items);
833 }
834 prev_socket_decl = nullptr;
835 }
836 }
837 return items;
838}
839
841static float get_margin_empty()
842{
843 return NODE_DYS;
844}
845
848{
849 const FlatNodeItem &first_item = items[0];
850 const flat_item::Type first_item_type = first_item.type();
851 switch (first_item_type) {
853 return 2 * NODE_ITEM_SPACING_Y;
855 return NODE_ITEM_SPACING_Y / 2;
857 return 3 * NODE_ITEM_SPACING_Y;
859 return 4 * NODE_ITEM_SPACING_Y;
862 break;
863 }
865 return 0;
866}
867
870{
871 const FlatNodeItem &last_item = items.last();
872 const flat_item::Type last_item_type = last_item.type();
873 switch (last_item_type) {
875 return 2 * NODE_ITEM_SPACING_Y;
877 return NODE_ITEM_SPACING_Y;
879 return 5 * NODE_ITEM_SPACING_Y;
881 return 4 * NODE_ITEM_SPACING_Y;
883 break;
885 return 1 * NODE_ITEM_SPACING_Y;
886 }
888 return 0;
889}
890
892static float get_margin_between_elements(const Span<FlatNodeItem> items, const int next_index)
893{
894 BLI_assert(next_index >= 1);
895 const FlatNodeItem &prev = items[next_index - 1];
896 const FlatNodeItem &next = items[next_index];
897 using flat_item::Type;
898 const Type prev_type = prev.type();
899 const Type next_type = next.type();
900
901 /* Handle all cases explicitly. This simplifies modifying the margins for specific cases
902 * without breaking other cases significantly. */
903 switch (prev_type) {
904 case Type::Socket: {
905 switch (next_type) {
906 case Type::Socket:
907 return NODE_ITEM_SPACING_Y;
908 case Type::Separator:
909 return 0;
910 case Type::Layout:
911 return 2 * NODE_ITEM_SPACING_Y;
912 case Type::PanelHeader:
913 return 3 * NODE_ITEM_SPACING_Y;
914 case Type::PanelContentBegin:
915 break;
916 case Type::PanelContentEnd:
917 return 2 * NODE_ITEM_SPACING_Y;
918 }
919 break;
920 }
921 case Type::Layout: {
922 switch (next_type) {
923 case Type::Socket:
924 return 2 * NODE_ITEM_SPACING_Y;
925 case Type::Separator:
926 return 0;
927 case Type::Layout:
928 return NODE_ITEM_SPACING_Y;
929 case Type::PanelHeader:
930 return 3 * NODE_ITEM_SPACING_Y;
931 case Type::PanelContentBegin:
932 break;
933 case Type::PanelContentEnd:
934 return 2 * NODE_ITEM_SPACING_Y;
935 }
936 break;
937 }
938 case Type::Separator: {
939 switch (next_type) {
940 case Type::Socket:
941 return 2 * NODE_ITEM_SPACING_Y;
942 case Type::Separator:
943 return NODE_ITEM_SPACING_Y;
944 case Type::Layout:
945 return NODE_ITEM_SPACING_Y;
946 case Type::PanelHeader:
947 return NODE_ITEM_SPACING_Y;
948 case Type::PanelContentBegin:
949 break;
950 case Type::PanelContentEnd:
951 return NODE_ITEM_SPACING_Y;
952 }
953 break;
954 }
955 case Type::PanelHeader: {
956 switch (next_type) {
957 case Type::Socket:
958 return 4 * NODE_ITEM_SPACING_Y;
959 case Type::Separator:
960 return 3 * NODE_ITEM_SPACING_Y;
961 case Type::Layout:
962 return 3 * NODE_ITEM_SPACING_Y;
963 case Type::PanelHeader:
964 return 5 * NODE_ITEM_SPACING_Y;
965 case Type::PanelContentBegin:
966 return 3 * NODE_ITEM_SPACING_Y;
967 case Type::PanelContentEnd:
968 return 3 * NODE_ITEM_SPACING_Y;
969 }
970 break;
971 }
972 case Type::PanelContentBegin: {
973 switch (next_type) {
974 case Type::Socket:
975 return 2 * NODE_ITEM_SPACING_Y;
976 case Type::Separator:
977 return NODE_ITEM_SPACING_Y;
978 case Type::Layout:
979 return 2 * NODE_ITEM_SPACING_Y;
980 case Type::PanelHeader:
981 return 3 * NODE_ITEM_SPACING_Y;
982 case Type::PanelContentBegin:
983 break;
984 case Type::PanelContentEnd:
985 return NODE_ITEM_SPACING_Y;
986 }
987 break;
988 }
989 case Type::PanelContentEnd: {
990 switch (next_type) {
991 case Type::Socket:
992 return NODE_ITEM_SPACING_Y;
993 case Type::Separator:
994 return NODE_ITEM_SPACING_Y;
995 case Type::Layout:
996 return NODE_ITEM_SPACING_Y;
997 case Type::PanelHeader:
998 return 3 * NODE_ITEM_SPACING_Y;
999 case Type::PanelContentBegin:
1000 break;
1001 case Type::PanelContentEnd:
1002 return 0;
1003 }
1004 break;
1005 }
1006 }
1008 return 0.0f;
1009}
1010
1013 const int node_left_x,
1014 const nodes::PanelDeclaration &visible_panel_decl,
1015 const nodes::PanelDeclaration &panel_decl)
1016{
1017 const bke::bNodePanelRuntime &visible_panel_runtime =
1018 node.runtime->panels[visible_panel_decl.index];
1019
1020 /* If the panel runtime is not initialized, then it is not visible. */
1021 if (!visible_panel_runtime.header_center_y.has_value()) {
1022 return;
1023 }
1024
1025 for (const nodes::ItemDeclaration *item_decl : panel_decl.items) {
1026 if (const auto *socket_decl = dynamic_cast<const nodes::SocketDeclaration *>(item_decl)) {
1027 bNodeSocket &socket = node.socket_by_decl(*socket_decl);
1028 const int socket_x = socket.in_out == SOCK_IN ? node_left_x : node_left_x + NODE_WIDTH(node);
1029 socket.runtime->location = math::round(
1030 float2(socket_x, *visible_panel_runtime.header_center_y));
1031 socket.flag |= SOCK_PANEL_COLLAPSED;
1032 }
1033 else if (const auto *sub_panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl))
1034 {
1035 mark_sockets_collapsed_recursive(node, node_left_x, visible_panel_decl, *sub_panel_decl);
1036 }
1037 }
1038}
1039
1041 const int node_left_x,
1042 const nodes::PanelDeclaration &panel_decl)
1043{
1044 const bNodePanelState &panel_state = node.panel_states_array[panel_decl.index];
1045 if (panel_state.is_collapsed()) {
1046 mark_sockets_collapsed_recursive(node, node_left_x, panel_decl, panel_decl);
1047 return;
1048 }
1049 for (const nodes::ItemDeclaration *item_decl : panel_decl.items) {
1050 if (const auto *sub_panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl)) {
1051 update_collapsed_sockets_recursive(node, node_left_x, *sub_panel_decl);
1052 }
1053 }
1054}
1055
1060static void update_collapsed_sockets(bNode &node, const int node_left_x)
1061{
1062 for (const nodes::ItemDeclaration *item_decl : node.declaration()->root_items) {
1063 if (const auto *panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl)) {
1064 update_collapsed_sockets_recursive(node, node_left_x, *panel_decl);
1065 }
1066 }
1067}
1068
1073static void tag_final_panel(bNode &node, const Span<FlatNodeItem> items)
1074{
1075 const flat_item::PanelContentEnd *final_panel = nullptr;
1076 for (int item_i = items.size() - 1; item_i >= 0; item_i--) {
1077 const FlatNodeItem &item = items[item_i];
1078 if (const auto *panel_item = std::get_if<flat_item::PanelContentEnd>(&item.item)) {
1079 final_panel = panel_item;
1080 }
1081 else {
1082 break;
1083 }
1084 }
1085 if (final_panel) {
1086 bke::bNodePanelRuntime &final_panel_runtime = node.runtime->panels[final_panel->decl->index];
1087 final_panel_runtime.content_extent->fill_node_end = true;
1088 }
1089}
1090
1091/* Advanced drawing with panels and arbitrary input/output ordering. */
1093 const bContext &C, bNodeTree &ntree, bNode &node, uiBlock &block, const int locx, int &locy)
1094{
1096 BLI_assert(node.runtime->panels.size() == node.num_panel_states);
1097
1098 /* Reset states. */
1099 for (bke::bNodePanelRuntime &panel_runtime : node.runtime->panels) {
1100 panel_runtime.header_center_y.reset();
1101 panel_runtime.content_extent.reset();
1102 panel_runtime.input_socket = nullptr;
1103 }
1104 for (bNodeSocket *socket : node.input_sockets()) {
1105 socket->flag &= ~SOCK_PANEL_COLLAPSED;
1106 }
1107 for (bNodeSocket *socket : node.output_sockets()) {
1108 socket->flag &= ~SOCK_PANEL_COLLAPSED;
1109 }
1110
1111 /* Gather flattened list of items in the node. */
1112 const Vector<FlatNodeItem> flat_items = make_flat_node_items(node);
1113 if (flat_items.is_empty()) {
1114 const float margin = get_margin_empty();
1115 locy -= margin;
1116 return;
1117 }
1118
1119 for (const int item_i : flat_items.index_range()) {
1120 /* Apply margins. This should be the only place that applies margins between elements so that
1121 * it is easy change later on. */
1122 if (item_i == 0) {
1123 const float margin = get_margin_from_top(flat_items);
1124 locy -= margin;
1125 }
1126 else {
1127 const float margin = get_margin_between_elements(flat_items, item_i);
1128 locy -= margin;
1129 }
1130
1131 const FlatNodeItem &item_variant = flat_items[item_i];
1132 std::visit(
1133 [&](const auto &item) {
1134 using ItemT = std::decay_t<decltype(item)>;
1135 if constexpr (std::is_same_v<ItemT, flat_item::Socket>) {
1136 bNodeSocket *input_socket = item.input;
1137 bNodeSocket *output_socket = item.output;
1138 const nodes::PanelDeclaration *panel_decl = item.panel_decl;
1139 const char *parent_label = panel_decl ? panel_decl->name.c_str() : "";
1141 C, ntree, node, parent_label, input_socket, output_socket, block, locx, locy);
1142 }
1143 else if constexpr (std::is_same_v<ItemT, flat_item::Layout>) {
1144 const nodes::LayoutDeclaration &decl = *item.decl;
1145 /* Round the node origin because text contents are always pixel-aligned. */
1146 const float2 loc = math::round(node_to_view(node.location));
1147 uiLayout *layout = UI_block_layout(&block,
1150 loc.x + NODE_DYS,
1151 locy,
1152 NODE_WIDTH(node) - NODE_DY,
1153 0,
1154 0,
1156 if (node.is_muted()) {
1157 uiLayoutSetActive(layout, false);
1158 }
1159 PointerRNA node_ptr = RNA_pointer_create_discrete(&ntree.id, &RNA_Node, &node);
1160 uiLayoutSetContextPointer(layout, "node", &node_ptr);
1161 decl.draw(layout, const_cast<bContext *>(&C), &node_ptr);
1162 UI_block_align_end(&block);
1163 int buty;
1164 UI_block_layout_resolve(&block, nullptr, &buty);
1165 locy = buty;
1166 }
1167 else if constexpr (std::is_same_v<ItemT, flat_item::Separator>) {
1168 uiLayout *layout = UI_block_layout(&block,
1171 locx + NODE_DYS,
1172 locy,
1173 NODE_WIDTH(node) - NODE_DY,
1174 NODE_DY,
1175 0,
1178 UI_block_layout_resolve(&block, nullptr, nullptr);
1179 }
1180 else if constexpr (std::is_same_v<ItemT, flat_item::PanelHeader>) {
1181 const nodes::PanelDeclaration &node_decl = *item.decl;
1182 bke::bNodePanelRuntime &panel_runtime = node.runtime->panels[node_decl.index];
1183 const float panel_header_height = NODE_DYS;
1184 locy -= panel_header_height / 2;
1185 panel_runtime.header_center_y = locy;
1186 locy -= panel_header_height / 2;
1187 bNodeSocket *input_socket = item.input;
1188 if (input_socket) {
1189 panel_runtime.input_socket = input_socket;
1190 input_socket->runtime->location = float2(locx, *panel_runtime.header_center_y);
1191 }
1192 }
1193 else if constexpr (std::is_same_v<ItemT, flat_item::PanelContentBegin>) {
1194 const nodes::PanelDeclaration &node_decl = *item.decl;
1195 bke::bNodePanelRuntime &panel_runtime = node.runtime->panels[node_decl.index];
1196 panel_runtime.content_extent.emplace();
1197 panel_runtime.content_extent->max_y = locy;
1198 }
1199 else if constexpr (std::is_same_v<ItemT, flat_item::PanelContentEnd>) {
1200 const nodes::PanelDeclaration &node_decl = *item.decl;
1201 bke::bNodePanelRuntime &panel_runtime = node.runtime->panels[node_decl.index];
1202 panel_runtime.content_extent->min_y = locy;
1203 }
1204 },
1205 item_variant.item);
1206 }
1207
1208 const float bottom_margin = get_margin_to_bottom(flat_items);
1209 locy -= bottom_margin;
1210
1211 update_collapsed_sockets(node, locx);
1212 tag_final_panel(node, flat_items);
1213}
1214
1215/* Conventional drawing in outputs/buttons/inputs order. */
1217 const bContext &C, bNodeTree &ntree, bNode &node, uiBlock &block, const int locx, int &locy)
1218{
1219 /* Space at the top. */
1220 locy -= NODE_DYS / 2;
1221
1222 /* Output sockets. */
1223 bool add_output_space = false;
1224
1225 for (bNodeSocket *socket : node.output_sockets()) {
1226 /* Clear flag, conventional drawing does not support panels. */
1227 socket->flag &= ~SOCK_PANEL_COLLAPSED;
1228
1229 if (node_update_basis_socket(C, ntree, node, nullptr, nullptr, socket, block, locx, locy)) {
1230 if (socket->next) {
1231 locy -= NODE_ITEM_SPACING_Y;
1232 }
1233 add_output_space = true;
1234 }
1235 }
1236
1237 if (add_output_space) {
1238 locy -= NODE_DY / 4;
1239 }
1240
1241 const bool add_button_space = node_update_basis_buttons(
1242 C, ntree, node, node.typeinfo->draw_buttons, block, locy);
1243
1244 bool add_input_space = false;
1245
1246 /* Input sockets. */
1247 for (bNodeSocket *socket : node.input_sockets()) {
1248 /* Clear flag, conventional drawing does not support panels. */
1249 socket->flag &= ~SOCK_PANEL_COLLAPSED;
1250
1251 if (node_update_basis_socket(C, ntree, node, nullptr, socket, nullptr, block, locx, locy)) {
1252 if (socket->next) {
1253 locy -= NODE_ITEM_SPACING_Y;
1254 }
1255 add_input_space = true;
1256 }
1257 }
1258
1259 /* Little bit of padding at the bottom. */
1260 if (add_input_space || add_button_space) {
1261 locy -= NODE_DYS / 2;
1262 }
1263}
1264
1268static void node_update_basis(const bContext &C,
1269 const TreeDrawContext & /*tree_draw_ctx*/,
1270 bNodeTree &ntree,
1271 bNode &node,
1272 uiBlock &block)
1273{
1274 /* Round the node origin because text contents are always pixel-aligned. */
1275 const float2 loc = math::round(node_to_view(node.location));
1276
1277 int dy = loc.y;
1278
1279 /* Header. */
1280 dy -= NODE_DY;
1281
1282 if (is_node_panels_supported(node)) {
1283 node_update_basis_from_declaration(C, ntree, node, block, loc.x, dy);
1284 }
1285 else {
1286 node_update_basis_from_socket_lists(C, ntree, node, block, loc.x, dy);
1287 }
1288
1289 node.runtime->draw_bounds.xmin = loc.x;
1290 node.runtime->draw_bounds.xmax = loc.x + NODE_WIDTH(node);
1291 node.runtime->draw_bounds.ymax = loc.y;
1292 node.runtime->draw_bounds.ymin = min_ff(dy, loc.y - 2 * NODE_DY);
1293
1294 /* Set the block bounds to clip mouse events from underlying nodes.
1295 * Add a margin for sockets on each side. */
1297 node.runtime->draw_bounds.xmin - NODE_SOCKSIZE,
1298 node.runtime->draw_bounds.ymin,
1299 node.runtime->draw_bounds.xmax + NODE_SOCKSIZE,
1300 node.runtime->draw_bounds.ymax);
1301}
1302
1306static void node_update_hidden(bNode &node, uiBlock &block)
1307{
1308 int totin = 0, totout = 0;
1309
1310 /* Round the node origin because text contents are always pixel-aligned. */
1311 const float2 loc = math::round(node_to_view(node.location));
1312
1313 /* Calculate minimal radius. */
1314 for (const bNodeSocket *socket : node.input_sockets()) {
1315 if (socket->is_visible()) {
1316 totin++;
1317 }
1318 }
1319 for (const bNodeSocket *socket : node.output_sockets()) {
1320 if (socket->is_visible()) {
1321 totout++;
1322 }
1323 }
1324
1325 float hiddenrad = HIDDEN_RAD;
1326 float tot = std::max(totin, totout);
1327 if (tot > 4) {
1328 hiddenrad += 5.0f * float(tot - 4);
1329 }
1330
1331 node.runtime->draw_bounds.xmin = loc.x;
1332 node.runtime->draw_bounds.xmax = loc.x + max_ff(NODE_WIDTH(node), 2 * hiddenrad);
1333 node.runtime->draw_bounds.ymax = loc.y + (hiddenrad - 0.5f * NODE_DY);
1334 node.runtime->draw_bounds.ymin = node.runtime->draw_bounds.ymax - 2 * hiddenrad;
1335
1336 /* Output sockets. */
1337 float rad = float(M_PI) / (1.0f + float(totout));
1338 float drad = rad;
1339
1340 for (bNodeSocket *socket : node.output_sockets()) {
1341 if (socket->is_visible()) {
1342 /* Round the socket location to stop it from jiggling. */
1343 socket->runtime->location = {
1344 round(node.runtime->draw_bounds.xmax - hiddenrad + sinf(rad) * hiddenrad),
1345 round(node.runtime->draw_bounds.ymin + hiddenrad + cosf(rad) * hiddenrad)};
1346 rad += drad;
1347 }
1348 }
1349
1350 /* Input sockets. */
1351 rad = drad = -float(M_PI) / (1.0f + float(totin));
1352
1353 for (bNodeSocket *socket : node.input_sockets()) {
1354 if (socket->is_visible()) {
1355 /* Round the socket location to stop it from jiggling. */
1356 socket->runtime->location = {
1357 round(node.runtime->draw_bounds.xmin + hiddenrad + sinf(rad) * hiddenrad),
1358 round(node.runtime->draw_bounds.ymin + hiddenrad + cosf(rad) * hiddenrad)};
1359 rad += drad;
1360 }
1361 }
1362
1363 /* Set the block bounds to clip mouse events from underlying nodes.
1364 * Add a margin for sockets on each side. */
1366 node.runtime->draw_bounds.xmin - NODE_SOCKSIZE,
1367 node.runtime->draw_bounds.ymin,
1368 node.runtime->draw_bounds.xmax + NODE_SOCKSIZE,
1369 node.runtime->draw_bounds.ymax);
1370}
1371
1372static int node_get_colorid(TreeDrawContext &tree_draw_ctx, const bNode &node)
1373{
1374 const int nclass = (node.typeinfo->ui_class == nullptr) ? node.typeinfo->nclass :
1375 node.typeinfo->ui_class(&node);
1376 switch (nclass) {
1377 case NODE_CLASS_INPUT:
1378 return TH_NODE_INPUT;
1379 case NODE_CLASS_OUTPUT: {
1380 if (node.type_legacy == GEO_NODE_VIEWER) {
1381 return &node == tree_draw_ctx.active_geometry_nodes_viewer ? TH_NODE_OUTPUT : TH_NODE;
1382 }
1383 const bool is_output_node = (node.flag & NODE_DO_OUTPUT) ||
1385 return is_output_node ? TH_NODE_OUTPUT : TH_NODE;
1386 }
1388 return TH_NODE_CONVERTER;
1390 return TH_NODE_COLOR;
1392 return TH_NODE_VECTOR;
1394 return TH_NODE_FILTER;
1395 case NODE_CLASS_GROUP:
1396 return TH_NODE_GROUP;
1398 return TH_NODE_INTERFACE;
1399 case NODE_CLASS_MATTE:
1400 return TH_NODE_MATTE;
1401 case NODE_CLASS_DISTORT:
1402 return TH_NODE_DISTORT;
1403 case NODE_CLASS_TEXTURE:
1404 return TH_NODE_TEXTURE;
1405 case NODE_CLASS_SHADER:
1406 return TH_NODE_SHADER;
1407 case NODE_CLASS_SCRIPT:
1408 return TH_NODE_SCRIPT;
1409 case NODE_CLASS_PATTERN:
1410 return TH_NODE_PATTERN;
1411 case NODE_CLASS_LAYOUT:
1412 return TH_NODE_LAYOUT;
1414 return TH_NODE_GEOMETRY;
1416 return TH_NODE_ATTRIBUTE;
1417 default:
1418 return TH_NODE;
1419 }
1420}
1421
1422static void node_draw_mute_line(const bContext &C,
1423 const View2D &v2d,
1424 const SpaceNode &snode,
1425 const bNode &node)
1426{
1428
1429 for (const bNodeLink &link : node.internal_links()) {
1430 if (!bke::node_link_is_hidden(link)) {
1431 node_draw_link_bezier(C, v2d, snode, link, TH_WIRE_INNER, TH_WIRE_INNER, TH_WIRE, false);
1432 }
1433 }
1434
1436}
1437
1439 const int socket_index_in_tree,
1440 const float2 location,
1441 const float2 size)
1442{
1443 /* Ideally sockets themselves should be buttons, but they aren't currently. So add an invisible
1444 * button on top of them for the tooltip. */
1445 uiBut *but = uiDefIconBut(&block,
1447 0,
1448 ICON_NONE,
1449 location.x - size.x / 2.0f,
1450 location.y - size.y / 2.0f,
1451 size.x,
1452 size.y,
1453 nullptr,
1454 0,
1455 0,
1456 std::nullopt);
1458 but,
1459 [](bContext *C, void *argN, const StringRef /*tip*/) {
1460 const SpaceNode &snode = *CTX_wm_space_node(C);
1461 const bNodeTree &ntree = *snode.edittree;
1462 const int index_in_tree = POINTER_AS_INT(argN);
1463 ntree.ensure_topology_cache();
1464 return node_socket_get_tooltip(&snode, ntree, *ntree.all_sockets()[index_in_tree]);
1465 },
1466 POINTER_FROM_INT(socket_index_in_tree),
1467 nullptr);
1468}
1469
1470static const float virtual_node_socket_outline_color[4] = {0.5, 0.5, 0.5, 1.0};
1471
1472static void node_socket_outline_color_get(const bool selected,
1473 const int socket_type,
1474 float r_outline_color[4])
1475{
1476 if (selected) {
1477 UI_GetThemeColor4fv(TH_ACTIVE, r_outline_color);
1478 }
1479 else if (socket_type == SOCK_CUSTOM) {
1480 /* Until there is a better place for per socket color,
1481 * the outline color for virtual sockets is set here. */
1483 }
1484 else {
1485 UI_GetThemeColor4fv(TH_WIRE, r_outline_color);
1486 r_outline_color[3] = 1.0f;
1487 }
1488}
1489
1491 const bNodeTree &ntree,
1492 PointerRNA &node_ptr,
1493 const bNodeSocket &sock,
1494 float r_color[4])
1495{
1496 if (!sock.typeinfo->draw_color) {
1497 /* Fall back to the simple variant. If not defined either, fall back to a magenta color. */
1498 if (sock.typeinfo->draw_color_simple) {
1499 sock.typeinfo->draw_color_simple(sock.typeinfo, r_color);
1500 }
1501 else {
1502 copy_v4_v4(r_color, float4(1.0f, 0.0f, 1.0f, 1.0f));
1503 }
1504 return;
1505 }
1506
1507 BLI_assert(RNA_struct_is_a(node_ptr.type, &RNA_Node));
1509 &const_cast<ID &>(ntree.id), &RNA_NodeSocket, &const_cast<bNodeSocket &>(sock));
1510 sock.typeinfo->draw_color((bContext *)&C, &ptr, &node_ptr, r_color);
1511}
1512
1514 const GPointer value,
1515 fmt::memory_buffer &buf)
1516{
1517 auto id_to_inspection_string = [&](const ID *id, const short idcode) {
1518 fmt::format_to(fmt::appender(buf), "{}", id ? id->name + 2 : TIP_("None"));
1519 fmt::format_to(fmt::appender(buf), " (");
1520 fmt::format_to(fmt::appender(buf), "{}", TIP_(BKE_idtype_idcode_to_name(idcode)));
1521 fmt::format_to(fmt::appender(buf), ")");
1522 };
1523
1524 const CPPType &value_type = *value.type();
1525 const void *buffer = value.get();
1526 if (value_type.is<Object *>()) {
1527 id_to_inspection_string(*static_cast<const ID *const *>(buffer), ID_OB);
1528 return;
1529 }
1530 if (value_type.is<Material *>()) {
1531 id_to_inspection_string(*static_cast<const ID *const *>(buffer), ID_MA);
1532 return;
1533 }
1534 if (value_type.is<Tex *>()) {
1535 id_to_inspection_string(*static_cast<const ID *const *>(buffer), ID_TE);
1536 return;
1537 }
1538 if (value_type.is<Image *>()) {
1539 id_to_inspection_string(*static_cast<const ID *const *>(buffer), ID_IM);
1540 return;
1541 }
1542 if (value_type.is<Collection *>()) {
1543 id_to_inspection_string(*static_cast<const ID *const *>(buffer), ID_GR);
1544 return;
1545 }
1546
1547 const CPPType &socket_type = *socket.typeinfo->base_cpp_type;
1548
1549 if (socket.type == SOCK_MENU) {
1550 if (!value_type.is<int>()) {
1551 return;
1552 }
1553 const int item_identifier = *static_cast<const int *>(buffer);
1554 const auto *socket_storage = socket.default_value_typed<bNodeSocketValueMenu>();
1555 if (!socket_storage->enum_items) {
1556 return;
1557 }
1558 if (socket_storage->has_conflict()) {
1559 return;
1560 }
1561 const bke::RuntimeNodeEnumItem *enum_item =
1562 socket_storage->enum_items->find_item_by_identifier(item_identifier);
1563 if (!enum_item) {
1564 return;
1565 }
1566 fmt::format_to(fmt::appender(buf), fmt::runtime(TIP_("{} (Menu)")), enum_item->name);
1567 return;
1568 }
1569
1571 if (value_type != socket_type) {
1572 if (!convert.is_convertible(value_type, socket_type)) {
1573 return;
1574 }
1575 }
1576 BUFFER_FOR_CPP_TYPE_VALUE(socket_type, socket_value);
1577 /* This will just copy the value if the types are equal. */
1578 convert.convert_to_uninitialized(value_type, socket_type, buffer, socket_value);
1579 BLI_SCOPED_DEFER([&]() { socket_type.destruct(socket_value); });
1580
1581 if (socket_type.is<int>()) {
1582 fmt::format_to(
1583 fmt::appender(buf), fmt::runtime(TIP_("{} (Integer)")), *static_cast<int *>(socket_value));
1584 }
1585 else if (socket_type.is<float>()) {
1586 const float float_value = *static_cast<float *>(socket_value);
1587 /* Above that threshold floats can't represent fractions anymore. */
1588 if (std::abs(float_value) > (1 << 24)) {
1589 /* Use higher precision to display correct integer value instead of one that is rounded to
1590 * fewer significant digits. */
1591 fmt::format_to(fmt::appender(buf), fmt::runtime(TIP_("{:.10} (Float)")), float_value);
1592 }
1593 else {
1594 fmt::format_to(fmt::appender(buf), fmt::runtime(TIP_("{} (Float)")), float_value);
1595 }
1596 }
1597 else if (socket_type.is<blender::float3>()) {
1598 const blender::float3 &vector = *static_cast<blender::float3 *>(socket_value);
1599 fmt::format_to(fmt::appender(buf),
1600 fmt::runtime(TIP_("({}, {}, {}) (Vector)")),
1601 vector.x,
1602 vector.y,
1603 vector.z);
1604 }
1605 else if (socket_type.is<blender::ColorGeometry4f>()) {
1606 const blender::ColorGeometry4f &color = *static_cast<blender::ColorGeometry4f *>(socket_value);
1607 fmt::format_to(fmt::appender(buf),
1608 fmt::runtime(TIP_("({}, {}, {}, {}) (Color)")),
1609 color.r,
1610 color.g,
1611 color.b,
1612 color.a);
1613 }
1614 else if (socket_type.is<math::Quaternion>()) {
1615 const math::Quaternion &rotation = *static_cast<math::Quaternion *>(socket_value);
1616 const math::EulerXYZ euler = math::to_euler(rotation);
1617 fmt::format_to(fmt::appender(buf),
1619 ", {}" BLI_STR_UTF8_DEGREE_SIGN ")"),
1620 euler.x().degree(),
1621 euler.y().degree(),
1622 euler.z().degree());
1623 fmt::format_to(fmt::appender(buf), "{}", TIP_("(Rotation)"));
1624 }
1625 else if (socket_type.is<bool>()) {
1626 fmt::format_to(fmt::appender(buf),
1627 fmt::runtime(TIP_("{} (Boolean)")),
1628 ((*static_cast<bool *>(socket_value)) ? TIP_("True") : TIP_("False")));
1629 }
1630 else if (socket_type.is<float4x4>()) {
1631 /* Transpose to be able to print row by row. */
1632 const float4x4 value = math::transpose(*static_cast<const float4x4 *>(socket_value));
1633 std::stringstream ss;
1634 ss << value[0] << ",\n";
1635 ss << value[1] << ",\n";
1636 ss << value[2] << ",\n";
1637 ss << value[3] << ",\n";
1638 buf.append(ss.str());
1639 fmt::format_to(fmt::appender(buf), "{}", TIP_("(Matrix)"));
1640 }
1641}
1642
1644 const geo_log::FieldInfoLog &value_log,
1645 fmt::memory_buffer &buf)
1646{
1647 const CPPType &socket_type = *socket.typeinfo->base_cpp_type;
1648 const Span<std::string> input_tooltips = value_log.input_tooltips;
1649
1650 if (input_tooltips.is_empty()) {
1651 /* Should have been logged as constant value. */
1653 fmt::format_to(fmt::appender(buf), "{}", TIP_("Value has not been logged"));
1654 }
1655 else {
1656 if (socket_type.is<int>()) {
1657 fmt::format_to(fmt::appender(buf), "{}", TIP_("Integer field based on:"));
1658 }
1659 else if (socket_type.is<float>()) {
1660 fmt::format_to(fmt::appender(buf), "{}", TIP_("Float field based on:"));
1661 }
1662 else if (socket_type.is<blender::float3>()) {
1663 fmt::format_to(fmt::appender(buf), "{}", TIP_("Vector field based on:"));
1664 }
1665 else if (socket_type.is<bool>()) {
1666 fmt::format_to(fmt::appender(buf), "{}", TIP_("Boolean field based on:"));
1667 }
1668 else if (socket_type.is<std::string>()) {
1669 fmt::format_to(fmt::appender(buf), "{}", TIP_("String field based on:"));
1670 }
1671 else if (socket_type.is<blender::ColorGeometry4f>()) {
1672 fmt::format_to(fmt::appender(buf), "{}", TIP_("Color field based on:"));
1673 }
1674 else if (socket_type.is<math::Quaternion>()) {
1675 fmt::format_to(fmt::appender(buf), "{}", TIP_("Rotation field based on:"));
1676 }
1677 fmt::format_to(fmt::appender(buf), "\n");
1678
1679 for (const int i : input_tooltips.index_range()) {
1680 const blender::StringRef tooltip = input_tooltips[i];
1681 fmt::format_to(fmt::appender(buf), fmt::runtime(TIP_("\u2022 {}")), TIP_(tooltip));
1682 if (i < input_tooltips.size() - 1) {
1683 fmt::format_to(fmt::appender(buf), ".\n");
1684 }
1685 }
1686 }
1687}
1688
1690 fmt::memory_buffer &buf)
1691{
1692 auto to_string = [](int value) {
1695 return std::string(str);
1696 };
1697
1698 if (value_log.grid_info) {
1699 const geo_log::GeometryInfoLog::GridInfo &grid_info = *value_log.grid_info;
1700 fmt::format_to(
1701 fmt::appender(buf), "{}", grid_info.is_empty ? TIP_("Empty Grid") : TIP_("\u2022 Grid"));
1702 return;
1703 }
1704
1705 Span<bke::GeometryComponent::Type> component_types = value_log.component_types;
1706 if (component_types.is_empty()) {
1707 fmt::format_to(fmt::appender(buf), "{}", TIP_("Empty Geometry"));
1708 return;
1709 }
1710
1711 fmt::format_to(fmt::appender(buf), "{}", TIP_("Geometry:"));
1712 if (!value_log.name.empty()) {
1713 fmt::format_to(fmt::appender(buf), " \"{}\"", value_log.name);
1714 }
1715 fmt::format_to(fmt::appender(buf), "\n");
1716 for (bke::GeometryComponent::Type type : component_types) {
1717 switch (type) {
1719 const geo_log::GeometryInfoLog::MeshInfo &mesh_info = *value_log.mesh_info;
1720 fmt::format_to(fmt::appender(buf),
1721 fmt::runtime(TIP_("\u2022 Mesh: {} vertices, {} edges, {} faces")),
1722 to_string(mesh_info.verts_num),
1723 to_string(mesh_info.edges_num),
1724 to_string(mesh_info.faces_num));
1725 break;
1726 }
1728 const geo_log::GeometryInfoLog::PointCloudInfo &pointcloud_info =
1729 *value_log.pointcloud_info;
1730 fmt::format_to(fmt::appender(buf),
1731 fmt::runtime(TIP_("\u2022 Point Cloud: {} points")),
1732 to_string(pointcloud_info.points_num));
1733 break;
1734 }
1736 const geo_log::GeometryInfoLog::CurveInfo &curve_info = *value_log.curve_info;
1737 fmt::format_to(fmt::appender(buf),
1738 fmt::runtime(TIP_("\u2022 Curve: {} points, {} splines")),
1739 to_string(curve_info.points_num),
1740 to_string(curve_info.splines_num));
1741 break;
1742 }
1744 const geo_log::GeometryInfoLog::InstancesInfo &instances_info = *value_log.instances_info;
1745 fmt::format_to(fmt::appender(buf),
1746 fmt::runtime(TIP_("\u2022 Instances: {}")),
1747 to_string(instances_info.instances_num));
1748 break;
1749 }
1751 const geo_log::GeometryInfoLog::VolumeInfo &volume_info = *value_log.volume_info;
1752 fmt::format_to(fmt::appender(buf),
1753 fmt::runtime(TIP_("\u2022 Volume: {} grids")),
1754 volume_info.grids_num);
1755 break;
1756 }
1758 if (value_log.edit_data_info.has_value()) {
1759 const geo_log::GeometryInfoLog::EditDataInfo &edit_info = *value_log.edit_data_info;
1760 fmt::format_to(fmt::appender(buf),
1761 fmt::runtime(TIP_("\u2022 Edit: {}, {}, {}")),
1762 edit_info.has_deformed_positions ? TIP_("positions") :
1763 TIP_("no positions"),
1764 edit_info.has_deform_matrices ? TIP_("matrices") : TIP_("no matrices"),
1765 edit_info.gizmo_transforms_num > 0 ? TIP_("gizmos") : TIP_("no gizmos"));
1766 }
1767 break;
1768 }
1770 const geo_log::GeometryInfoLog::GreasePencilInfo &grease_pencil_info =
1771 *value_log.grease_pencil_info;
1772 fmt::format_to(fmt::appender(buf),
1773 fmt::runtime(TIP_("\u2022 Grease Pencil: {} layers")),
1774 to_string(grease_pencil_info.layers_num));
1775 break;
1776 }
1777 }
1778 if (type != component_types.last()) {
1779 fmt::format_to(fmt::appender(buf), ".\n");
1780 }
1781 }
1782}
1783
1784static void create_inspection_string_for_geometry_socket(fmt::memory_buffer &buf,
1785 const nodes::decl::Geometry *socket_decl)
1786{
1787 /* If the geometry declaration is null, as is the case for input to group output,
1788 * or it is an output socket don't show supported types. */
1789 if (socket_decl == nullptr || socket_decl->in_out == SOCK_OUT) {
1790 return;
1791 }
1792
1793 Span<bke::GeometryComponent::Type> supported_types = socket_decl->supported_types();
1794 if (supported_types.is_empty()) {
1795 fmt::format_to(fmt::appender(buf), "{}", TIP_("Supported: All Types"));
1796 return;
1797 }
1798
1799 fmt::format_to(fmt::appender(buf), "{}", TIP_("Supported: "));
1800 for (bke::GeometryComponent::Type type : supported_types) {
1801 switch (type) {
1803 fmt::format_to(fmt::appender(buf), "{}", TIP_("Mesh"));
1804 break;
1805 }
1807 fmt::format_to(fmt::appender(buf), "{}", TIP_("Point Cloud"));
1808 break;
1809 }
1811 fmt::format_to(fmt::appender(buf), "{}", TIP_("Curve"));
1812 break;
1813 }
1815 fmt::format_to(fmt::appender(buf), "{}", TIP_("Instances"));
1816 break;
1817 }
1819 fmt::format_to(fmt::appender(buf), "{}", CTX_TIP_(BLT_I18NCONTEXT_ID_ID, "Volume"));
1820 break;
1821 }
1823 break;
1824 }
1826 fmt::format_to(fmt::appender(buf), "{}", TIP_("Grease Pencil"));
1827 break;
1828 }
1829 }
1830 if (type != supported_types.last()) {
1831 fmt::format_to(fmt::appender(buf), ", ");
1832 }
1833 }
1834}
1835
1837 fmt::memory_buffer &buf)
1838{
1839 if (value_log.items.is_empty()) {
1840 fmt::format_to(fmt::appender(buf), "{}", TIP_("Empty Bundle"));
1841 return;
1842 }
1843 fmt::format_to(fmt::appender(buf), "{}", TIP_("Bundle values:\n"));
1844 for (const geo_log::BundleValueLog::Item &item : value_log.items) {
1845 fmt::format_to(fmt::appender(buf),
1846 fmt::runtime("\u2022 \"{}\" ({})\n"),
1847 item.key.identifiers().first(),
1848 TIP_(item.type->label));
1849 }
1850}
1851
1853 fmt::memory_buffer &buf)
1854{
1855 if (value_log.inputs.is_empty() && value_log.outputs.is_empty()) {
1856 fmt::format_to(fmt::appender(buf), "{}", TIP_("Empty Closure"));
1857 }
1858 if (!value_log.inputs.is_empty()) {
1859 fmt::format_to(fmt::appender(buf), "{}:\n", TIP_("Inputs"));
1860 for (const geo_log::ClosureValueLog::Item &item : value_log.inputs) {
1861 fmt::format_to(fmt::appender(buf),
1862 fmt::runtime("\u2022 {} ({})\n"),
1863 item.key.identifiers().first(),
1864 IFACE_(item.type->label));
1865 }
1866 }
1867 if (!value_log.outputs.is_empty()) {
1868 fmt::format_to(fmt::appender(buf), "{}:\n", TIP_("Outputs"));
1869 for (const geo_log::ClosureValueLog::Item &item : value_log.outputs) {
1870 fmt::format_to(fmt::appender(buf),
1871 fmt::runtime("\u2022 {} ({})\n"),
1872 item.key.identifiers().first(),
1873 IFACE_(item.type->label));
1874 }
1875 }
1876}
1877
1879 fmt::memory_buffer &buf)
1880{
1881 if (!socket.is_input()) {
1882 return;
1883 }
1884 if (socket.is_multi_input()) {
1885 return;
1886 }
1887 if (socket.owner_node().is_reroute()) {
1888 return;
1889 }
1890 const Span<const bNodeSocket *> connected_sockets = socket.directly_linked_sockets();
1891 if (!connected_sockets.is_empty() && !connected_sockets[0]->owner_node().is_dangling_reroute()) {
1892 return;
1893 }
1894 if (const nodes::SocketDeclaration *socket_decl = socket.runtime->declaration) {
1895 if (socket_decl->input_field_type == nodes::InputSocketFieldType::Implicit) {
1896 return;
1897 }
1898 }
1899 if (socket.typeinfo->base_cpp_type == nullptr) {
1900 return;
1901 }
1902
1903 const CPPType &value_type = *socket.typeinfo->base_cpp_type;
1904 BUFFER_FOR_CPP_TYPE_VALUE(value_type, socket_value);
1905 socket.typeinfo->get_base_cpp_value(socket.default_value, socket_value);
1906 create_inspection_string_for_generic_value(socket, GPointer(value_type, socket_value), buf);
1907 value_type.destruct(socket_value);
1908}
1909
1910static std::optional<std::string> create_description_inspection_string(const bNodeSocket &socket)
1911{
1912 if (socket.runtime->declaration == nullptr) {
1913 if (socket.description[0]) {
1914 return socket.description;
1915 }
1916 return std::nullopt;
1917 }
1918 const blender::nodes::SocketDeclaration &socket_decl = *socket.runtime->declaration;
1919 blender::StringRefNull description = socket_decl.description;
1920 if (description.is_empty()) {
1921 return std::nullopt;
1922 }
1923
1924 return TIP_(description);
1925}
1926
1927static std::optional<std::string> create_log_inspection_string(geo_log::GeoTreeLog *geo_tree_log,
1928 const bNodeSocket &socket)
1929{
1930 if (geo_tree_log == nullptr) {
1931 return std::nullopt;
1932 }
1933 if (socket.typeinfo->base_cpp_type == nullptr) {
1934 return std::nullopt;
1935 }
1936
1937 geo_tree_log->ensure_socket_values();
1938 geo_log::ValueLog *value_log = geo_tree_log->find_socket_value_log(socket);
1939 fmt::memory_buffer buf;
1940 if (const geo_log::GenericValueLog *generic_value_log =
1941 dynamic_cast<const geo_log::GenericValueLog *>(value_log))
1942 {
1943 create_inspection_string_for_generic_value(socket, generic_value_log->value, buf);
1944 }
1945 else if (const geo_log::StringLog *string_log = dynamic_cast<const geo_log::StringLog *>(
1946 value_log))
1947 {
1948 if (string_log->truncated) {
1949 fmt::format_to(fmt::appender(buf), fmt::runtime(TIP_("{}... (String)")), string_log->value);
1950 }
1951 else {
1952 fmt::format_to(fmt::appender(buf), fmt::runtime(TIP_("{} (String)")), string_log->value);
1953 }
1954 }
1955 else if (const geo_log::FieldInfoLog *gfield_value_log =
1956 dynamic_cast<const geo_log::FieldInfoLog *>(value_log))
1957 {
1958 create_inspection_string_for_field_info(socket, *gfield_value_log, buf);
1959 }
1960 else if (const geo_log::GeometryInfoLog *geo_value_log =
1961 dynamic_cast<const geo_log::GeometryInfoLog *>(value_log))
1962 {
1963 create_inspection_string_for_geometry_info(*geo_value_log, buf);
1964 }
1965 else if (const geo_log::BundleValueLog *bundle_value_log =
1966 dynamic_cast<const geo_log::BundleValueLog *>(value_log))
1967 {
1968 create_inspection_string_for_bundle(*bundle_value_log, buf);
1969 }
1970 else if (const geo_log::ClosureValueLog *closure_value_log =
1971 dynamic_cast<const geo_log::ClosureValueLog *>(value_log))
1972 {
1973 create_inspection_string_for_closure(*closure_value_log, buf);
1974 }
1975
1976 std::string str = fmt::to_string(buf);
1977 if (str.empty()) {
1978 return std::nullopt;
1979 }
1980 return str;
1981}
1982
1983static std::optional<std::string> create_declaration_inspection_string(const bNodeSocket &socket)
1984{
1985 fmt::memory_buffer buf;
1986 if (const nodes::decl::Geometry *socket_decl = dynamic_cast<const nodes::decl::Geometry *>(
1987 socket.runtime->declaration))
1988 {
1990 }
1991
1992 std::string str = fmt::to_string(buf);
1993 if (str.empty()) {
1994 return std::nullopt;
1995 }
1996 return str;
1997}
1998
2000{
2002 std::istringstream text_stream(text);
2003 for (std::string line; std::getline(text_stream, line);) {
2004 result.append(line);
2005 }
2006 return result;
2007}
2008
2009static std::optional<std::string> create_multi_input_log_inspection_string(
2010 const bNodeSocket &socket, TreeDrawContext &tree_draw_ctx)
2011{
2012 if (!socket.is_multi_input()) {
2013 return std::nullopt;
2014 }
2015
2016 Vector<std::pair<int, std::string>, 8> numerated_info;
2017
2018 const Span<const bNodeLink *> connected_links = socket.directly_linked_links();
2019 for (const int index : connected_links.index_range()) {
2020 const bNodeLink *link = connected_links[index];
2021 const int connection_number = index + 1;
2022 if (!link->is_used()) {
2023 continue;
2024 }
2025 if (!(link->flag & NODE_LINK_VALID)) {
2026 continue;
2027 }
2028 if (link->fromnode->is_dangling_reroute()) {
2029 continue;
2030 }
2031 const bNodeSocket &connected_socket = *link->fromsock;
2032 geo_log::GeoTreeLog *geo_tree_log = tree_draw_ctx.tree_logs.get_main_tree_log(
2033 connected_socket);
2034 const std::optional<std::string> input_log = create_log_inspection_string(geo_tree_log,
2035 connected_socket);
2036 if (!input_log.has_value()) {
2037 continue;
2038 }
2039 numerated_info.append({connection_number, std::move(*input_log)});
2040 }
2041
2042 if (numerated_info.is_empty()) {
2043 return std::nullopt;
2044 }
2045
2046 fmt::memory_buffer buf;
2047 for (const std::pair<int, std::string> &info : numerated_info) {
2048 const Vector<std::string> lines = lines_of_text(info.second);
2049 fmt::format_to(fmt::appender(buf), "{}", info.first);
2050 fmt::format_to(fmt::appender(buf), ". ");
2051 fmt::format_to(fmt::appender(buf), "{}", lines.first());
2052 for (const std::string &line : lines.as_span().drop_front(1)) {
2053 fmt::format_to(fmt::appender(buf), "\n {}", line);
2054 }
2055 if (&info != &numerated_info.last()) {
2056 buf.append(StringRef(".\n"));
2057 }
2058 }
2059
2060 const std::string str = fmt::to_string(buf);
2061 if (str.empty()) {
2062 return std::nullopt;
2063 }
2064
2065 return str;
2066}
2067
2068static std::optional<std::string> create_default_value_inspection_string(const bNodeSocket &socket)
2069{
2070 fmt::memory_buffer buf;
2072
2073 std::string str = fmt::to_string(buf);
2074 if (str.empty()) {
2075 return std::nullopt;
2076 }
2077 return str;
2078}
2079
2080static const bNodeSocket *target_for_reroute(const bNodeSocket &reroute_output)
2081{
2082 const bNodeSocket *output = &reroute_output;
2083 Set<const bNode *> visited_nodes;
2084 visited_nodes.add(&reroute_output.owner_node());
2085 while (true) {
2086 const Span<const bNodeSocket *> linked_sockets = output->directly_linked_sockets();
2087 if (linked_sockets.size() != 1) {
2088 return nullptr;
2089 }
2090 const bNode &target_node = linked_sockets[0]->owner_node();
2091 if (!visited_nodes.add(&target_node)) {
2092 return nullptr;
2093 }
2094 if (!target_node.is_dangling_reroute()) {
2095 return linked_sockets[0];
2096 }
2097 output = target_node.output_sockets()[0];
2098 }
2099}
2100
2101static std::optional<std::string> create_dangling_reroute_inspection_string(
2102 const bNodeTree &ntree, const bNodeSocket &socket)
2103{
2104 if (ntree.type != NTREE_GEOMETRY) {
2105 return std::nullopt;
2106 }
2107
2108 const bNode &node = socket.owner_node();
2109 if (!node.is_dangling_reroute()) {
2110 return std::nullopt;
2111 }
2112
2113 const bNodeSocket &output_socket = *node.output_sockets()[0];
2114 const bNodeSocket *target_socket = target_for_reroute(output_socket);
2115
2116 if (target_socket == nullptr) {
2117 if (!output_socket.directly_linked_sockets().is_empty()) {
2118 return TIP_("Dangling reroute is ignored by all targets");
2119 }
2120 return std::nullopt;
2121 }
2122
2123 if (target_socket->is_multi_input()) {
2124 return TIP_("Dangling reroute branch is ignored by multi input socket");
2125 }
2126
2127 fmt::memory_buffer buf;
2129 std::string str = fmt::to_string(buf);
2130 if (str.empty()) {
2131 return TIP_("Dangling reroute is ignored");
2132 }
2133 fmt::format_to(fmt::appender(buf), ".\n\n");
2134 fmt::format_to(fmt::appender(buf),
2135 "{}",
2136 TIP_("Dangling reroute is ignored, default value of target socket is used"));
2137 return str;
2138}
2139
2140static std::string node_socket_get_tooltip(const SpaceNode *snode,
2141 const bNodeTree &ntree,
2142 const bNodeSocket &socket)
2143{
2144 TreeDrawContext tree_draw_ctx;
2145 if (snode != nullptr) {
2146 if (ntree.type == NTREE_GEOMETRY) {
2148 }
2149 }
2150
2151 geo_log::GeoTreeLog *geo_tree_log = tree_draw_ctx.tree_logs.get_main_tree_log(socket);
2152
2153 Vector<std::string> inspection_strings;
2154
2155 if (std::optional<std::string> info = create_description_inspection_string(socket)) {
2156 inspection_strings.append(std::move(*info));
2157 }
2158 if (std::optional<std::string> info = create_log_inspection_string(geo_tree_log, socket)) {
2159 inspection_strings.append(std::move(*info));
2160 }
2161 else if (std::optional<std::string> info = create_dangling_reroute_inspection_string(ntree,
2162 socket))
2163 {
2164 inspection_strings.append(std::move(*info));
2165 }
2166 else if (std::optional<std::string> info = create_default_value_inspection_string(socket)) {
2167 inspection_strings.append(std::move(*info));
2168 }
2169 else if (std::optional<std::string> info = create_multi_input_log_inspection_string(
2170 socket, tree_draw_ctx))
2171 {
2172 inspection_strings.append(std::move(*info));
2173 }
2174 if (std::optional<std::string> info = create_declaration_inspection_string(socket)) {
2175 inspection_strings.append(std::move(*info));
2176 }
2177 if (U.experimental.use_socket_structure_type) {
2178 if (socket.runtime->declaration) {
2179 switch (socket.runtime->declaration->structure_type) {
2180 case nodes::StructureType::Single:
2181 inspection_strings.append(TIP_("(Single Value)"));
2182 break;
2183 case nodes::StructureType::Dynamic:
2184 inspection_strings.append(TIP_("(Dynamic Structure Type)"));
2185 break;
2186 case nodes::StructureType::Field:
2187 inspection_strings.append(TIP_("(Field)"));
2188 break;
2189 case nodes::StructureType::Grid:
2190 inspection_strings.append(TIP_("(Volume Grid)"));
2191 break;
2192 }
2193 }
2194 }
2195
2196 std::stringstream output;
2197 for (const std::string &info : inspection_strings) {
2198 output << info;
2199 if (&info != &inspection_strings.last()) {
2200 output << ".\n\n";
2201 }
2202 }
2203
2204 if (inspection_strings.is_empty()) {
2205 const bool is_extend = StringRef(socket.idname) == "NodeSocketVirtual";
2206 const bNode &node = socket.owner_node();
2207 if (node.is_reroute()) {
2208 output << bke::node_label(ntree, node);
2209 }
2210 else if (is_extend) {
2211 output << TIP_("Connect a link to create a new socket");
2212 }
2213 else {
2214 const StringRefNull socket_label = bke::node_socket_label(socket);
2215 const char *socket_translation_context = node_socket_get_translation_context(socket);
2216 const char *translated_socket_label = CTX_TIP_(socket_translation_context,
2217 socket_label.c_str());
2218 output << translated_socket_label;
2219 }
2220
2221 if (ntree.type == NTREE_GEOMETRY && !is_extend) {
2222 output << ".\n\n";
2223 output << TIP_(
2224 "Unknown socket value. Either the socket was not used or its value was not logged "
2225 "during the last evaluation");
2226 }
2227 }
2228
2229 return output.str();
2230}
2231
2233{
2235 &layout,
2236 [](bContext *C, void *argN, const StringRef /*tip*/) {
2237 const SpaceNode &snode = *CTX_wm_space_node(C);
2238 const bNodeTree &ntree = *snode.edittree;
2239 const int index_in_tree = POINTER_AS_INT(argN);
2240 ntree.ensure_topology_cache();
2241 return node_socket_get_tooltip(&snode, ntree, *ntree.all_sockets()[index_in_tree]);
2242 },
2243 POINTER_FROM_INT(sock.index_in_tree()),
2244 nullptr,
2245 nullptr);
2246}
2247
2248void node_socket_add_tooltip(const bNodeTree &ntree, const bNodeSocket &sock, uiLayout &layout)
2249{
2250 struct SocketTooltipData {
2251 const bNodeTree *ntree;
2252 const bNodeSocket *socket;
2253 };
2254
2255 SocketTooltipData *data = MEM_callocN<SocketTooltipData>(__func__);
2256 data->ntree = &ntree;
2257 data->socket = &sock;
2258
2260 &layout,
2261 [](bContext *C, void *argN, const StringRef /*tip*/) {
2262 SocketTooltipData *data = static_cast<SocketTooltipData *>(argN);
2263 const SpaceNode *snode = CTX_wm_space_node(C);
2264 return node_socket_get_tooltip(snode, *data->ntree, *data->socket);
2265 },
2266 data,
2268 MEM_freeN);
2269}
2270
2271#define NODE_SOCKET_OUTLINE U.pixelsize
2272
2273void node_socket_draw(bNodeSocket *sock, const rcti *rect, const float color[4], float scale)
2274{
2275 const float radius = NODE_SOCKSIZE * scale;
2276 const float2 center = {BLI_rcti_cent_x_fl(rect), BLI_rcti_cent_y_fl(rect)};
2277 const rctf draw_rect = {
2278 center.x - radius,
2279 center.x + radius,
2280 center.y - radius,
2281 center.y + radius,
2282 };
2283
2284 ColorTheme4f outline_color;
2285 node_socket_outline_color_get(sock->flag & SELECT, sock->type, outline_color);
2286
2287 node_draw_nodesocket(&draw_rect,
2288 color,
2289 outline_color,
2290 NODE_SOCKET_OUTLINE * scale,
2291 sock->display_shape,
2292 1.0 / scale);
2293}
2294
2296#define NODE_TREE_SCALE_SMALL 0.2f
2297
2299static float node_tree_view_scale(const SpaceNode &snode)
2300{
2301 return (1.0f / snode.runtime->aspect) * UI_SCALE_FAC;
2302}
2303
2304/* Some elements of the node tree like labels or node sockets are hardly visible when zoomed
2305 * out and can slow down the drawing quite a bit.
2306 * This function can be used to check if it's worth to draw those details and return
2307 * early. */
2308static bool draw_node_details(const SpaceNode &snode)
2309{
2311}
2312
2314{
2317
2319
2320 /* Drawing the checkerboard. */
2321 const float checker_dark = UI_ALPHA_CHECKER_DARK / 255.0f;
2322 const float checker_light = UI_ALPHA_CHECKER_LIGHT / 255.0f;
2323 immUniform4f("color1", checker_dark, checker_dark, checker_dark, 1.0f);
2324 immUniform4f("color2", checker_light, checker_light, checker_light, 1.0f);
2325 immUniform1i("size", 8);
2326 immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax);
2328}
2329
2330/* Not a callback. */
2331static void node_draw_preview(const Scene *scene, ImBuf *preview, const rctf *prv)
2332{
2333 float xrect = BLI_rctf_size_x(prv);
2334 float yrect = BLI_rctf_size_y(prv);
2335 float xscale = xrect / float(preview->x);
2336 float yscale = yrect / float(preview->y);
2337 float scale;
2338
2339 /* Uniform scale and offset. */
2340 rctf draw_rect = *prv;
2341 if (xscale < yscale) {
2342 float offset = 0.5f * (yrect - float(preview->y) * xscale);
2343 draw_rect.ymin += offset;
2344 draw_rect.ymax -= offset;
2345 scale = xscale;
2346 }
2347 else {
2348 float offset = 0.5f * (xrect - float(preview->x) * yscale);
2349 draw_rect.xmin += offset;
2350 draw_rect.xmax -= offset;
2351 scale = yscale;
2352 }
2353
2354 node_draw_preview_background(&draw_rect);
2355
2357 /* Pre-multiply graphics. */
2359
2360 ED_draw_imbuf(preview,
2361 draw_rect.xmin,
2362 draw_rect.ymin,
2363 false,
2364 &scene->view_settings,
2365 &scene->display_settings,
2366 scale,
2367 scale);
2368
2370
2371 float black[4] = {0.0f, 0.0f, 0.0f, 1.0f};
2373 const float outline_width = 1.0f;
2374 draw_rect.xmin -= outline_width;
2375 draw_rect.xmax += outline_width;
2376 draw_rect.ymin -= outline_width;
2377 draw_rect.ymax += outline_width;
2378 UI_draw_roundbox_4fv(&draw_rect, false, BASIS_RAD / 2, black);
2379}
2380
2381/* Common handle function for operator buttons that need to select the node first. */
2382static void node_toggle_button_cb(bContext *C, void *node_argv, void *op_argv)
2383{
2384 SpaceNode &snode = *CTX_wm_space_node(C);
2385 bNodeTree &node_tree = *snode.edittree;
2386 bNode &node = *node_tree.node_by_id(POINTER_AS_INT(node_argv));
2387 const char *opname = (const char *)op_argv;
2388
2389 /* Select & activate only the button's node. */
2390 node_select_single(*C, node);
2391
2392 WM_operator_name_call(C, opname, WM_OP_INVOKE_DEFAULT, nullptr, nullptr);
2393}
2394
2395static void node_draw_shadow(const SpaceNode &snode,
2396 const bNode &node,
2397 const float radius,
2398 const float alpha)
2399{
2400 const rctf &rct = node.runtime->draw_bounds;
2402
2403 const float shadow_width = 0.6f * U.widget_unit;
2404 const float shadow_alpha = 0.5f * alpha;
2405
2406 ui_draw_dropshadow(&rct, radius, shadow_width, snode.runtime->aspect, shadow_alpha);
2407
2408 /* Outline emphasis. Slight darkening _inside_ the outline. */
2409 const float color[4] = {0.0f, 0.0f, 0.0f, 0.4f};
2410 rctf rect{};
2411 rect.xmin = rct.xmin - 0.5f;
2412 rect.xmax = rct.xmax + 0.5f;
2413 rect.ymin = rct.ymin - 0.5f;
2414 rect.ymax = rct.ymax + 0.5f;
2415 UI_draw_roundbox_4fv(&rect, false, radius + 0.5f, color);
2416}
2417
2418static void node_draw_socket(const bContext &C,
2419 const bNodeTree &ntree,
2420 const bNode &node,
2421 PointerRNA &node_ptr,
2422 uiBlock &block,
2423 const bNodeSocket &sock,
2424 const float outline_thickness,
2425 const bool selected,
2426 const float aspect)
2427{
2428 const float half_width = NODE_SOCKSIZE;
2429
2430 const bool multi_socket = (sock.flag & SOCK_MULTI_INPUT) && !(node.flag & NODE_HIDDEN);
2431 float half_height = multi_socket ? node_socket_calculate_height(sock) : half_width;
2432
2433 ColorTheme4f socket_color;
2434 ColorTheme4f outline_color;
2435 node_socket_color_get(C, ntree, node_ptr, sock, socket_color);
2436 node_socket_outline_color_get(selected, sock.type, outline_color);
2437
2438 const float2 socket_location = sock.runtime->location;
2439
2440 const rctf rect = {
2441 socket_location.x - half_width,
2442 socket_location.x + half_width,
2443 socket_location.y - half_height,
2444 socket_location.y + half_height,
2445 };
2446
2448 &rect, socket_color, outline_color, outline_thickness, sock.display_shape, aspect);
2449
2451 block, sock.index_in_tree(), socket_location, float2(2.0f * half_width, 2.0f * half_height));
2452}
2453
2454static void node_draw_sockets(const bContext &C,
2455 uiBlock &block,
2456 const SpaceNode &snode,
2457 const bNodeTree &ntree,
2458 const bNode &node)
2459{
2460 if (!draw_node_details(snode)) {
2461 return;
2462 }
2463
2464 if (node.input_sockets().is_empty() && node.output_sockets().is_empty()) {
2465 return;
2466 }
2467
2469 const_cast<ID *>(&ntree.id), &RNA_Node, const_cast<bNode *>(&node));
2470
2471 const float outline_thickness = NODE_SOCKET_OUTLINE;
2472
2474 /* Input sockets. */
2475 for (const bNodeSocket *sock : node.input_sockets()) {
2476 if (!sock->is_icon_visible()) {
2477 continue;
2478 }
2479 const bool selected = (sock->flag & SELECT);
2481 C, ntree, node, nodeptr, block, *sock, outline_thickness, selected, snode.runtime->aspect);
2482 }
2483
2484 /* Output sockets. */
2485 for (const bNodeSocket *sock : node.output_sockets()) {
2486 if (!sock->is_icon_visible()) {
2487 continue;
2488 }
2489 const bool selected = (sock->flag & SELECT);
2491 C, ntree, node, nodeptr, block, *sock, outline_thickness, selected, snode.runtime->aspect);
2492 }
2494}
2495
2496static void node_panel_toggle_button_cb(bContext *C, void *panel_state_argv, void *ntree_argv)
2497{
2498 Main *bmain = CTX_data_main(C);
2499 bNodePanelState *panel_state = static_cast<bNodePanelState *>(panel_state_argv);
2500 bNodeTree *ntree = static_cast<bNodeTree *>(ntree_argv);
2501
2502 panel_state->flag ^= NODE_PANEL_COLLAPSED;
2503
2504 BKE_main_ensure_invariants(*bmain, ntree->id);
2505}
2506
2507/* Draw panel backgrounds first, so other node elements can be rendered on top. */
2508static void node_draw_panels_background(const bNode &node)
2509{
2511
2512 float panel_color[4];
2514 /* Increase contrast in nodes a bit. */
2515 panel_color[3] *= 1.5f;
2516 const rctf &draw_bounds = node.runtime->draw_bounds;
2517
2518 const nodes::PanelDeclaration *final_panel_decl = nullptr;
2519
2520 const nodes::NodeDeclaration &node_decl = *node.declaration();
2521 for (const int panel_i : node_decl.panels.index_range()) {
2522 const nodes::PanelDeclaration &panel_decl = *node_decl.panels[panel_i];
2523 const bke::bNodePanelRuntime &panel_runtime = node.runtime->panels[panel_i];
2524 if (!panel_runtime.content_extent.has_value()) {
2525 continue;
2526 }
2527 const rctf content_rect = {draw_bounds.xmin,
2528 draw_bounds.xmax,
2529 panel_runtime.content_extent->min_y,
2530 panel_runtime.content_extent->max_y};
2532 UI_draw_roundbox_4fv(&content_rect, true, BASIS_RAD, panel_color);
2533 if (panel_runtime.content_extent->fill_node_end) {
2534 final_panel_decl = &panel_decl;
2535 }
2536 }
2537 if (final_panel_decl) {
2538 const bke::bNodePanelRuntime &final_panel_runtime =
2539 node.runtime->panels[final_panel_decl->index];
2540 const rctf content_rect = {draw_bounds.xmin,
2541 draw_bounds.xmax,
2542 draw_bounds.ymin,
2543 final_panel_runtime.content_extent->min_y};
2545 const int repeats = final_panel_decl->depth() + 1;
2546 for ([[maybe_unused]] const int i : IndexRange(repeats)) {
2547 UI_draw_roundbox_4fv(&content_rect, true, BASIS_RAD, panel_color);
2548 }
2549 }
2550}
2551
2558 const nodes::PanelDeclaration &panel_decl)
2559{
2560 for (const nodes::ItemDeclaration *item_decl : panel_decl.items) {
2561 if (const auto *socket_decl = dynamic_cast<const nodes::SocketDeclaration *>(item_decl)) {
2562 if (socket_decl->in_out == SOCK_OUT) {
2563 return false;
2564 }
2565 const bNodeSocket &socket = node.socket_by_decl(*socket_decl);
2566 if (!socket.is_inactive()) {
2567 return false;
2568 }
2569 }
2570 else if (const auto *sub_panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl))
2571 {
2572 if (!panel_has_only_inactive_inputs(node, *sub_panel_decl)) {
2573 return false;
2574 }
2575 }
2576 }
2577 return true;
2578}
2579
2580static void node_draw_panels(bNodeTree &ntree, const bNode &node, uiBlock &block)
2581{
2583 const rctf &draw_bounds = node.runtime->draw_bounds;
2584
2585 const nodes::NodeDeclaration &node_decl = *node.declaration();
2586 for (const int panel_i : node_decl.panels.index_range()) {
2587 const nodes::PanelDeclaration &panel_decl = *node_decl.panels[panel_i];
2588 const bke::bNodePanelRuntime &panel_runtime = node.runtime->panels[panel_i];
2589 bNodeSocket *input_socket = panel_runtime.input_socket;
2590 const bNodePanelState &panel_state = node.panel_states_array[panel_i];
2591 if (!panel_runtime.header_center_y.has_value()) {
2592 continue;
2593 }
2594
2595 const rctf header_rect = {draw_bounds.xmin,
2596 draw_bounds.xmax,
2597 *panel_runtime.header_center_y - NODE_DYS,
2598 *panel_runtime.header_center_y + NODE_DYS};
2600
2601 /* Invisible button covering the entire header for collapsing/expanding. */
2602 const int header_but_margin = NODE_MARGIN_X / 3;
2603 uiBut *toggle_action_but = uiDefIconBut(
2604 &block,
2606 0,
2607 ICON_NONE,
2608 header_rect.xmin + header_but_margin,
2609 header_rect.ymin,
2610 std::max(int(header_rect.xmax - header_rect.xmin - 2 * header_but_margin), 0),
2611 header_rect.ymax - header_rect.ymin,
2612 nullptr,
2613 0.0f,
2614 0.0f,
2615 panel_decl.description.c_str());
2617 toggle_action_but, [&panel_state](const uiBut &) { return panel_state.is_collapsed(); });
2618 UI_but_func_set(toggle_action_but,
2620 const_cast<bNodePanelState *>(&panel_state),
2621 &ntree);
2622
2623 /* Collapse/expand icon. */
2624 const int but_size = U.widget_unit * 0.8f;
2625 const int but_padding = NODE_MARGIN_X / 4;
2626 int offsetx = draw_bounds.xmin + (NODE_MARGIN_X / 3);
2627 uiDefIconBut(&block,
2629 0,
2630 panel_state.is_collapsed() ? ICON_RIGHTARROW : ICON_DOWNARROW_HLT,
2631 offsetx,
2632 *panel_runtime.header_center_y - but_size / 2,
2633 but_size,
2634 but_size,
2635 nullptr,
2636 0.0f,
2637 0.0f,
2638 "");
2639 offsetx += but_size + but_padding;
2640
2642
2643 /* Panel toggle. */
2644 if (input_socket && !input_socket->is_logically_linked()) {
2646 &ntree.id, &RNA_NodeSocket, input_socket);
2647 uiDefButR(&block,
2649 -1,
2650 "",
2651 offsetx,
2652 int(*panel_runtime.header_center_y - NODE_DYS),
2653 UI_UNIT_X,
2654 NODE_DY,
2655 &socket_ptr,
2656 "default_value",
2657 0,
2658 0,
2659 0,
2660 "");
2661 offsetx += UI_UNIT_X;
2662 }
2663
2664 /* Panel label. */
2665 const char *panel_translation_context = (panel_decl.translation_context.has_value() ?
2666 panel_decl.translation_context->c_str() :
2667 nullptr);
2668 uiBut *label_but = uiDefBut(
2669 &block,
2671 0,
2672 CTX_IFACE_(panel_translation_context, panel_decl.name),
2673 offsetx,
2674 int(*panel_runtime.header_center_y - NODE_DYS),
2675 short(draw_bounds.xmax - draw_bounds.xmin - (30.0f * UI_SCALE_FAC)),
2676 NODE_DY,
2677 nullptr,
2678 0,
2679 0,
2680 "");
2681
2682 const bool only_inactive_inputs = panel_has_only_inactive_inputs(node, panel_decl);
2683 if (node.is_muted() || only_inactive_inputs) {
2685 }
2686 }
2687}
2688
2690{
2691 int highest_priority = 0;
2693 for (const geo_log::NodeWarning &warning : warnings) {
2694 const int priority = node_warning_type_severity(warning.type);
2695 if (priority > highest_priority) {
2696 highest_priority = priority;
2697 highest_priority_type = warning.type;
2698 }
2699 }
2700 return highest_priority_type;
2701}
2702
2703static std::string node_errors_tooltip_fn(const Span<geo_log::NodeWarning> warnings)
2704{
2705 std::string complete_string;
2706
2707 for (const geo_log::NodeWarning &warning : warnings.drop_back(1)) {
2708 complete_string += warning.message;
2709 /* Adding the period is not ideal for multi-line messages, but it is consistent
2710 * with other tooltip implementations in Blender, so it is added here. */
2711 complete_string += '.';
2712 complete_string += '\n';
2713 }
2714
2715 /* Let the tooltip system automatically add the last period. */
2716 complete_string += warnings.last().message;
2717
2718 return complete_string;
2719}
2720
2721#define NODE_HEADER_ICON_SIZE (0.8f * U.widget_unit)
2722
2724 uiBlock &block,
2725 const rctf &rect,
2726 float &icon_offset)
2727{
2728 icon_offset -= NODE_HEADER_ICON_SIZE;
2730 uiDefIconBut(&block,
2732 0,
2733 ICON_ERROR,
2734 icon_offset,
2735 rect.ymax - NODE_DY,
2737 UI_UNIT_Y,
2738 nullptr,
2739 0,
2740 0,
2741 TIP_(node.typeinfo->compositor_unsupported_message));
2743}
2744
2745static void node_add_error_message_button(const TreeDrawContext &tree_draw_ctx,
2746 const bNode &node,
2747 uiBlock &block,
2748 const rctf &rect,
2749 float &icon_offset)
2750{
2751 if (tree_draw_ctx.used_by_compositor && node.typeinfo->compositor_unsupported_message) {
2753 return;
2754 }
2755
2756 geo_log::GeoTreeLog *geo_tree_log = [&]() -> geo_log::GeoTreeLog * {
2757 const bNodeTreeZones *zones = node.owner_tree().zones();
2758 if (!zones) {
2759 return nullptr;
2760 }
2761 const bNodeTreeZone *zone = zones->get_zone_by_node(node.identifier);
2762 if (zone && ELEM(node.identifier, zone->input_node_id, zone->output_node_id)) {
2763 zone = zone->parent_zone;
2764 }
2765 return tree_draw_ctx.tree_logs.get_main_tree_log(zone);
2766 }();
2767
2769 if (geo_tree_log) {
2770 geo_log::GeoNodeLog *node_log = geo_tree_log->nodes.lookup_ptr(node.identifier);
2771 if (node_log != nullptr) {
2772 warnings = node_log->warnings;
2773 }
2774 }
2775 if (warnings.is_empty()) {
2776 return;
2777 }
2778
2779 const nodes::NodeWarningType display_type = node_error_highest_priority(warnings);
2780
2781 icon_offset -= NODE_HEADER_ICON_SIZE;
2783 uiBut *but = uiDefIconBut(&block,
2785 0,
2786 nodes::node_warning_type_icon(display_type),
2787 icon_offset,
2788 rect.ymax - NODE_DY,
2790 UI_UNIT_Y,
2791 nullptr,
2792 0,
2793 0,
2794 nullptr);
2796 but, [warnings = Array<geo_log::NodeWarning>(warnings)](const uiBut * /*but*/) {
2797 return node_errors_tooltip_fn(warnings);
2798 });
2800}
2801
2802static std::optional<std::chrono::nanoseconds> geo_node_get_execution_time(
2803 const TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, const bNode &node)
2804{
2805 const bNodeTree &ntree = *snode.edittree;
2806
2807 geo_log::GeoTreeLog *tree_log = [&]() -> geo_log::GeoTreeLog * {
2808 const bNodeTreeZones *zones = ntree.zones();
2809 if (!zones) {
2810 return nullptr;
2811 }
2812 const bNodeTreeZone *zone = zones->get_zone_by_node(node.identifier);
2813 if (zone && ELEM(node.identifier, zone->input_node_id, zone->output_node_id)) {
2814 zone = zone->parent_zone;
2815 }
2816 return tree_draw_ctx.tree_logs.get_main_tree_log(zone);
2817 }();
2818
2819 if (tree_log == nullptr) {
2820 return std::nullopt;
2821 }
2822 if (node.is_group_output()) {
2823 return tree_log->execution_time;
2824 }
2825 if (node.is_frame()) {
2826 /* Could be cached in the future if this recursive code turns out to be slow. */
2827 std::chrono::nanoseconds run_time{0};
2828 bool found_node = false;
2829
2830 for (const bNode *tnode : node.direct_children_in_frame()) {
2831 if (tnode->is_frame()) {
2832 std::optional<std::chrono::nanoseconds> sub_frame_run_time = geo_node_get_execution_time(
2833 tree_draw_ctx, snode, *tnode);
2834 if (sub_frame_run_time.has_value()) {
2835 run_time += *sub_frame_run_time;
2836 found_node = true;
2837 }
2838 }
2839 else {
2840 if (const geo_log::GeoNodeLog *node_log = tree_log->nodes.lookup_ptr_as(tnode->identifier))
2841 {
2842 found_node = true;
2843 run_time += node_log->execution_time;
2844 }
2845 }
2846 }
2847 if (found_node) {
2848 return run_time;
2849 }
2850 return std::nullopt;
2851 }
2852 if (const geo_log::GeoNodeLog *node_log = tree_log->nodes.lookup_ptr(node.identifier)) {
2853 return node_log->execution_time;
2854 }
2855 return std::nullopt;
2856}
2857
2858/* Create node key instance, assuming the node comes from the currently edited node tree. */
2860{
2861 const bNodeTreePath *path = static_cast<const bNodeTreePath *>(snode.treepath.last);
2862
2863 /* Some code in this file checks for the non-null elements of the tree path. However, if we did
2864 * iterate into a node it is expected that there is a tree, and it should be in the path.
2865 * Otherwise something else went wrong. */
2866 BLI_assert(path);
2867
2868 /* Assume that the currently editing tree is the last in the path. */
2869 BLI_assert(snode.edittree == path->nodetree);
2870
2871 return bke::node_instance_key(path->parent_key, snode.edittree, &node);
2872}
2873
2874static std::optional<std::chrono::nanoseconds> compositor_accumulate_frame_node_execution_time(
2875 const TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, const bNode &node)
2876{
2878
2879 timeit::Nanoseconds frame_execution_time(0);
2880 bool has_any_execution_time = false;
2881
2882 for (const bNode *current_node : node.direct_children_in_frame()) {
2883 const bNodeInstanceKey key = current_node_instance_key(snode, *current_node);
2884 if (const timeit::Nanoseconds *node_execution_time =
2886 {
2887 frame_execution_time += *node_execution_time;
2888 has_any_execution_time = true;
2889 }
2890 }
2891
2892 if (!has_any_execution_time) {
2893 return std::nullopt;
2894 }
2895
2896 return frame_execution_time;
2897}
2898
2899static std::optional<std::chrono::nanoseconds> compositor_node_get_execution_time(
2900 const TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, const bNode &node)
2901{
2903
2904 /* For the frame nodes accumulate execution time of its children. */
2905 if (node.is_frame()) {
2906 return compositor_accumulate_frame_node_execution_time(tree_draw_ctx, snode, node);
2907 }
2908
2909 /* For other nodes simply lookup execution time.
2910 * The group node instances have their own entries in the execution times map. */
2911 const bNodeInstanceKey key = current_node_instance_key(snode, node);
2912 if (const timeit::Nanoseconds *execution_time =
2914 {
2915 return *execution_time;
2916 }
2917
2918 return std::nullopt;
2919}
2920
2921static std::optional<std::chrono::nanoseconds> node_get_execution_time(
2922 const TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, const bNode &node)
2923{
2924 switch (snode.edittree->type) {
2925 case NTREE_GEOMETRY:
2926 return geo_node_get_execution_time(tree_draw_ctx, snode, node);
2927 case NTREE_COMPOSIT:
2928 return compositor_node_get_execution_time(tree_draw_ctx, snode, node);
2929 default:
2930 return std::nullopt;
2931 }
2932}
2933
2934static std::string node_get_execution_time_label(TreeDrawContext &tree_draw_ctx,
2935 const SpaceNode &snode,
2936 const bNode &node)
2937{
2938 const std::optional<std::chrono::nanoseconds> exec_time = node_get_execution_time(
2939 tree_draw_ctx, snode, node);
2940
2941 if (!exec_time.has_value()) {
2942 return std::string("");
2943 }
2944
2945 const uint64_t exec_time_us =
2946 std::chrono::duration_cast<std::chrono::microseconds>(*exec_time).count();
2947
2948 /* Don't show time if execution time is 0 microseconds. */
2949 if (exec_time_us == 0) {
2950 return std::string("-");
2951 }
2952 if (exec_time_us < 100) {
2953 return std::string("< 0.1 ms");
2954 }
2955
2956 int precision = 0;
2957 /* Show decimal if value is below 1ms */
2958 if (exec_time_us < 1000) {
2959 precision = 2;
2960 }
2961 else if (exec_time_us < 10000) {
2962 precision = 1;
2963 }
2964
2965 std::stringstream stream;
2966 stream << std::fixed << std::setprecision(precision) << (exec_time_us / 1000.0f);
2967 return stream.str() + " ms";
2968}
2969
2973
2974static std::string named_attribute_tooltip(bContext * /*C*/, void *argN, const StringRef /*tip*/)
2975{
2976 NamedAttributeTooltipArg &arg = *static_cast<NamedAttributeTooltipArg *>(argN);
2977
2978 fmt::memory_buffer buf;
2979 fmt::format_to(fmt::appender(buf), "{}", TIP_("Accessed named attributes:"));
2980 fmt::format_to(fmt::appender(buf), "\n");
2981
2982 struct NameWithUsage {
2983 StringRefNull name;
2985 };
2986
2987 Vector<NameWithUsage> sorted_used_attribute;
2988 for (auto &&item : arg.usage_by_attribute.items()) {
2989 sorted_used_attribute.append({item.key, item.value});
2990 }
2991 std::sort(sorted_used_attribute.begin(),
2992 sorted_used_attribute.end(),
2993 [](const NameWithUsage &a, const NameWithUsage &b) {
2994 return BLI_strcasecmp_natural(a.name.c_str(), b.name.c_str()) < 0;
2995 });
2996
2997 for (const NameWithUsage &attribute : sorted_used_attribute) {
2998 const StringRefNull name = attribute.name;
2999 const geo_log::NamedAttributeUsage usage = attribute.usage;
3000 fmt::format_to(fmt::appender(buf), fmt::runtime(TIP_(" \u2022 \"{}\": ")), name);
3001 Vector<std::string> usages;
3003 usages.append(TIP_("read"));
3004 }
3006 usages.append(TIP_("write"));
3007 }
3009 usages.append(TIP_("remove"));
3010 }
3011 for (const int i : usages.index_range()) {
3012 fmt::format_to(fmt::appender(buf), "{}", usages[i]);
3013 if (i < usages.size() - 1) {
3014 fmt::format_to(fmt::appender(buf), ", ");
3015 }
3016 }
3017 fmt::format_to(fmt::appender(buf), "\n");
3018 }
3019 fmt::format_to(fmt::appender(buf), "\n");
3020 fmt::format_to(
3021 fmt::appender(buf),
3022 fmt::runtime(TIP_("Attributes with these names used within the group may conflict with "
3023 "existing attributes")));
3024 return fmt::to_string(buf);
3025}
3026
3028 const Map<StringRefNull, geo_log::NamedAttributeUsage> &usage_by_attribute_name)
3029{
3030 const int attributes_num = usage_by_attribute_name.size();
3031
3032 NodeExtraInfoRow row;
3033 row.text = std::to_string(attributes_num) +
3034 (attributes_num == 1 ? RPT_(" Named Attribute") : RPT_(" Named Attributes"));
3035 row.icon = ICON_SPREADSHEET;
3037 row.tooltip_fn_arg = new NamedAttributeTooltipArg{usage_by_attribute_name};
3038 row.tooltip_fn_free_arg = [](void *arg) { delete static_cast<NamedAttributeTooltipArg *>(arg); };
3039 return row;
3040}
3041
3042static std::optional<NodeExtraInfoRow> node_get_accessed_attributes_row(
3043 TreeDrawContext &tree_draw_ctx, const bNode &node)
3044{
3045 geo_log::GeoTreeLog *geo_tree_log = tree_draw_ctx.tree_logs.get_main_tree_log(node);
3046 if (geo_tree_log == nullptr) {
3047 return std::nullopt;
3048 }
3049 if (ELEM(node.type_legacy,
3053 {
3054 /* Only show the overlay when the name is passed in from somewhere else. */
3055 for (const bNodeSocket *socket : node.input_sockets()) {
3056 if (STREQ(socket->name, "Name")) {
3057 if (!socket->is_directly_linked()) {
3058 return std::nullopt;
3059 }
3060 }
3061 }
3062 }
3063 geo_tree_log->ensure_used_named_attributes();
3064 geo_log::GeoNodeLog *node_log = geo_tree_log->nodes.lookup_ptr(node.identifier);
3065 if (node_log == nullptr) {
3066 return std::nullopt;
3067 }
3068 if (node_log->used_named_attributes.is_empty()) {
3069 return std::nullopt;
3070 }
3072}
3073
3074static std::optional<NodeExtraInfoRow> node_get_execution_time_label_row(
3075 TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, const bNode &node)
3076{
3077 NodeExtraInfoRow row;
3078 row.text = node_get_execution_time_label(tree_draw_ctx, snode, node);
3079 if (row.text.empty()) {
3080 return std::nullopt;
3081 }
3082 row.tooltip = TIP_(
3083 "The execution time from the node tree's latest evaluation. For frame and group "
3084 "nodes, the time for all sub-nodes");
3085 row.icon = ICON_PREVIEW_RANGE;
3086 return row;
3087}
3088
3090 const SpaceNode &snode,
3091 const bNode &node,
3093{
3094 if (snode.overlay.flag & SN_OVERLAY_SHOW_TIMINGS) {
3095 std::optional<NodeExtraInfoRow> row = node_get_execution_time_label_row(
3096 tree_draw_ctx, snode, node);
3097 if (row.has_value()) {
3098 rows.append(std::move(*row));
3099 }
3100 }
3101}
3102
3104 TreeDrawContext &tree_draw_ctx,
3105 const SpaceNode &snode,
3106 const bNode &node)
3107{
3109
3110 if (node.typeinfo->get_extra_info) {
3111 nodes::NodeExtraInfoParams params{rows, *snode.edittree, node, C};
3112 node.typeinfo->get_extra_info(params);
3113 }
3114
3115 if (node.typeinfo->deprecation_notice) {
3116 NodeExtraInfoRow row;
3117 row.text = IFACE_("Deprecated");
3118 row.icon = ICON_INFO;
3119 row.tooltip = TIP_(node.typeinfo->deprecation_notice);
3120 rows.append(std::move(row));
3121 }
3122
3123 if (snode.edittree->type == NTREE_COMPOSIT) {
3124 node_get_compositor_extra_info(tree_draw_ctx, snode, node, rows);
3125 return rows;
3126 }
3127
3128 if (!(snode.edittree->type == NTREE_GEOMETRY)) {
3129 /* Currently geometry and compositor nodes are the only nodes to have extra info per nodes. */
3130 return rows;
3131 }
3132
3134 if (std::optional<NodeExtraInfoRow> row = node_get_accessed_attributes_row(tree_draw_ctx,
3135 node))
3136 {
3137 rows.append(std::move(*row));
3138 }
3139 }
3140
3141 if (snode.overlay.flag & SN_OVERLAY_SHOW_TIMINGS &&
3143 ELEM(node.type_legacy,
3144 NODE_FRAME,
3150 StringRef(node.idname).startswith("GeometryNodeImport")))
3151 {
3152 std::optional<NodeExtraInfoRow> row = node_get_execution_time_label_row(
3153 tree_draw_ctx, snode, node);
3154 if (row.has_value()) {
3155 rows.append(std::move(*row));
3156 }
3157 }
3158
3159 geo_log::GeoTreeLog *tree_log = tree_draw_ctx.tree_logs.get_main_tree_log(node);
3160
3161 if (tree_log) {
3162 tree_log->ensure_debug_messages();
3163 const geo_log::GeoNodeLog *node_log = tree_log->nodes.lookup_ptr(node.identifier);
3164 if (node_log != nullptr) {
3165 for (const StringRef message : node_log->debug_messages) {
3166 NodeExtraInfoRow row;
3167 row.text = message;
3168 row.icon = ICON_INFO;
3169 rows.append(std::move(row));
3170 }
3171 }
3172 }
3173
3174 return rows;
3175}
3176
3177static void node_draw_extra_info_row(const bNode &node,
3178 uiBlock &block,
3179 const rctf &rect,
3180 const int row,
3181 const NodeExtraInfoRow &extra_info_row)
3182{
3183 const float but_icon_left = rect.xmin + 6.0f * UI_SCALE_FAC;
3184 const float but_icon_width = NODE_HEADER_ICON_SIZE * 0.8f;
3185 const float but_icon_right = but_icon_left + but_icon_width;
3186
3188 uiBut *but_icon = uiDefIconBut(&block,
3190 0,
3191 extra_info_row.icon,
3192 int(but_icon_left),
3193 int(rect.ymin + row * EXTRA_INFO_ROW_HEIGHT),
3194 but_icon_width,
3195 UI_UNIT_Y,
3196 nullptr,
3197 0,
3198 0,
3199 extra_info_row.tooltip);
3200 if (extra_info_row.tooltip_fn != nullptr) {
3201 UI_but_func_tooltip_set(but_icon,
3202 extra_info_row.tooltip_fn,
3203 extra_info_row.tooltip_fn_arg,
3204 extra_info_row.tooltip_fn_free_arg);
3205 }
3207
3208 const float but_text_left = but_icon_right + 6.0f * UI_SCALE_FAC;
3209 const float but_text_right = rect.xmax;
3210 const float but_text_width = but_text_right - but_text_left;
3211
3212 uiBut *but_text = uiDefBut(&block,
3214 0,
3215 extra_info_row.text.c_str(),
3216 int(but_text_left),
3217 int(rect.ymin + row * EXTRA_INFO_ROW_HEIGHT),
3218 short(but_text_width),
3219 NODE_DY,
3220 nullptr,
3221 0,
3222 0,
3223 extra_info_row.tooltip);
3224
3225 if (extra_info_row.tooltip_fn != nullptr) {
3226 /* Don't pass tooltip free function because it's already used on the uiBut above. */
3228 but_text, extra_info_row.tooltip_fn, extra_info_row.tooltip_fn_arg, nullptr);
3229 }
3230
3231 if (node.is_muted()) {
3234 }
3235}
3236
3237static void node_draw_extra_info_panel_back(const bNode &node, const rctf &extra_info_rect)
3238{
3239 const rctf &node_rect = node.runtime->draw_bounds;
3240 rctf panel_back_rect = extra_info_rect;
3241 /* Extend the panel behind hidden nodes to accommodate the large rounded corners. */
3242 if (node.flag & NODE_HIDDEN) {
3243 panel_back_rect.ymin = BLI_rctf_cent_y(&node_rect);
3244 }
3245
3247 if (node.is_muted()) {
3249 }
3250 else {
3252 }
3253 color.a -= 0.35f;
3254
3255 ColorTheme4f color_outline;
3256 UI_GetThemeColorBlendShade4fv(TH_BACK, TH_NODE, 0.4f, -20, color_outline);
3257
3258 const float outline_width = U.pixelsize;
3259 BLI_rctf_pad(&panel_back_rect, outline_width, outline_width);
3260
3263 &panel_back_rect, color, nullptr, 0.0f, color_outline, outline_width, BASIS_RAD);
3264}
3265
3267 TreeDrawContext &tree_draw_ctx,
3268 const SpaceNode &snode,
3269 const bNode &node,
3270 ImBuf *preview,
3271 uiBlock &block)
3272{
3273 const Scene *scene = CTX_data_scene(&C);
3274 if (!(snode.overlay.flag & SN_OVERLAY_SHOW_OVERLAYS)) {
3275 return;
3276 }
3277 if (preview && !(preview->x > 0 && preview->y > 0)) {
3278 /* If the preview has an non-drawable size, just don't draw it. */
3279 preview = nullptr;
3280 }
3281 const Span<NodeExtraInfoRow> extra_info_rows =
3282 tree_draw_ctx.extra_info_rows_per_node[node.index()];
3283 if (extra_info_rows.is_empty() && !preview) {
3284 return;
3285 }
3286
3287 const rctf &rct = node.runtime->draw_bounds;
3288 rctf extra_info_rect;
3289
3290 if (node.is_frame()) {
3291 extra_info_rect.xmin = rct.xmin;
3292 extra_info_rect.xmax = rct.xmin + 95.0f * UI_SCALE_FAC;
3293 extra_info_rect.ymin = rct.ymin + 2.0f * UI_SCALE_FAC;
3294 extra_info_rect.ymax = rct.ymin + 2.0f * UI_SCALE_FAC;
3295 }
3296 else {
3297 const float padding = 3.0f * UI_SCALE_FAC;
3298
3299 extra_info_rect.xmin = rct.xmin + padding;
3300 extra_info_rect.xmax = rct.xmax - padding;
3301 extra_info_rect.ymin = rct.ymax;
3302 extra_info_rect.ymax = rct.ymax + extra_info_rows.size() * EXTRA_INFO_ROW_HEIGHT;
3303
3304 float preview_height = 0.0f;
3305 rctf preview_rect;
3306 if (preview) {
3307 const float width = BLI_rctf_size_x(&extra_info_rect);
3308 if (preview->x > preview->y) {
3309 preview_height = (width - 2.0f * padding) * float(preview->y) / float(preview->x) +
3310 2.0f * padding;
3311 preview_rect.ymin = extra_info_rect.ymin + padding;
3312 preview_rect.ymax = extra_info_rect.ymin + preview_height - padding;
3313 preview_rect.xmin = extra_info_rect.xmin + padding;
3314 preview_rect.xmax = extra_info_rect.xmax - padding;
3315 extra_info_rect.ymax += preview_height;
3316 }
3317 else {
3318 preview_height = width;
3319 const float preview_width = (width - 2.0f * padding) * float(preview->x) /
3320 float(preview->y) +
3321 2.0f * padding;
3322 preview_rect.ymin = extra_info_rect.ymin + padding;
3323 preview_rect.ymax = extra_info_rect.ymin + preview_height - padding;
3324 preview_rect.xmin = extra_info_rect.xmin + padding + (width - preview_width) / 2;
3325 preview_rect.xmax = extra_info_rect.xmax - padding - (width - preview_width) / 2;
3326 extra_info_rect.ymax += preview_height;
3327 }
3328 }
3329
3330 node_draw_extra_info_panel_back(node, extra_info_rect);
3331
3332 if (preview) {
3333 node_draw_preview(scene, preview, &preview_rect);
3334 }
3335
3336 /* Resize the rect to draw the textual infos on top of the preview. */
3337 extra_info_rect.ymin += preview_height;
3338 }
3339
3340 for (int row : extra_info_rows.index_range()) {
3341 node_draw_extra_info_row(node, block, extra_info_rect, row, extra_info_rows[row]);
3342 }
3343}
3344
3345static short get_viewer_shortcut_icon(const bNode &node)
3346{
3347 BLI_assert(node.is_type("CompositorNodeViewer") || node.is_type("GeometryNodeViewer"));
3348 switch (node.custom1) {
3350 /* No change by default. */
3351 return node.typeinfo->ui_icon;
3353 return ICON_EVENT_ONEKEY;
3355 return ICON_EVENT_TWOKEY;
3357 return ICON_EVENT_THREEKEY;
3359 return ICON_EVENT_FOURKEY;
3361 return ICON_EVENT_FIVEKEY;
3363 return ICON_EVENT_SIXKEY;
3365 return ICON_EVENT_SEVENKEY;
3367 return ICON_EVENT_EIGHTKEY;
3369 return ICON_EVENT_NINEKEY;
3370 }
3371
3372 return node.typeinfo->ui_icon;
3373}
3374
3375/* Returns true if the given node has an undefined type, a missing group node tree, or is
3376 * unsupported in the given node tree. */
3377static bool node_undefined_or_unsupported(const bNodeTree &node_tree, const bNode &node)
3378{
3379 if (node.typeinfo == &bke::NodeTypeUndefined) {
3380 return true;
3381 }
3382
3383 const char *disabled_hint = nullptr;
3384 if (!node.typeinfo->poll(node.typeinfo, &node_tree, &disabled_hint)) {
3385 return true;
3386 }
3387
3388 if (node.is_group()) {
3389 const ID *group_tree = node.id;
3390 if (group_tree == nullptr) {
3391 return false;
3392 }
3393 if (!ID_IS_LINKED(group_tree)) {
3394 return false;
3395 }
3396 if ((group_tree->tag & ID_TAG_MISSING) == 0) {
3397 return false;
3398 }
3399 return true;
3400 }
3401 return false;
3402}
3403
3404static void node_draw_basis(const bContext &C,
3405 TreeDrawContext &tree_draw_ctx,
3406 const View2D &v2d,
3407 const SpaceNode &snode,
3408 bNodeTree &ntree,
3409 const bNode &node,
3410 uiBlock &block,
3411 bNodeInstanceKey key)
3412{
3413 const float iconbutw = NODE_HEADER_ICON_SIZE;
3414 const bool show_preview = (snode.overlay.flag & SN_OVERLAY_SHOW_OVERLAYS) &&
3416 (node.flag & NODE_PREVIEW) &&
3417 (USER_EXPERIMENTAL_TEST(&U, use_shader_node_previews) ||
3418 ntree.type != NTREE_SHADER);
3419
3420 /* Skip if out of view. */
3421 rctf rect_with_preview = node.runtime->draw_bounds;
3422 if (show_preview) {
3423 rect_with_preview.ymax += NODE_WIDTH(node);
3424 }
3425 if (BLI_rctf_isect(&rect_with_preview, &v2d.cur, nullptr) == false) {
3427 tree_draw_ctx.bmain,
3428 tree_draw_ctx.window,
3429 tree_draw_ctx.scene,
3430 tree_draw_ctx.region,
3431 tree_draw_ctx.depsgraph,
3432 &block);
3433 return;
3434 }
3435
3436 /* Shadow. */
3437 if (!bke::all_zone_node_types().contains(node.type_legacy)) {
3438 node_draw_shadow(snode, node, BASIS_RAD, 1.0f);
3439 }
3440
3441 const rctf &rct = node.runtime->draw_bounds;
3442 float color[4];
3443 int color_id = node_get_colorid(tree_draw_ctx, node);
3444
3445 GPU_line_width(1.0f);
3446
3447 /* Overlay atop the node. */
3448 {
3449 bool drawn_with_previews = false;
3450
3451 if (show_preview) {
3454 CTX_data_pointer_get(&C, "node_previews").data);
3455 NestedTreePreviews *previews_shader = tree_draw_ctx.nested_group_infos;
3456
3457 if (previews_shader) {
3458 ImBuf *preview = node_preview_acquire_ibuf(ntree, *previews_shader, node);
3459 node_draw_extra_info_panel(C, tree_draw_ctx, snode, node, preview, block);
3460 node_release_preview_ibuf(*previews_shader);
3461 drawn_with_previews = true;
3462 }
3463 else if (previews_compo) {
3464 if (bke::bNodePreview *preview_compositor = previews_compo->lookup_ptr(key)) {
3466 C, tree_draw_ctx, snode, node, preview_compositor->ibuf, block);
3467 drawn_with_previews = true;
3468 }
3469 }
3470 }
3471
3472 if (drawn_with_previews == false) {
3473 node_draw_extra_info_panel(C, tree_draw_ctx, snode, node, nullptr, block);
3474 }
3475 }
3476
3477 const float padding = 0.5f;
3478 const float corner_radius = BASIS_RAD + padding;
3479 /* Header. */
3480 {
3481 /* Add some padding to prevent transparent gaps with the outline. */
3482 const rctf rect = {
3483 rct.xmin - padding,
3484 rct.xmax + padding,
3485 rct.ymax - NODE_DY - padding,
3486 rct.ymax + padding,
3487 };
3488
3489 float color_header[4];
3490
3491 /* Muted nodes get a mix of the background with the node color. */
3492 if (node.is_muted()) {
3493 UI_GetThemeColorBlend4f(TH_BACK, color_id, 0.1f, color_header);
3494 }
3495 else {
3496 UI_GetThemeColorBlend4f(TH_NODE, color_id, 0.4f, color_header);
3497 }
3498
3500 UI_draw_roundbox_4fv(&rect, true, corner_radius, color_header);
3501 }
3502
3503 /* Show/hide icons. */
3504 float iconofs = rct.xmax - 0.35f * U.widget_unit;
3505
3506 /* Group edit. This icon should be the first for the node groups. Note that we intentionally
3507 * don't check for NODE_GROUP_CUSTOM here. */
3508 if (node.type_legacy == NODE_GROUP) {
3509 iconofs -= iconbutw;
3511 uiBut *but = uiDefIconBut(&block,
3513 0,
3514 ICON_NODETREE,
3515 iconofs,
3516 rct.ymax - NODE_DY,
3517 iconbutw,
3518 UI_UNIT_Y,
3519 nullptr,
3520 0,
3521 0,
3522 "");
3523 UI_but_func_set(but,
3526 (void *)"NODE_OT_group_edit");
3527 if (node.id) {
3529 }
3531 }
3532 /* Preview. */
3533 if (node_is_previewable(snode, ntree, node)) {
3534 const bool is_active = node.flag & NODE_PREVIEW;
3535 iconofs -= iconbutw;
3537 uiBut *but = uiDefIconBut(&block,
3539 0,
3540 is_active ? ICON_HIDE_OFF : ICON_HIDE_ON,
3541 iconofs,
3542 rct.ymax - NODE_DY,
3543 iconbutw,
3544 UI_UNIT_Y,
3545 nullptr,
3546 0,
3547 0,
3548 "");
3549 UI_but_func_set(but,
3552 (void *)"NODE_OT_preview_toggle");
3554 }
3556 node.typeinfo->ui_icon != ICON_NONE)
3557 {
3558 iconofs -= iconbutw;
3560 uiDefIconBut(&block,
3562 0,
3563 node.typeinfo->ui_icon,
3564 iconofs,
3565 rct.ymax - NODE_DY,
3566 iconbutw,
3567 UI_UNIT_Y,
3568 nullptr,
3569 0,
3570 0,
3571 "");
3573 }
3574 if (node.type_legacy == GEO_NODE_VIEWER) {
3575 const bool is_active = &node == tree_draw_ctx.active_geometry_nodes_viewer;
3576 iconofs -= iconbutw;
3578 uiBut *but = uiDefIconBut(&block,
3580 0,
3581 is_active ? ICON_RESTRICT_VIEW_OFF : ICON_RESTRICT_VIEW_ON,
3582 iconofs,
3583 rct.ymax - NODE_DY,
3584 iconbutw,
3585 UI_UNIT_Y,
3586 nullptr,
3587 0,
3588 0,
3589 "");
3590 /* Selection implicitly activates the node. */
3591 const char *operator_idname = is_active ? "NODE_OT_deactivate_viewer" :
3592 "NODE_OT_activate_viewer";
3594 but, node_toggle_button_cb, POINTER_FROM_INT(node.identifier), (void *)operator_idname);
3595
3596 short shortcut_icon = get_viewer_shortcut_icon(node);
3597 uiDefIconBut(&block,
3599 0,
3600 shortcut_icon,
3601 iconofs - 1.2 * iconbutw,
3602 rct.ymax - NODE_DY,
3603 iconbutw,
3604 UI_UNIT_Y,
3605 nullptr,
3606 0,
3607 0,
3608 "");
3610 }
3611 /* Viewer node shortcuts. */
3612 if (node.is_type("CompositorNodeViewer")) {
3613 short shortcut_icon = get_viewer_shortcut_icon(node);
3614 iconofs -= iconbutw;
3615 const bool is_active = node.flag & NODE_DO_OUTPUT;
3617 uiBut *but = uiDefIconBut(&block,
3619 0,
3620 is_active ? ICON_RESTRICT_VIEW_OFF : ICON_RESTRICT_VIEW_ON,
3621 iconofs,
3622 rct.ymax - NODE_DY,
3623 iconbutw,
3624 UI_UNIT_Y,
3625 nullptr,
3626 0,
3627 0,
3628 "");
3629
3630 UI_but_func_set(but,
3633 (void *)"NODE_OT_activate_viewer");
3634
3635 uiDefIconBut(&block,
3637 0,
3638 shortcut_icon,
3639 iconofs - 1.2 * iconbutw,
3640 rct.ymax - NODE_DY,
3641 iconbutw,
3642 UI_UNIT_Y,
3643 nullptr,
3644 0,
3645 0,
3646 "");
3648 }
3649
3650 node_add_error_message_button(tree_draw_ctx, node, block, rct, iconofs);
3651
3652 /* Title. */
3653 if (node.flag & SELECT) {
3655 }
3656 else {
3657 UI_GetThemeColorBlendShade4fv(TH_SELECT, color_id, 0.4f, 10, color);
3658 }
3659
3660 /* Collapse/expand icon. */
3661 {
3662 const int but_size = U.widget_unit * 0.8f;
3664
3665 uiBut *but = uiDefIconBut(&block,
3667 0,
3668 ICON_DOWNARROW_HLT,
3669 rct.xmin + (NODE_MARGIN_X / 3),
3670 rct.ymax - NODE_DY / 2.2f - but_size / 2,
3671 but_size,
3672 but_size,
3673 nullptr,
3674 0.0f,
3675 0.0f,
3676 "");
3677
3678 UI_but_func_set(but,
3681 (void *)"NODE_OT_hide_toggle");
3683 }
3684
3685 const std::string showname = bke::node_label(ntree, node);
3686
3687 uiBut *but = uiDefBut(&block,
3689 0,
3690 showname,
3691 int(rct.xmin + NODE_MARGIN_X + 0.4f),
3692 int(rct.ymax - NODE_DY),
3693 short(iconofs - rct.xmin - NODE_MARGIN_X),
3694 NODE_DY,
3695 nullptr,
3696 0,
3697 0,
3698 TIP_(node.typeinfo->ui_description.c_str()));
3700 but,
3701 [](bContext * /*C*/, void *arg, const StringRef tip) -> std::string {
3702 const bNode &node = *static_cast<const bNode *>(arg);
3703 if (node.typeinfo->ui_description_fn) {
3704 return node.typeinfo->ui_description_fn(node);
3705 }
3706 return tip;
3707 },
3708 const_cast<bNode *>(&node),
3709 nullptr);
3710
3711 if (node.is_muted()) {
3713 }
3714
3715 /* Wire across the node when muted/disabled. */
3716 if (node.is_muted()) {
3717 node_draw_mute_line(C, v2d, snode, node);
3718 }
3719
3720 /* Body. */
3721 const float outline_width = U.pixelsize;
3722 {
3723 /* Use warning color to indicate undefined types. */
3724 if (node_undefined_or_unsupported(ntree, node)) {
3726 }
3727 /* Muted nodes get a mix of the background with the node color. */
3728 else if (node.is_muted()) {
3730 }
3731 else if (node.flag & NODE_CUSTOM_COLOR) {
3732 rgba_float_args_set(color, node.color[0], node.color[1], node.color[2], 1.0f);
3733 }
3734 else {
3736 }
3737
3738 /* Draw selected nodes fully opaque. */
3739 if (node.flag & SELECT) {
3740 color[3] = 1.0f;
3741 }
3742
3743 /* Draw muted nodes slightly transparent so the wires inside are visible. */
3744 if (node.is_muted()) {
3745 color[3] -= 0.2f;
3746 }
3747
3748 /* Add some padding to prevent transparent gaps with the outline. */
3749 const rctf rect = {
3750 rct.xmin - padding,
3751 rct.xmax + padding,
3752 rct.ymin - padding,
3753 rct.ymax - (NODE_DY + outline_width) + padding,
3754 };
3755
3757 UI_draw_roundbox_4fv(&rect, true, corner_radius, color);
3758
3759 if (is_node_panels_supported(node)) {
3761 }
3762 }
3763
3764 /* Header underline. */
3765 {
3766 float color_underline[4];
3767
3768 if (node.is_muted()) {
3769 UI_GetThemeColorBlend4f(TH_BACK, color_id, 0.05f, color_underline);
3770 color_underline[3] = 1.0f;
3771 }
3772 else {
3773 UI_GetThemeColorBlend4f(TH_BACK, color_id, 0.2f, color_underline);
3774 }
3775
3776 const rctf rect = {
3777 rct.xmin,
3778 rct.xmax,
3779 rct.ymax - (NODE_DY + outline_width),
3780 rct.ymax - NODE_DY,
3781 };
3782
3784 UI_draw_roundbox_4fv(&rect, true, 0.0f, color_underline);
3785 }
3786
3787 /* Outline. */
3788 {
3789 const rctf rect = {
3790 rct.xmin - outline_width,
3791 rct.xmax + outline_width,
3792 rct.ymin - outline_width,
3793 rct.ymax + outline_width,
3794 };
3795
3796 /* Color the outline according to active, selected, or undefined status. */
3797 float color_outline[4];
3798
3799 if (node.flag & SELECT) {
3800 UI_GetThemeColor4fv((node.flag & NODE_ACTIVE) ? TH_ACTIVE : TH_SELECT, color_outline);
3801 }
3802 else if (node_undefined_or_unsupported(ntree, node)) {
3803 UI_GetThemeColor4fv(TH_REDALERT, color_outline);
3804 }
3805 else if (const bke::bNodeZoneType *zone_type = bke::zone_type_by_node_type(node.type_legacy)) {
3806 UI_GetThemeColor4fv(zone_type->theme_id, color_outline);
3807 color_outline[3] = 1.0f;
3808 }
3809 else {
3810 UI_GetThemeColorBlendShade4fv(TH_BACK, TH_NODE, 0.4f, -20, color_outline);
3811 }
3812
3814 UI_draw_roundbox_4fv(&rect, false, BASIS_RAD + outline_width, color_outline);
3815 }
3816
3817 /* Skip slow socket drawing if zoom is small. */
3818 if (draw_node_details(snode)) {
3819 node_draw_sockets(C, block, snode, ntree, node);
3820 }
3821
3822 if (is_node_panels_supported(node)) {
3823 node_draw_panels(ntree, node, block);
3824 }
3825
3827 tree_draw_ctx.bmain,
3828 tree_draw_ctx.window,
3829 tree_draw_ctx.scene,
3830 tree_draw_ctx.region,
3831 tree_draw_ctx.depsgraph,
3832 &block);
3833 UI_block_draw(&C, &block);
3834}
3835
3836static void node_draw_hidden(const bContext &C,
3837 TreeDrawContext &tree_draw_ctx,
3838 const View2D &v2d,
3839 const SpaceNode &snode,
3840 bNodeTree &ntree,
3841 bNode &node,
3842 uiBlock &block)
3843{
3844 const rctf &rct = node.runtime->draw_bounds;
3845 float centy = BLI_rctf_cent_y(&rct);
3846 float hiddenrad = BLI_rctf_size_y(&rct) / 2.0f;
3847
3848 float scale;
3849 UI_view2d_scale_get(&v2d, &scale, nullptr);
3850
3851 const int color_id = node_get_colorid(tree_draw_ctx, node);
3852
3853 node_draw_extra_info_panel(C, tree_draw_ctx, snode, node, nullptr, block);
3854
3855 /* Shadow. */
3856 node_draw_shadow(snode, node, hiddenrad, 1.0f);
3857
3858 /* Wire across the node when muted/disabled. */
3859 if (node.is_muted()) {
3860 node_draw_mute_line(C, v2d, snode, node);
3861 }
3862
3863 /* Body. */
3864 float color[4];
3865 {
3866 if (node_undefined_or_unsupported(ntree, node)) {
3867 /* Use warning color to indicate undefined types. */
3869 }
3870 else if (node.is_muted()) {
3871 /* Muted nodes get a mix of the background with the node color. */
3872 UI_GetThemeColorBlendShade4fv(TH_BACK, color_id, 0.1f, 0, color);
3873 }
3874 else if (node.flag & NODE_CUSTOM_COLOR) {
3875 rgba_float_args_set(color, node.color[0], node.color[1], node.color[2], 1.0f);
3876 }
3877 else {
3878 UI_GetThemeColorBlend4f(TH_NODE, color_id, 0.4f, color);
3879 }
3880
3881 /* Draw selected nodes fully opaque. */
3882 if (node.flag & SELECT) {
3883 color[3] = 1.0f;
3884 }
3885
3886 /* Draw muted nodes slightly transparent so the wires inside are visible. */
3887 if (node.is_muted()) {
3888 color[3] -= 0.2f;
3889 }
3890
3891 /* Add some padding to prevent transparent gaps with the outline. */
3892 const float padding = 0.5f;
3893 const rctf rect = {
3894 rct.xmin - padding,
3895 rct.xmax + padding,
3896 rct.ymin - padding,
3897 rct.ymax + padding,
3898 };
3899
3900 UI_draw_roundbox_4fv(&rect, true, hiddenrad + padding, color);
3901 }
3902
3903 /* Title. */
3904 if (node.flag & SELECT) {
3906 }
3907 else {
3908 UI_GetThemeColorBlendShade4fv(TH_SELECT, color_id, 0.4f, 10, color);
3909 }
3910
3911 /* Collapse/expand icon. */
3912 {
3913 const int but_size = U.widget_unit * 1.0f;
3915
3916 uiBut *but = uiDefIconBut(&block,
3918 0,
3919 ICON_RIGHTARROW,
3920 rct.xmin + (NODE_MARGIN_X / 3),
3921 centy - but_size / 2,
3922 but_size,
3923 but_size,
3924 nullptr,
3925 0.0f,
3926 0.0f,
3927 "");
3928
3929 UI_but_func_set(but,
3932 (void *)"NODE_OT_hide_toggle");
3934 }
3935
3936 const std::string showname = bke::node_label(ntree, node);
3937
3938 uiBut *but = uiDefBut(&block,
3940 0,
3941 showname,
3943 round_fl_to_int(centy - NODE_DY * 0.5f),
3944 short(BLI_rctf_size_x(&rct) - (2 * U.widget_unit)),
3945 NODE_DY,
3946 nullptr,
3947 0,
3948 0,
3949 TIP_(node.typeinfo->ui_description.c_str()));
3950
3951 /* Outline. */
3952 {
3953 const float outline_width = U.pixelsize;
3954 const rctf rect = {
3955 rct.xmin - outline_width,
3956 rct.xmax + outline_width,
3957 rct.ymin - outline_width,
3958 rct.ymax + outline_width,
3959 };
3960
3961 /* Color the outline according to active, selected, or undefined status. */
3962 float color_outline[4];
3963
3964 if (node.flag & SELECT) {
3965 UI_GetThemeColor4fv((node.flag & NODE_ACTIVE) ? TH_ACTIVE : TH_SELECT, color_outline);
3966 }
3967 else if (node_undefined_or_unsupported(ntree, node)) {
3968 UI_GetThemeColor4fv(TH_REDALERT, color_outline);
3969 }
3970 else {
3971 UI_GetThemeColorBlendShade4fv(TH_BACK, TH_NODE, 0.4f, -20, color_outline);
3972 }
3973
3975 UI_draw_roundbox_4fv(&rect, false, hiddenrad + outline_width, color_outline);
3976 }
3977
3978 if (node.is_muted()) {
3980 }
3981
3982 /* Scale widget thing. */
3986
3988 float dx = 0.5f * U.widget_unit;
3989 const float dx2 = 0.15f * U.widget_unit * snode.runtime->aspect;
3990 const float dy = 0.2f * U.widget_unit;
3991
3993 immVertex2f(pos, rct.xmax - dx, centy - dy);
3994 immVertex2f(pos, rct.xmax - dx, centy + dy);
3995
3996 immVertex2f(pos, rct.xmax - dx - dx2, centy - dy);
3997 immVertex2f(pos, rct.xmax - dx - dx2, centy + dy);
3998 immEnd();
3999
4001 dx -= snode.runtime->aspect;
4002
4004 immVertex2f(pos, rct.xmax - dx, centy - dy);
4005 immVertex2f(pos, rct.xmax - dx, centy + dy);
4006
4007 immVertex2f(pos, rct.xmax - dx - dx2, centy - dy);
4008 immVertex2f(pos, rct.xmax - dx - dx2, centy + dy);
4009 immEnd();
4010
4013
4014 node_draw_sockets(C, block, snode, ntree, node);
4015
4017 tree_draw_ctx.bmain,
4018 tree_draw_ctx.window,
4019 tree_draw_ctx.scene,
4020 tree_draw_ctx.region,
4021 tree_draw_ctx.depsgraph,
4022 &block);
4023 UI_block_draw(&C, &block);
4024}
4025
4027{
4028 if (directions == 0) {
4029 return WM_CURSOR_DEFAULT;
4030 }
4031 if ((directions & ~(NODE_RESIZE_TOP | NODE_RESIZE_BOTTOM)) == 0) {
4032 return WM_CURSOR_Y_MOVE;
4033 }
4034 if ((directions & ~(NODE_RESIZE_RIGHT | NODE_RESIZE_LEFT)) == 0) {
4035 return WM_CURSOR_X_MOVE;
4036 }
4037 return WM_CURSOR_EDIT;
4038}
4039
4040static const bNode *find_node_under_cursor(SpaceNode &snode, const float2 &cursor)
4041{
4042 for (const bNode *node : tree_draw_order_calc_nodes_reversed(*snode.edittree)) {
4043 if (BLI_rctf_isect_pt(&node->runtime->draw_bounds, cursor[0], cursor[1])) {
4044 return node;
4045 }
4046 }
4047 return nullptr;
4048}
4049
4050void node_set_cursor(wmWindow &win, ARegion &region, SpaceNode &snode, const float2 &cursor)
4051{
4052 const bNodeTree *ntree = snode.edittree;
4053 if (ntree == nullptr) {
4055 return;
4056 }
4057 if (node_find_indicated_socket(snode, region, cursor, SOCK_IN | SOCK_OUT)) {
4059 return;
4060 }
4061 const bNode *node = find_node_under_cursor(snode, cursor);
4062 if (!node) {
4064 return;
4065 }
4066 const NodeResizeDirection dir = node_get_resize_direction(snode, node, cursor[0], cursor[1]);
4067 if (node->is_frame() && dir == NODE_RESIZE_NONE) {
4068 /* Indicate that frame nodes can be moved/selected on their borders. */
4069 const rctf frame_inside = node_frame_rect_inside(snode, *node);
4070 if (!BLI_rctf_isect_pt(&frame_inside, cursor[0], cursor[1])) {
4072 return;
4073 }
4075 return;
4076 }
4077
4079}
4080
4082{
4083 for (bNode *node : ntree.all_nodes()) {
4084 for (bNodeSocket *socket : node->input_sockets()) {
4085 if (socket->is_multi_input()) {
4086 socket->runtime->total_inputs = socket->directly_linked_links().size();
4087 }
4088 }
4089 }
4090 /* Count temporary links going into this socket. */
4091 if (snode.runtime->linkdrag) {
4092 for (const bNodeLink &link : snode.runtime->linkdrag->links) {
4093 if (link.tosock && (link.tosock->flag & SOCK_MULTI_INPUT)) {
4094 link.tosock->runtime->total_inputs++;
4095 }
4096 }
4097 }
4098}
4099
4101 float margin = 0;
4102 float margin_top = 0;
4103 float label_height = 0;
4105 bool has_label = 0;
4106};
4107
4108static FrameNodeLayout frame_node_layout(const bNode &frame_node)
4109{
4110 BLI_assert(frame_node.is_frame());
4111
4112 const NodeFrame *frame_data = (NodeFrame *)frame_node.storage;
4113
4114 FrameNodeLayout frame_layout;
4115
4116 frame_layout.has_label = frame_node.label[0] != '\0';
4117
4118 /* This is not the actual height of the letters in the label, but an approximation that includes
4119 * some of the white-space above and below the actual letters. */
4120 frame_layout.label_height = frame_data->label_size * UI_SCALE_FAC;
4121
4122 /* The side and bottom margins are 50% bigger than the widget unit */
4123 frame_layout.margin = 1.5f * U.widget_unit;
4124
4125 if (frame_layout.has_label) {
4126 /* The label takes up 1.5 times the label height plus 0.2 times the margin.
4127 * These coefficients are selected to provide good layout and spacing for the descenders. */
4128 float room_for_label = 1.5f * frame_layout.label_height + 0.2f * frame_layout.margin;
4129
4130 /* Make top margin bigger, if needed for the label, but never smaller than the side margins. */
4131 frame_layout.margin_top = std::max(frame_layout.margin, room_for_label);
4132
4133 /* This adjustment approximately centers the cap height in the margin.
4134 * This is achieved by finding the y value that is the center of the top margin, then lowering
4135 * that by 35% of the label height. Since font cap heights are typically about 70% of the total
4136 * line height, moving the text by half that achieves rough centering. */
4137 frame_layout.label_baseline = 0.5f * frame_layout.margin_top +
4138 0.35f * frame_layout.label_height;
4139 }
4140 else {
4141 /* If there is no label, the top margin is the same as the sides. */
4142 frame_layout.margin_top = frame_layout.margin;
4143 frame_layout.label_baseline = 0;
4144 }
4145
4146 return frame_layout;
4147}
4148
4156 TreeDrawContext &tree_draw_ctx,
4157 const SpaceNode &snode,
4158 bNode &node)
4159{
4160 if (!node.is_frame()) {
4161 rctf node_bounds = node.runtime->draw_bounds;
4162
4163 float zone_padding = 0;
4164 float extra_row_padding = 0;
4165
4166 /* Pad if the node type is a zone input or output. */
4167 if (bke::zone_type_by_node_type(node.type_legacy) != nullptr) {
4168 zone_padding = NODE_ZONE_PADDING;
4169 }
4170
4171 /* Compute the height of the info row for each node, which may vary per child node.
4172 * This has to get the full extra_rows information (including all the text strings), even
4173 * though all that's actually needed is the count of how many info_rows there are. */
4175 extra_row_padding = tree_draw_ctx.extra_info_rows_per_node[node.index()].size() *
4177 }
4178
4179 node_bounds.ymax += std::max(zone_padding, extra_row_padding);
4180 node_bounds.ymin -= zone_padding;
4181
4182 return node_bounds;
4183 }
4184
4185 NodeFrame *data = (NodeFrame *)node.storage;
4186
4187 const FrameNodeLayout frame_layout = frame_node_layout(node);
4188
4189 /* Initialize rect from current frame size. */
4190 rctf rect;
4191 node_to_updated_rect(node, rect);
4192
4193 /* Frame can be resized manually only if shrinking is disabled or no children are attached. */
4194 data->flag |= NODE_FRAME_RESIZEABLE;
4195 /* For shrinking bounding box, initialize the rect from first child node. */
4196 bool bbinit = (data->flag & NODE_FRAME_SHRINK);
4197 /* Fit bounding box to all children. */
4198 for (bNode *child : node.direct_children_in_frame()) {
4199 /* Add margin to node rect. */
4200 rctf noderect = calc_node_frame_dimensions(C, tree_draw_ctx, snode, *child);
4201
4202 noderect.xmin -= frame_layout.margin;
4203 noderect.xmax += frame_layout.margin;
4204 noderect.ymin -= frame_layout.margin;
4205 noderect.ymax += frame_layout.margin_top;
4206
4207 /* First child initializes frame. */
4208 if (bbinit) {
4209 bbinit = false;
4210 rect = noderect;
4212 }
4213 else {
4214 BLI_rctf_union(&rect, &noderect);
4215 }
4216 }
4217
4218 /* Now adjust the frame size from view-space bounding box. */
4219 const float2 min = node_from_view({rect.xmin, rect.ymin});
4220 const float2 max = node_from_view({rect.xmax, rect.ymax});
4221 node.location[0] = min.x;
4222 node.location[1] = max.y;
4223 node.width = max.x - min.x;
4224 node.height = max.y - min.y;
4225
4226 node.runtime->draw_bounds = rect;
4227 return rect;
4228}
4229
4231{
4232 const float2 loc = node_to_view(node.location);
4233
4234 /* When the node is hidden, the input and output socket are both in the same place. */
4235 node.input_socket(0).runtime->location = loc;
4236 node.output_socket(0).runtime->location = loc;
4237
4238 const float radius = NODE_SOCKSIZE;
4239 node.width = radius * 2;
4240 node.runtime->draw_bounds.xmin = loc.x - radius;
4241 node.runtime->draw_bounds.xmax = loc.x + radius;
4242 node.runtime->draw_bounds.ymax = loc.y + radius;
4243 node.runtime->draw_bounds.ymin = loc.y - radius;
4244}
4245
4247 TreeDrawContext &tree_draw_ctx,
4248 bNodeTree &ntree,
4250 Span<uiBlock *> blocks)
4251{
4252 /* Make sure socket "used" tags are correct, for displaying value buttons. */
4253 SpaceNode *snode = CTX_wm_space_node(&C);
4254
4255 count_multi_input_socket_links(ntree, *snode);
4256
4257 for (const int i : nodes.index_range()) {
4258 bNode &node = *nodes[i];
4259 uiBlock &block = *blocks[node.index()];
4260 if (node.is_frame()) {
4261 /* Frame sizes are calculated after all other nodes have calculating their #draw_bounds. */
4262 continue;
4263 }
4264
4265 if (node.is_reroute()) {
4267 }
4268 else {
4269 if (node.flag & NODE_HIDDEN) {
4270 node_update_hidden(node, block);
4271 }
4272 else {
4273 node_update_basis(C, tree_draw_ctx, ntree, node, block);
4274 }
4275 }
4276 }
4277
4278 /* Now calculate the size of frame nodes, which can depend on the size of other nodes. */
4279 for (bNode *frame : ntree.root_frames()) {
4280 calc_node_frame_dimensions(C, tree_draw_ctx, *snode, *frame);
4281 }
4282}
4283
4284static void frame_node_draw_label(TreeDrawContext &tree_draw_ctx,
4285 const bNode &node,
4286 const SpaceNode &snode)
4287{
4288 /* XXX font id is crap design */
4289 const int fontid = UI_style_get()->widget.uifont_id;
4290 const NodeFrame *data = (const NodeFrame *)node.storage;
4291
4292 /* Setting BLF_aspect() and then counter-scaling by aspect in BLF_size() has no effect on the
4293 * rendered text size, because the two adjustments cancel each other out. But, using aspect
4294 * renders the text at higher resolution, which sharpens the rasterization of the text. */
4295 const float aspect = snode.runtime->aspect;
4296 BLF_enable(fontid, BLF_ASPECT);
4297 BLF_aspect(fontid, aspect, aspect, 1.0f);
4298 BLF_size(fontid, data->label_size * UI_SCALE_FAC / aspect);
4299
4300 const FrameNodeLayout frame_layout = frame_node_layout(node);
4301
4302 /* Title color. */
4303 int color_id = node_get_colorid(tree_draw_ctx, node);
4304 uchar color[3];
4305 UI_GetThemeColorBlendShade3ubv(TH_TEXT, color_id, 0.4f, 10, color);
4306 BLF_color3ubv(fontid, color);
4307
4308 const float label_width = BLF_width(fontid, node.label, strlen(node.label));
4309
4310 const rctf &rct = node.runtime->draw_bounds;
4311 const float label_x = BLI_rctf_cent_x(&rct) - (0.5f * label_width);
4312 const float label_y = rct.ymax - frame_layout.label_baseline;
4313
4314 /* Label. */
4315 if (frame_layout.has_label) {
4316 BLF_position(fontid, label_x, label_y, 0);
4317 BLF_draw(fontid, node.label, strlen(node.label));
4318 }
4319
4320 /* Draw text body. */
4321 if (node.id) {
4322 const Text *text = (const Text *)node.id;
4323 const float line_spacing = BLF_height_max(fontid) * aspect;
4324 const float line_width = (BLI_rctf_size_x(&rct) - 2 * frame_layout.margin) / aspect;
4325
4326 const float x = rct.xmin + frame_layout.margin;
4327 float y = rct.ymax - frame_layout.label_height -
4328 (frame_layout.has_label ? line_spacing + frame_layout.margin : 0);
4329
4330 const int y_min = rct.ymin + frame_layout.margin;
4331
4333 BLF_clipping(fontid, rct.xmin, rct.ymin + frame_layout.margin, rct.xmax, rct.ymax);
4334
4335 BLF_wordwrap(fontid, line_width);
4336
4337 LISTBASE_FOREACH (const TextLine *, line, &text->lines) {
4338 if (line->line[0]) {
4339 BLF_position(fontid, x, y, 0);
4340 ResultBLF info;
4341 BLF_draw(fontid, line->line, line->len, &info);
4342 y -= line_spacing * info.lines;
4343 }
4344 else {
4345 y -= line_spacing;
4346 }
4347 if (y < y_min) {
4348 break;
4349 }
4350 }
4351
4353 }
4354
4355 BLF_disable(fontid, BLF_ASPECT);
4356}
4357
4358static void frame_node_draw_background(const ARegion &region,
4359 const SpaceNode &snode,
4360 const bNode &node)
4361{
4362 /* Skip if out of view. */
4363 if (BLI_rctf_isect(&node.runtime->draw_bounds, &region.v2d.cur, nullptr) == false) {
4364 return;
4365 }
4366
4367 float color[4];
4369 const float alpha = color[3];
4370
4371 node_draw_shadow(snode, node, BASIS_RAD, alpha);
4372
4373 if (node.flag & NODE_CUSTOM_COLOR) {
4374 rgba_float_args_set(color, node.color[0], node.color[1], node.color[2], alpha);
4375 }
4376 else {
4377 int depth = 0;
4378 for (const bNode *parent = node.parent; parent; parent = parent->parent) {
4379 depth++;
4380 }
4381
4382 if (depth % 2 == 0) {
4384 }
4385 else {
4387 }
4388 }
4389
4390 const rctf &rct = node.runtime->draw_bounds;
4392 UI_draw_roundbox_4fv(&rct, true, BASIS_RAD, color);
4393}
4394
4395static void frame_node_draw_outline(const ARegion &region,
4396 const SpaceNode &snode,
4397 const bNode &node)
4398{
4399 /* Skip if out of view. */
4400 const rctf &rct = node.runtime->draw_bounds;
4401 if (BLI_rctf_isect(&rct, &region.v2d.cur, nullptr) == false) {
4402 return;
4403 }
4404
4405 ColorTheme4f outline_color;
4406 bool draw_outline = false;
4407
4409 draw_outline = true;
4410 UI_GetThemeColorShadeAlpha4fv(TH_ACTIVE, 0, -100, outline_color);
4411 }
4412 else if (node.flag & SELECT) {
4413 draw_outline = true;
4414 if (node.flag & NODE_ACTIVE) {
4415 UI_GetThemeColorShadeAlpha4fv(TH_ACTIVE, 0, -40, outline_color);
4416 }
4417 else {
4418 UI_GetThemeColorShadeAlpha4fv(TH_SELECT, 0, -40, outline_color);
4419 }
4420 }
4421
4422 if (draw_outline) {
4423 UI_draw_roundbox_aa(&rct, false, BASIS_RAD, outline_color);
4424 }
4425}
4426
4428 TreeDrawContext &tree_draw_ctx,
4429 const ARegion &region,
4430 const SpaceNode &snode,
4431 const bNode &node,
4432 uiBlock &block)
4433{
4434 /* Skip if out of view. */
4435 if (BLI_rctf_isect(&node.runtime->draw_bounds, &region.v2d.cur, nullptr) == false) {
4437 tree_draw_ctx.bmain,
4438 tree_draw_ctx.window,
4439 tree_draw_ctx.scene,
4440 tree_draw_ctx.region,
4441 tree_draw_ctx.depsgraph,
4442 &block);
4443 return;
4444 }
4445
4446 /* Label and text. */
4447 frame_node_draw_label(tree_draw_ctx, node, snode);
4448
4449 node_draw_extra_info_panel(C, tree_draw_ctx, snode, node, nullptr, block);
4450
4452 tree_draw_ctx.bmain,
4453 tree_draw_ctx.window,
4454 tree_draw_ctx.scene,
4455 tree_draw_ctx.region,
4456 tree_draw_ctx.depsgraph,
4457 &block);
4458 UI_block_draw(&C, &block);
4459}
4460
4462 const SpaceNode &snode)
4463{
4464 const std::optional<ed::space_node::ObjectAndModifier> object_and_modifier =
4466 if (!object_and_modifier) {
4467 return {};
4468 }
4469 snode.edittree->ensure_topology_cache();
4470
4471 bke::ComputeContextCache compute_context_cache;
4472 const ComputeContext *current_compute_context = ed::space_node::compute_context_for_edittree(
4473 snode, compute_context_cache);
4474 if (!current_compute_context) {
4475 return {};
4476 }
4477
4478 Set<const bNodeSocket *> sockets_on_gizmo_paths;
4479
4481 C,
4482 compute_context_cache,
4483 [&](const Object &gizmo_object,
4484 const NodesModifierData &gizmo_nmd,
4485 const ComputeContext &gizmo_context,
4486 const bNode &gizmo_node,
4487 const bNodeSocket &gizmo_socket) {
4488 if (&gizmo_object != object_and_modifier->object) {
4489 return;
4490 }
4491 if (&gizmo_nmd != object_and_modifier->nmd) {
4492 return;
4493 }
4495 gizmo_context,
4496 gizmo_node,
4497 gizmo_socket,
4498 [&](const ComputeContext &compute_context,
4499 const bNodeSocket &socket,
4500 const nodes::inverse_eval::ElemVariant & /*elem*/) {
4501 if (compute_context.hash() == current_compute_context->hash()) {
4502 sockets_on_gizmo_paths.add(&socket);
4503 }
4504 });
4505 });
4506
4507 return sockets_on_gizmo_paths;
4508}
4509
4513static const bNode *reroute_node_get_linked_reroute(const bNode &reroute)
4514{
4515 BLI_assert(reroute.is_reroute());
4516
4517 const bNodeSocket *input_socket = reroute.input_sockets().first();
4518 if (input_socket->directly_linked_links().is_empty()) {
4519 return nullptr;
4520 }
4521 const bNodeLink *input_link = input_socket->directly_linked_links().first();
4522 const bNode *from_node = input_link->fromnode;
4523 return from_node->is_reroute() ? from_node : nullptr;
4524}
4525
4531 const bNode &src_reroute)
4532{
4533 BLI_assert(src_reroute.is_reroute());
4534
4535 if (src_reroute.label[0] != '\0') {
4536 return src_reroute.label;
4537 }
4538
4539 Map<const bNode *, StringRef> &reroute_auto_labels = tree_draw_ctx.reroute_auto_labels;
4540
4541 StringRef label;
4542 Vector<const bNode *> reroute_path;
4543
4544 /* Traverse reroute path backwards until label, non-reroute node or link-cycle is found. */
4545 for (const bNode *reroute = &src_reroute; reroute;
4546 reroute = reroute_node_get_linked_reroute(*reroute))
4547 {
4548 reroute_path.append(reroute);
4549 if (const StringRef *label_ptr = reroute_auto_labels.lookup_ptr(reroute)) {
4550 label = *label_ptr;
4551 break;
4552 }
4553 if (reroute->label[0] != '\0') {
4554 label = reroute->label;
4555 break;
4556 }
4557 /* This makes sure that the loop eventually ends even if there are link-cycles. */
4558 reroute_auto_labels.add(reroute, "");
4559 }
4560
4561 /* Remember the label for each node on the path to avoid recomputing it. */
4562 for (const bNode *reroute : reroute_path) {
4563 reroute_auto_labels.add_overwrite(reroute, label);
4564 }
4565
4566 return label;
4567}
4568
4570 const SpaceNode &snode,
4571 const bNodeTree &ntree,
4572 const bNode &node,
4573 uiBlock &block,
4574 const bool selected)
4575{
4576 BLI_assert(node.is_reroute());
4577
4578 bNodeSocket &sock = *static_cast<bNodeSocket *>(node.inputs.first);
4579
4581 const_cast<ID *>(&ntree.id), &RNA_Node, const_cast<bNode *>(&node));
4582
4583 ColorTheme4f socket_color;
4584 ColorTheme4f outline_color;
4585
4586 node_socket_color_get(C, ntree, nodeptr, sock, socket_color);
4587 node_socket_outline_color_get(selected, sock.type, outline_color);
4588
4589 node_draw_nodesocket(&node.runtime->draw_bounds,
4590 socket_color,
4591 outline_color,
4593 sock.display_shape,
4594 snode.runtime->aspect);
4595
4596 const float2 location = float2(BLI_rctf_cent_x(&node.runtime->draw_bounds),
4597 BLI_rctf_cent_y(&node.runtime->draw_bounds));
4598 const float2 size = float2(BLI_rctf_size_x(&node.runtime->draw_bounds),
4599 BLI_rctf_size_y(&node.runtime->draw_bounds));
4600 node_socket_tooltip_set(block, sock.index_in_tree(), location, size);
4601}
4602
4603static void reroute_node_draw_label(TreeDrawContext &tree_draw_ctx,
4604 const SpaceNode &snode,
4605 const bNode &node,
4606 uiBlock &block)
4607{
4608 const bool has_label = node.label[0] != '\0';
4609 const bool use_auto_label = !has_label && (snode.overlay.flag & SN_OVERLAY_SHOW_OVERLAYS) &&
4611
4612 if (!has_label && !use_auto_label) {
4613 return;
4614 }
4615
4616 /* Don't show the automatic label, when being zoomed out. */
4617 if (!has_label && !draw_node_details(snode)) {
4618 return;
4619 }
4620
4621 const StringRef text = has_label ? node.label : reroute_node_get_auto_label(tree_draw_ctx, node);
4622 if (text.is_empty()) {
4623 return;
4624 }
4625
4626 const short width = 512;
4627 const int x = BLI_rctf_cent_x(&node.runtime->draw_bounds) - (width / 2);
4628 const int y = node.runtime->draw_bounds.ymax;
4629
4630 uiBut *label_but = uiDefBut(
4631 &block, UI_BTYPE_LABEL, 0, text, x, y, width, NODE_DY, nullptr, 0, 0, std::nullopt);
4632
4634
4635 if (use_auto_label && !(node.flag & NODE_SELECT)) {
4637 }
4638}
4639
4640static void reroute_node_draw(const bContext &C,
4641 TreeDrawContext &tree_draw_ctx,
4642 ARegion &region,
4643 const SpaceNode &snode,
4644 bNodeTree &ntree,
4645 const bNode &node,
4646 uiBlock &block)
4647{
4648 const rctf &rct = node.runtime->draw_bounds;
4649 const View2D &v2d = region.v2d;
4650
4651 /* Skip if out of view. */
4652 if (rct.xmax < v2d.cur.xmin || rct.xmin > v2d.cur.xmax || rct.ymax < v2d.cur.ymin ||
4653 node.runtime->draw_bounds.ymin > v2d.cur.ymax)
4654 {
4656 tree_draw_ctx.bmain,
4657 tree_draw_ctx.window,
4658 tree_draw_ctx.scene,
4659 tree_draw_ctx.region,
4660 tree_draw_ctx.depsgraph,
4661 &block);
4662 return;
4663 }
4664
4665 if (draw_node_details(snode)) {
4666 reroute_node_draw_label(tree_draw_ctx, snode, node, block);
4667 }
4668
4669 /* Only draw the input socket, since all sockets are at the same location. */
4670 const bool selected = node.flag & NODE_SELECT;
4671 reroute_node_draw_body(C, snode, ntree, node, block, selected);
4672
4674 tree_draw_ctx.bmain,
4675 tree_draw_ctx.window,
4676 tree_draw_ctx.scene,
4677 tree_draw_ctx.region,
4678 tree_draw_ctx.depsgraph,
4679 &block);
4680 UI_block_draw(&C, &block);
4681}
4682
4683static void node_draw(const bContext &C,
4684 TreeDrawContext &tree_draw_ctx,
4685 ARegion &region,
4686 const SpaceNode &snode,
4687 bNodeTree &ntree,
4688 bNode &node,
4689 uiBlock &block,
4690 bNodeInstanceKey key)
4691{
4692 if (node.is_frame()) {
4693 /* Should have been drawn before already. */
4695 }
4696 else if (node.is_reroute()) {
4697 reroute_node_draw(C, tree_draw_ctx, region, snode, ntree, node, block);
4698 }
4699 else {
4700 const View2D &v2d = region.v2d;
4701 if (node.flag & NODE_HIDDEN) {
4702 node_draw_hidden(C, tree_draw_ctx, v2d, snode, ntree, node, block);
4703 }
4704 else {
4705 node_draw_basis(C, tree_draw_ctx, v2d, snode, ntree, node, block, key);
4706 }
4707 }
4708}
4709
4710static void add_rect_corner_positions(Vector<float2> &positions, const rctf &rect)
4711{
4712 positions.append({rect.xmin, rect.ymin});
4713 positions.append({rect.xmin, rect.ymax});
4714 positions.append({rect.xmax, rect.ymin});
4715 positions.append({rect.xmax, rect.ymax});
4716}
4717
4719 const bNodeTreeZone &zone,
4720 const Span<const bNodeTreeZone *> all_zones,
4721 MutableSpan<Vector<float2>> r_bounds_by_zone)
4722{
4723 const float node_padding = NODE_ZONE_PADDING;
4724 const float zone_padding = ZONE_ZONE_PADDING;
4725
4726 Vector<float2> &bounds = r_bounds_by_zone[zone.index];
4727 if (!bounds.is_empty()) {
4728 return;
4729 }
4730
4731 Vector<float2> possible_bounds;
4732 for (const bNodeTreeZone *child_zone : zone.child_zones) {
4733 find_bounds_by_zone_recursive(snode, *child_zone, all_zones, r_bounds_by_zone);
4734 const Span<float2> child_bounds = r_bounds_by_zone[child_zone->index];
4735 for (const float2 &pos : child_bounds) {
4736 rctf rect;
4737 BLI_rctf_init_pt_radius(&rect, pos, zone_padding);
4738 add_rect_corner_positions(possible_bounds, rect);
4739 }
4740 }
4741 for (const int child_node_id : zone.child_node_ids) {
4742 const bNode *child_node = snode.edittree->node_by_id(child_node_id);
4743 if (!child_node) {
4744 /* Can happen when drawing zone errors. */
4745 continue;
4746 }
4747 rctf rect = child_node->runtime->draw_bounds;
4748 BLI_rctf_pad(&rect, node_padding, node_padding);
4749 add_rect_corner_positions(possible_bounds, rect);
4750 }
4751 if (const bNode *input_node = zone.input_node()) {
4752 const rctf &draw_bounds = input_node->runtime->draw_bounds;
4753 rctf rect = draw_bounds;
4754 BLI_rctf_pad(&rect, node_padding, node_padding);
4755 rect.xmin = math::interpolate(draw_bounds.xmin, draw_bounds.xmax, 0.25f);
4756 add_rect_corner_positions(possible_bounds, rect);
4757 }
4758 if (const bNode *output_node = zone.output_node()) {
4759 const rctf &draw_bounds = output_node->runtime->draw_bounds;
4760 rctf rect = draw_bounds;
4761 BLI_rctf_pad(&rect, node_padding, node_padding);
4762 rect.xmax = math::interpolate(draw_bounds.xmin, draw_bounds.xmax, 0.75f);
4763 add_rect_corner_positions(possible_bounds, rect);
4764 }
4765
4766 if (snode.runtime->linkdrag) {
4767 for (const bNodeLink &link : snode.runtime->linkdrag->links) {
4768 if (link.fromnode == nullptr) {
4769 continue;
4770 }
4771 if (zone.contains_node_recursively(*link.fromnode) &&
4772 zone.output_node_id != link.fromnode->identifier)
4773 {
4774 const float2 pos = node_link_bezier_points_dragged(snode, link)[3];
4775 rctf rect;
4776 BLI_rctf_init_pt_radius(&rect, pos, node_padding);
4777 add_rect_corner_positions(possible_bounds, rect);
4778 }
4779 }
4780 }
4781
4782 Vector<int> convex_indices(possible_bounds.size());
4783 const int convex_positions_num = BLI_convexhull_2d(
4784 reinterpret_cast<float(*)[2]>(possible_bounds.data()),
4785 possible_bounds.size(),
4786 convex_indices.data());
4787 convex_indices.resize(convex_positions_num);
4788
4789 for (const int i : convex_indices) {
4790 bounds.append(possible_bounds[i]);
4791 }
4792}
4793
4794static void node_draw_zones_and_frames(const ARegion &region,
4795 const SpaceNode &snode,
4796 const bNodeTree &ntree)
4797{
4798 const bNodeTreeZones *zones = ntree.zones();
4799 if (!zones) {
4800 /* Try use backup zones. */
4801 zones = ntree.runtime->last_valid_zones.get();
4802 }
4803 const int zones_num = zones ? zones->zones.size() : 0;
4804
4805 Array<Vector<float2>> bounds_by_zone(zones_num);
4806 Array<std::optional<bke::CurvesGeometry>> fillet_curve_by_zone(zones_num);
4807 /* Bounding box area of zones is used to determine draw order. */
4808 Array<float> bounding_box_width_by_zone(zones_num);
4809
4810 for (const int zone_i : IndexRange(zones_num)) {
4811 const bNodeTreeZone &zone = *zones->zones[zone_i];
4812
4813 find_bounds_by_zone_recursive(snode, zone, zones->zones, bounds_by_zone);
4814 const Span<float2> boundary_positions = bounds_by_zone[zone_i];
4815 const int boundary_positions_num = boundary_positions.size();
4816 if (boundary_positions_num < 3) {
4817 /* Can happen when drawing zone errors. */
4818 continue;
4819 }
4820
4821 const Bounds<float2> bounding_box = *bounds::min_max(boundary_positions);
4822 const float bounding_box_width = bounding_box.max.x - bounding_box.min.x;
4823 bounding_box_width_by_zone[zone_i] = bounding_box_width;
4824
4825 bke::CurvesGeometry boundary_curve(boundary_positions_num, 1);
4826 boundary_curve.cyclic_for_write().first() = true;
4827 boundary_curve.fill_curve_types(CURVE_TYPE_POLY);
4828 MutableSpan<float3> boundary_curve_positions = boundary_curve.positions_for_write();
4829 boundary_curve.offsets_for_write().copy_from({0, boundary_positions_num});
4830 for (const int i : boundary_positions.index_range()) {
4831 boundary_curve_positions[i] = float3(boundary_positions[i], 0.0f);
4832 }
4833
4834 fillet_curve_by_zone[zone_i] = geometry::fillet_curves_poly(
4835 boundary_curve,
4836 IndexRange(1),
4837 VArray<float>::ForSingle(BASIS_RAD, boundary_positions_num),
4838 VArray<int>::ForSingle(5, boundary_positions_num),
4839 true,
4840 {});
4841 }
4842
4843 const View2D &v2d = region.v2d;
4844 float scale;
4845 UI_view2d_scale_get(&v2d, &scale, nullptr);
4846 float line_width = 1.0f * scale;
4847 float viewport[4] = {};
4848 GPU_viewport_size_get_f(viewport);
4849
4850 const auto get_theme_id = [&](const int zone_i) {
4851 const bNode *node = zones->zones[zone_i]->output_node();
4852 if (!node) {
4853 return TH_REDALERT;
4854 }
4855 return ThemeColorID(bke::zone_type_by_node_type(node->type_legacy)->theme_id);
4856 };
4857
4860
4861 using ZoneOrNode = std::variant<const bNodeTreeZone *, const bNode *>;
4862 Vector<ZoneOrNode> draw_order;
4863 for (const int zone_i : IndexRange(zones_num)) {
4864 draw_order.append(zones->zones[zone_i]);
4865 }
4866 for (const bNode *node : ntree.all_nodes()) {
4867 if (node->flag & NODE_BACKGROUND) {
4868 draw_order.append(node);
4869 }
4870 }
4871 auto get_zone_or_node_width = [&](const ZoneOrNode &zone_or_node) {
4872 if (const bNodeTreeZone *const *zone_p = std::get_if<const bNodeTreeZone *>(&zone_or_node)) {
4873 const bNodeTreeZone &zone = **zone_p;
4874 return bounding_box_width_by_zone[zone.index];
4875 }
4876 if (const bNode *const *node_p = std::get_if<const bNode *>(&zone_or_node)) {
4877 const bNode &node = **node_p;
4878 return BLI_rctf_size_x(&node.runtime->draw_bounds);
4879 }
4881 return 0.0f;
4882 };
4883 std::sort(draw_order.begin(), draw_order.end(), [&](const ZoneOrNode &a, const ZoneOrNode &b) {
4884 /* Draw zones with smaller bounding box on top to make them visible. */
4885 return get_zone_or_node_width(a) > get_zone_or_node_width(b);
4886 });
4887
4888 for (const ZoneOrNode &zone_or_node : draw_order) {
4889 if (const bNodeTreeZone *const *zone_p = std::get_if<const bNodeTreeZone *>(&zone_or_node)) {
4890 const bNodeTreeZone &zone = **zone_p;
4891 const int zone_i = zone.index;
4892 float zone_color[4];
4893 UI_GetThemeColor4fv(get_theme_id(zone_i), zone_color);
4894 if (zone_color[3] == 0.0f) {
4895 continue;
4896 }
4897 if (!fillet_curve_by_zone[zone_i].has_value()) {
4898 /* Can happen when drawing zone errors. */
4899 continue;
4900 }
4901 const Span<float3> fillet_boundary_positions = fillet_curve_by_zone[zone_i]->positions();
4902 /* Draw the background. */
4904 immUniformThemeColorBlend(TH_BACK, get_theme_id(zone_i), zone_color[3]);
4905
4906 immBegin(GPU_PRIM_TRI_FAN, fillet_boundary_positions.size() + 1);
4907 for (const float3 &p : fillet_boundary_positions) {
4908 immVertex3fv(pos, p);
4909 }
4910 immVertex3fv(pos, fillet_boundary_positions[0]);
4911 immEnd();
4912
4914 }
4915 if (const bNode *const *node_p = std::get_if<const bNode *>(&zone_or_node)) {
4916 const bNode &node = **node_p;
4917 frame_node_draw_background(region, snode, node);
4918 }
4919 }
4920
4922
4923 /* Draw all the contour lines after to prevent them from getting hidden by overlapping zones. */
4924 for (const ZoneOrNode &zone_or_node : draw_order) {
4925 if (const bNodeTreeZone *const *zone_p = std::get_if<const bNodeTreeZone *>(&zone_or_node)) {
4926 const bNodeTreeZone &zone = **zone_p;
4927 const int zone_i = zone.index;
4928 if (!fillet_curve_by_zone[zone_i].has_value()) {
4929 /* Can happen when drawing zone errors. */
4930 continue;
4931 }
4932 const Span<float3> fillet_boundary_positions = fillet_curve_by_zone[zone_i]->positions();
4933 /* Draw the contour lines. */
4935
4936 immUniform2fv("viewportSize", &viewport[2]);
4937 immUniform1f("lineWidth", line_width * U.pixelsize);
4938
4939 const ThemeColorID theme_id = ntree.runtime->invalid_zone_output_node_ids.contains(
4940 *zone.output_node_id) ?
4941 TH_REDALERT :
4942 get_theme_id(zone_i);
4943
4944 immUniformThemeColorAlpha(theme_id, 1.0f);
4945 immBegin(GPU_PRIM_LINE_STRIP, fillet_boundary_positions.size() + 1);
4946 for (const float3 &p : fillet_boundary_positions) {
4947 immVertex3fv(pos, p);
4948 }
4949 immVertex3fv(pos, fillet_boundary_positions[0]);
4950 immEnd();
4951
4953 }
4954 if (const bNode *const *node_p = std::get_if<const bNode *>(&zone_or_node)) {
4955 const bNode &node = **node_p;
4956 frame_node_draw_outline(region, snode, node);
4957 }
4958 }
4959
4961}
4962
4963static void draw_frame_overlays(const bContext &C,
4964 TreeDrawContext &tree_draw_ctx,
4965 const ARegion &region,
4966 const SpaceNode &snode,
4967 const bNodeTree &ntree,
4968 Span<uiBlock *> blocks)
4969{
4970 for (const bNode *node : ntree.nodes_by_type("NodeFrame")) {
4971 frame_node_draw_overlay(C, tree_draw_ctx, region, snode, *node, *blocks[node->index()]);
4972 }
4973}
4974
4980static std::optional<float2> find_visible_center_of_link(const View2D &v2d,
4981 const bNodeLink &link,
4982 const float radius,
4983 const float region_padding)
4984{
4985 /* Compute center of the link because that's used as "ideal" position. */
4986 const float2 start = socket_link_connection_location(*link.fromnode, *link.fromsock, link);
4987 const float2 end = socket_link_connection_location(*link.tonode, *link.tosock, link);
4988 const float2 center = math::midpoint(start, end);
4989
4990 /* The rectangle that we would like to stay within if possible. */
4991 rctf inner_rect = v2d.cur;
4992 BLI_rctf_pad(&inner_rect, -(region_padding + radius), -(region_padding + radius));
4993
4994 if (BLI_rctf_isect_pt_v(&inner_rect, center)) {
4995 /* The center is visible. */
4996 return center;
4997 }
4998
4999 /* The rectangle containing all points which are valid result positions. */
5000 rctf outer_rect = v2d.cur;
5001 BLI_rctf_pad(&outer_rect, radius, radius);
5002
5003 /* Get the straight individual link segments. */
5004 std::array<float2, NODE_LINK_RESOL + 1> link_points;
5005 node_link_bezier_points_evaluated(link, link_points);
5006
5007 const float required_socket_distance = UI_UNIT_X;
5008 /* Define a cost function that returns a value that is larger the worse the given position is.
5009 * The point on the link with the lowest cost will be picked. */
5010 const auto cost_function = [&](const float2 &p) -> float {
5011 const float distance_to_inner_rect = std::max(BLI_rctf_length_x(&inner_rect, p.x),
5012 BLI_rctf_length_y(&inner_rect, p.y));
5013 const float distance_to_center = math::distance(p, center);
5014
5015 /* Set a high cost when the point is close to a socket. The distance to the center still has to
5016 * be taken account though. Otherwise there is bad behavior when both sockets are close to the
5017 * point. */
5018 const float distance_to_socket = std::min(math::distance(p, start), math::distance(p, end));
5019 if (distance_to_socket < required_socket_distance) {
5020 return 1e5f + distance_to_center;
5021 }
5022 return
5023 /* The larger the distance to the link center, the higher the cost. The importance of this
5024 distance decreases the further the center is away. */
5025 std::sqrt(distance_to_center)
5026 /* The larger the distance to the inner rectangle, the higher the cost. Apply an additional
5027 * factor because it's more important that the position stays visible than that it is at
5028 * the center. */
5029 + 10.0f * distance_to_inner_rect;
5030 };
5031
5032 /* Iterate over visible points on the link, compute the cost of each and pick the best one. A
5033 * more direct algorithm to find a good position would be nice. However, that seems to be
5034 * surprisingly tricky to achieve without resulting in very "jumpy" positions, especially when
5035 * the link is colinear to the region border. */
5036 float best_cost;
5037 std::optional<float2> best_position;
5038 for (const int i : IndexRange(link_points.size() - 1)) {
5039 float2 p0 = link_points[i];
5040 float2 p1 = link_points[i + 1];
5041 if (!BLI_rctf_clamp_segment(&outer_rect, p0, p1)) {
5042 continue;
5043 }
5044 const float length = math::distance(p0, p1);
5045 const float point_distance = 1.0f;
5046 /* Might be possible to do a smarter scan of the cost function using some sort of binary sort,
5047 * but it's not entirely straight forward because the cost function is not monotonic. */
5048 const int points_to_check = std::max(2, 1 + int(length / point_distance));
5049 for (const int j : IndexRange(points_to_check)) {
5050 const float t = float(j) / (points_to_check - 1);
5051 const float2 p = math::interpolate(p0, p1, t);
5052 const float cost = cost_function(p);
5053 if (!best_position.has_value() || cost < best_cost) {
5054 best_cost = cost;
5055 best_position = p;
5056 }
5057 }
5058 }
5059 return best_position;
5060}
5061
5062static void draw_link_errors(const bContext &C,
5063 SpaceNode &snode,
5064 const bNodeLink &link,
5065 const Span<bke::NodeLinkError> errors,
5066 uiBlock &invalid_links_block)
5067{
5068 const ARegion &region = *CTX_wm_region(&C);
5069 if (errors.is_empty()) {
5070 return;
5071 }
5072 if (!link.fromsock || !link.tosock || !link.fromnode || !link.tonode) {
5073 /* Likely because the link is being dragged. */
5074 return;
5075 }
5076
5077 /* Generate full tooltip from potentially multiple errors. */
5078 std::string error_tooltip;
5079 if (errors.size() == 1) {
5080 error_tooltip = errors[0].tooltip;
5081 }
5082 else {
5083 for (const bke::NodeLinkError &error : errors) {
5084 error_tooltip += fmt::format("\u2022 {}\n", error.tooltip);
5085 }
5086 }
5087
5088 const float bg_radius = UI_UNIT_X * 0.5f;
5089 const float bg_corner_radius = UI_UNIT_X * 0.2f;
5090 const float icon_size = UI_UNIT_X;
5091 const float region_padding = UI_UNIT_X * 0.5f;
5092
5093 /* Compute error icon location. */
5094 std::optional<float2> draw_position_opt = find_visible_center_of_link(
5095 region.v2d, link, bg_radius, region_padding);
5096 if (!draw_position_opt.has_value()) {
5097 return;
5098 }
5099 const int2 draw_position = int2(draw_position_opt.value());
5100
5101 /* Draw a background for the error icon. */
5102 rctf bg_rect;
5103 BLI_rctf_init_pt_radius(&bg_rect, float2(draw_position), bg_radius);
5104 ColorTheme4f bg_color;
5107 ui_draw_dropshadow(&bg_rect, bg_corner_radius, UI_UNIT_X * 0.2f, snode.runtime->aspect, 0.5f);
5108 UI_draw_roundbox_4fv(&bg_rect, true, bg_corner_radius, bg_color);
5109
5110 /* Draw the icon itself with a tooltip. */
5111 UI_block_emboss_set(&invalid_links_block, ui::EmbossType::None);
5112 uiBut *but = uiDefIconBut(&invalid_links_block,
5114 0,
5115 ICON_ERROR,
5116 draw_position.x - icon_size / 2,
5117 draw_position.y - icon_size / 2,
5118 icon_size,
5119 icon_size,
5120 nullptr,
5121 0,
5122 0,
5123 std::nullopt);
5125 but, [tooltip = std::move(error_tooltip)](const uiBut * /*but*/) { return tooltip; });
5126}
5127
5129{
5130 Scene *scene = CTX_data_scene(&C);
5131 wmWindow *window = CTX_wm_window(&C);
5132 ARegion *region = CTX_wm_region(&C);
5133 return *UI_block_begin(
5134 &C, scene, window, region, "invalid_links", blender::ui::EmbossType::None);
5135}
5136
5137#define USE_DRAW_TOT_UPDATE
5138
5139static void node_draw_nodetree(const bContext &C,
5140 TreeDrawContext &tree_draw_ctx,
5141 ARegion &region,
5142 SpaceNode &snode,
5143 bNodeTree &ntree,
5145 Span<uiBlock *> blocks,
5146 bNodeInstanceKey parent_key)
5147{
5148#ifdef USE_DRAW_TOT_UPDATE
5149 BLI_rctf_init_minmax(&region.v2d.tot);
5150#endif
5151
5152 for (const int i : nodes.index_range()) {
5153#ifdef USE_DRAW_TOT_UPDATE
5154 /* Unrelated to background nodes, update the v2d->tot,
5155 * can be anywhere before we draw the scroll bars. */
5156 BLI_rctf_union(&region.v2d.tot, &nodes[i]->runtime->draw_bounds);
5157#endif
5158 }
5159
5160 /* Node lines. */
5162 nodelink_batch_start(snode);
5163
5164 for (const bNodeLink *link : ntree.all_links()) {
5166 node_draw_link(C, region.v2d, snode, *link, false);
5167 }
5168 }
5169
5170 /* Draw selected node links after the unselected ones, so they are shown on top. */
5171 for (const bNodeLink *link : ntree.all_links()) {
5173 node_draw_link(C, region.v2d, snode, *link, true);
5174 }
5175 }
5176
5177 nodelink_batch_end(snode);
5178
5180
5181 draw_frame_overlays(C, tree_draw_ctx, region, snode, ntree, blocks);
5182
5183 /* Draw foreground nodes, last nodes in front. */
5184 for (const int i : nodes.index_range()) {
5185 bNode &node = *nodes[i];
5186 if (node.flag & NODE_BACKGROUND) {
5187 /* Background nodes are drawn before mixed with zones already. */
5188 continue;
5189 }
5190
5191 const bNodeInstanceKey key = bke::node_instance_key(parent_key, &ntree, &node);
5192 node_draw(C, tree_draw_ctx, region, snode, ntree, node, *blocks[node.index()], key);
5193 }
5194
5195 uiBlock &invalid_links_block = invalid_links_uiblock_init(C);
5196 for (auto &&item : ntree.runtime->link_errors.items()) {
5197 if (const bNodeLink *link = item.key.try_find(ntree)) {
5198 if (!bke::node_link_is_hidden(*link)) {
5199 draw_link_errors(C, snode, *link, item.value, invalid_links_block);
5200 }
5201 }
5202 }
5203 UI_block_end(&C, &invalid_links_block);
5204 UI_block_draw(&C, &invalid_links_block);
5205}
5206
5207/* Draw the breadcrumb on the top of the editor. */
5208static void draw_tree_path(const bContext &C, ARegion &region)
5209{
5212
5213 const rcti *rect = ED_region_visible_rect(&region);
5214
5215 const uiStyle *style = UI_style_get_dpi();
5216 const float padding_x = 16 * UI_SCALE_FAC;
5217 const int x = rect->xmin + padding_x;
5218 const int y = region.winy - UI_UNIT_Y * 0.6f;
5219 const int width = BLI_rcti_size_x(rect) - 2 * padding_x;
5220
5221 uiBlock *block = UI_block_begin(&C, &region, __func__, blender::ui::EmbossType::None);
5222 uiLayout *layout = UI_block_layout(
5223 block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, x, y, width, 1, 0, style);
5224
5226 ui::template_breadcrumbs(*layout, context_path);
5227
5228 UI_block_layout_resolve(block, nullptr, nullptr);
5229 UI_block_end(&C, block);
5230 UI_block_draw(&C, block);
5231
5233}
5234
5235static void snode_setup_v2d(SpaceNode &snode, ARegion &region, const float2 &center)
5236{
5237 View2D &v2d = region.v2d;
5238
5239 /* Shift view to node tree center. */
5240 UI_view2d_center_set(&v2d, center[0], center[1]);
5242
5243 snode.runtime->aspect = BLI_rctf_size_x(&v2d.cur) / float(region.winx);
5244}
5245
5246/* Similar to DRW_is_viewport_compositor_enabled() in `draw_manager.cc` but checks all 3D views. */
5247static bool compositor_is_in_use(const bContext &context)
5248{
5249 const Scene *scene = CTX_data_scene(&context);
5250 if (!scene->use_nodes) {
5251 return false;
5252 }
5253
5254 if (!scene->nodetree) {
5255 return false;
5256 }
5257
5258 wmWindowManager *wm = CTX_wm_manager(&context);
5259 LISTBASE_FOREACH (const wmWindow *, win, &wm->windows) {
5260 const bScreen *screen = WM_window_get_active_screen(win);
5261 LISTBASE_FOREACH (const ScrArea *, area, &screen->areabase) {
5262 const SpaceLink &space = *static_cast<const SpaceLink *>(area->spacedata.first);
5263 if (space.spacetype == SPACE_VIEW3D) {
5264 const View3D &view_3d = reinterpret_cast<const View3D &>(space);
5265
5267 continue;
5268 }
5269
5270 if (!(view_3d.shading.type >= OB_MATERIAL)) {
5271 continue;
5272 }
5273
5274 return true;
5275 }
5276 }
5277 }
5278
5279 return false;
5280}
5281
5282static void draw_nodetree(const bContext &C,
5283 ARegion &region,
5284 bNodeTree &ntree,
5285 bNodeInstanceKey parent_key)
5286{
5287 SpaceNode *snode = CTX_wm_space_node(&C);
5288 ntree.ensure_topology_cache();
5289
5291
5293
5294 TreeDrawContext tree_draw_ctx;
5295 tree_draw_ctx.bmain = CTX_data_main(&C);
5296 tree_draw_ctx.window = CTX_wm_window(&C);
5297 tree_draw_ctx.scene = CTX_data_scene(&C);
5298 tree_draw_ctx.region = CTX_wm_region(&C);
5299 tree_draw_ctx.depsgraph = CTX_data_depsgraph_pointer(&C);
5300 tree_draw_ctx.extra_info_rows_per_node.reinitialize(nodes.size());
5301
5302 BLI_SCOPED_DEFER([&]() { ntree.runtime->sockets_on_active_gizmo_paths.clear(); });
5303 if (ntree.type == NTREE_GEOMETRY) {
5306 log.ensure_node_warnings(*tree_draw_ctx.bmain);
5307 log.ensure_execution_times();
5308 });
5309 const WorkSpace *workspace = CTX_wm_workspace(&C);
5311 workspace->viewer_path, *snode);
5312
5313 /* This set of socket is used when drawing links to determine which links should use the
5314 * special gizmo drawing. */
5315 ntree.runtime->sockets_on_active_gizmo_paths = find_sockets_on_active_gizmo_paths(C, *snode);
5316 }
5317 else if (ntree.type == NTREE_COMPOSIT) {
5318 const Scene *scene = CTX_data_scene(&C);
5319 tree_draw_ctx.used_by_compositor = compositor_is_in_use(C);
5321 &scene->runtime->compositor.per_node_execution_time;
5322 }
5323 else if (ntree.type == NTREE_SHADER && USER_EXPERIMENTAL_TEST(&U, use_shader_node_previews) &&
5327 {
5328 tree_draw_ctx.nested_group_infos = get_nested_previews(C, *snode);
5329 }
5330
5331 for (const int i : nodes.index_range()) {
5332 const bNode &node = *nodes[i];
5333 tree_draw_ctx.extra_info_rows_per_node[node.index()] = node_get_extra_info(
5334 C, tree_draw_ctx, *snode, node);
5335 }
5336
5337 node_update_nodetree(C, tree_draw_ctx, ntree, nodes, blocks);
5338 node_draw_zones_and_frames(region, *snode, ntree);
5339 node_draw_nodetree(C, tree_draw_ctx, region, *snode, ntree, nodes, blocks, parent_key);
5340}
5341
5345static void draw_background_color(const SpaceNode &snode)
5346{
5347 const int max_tree_length = 3;
5348 const float bright_factor = 0.25f;
5349
5350 /* We ignore the first element of the path since it is the top-most tree and it doesn't need to
5351 * be brighter. We also set a cap to how many levels we want to set apart, to avoid the
5352 * background from getting too bright. */
5353 const int clamped_tree_path_length = BLI_listbase_count_at_most(&snode.treepath,
5354 max_tree_length);
5355 const int depth = max_ii(0, clamped_tree_path_length - 1);
5356
5357 float color[3];
5359 mul_v3_fl(color, 1.0f + bright_factor * depth);
5360 GPU_clear_color(color[0], color[1], color[2], 1.0);
5361}
5362
5363void node_draw_space(const bContext &C, ARegion &region)
5364{
5365 wmWindow *win = CTX_wm_window(&C);
5366 SpaceNode &snode = *CTX_wm_space_node(&C);
5367 View2D &v2d = region.v2d;
5368
5369 /* Setup off-screen buffers. */
5370 GPUViewport *viewport = WM_draw_region_get_viewport(&region);
5371
5372 GPUFrameBuffer *framebuffer_overlay = GPU_viewport_framebuffer_overlay_get(viewport);
5373 GPU_framebuffer_bind_no_srgb(framebuffer_overlay);
5374
5376 draw_background_color(snode);
5378 GPU_scissor_test(true);
5379
5380 /* XXX `snode->runtime->cursor` set in coordinate-space for placing new nodes,
5381 * used for drawing noodles too. */
5383 win->eventstate->xy[0] - region.winrct.xmin,
5384 win->eventstate->xy[1] - region.winrct.ymin,
5385 &snode.runtime->cursor[0],
5386 &snode.runtime->cursor[1]);
5387 snode.runtime->cursor[0] /= UI_SCALE_FAC;
5388 snode.runtime->cursor[1] /= UI_SCALE_FAC;
5389
5391
5392 /* Only set once. */
5394
5395 /* Nodes. */
5397
5398 const int grid_levels = UI_GetThemeValueType(TH_NODE_GRID_LEVELS, SPACE_NODE);
5400
5401 /* Draw parent node trees. */
5402 if (snode.treepath.last) {
5403 bNodeTreePath *path = (bNodeTreePath *)snode.treepath.last;
5404
5405 /* Update tree path name (drawn in the bottom left). */
5406 ID *name_id = (path->nodetree && path->nodetree != snode.nodetree) ? &path->nodetree->id :
5407 snode.id;
5408
5409 if (name_id && UNLIKELY(!STREQ(path->display_name, name_id->name + 2))) {
5410 STRNCPY(path->display_name, name_id->name + 2);
5411 }
5412
5413 /* Current View2D center, will be set temporarily for parent node trees. */
5414 float2 center;
5415 UI_view2d_center_get(&v2d, &center.x, &center.y);
5416
5417 /* Store new view center in path and current edit tree. */
5418 copy_v2_v2(path->view_center, center);
5419 if (snode.edittree) {
5420 copy_v2_v2(snode.edittree->view_center, center);
5421 }
5422
5423 /* Top-level edit tree. */
5424 bNodeTree *ntree = path->nodetree;
5425 if (ntree) {
5426 snode_setup_v2d(snode, region, center);
5427
5428 /* Backdrop. */
5429 draw_nodespace_back_pix(C, region, snode, path->parent_key);
5430
5431 {
5432 float original_proj[4][4];
5433 GPU_matrix_projection_get(original_proj);
5434
5437
5438 wmOrtho2_pixelspace(region.winx, region.winy);
5439
5440 WM_gizmomap_draw(region.runtime->gizmo_map, &C, WM_GIZMOMAP_DRAWSTEP_2D);
5441
5443 GPU_matrix_projection_set(original_proj);
5444 }
5445
5446 draw_nodetree(C, region, *ntree, path->parent_key);
5447 }
5448
5449 /* Temporary links. */
5451 GPU_line_smooth(true);
5452 if (snode.runtime->linkdrag) {
5453 for (const bNodeLink &link : snode.runtime->linkdrag->links) {
5454 node_draw_link_dragged(C, v2d, snode, link);
5455 }
5456 }
5457 GPU_line_smooth(false);
5459
5461 /* Draw grease-pencil annotations. */
5463 }
5464 }
5465 else {
5466
5467 /* Backdrop. */
5469 }
5470
5472
5473 /* Reset view matrix. */
5475
5477 if (snode.flag & SNODE_SHOW_GPENCIL && snode.treepath.last) {
5478 /* Draw grease-pencil (screen strokes, and also paint-buffer). */
5480 }
5481
5482 /* Draw context path. */
5483 if (snode.overlay.flag & SN_OVERLAY_SHOW_PATH && snode.edittree) {
5484 draw_tree_path(C, region);
5485 }
5486 }
5487
5488 /* Scrollers. */
5489 UI_view2d_scrollers_draw(&v2d, nullptr);
5490}
5491
5492} // namespace blender::ed::space_node
void ED_draw_imbuf(ImBuf *ibuf, float x, float y, bool use_filter, const ColorManagedViewSettings *view_settings, const ColorManagedDisplaySettings *display_settings, float zoom_x, float zoom_y)
Definition glutil.cc:544
PointerRNA CTX_data_pointer_get(const bContext *C, const char *member)
WorkSpace * CTX_wm_workspace(const bContext *C)
SpaceNode * CTX_wm_space_node(const bContext *C)
wmWindow * CTX_wm_window(const bContext *C)
Depsgraph * CTX_data_depsgraph_pointer(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
Main * CTX_data_main(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
Low-level operations for curves.
const char * BKE_idtype_idcode_to_name(short idcode)
Definition idtype.cc:165
void id_us_ensure_real(ID *id)
Definition lib_id.cc:308
void BKE_main_ensure_invariants(Main &bmain, std::optional< blender::Span< ID * > > modified_ids=std::nullopt)
#define NODE_CLASS_OUTPUT
Definition BKE_node.hh:434
#define NODE_CLASS_INTERFACE
Definition BKE_node.hh:445
#define NODE_CUSTOM_GROUP
Definition BKE_node.hh:801
#define NODE_CLASS_MATTE
Definition BKE_node.hh:440
#define NODE_CLASS_CONVERTER
Definition BKE_node.hh:439
#define NODE_CUSTOM
Definition BKE_node.hh:795
#define NODE_CLASS_PATTERN
Definition BKE_node.hh:442
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:447
#define NODE_GROUP_OUTPUT
Definition BKE_node.hh:800
#define NODE_CLASS_DISTORT
Definition BKE_node.hh:441
#define NODE_CLASS_OP_VECTOR
Definition BKE_node.hh:436
#define NODE_GROUP
Definition BKE_node.hh:796
#define NODE_CLASS_LAYOUT
Definition BKE_node.hh:449
#define NODE_CLASS_OP_COLOR
Definition BKE_node.hh:435
#define NODE_CLASS_INPUT
Definition BKE_node.hh:433
#define NODE_CLASS_OP_FILTER
Definition BKE_node.hh:437
#define NODE_FRAME
Definition BKE_node.hh:797
#define NODE_CLASS_GROUP
Definition BKE_node.hh:438
#define NODE_CLASS_ATTRIBUTE
Definition BKE_node.hh:448
#define NODE_CLASS_TEXTURE
Definition BKE_node.hh:443
#define NODE_CLASS_SHADER
Definition BKE_node.hh:446
#define NODE_CLASS_SCRIPT
Definition BKE_node.hh:444
#define GEO_NODE_STORE_NAMED_ATTRIBUTE
#define GEO_NODE_VIEWER
#define GEO_NODE_REMOVE_ATTRIBUTE
#define GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT
#define GEO_NODE_INPUT_NAMED_ATTRIBUTE
#define GEO_NODE_SIMULATION_OUTPUT
#define CMP_NODE_OUTPUT_FILE
#define GEO_NODE_EVALUATE_CLOSURE
#define GEO_NODE_REPEAT_OUTPUT
General operations, lookup, etc. for blender objects.
bool BKE_scene_uses_shader_previews(const Scene *scene)
Definition scene.cc:2837
void BLF_size(int fontid, float size)
Definition blf.cc:440
void BLF_aspect(int fontid, float x, float y, float z)
Definition blf.cc:374
void BLF_color3ubv(int fontid, const unsigned char rgb[3])
Definition blf.cc:473
void BLF_clipping(int fontid, int xmin, int ymin, int xmax, int ymax)
Definition blf.cc:906
@ BLF_WORD_WRAP
Definition BLF_api.hh:439
@ BLF_ASPECT
Definition BLF_api.hh:438
@ BLF_CLIPPING
Definition BLF_api.hh:434
void BLF_disable(int fontid, int option)
Definition blf.cc:329
void BLF_draw(int fontid, const char *str, size_t str_len, ResultBLF *r_info=nullptr) ATTR_NONNULL(2)
Definition blf.cc:582
void BLF_wordwrap(int fontid, int wrap_width, BLFWrapMode mode=BLFWrapMode::Minimal)
Definition blf.cc:918
void BLF_enable(int fontid, int option)
Definition blf.cc:320
float BLF_width(int fontid, const char *str, size_t str_len, ResultBLF *r_info=nullptr) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(2)
Definition blf.cc:805
int BLF_height_max(int fontid) ATTR_WARN_UNUSED_RESULT
Definition blf.cc:853
void BLF_position(int fontid, float x, float y, float z)
Definition blf.cc:385
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
int BLI_convexhull_2d(const float(*points)[2], int points_num, int r_points[])
#define BUFFER_FOR_CPP_TYPE_VALUE(type, variable_name)
#define LISTBASE_FOREACH(type, var, list)
int BLI_listbase_count_at_most(const ListBase *listbase, int count_max) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:511
MINLINE int round_fl_to_int(float a)
MINLINE float max_ff(float a, float b)
MINLINE int min_ii(int a, int b)
MINLINE float min_ff(float a, float b)
MINLINE int max_ii(int a, int b)
MINLINE void rgba_float_args_set(float col[4], float r, float g, float b, float a)
#define M_PI
MINLINE void copy_v4_v4(float r[4], const float a[4])
MINLINE void copy_v2_v2(float r[2], const float a[2])
MINLINE void mul_v3_fl(float r[3], float f)
#define BLI_SCOPED_DEFER(function_to_defer)
void BLI_rctf_union(struct rctf *rct_a, const struct rctf *rct_b)
BLI_INLINE float BLI_rcti_cent_x_fl(const struct rcti *rct)
Definition BLI_rect.h:169
bool BLI_rctf_isect_pt_v(const struct rctf *rect, const float xy[2])
BLI_INLINE float BLI_rctf_cent_y(const struct rctf *rct)
Definition BLI_rect.h:189
bool BLI_rctf_isect(const struct rctf *src1, const struct rctf *src2, struct rctf *dest)
BLI_INLINE float BLI_rctf_cent_x(const struct rctf *rct)
Definition BLI_rect.h:185
bool BLI_rctf_clamp_segment(const struct rctf *rect, float s1[2], float s2[2])
float BLI_rctf_length_x(const rctf *rect, float x)
Definition rct.cc:171
BLI_INLINE int BLI_rcti_size_x(const struct rcti *rct)
Definition BLI_rect.h:194
void BLI_rctf_pad(struct rctf *rect, float pad_x, float pad_y)
Definition rct.cc:637
bool BLI_rctf_isect_pt(const struct rctf *rect, float x, float y)
BLI_INLINE float BLI_rcti_cent_y_fl(const struct rcti *rct)
Definition BLI_rect.h:173
BLI_INLINE float BLI_rctf_size_x(const struct rctf *rct)
Definition BLI_rect.h:202
void BLI_rctf_mul(struct rctf *rect, float factor)
Definition rct.cc:588
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition BLI_rect.h:206
void BLI_rctf_init_pt_radius(struct rctf *rect, const float xy[2], float size)
Definition rct.cc:458
float BLI_rctf_length_y(const rctf *rect, float y)
Definition rct.cc:182
void BLI_rctf_init_minmax(struct rctf *rect)
Definition rct.cc:480
size_t BLI_str_format_int_grouped(char dst[BLI_STR_FORMAT_INT32_GROUPED_SIZE], int num) ATTR_NONNULL(1)
Definition string.cc:1181
#define BLI_STR_FORMAT_INT32_GROUPED_SIZE
Definition BLI_string.h:25
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
#define BLI_STR_UTF8_DEGREE_SIGN
unsigned char uchar
unsigned int uint
#define STREQLEN(a, b, n)
#define POINTER_FROM_INT(i)
#define POINTER_AS_INT(i)
#define UNLIKELY(x)
#define ELEM(...)
#define STREQ(a, b)
#define RPT_(msgid)
#define BLT_I18NCONTEXT_ID_ID
#define TIP_(msgid)
#define CTX_IFACE_(context, msgid)
#define IFACE_(msgid)
#define CTX_TIP_(context, msgid)
void DEG_id_tag_update(ID *id, unsigned int flags)
@ ID_TAG_MISSING
Definition DNA_ID.h:775
@ ID_TE
@ ID_IM
@ ID_NT
@ ID_LA
@ ID_WO
@ ID_MA
@ ID_GR
@ ID_OB
@ CURVE_TYPE_POLY
@ NTREE_TEXTURE
@ NTREE_SHADER
@ NTREE_GEOMETRY
@ NTREE_COMPOSIT
@ SOCK_OUT
@ SOCK_IN
@ NODE_OPTIONS
@ NODE_DO_OUTPUT
@ NODE_HIDDEN
@ NODE_ACTIVE
@ NODE_CUSTOM_COLOR
@ NODE_BACKGROUND
@ NODE_SELECT
@ NODE_PREVIEW
@ NODE_VIEWER_SHORCTUT_SLOT_9
@ NODE_VIEWER_SHORCTUT_SLOT_5
@ NODE_VIEWER_SHORCTUT_SLOT_4
@ NODE_VIEWER_SHORCTUT_SLOT_2
@ NODE_VIEWER_SHORCTUT_SLOT_7
@ NODE_VIEWER_SHORCTUT_SLOT_1
@ NODE_VIEWER_SHORCTUT_SLOT_3
@ NODE_VIEWER_SHORTCUT_NONE
@ NODE_VIEWER_SHORCTUT_SLOT_8
@ NODE_VIEWER_SHORCTUT_SLOT_6
@ SOCK_MULTI_INPUT
@ SOCK_PANEL_COLLAPSED
@ NODE_LINK_VALID
@ SOCK_CUSTOM
@ SOCK_MENU
@ NODE_FRAME_RESIZEABLE
@ NODE_FRAME_SHRINK
@ NODE_PANEL_COLLAPSED
@ OB_MATERIAL
@ SN_OVERLAY_SHOW_PATH
@ SN_OVERLAY_SHOW_PREVIEWS
@ SN_OVERLAY_SHOW_REROUTE_AUTO_LABELS
@ SN_OVERLAY_SHOW_TIMINGS
@ SN_OVERLAY_SHOW_OVERLAYS
@ SN_OVERLAY_SHOW_NAMED_ATTRIBUTES
@ SNODE_SHOW_GPENCIL
@ SPACE_NODE
@ SPACE_VIEW3D
#define UI_SCALE_FAC
#define UI_INV_SCALE_FAC
#define USER_EXPERIMENTAL_TEST(userdef, member)
@ V3D_SHADING_USE_COMPOSITOR_DISABLED
#define NODE_GRID_STEP_SIZE
Definition ED_node_c.hh:27
const rcti * ED_region_visible_rect(ARegion *region)
Definition area.cc:4118
void ED_region_draw_cb_draw(const bContext *C, ARegion *region, int type)
#define REGION_DRAW_POST_VIEW
#define REGION_DRAW_PRE_VIEW
void GPU_framebuffer_bind_no_srgb(GPUFrameBuffer *fb)
void GPU_clear_color(float red, float green, float blue, float alpha)
void immUniform4f(const char *name, float x, float y, float z, float w)
void immUniformThemeColorAlpha(int color_id, float a)
void immEnd()
void immUniform2fv(const char *name, const float data[2])
void immUnbindProgram()
void immUniformThemeColorShadeAlpha(int color_id, int color_offset, int alpha_offset)
void immVertex2f(uint attr_id, float x, float y)
void immBindBuiltinProgram(eGPUBuiltinShader shader_id)
void immUniform1i(const char *name, int x)
void immUniform1f(const char *name, float x)
GPUVertFormat * immVertexFormat()
void immUniformThemeColorBlend(int color_id1, int color_id2, float fac)
void immVertex3fv(uint attr_id, const float data[3])
void immBegin(GPUPrimType, uint vertex_len)
void immRectf(uint pos, float x1, float y1, float x2, float y2)
void GPU_matrix_identity_set()
void GPU_matrix_push()
void GPU_matrix_push_projection()
void GPU_matrix_pop_projection()
#define GPU_matrix_projection_get(x)
#define GPU_matrix_projection_set(x)
void GPU_matrix_pop()
@ GPU_PRIM_TRI_FAN
@ GPU_PRIM_LINES
@ GPU_PRIM_LINE_STRIP
@ GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR
@ GPU_SHADER_2D_CHECKER
@ GPU_SHADER_3D_UNIFORM_COLOR
@ GPU_BLEND_NONE
Definition GPU_state.hh:85
@ GPU_BLEND_ALPHA
Definition GPU_state.hh:87
void GPU_blend(eGPUBlend blend)
Definition gpu_state.cc:42
void GPU_scissor_test(bool enable)
Definition gpu_state.cc:188
void GPU_line_width(float width)
Definition gpu_state.cc:166
void GPU_line_smooth(bool enable)
Definition gpu_state.cc:78
@ GPU_DEPTH_NONE
Definition GPU_state.hh:111
void GPU_depth_test(eGPUDepthTest test)
Definition gpu_state.cc:68
void GPU_viewport_size_get_f(float coords[4])
Definition gpu_state.cc:273
@ GPU_FETCH_FLOAT
uint GPU_vertformat_attr_add(GPUVertFormat *, blender::StringRef name, GPUVertCompType, uint comp_len, GPUVertFetchMode)
@ GPU_COMP_F32
GPUFrameBuffer * GPU_viewport_framebuffer_overlay_get(GPUViewport *viewport)
Read Guarded memory(de)allocation.
#define C
Definition RandGen.cpp:29
void UI_but_func_set(uiBut *but, std::function< void(bContext &)> func)
void UI_but_func_pushed_state_set(uiBut *but, std::function< bool(const uiBut &)> func)
#define UI_ALPHA_CHECKER_LIGHT
#define UI_UNIT_Y
void UI_draw_roundbox_4fv(const rctf *rect, bool filled, float rad, const float col[4])
void ui_draw_dropshadow(const rctf *rct, float radius, float width, float aspect, float alpha)
void UI_block_emboss_set(uiBlock *block, blender::ui::EmbossType emboss)
uiBlock * UI_block_begin(const bContext *C, ARegion *region, std::string name, blender::ui::EmbossType emboss)
void UI_but_icon_indicator_number_set(uiBut *but, const int indicator_number)
void UI_but_func_tooltip_set(uiBut *but, uiButToolTipFunc func, void *arg, uiFreeArgFunc free_arg)
uiBut * uiDefBut(uiBlock *block, int type, int retval, blender::StringRef str, int x, int y, short width, short height, void *poin, float min, float max, std::optional< blender::StringRef > tip)
void UI_draw_roundbox_4fv_ex(const rctf *rect, const float inner1[4], const float inner2[4], float shade_dir, const float outline[4], float outline_width, float rad)
#define UI_ALPHA_CHECKER_DARK
const uiStyle * UI_style_get_dpi()
void UI_draw_roundbox_corner_set(int type)
@ UI_CNR_BOTTOM_LEFT
@ UI_CNR_BOTTOM_RIGHT
@ UI_CNR_ALL
@ UI_CNR_TOP_LEFT
@ UI_CNR_TOP_RIGHT
@ UI_CNR_NONE
const uiStyle * UI_style_get()
void UI_but_drawflag_disable(uiBut *but, int flag)
void UI_block_draw(const bContext *C, uiBlock *block)
uiBut * uiDefIconBut(uiBlock *block, int type, int retval, int icon, int x, int y, short width, short height, void *poin, float min, float max, std::optional< blender::StringRef > tip)
@ UI_BUT_TEXT_LEFT
#define UI_UNIT_X
void UI_block_flag_enable(uiBlock *block, int flag)
@ UI_BTYPE_BUT
@ UI_BTYPE_BUT_TOGGLE
@ UI_BTYPE_LABEL
@ UI_BTYPE_CHECKBOX
@ UI_BUT_INACTIVE
uiBut * uiDefButR(uiBlock *block, int type, int retval, std::optional< blender::StringRef > str, int x, int y, short width, short height, PointerRNA *ptr, blender::StringRefNull propname, int index, float min, float max, std::optional< blender::StringRef > tip)
void UI_draw_roundbox_aa(const rctf *rect, bool filled, float rad, const float color[4])
@ UI_BLOCK_CLIP_EVENTS
void UI_block_end_ex(const bContext *C, Main *bmain, wmWindow *window, Scene *scene, ARegion *region, Depsgraph *depsgraph, uiBlock *block, const int xy[2]=nullptr, int r_xy[2]=nullptr)
void UI_block_end(const bContext *C, uiBlock *block)
void UI_but_func_quick_tooltip_set(uiBut *but, std::function< std::string(const uiBut *but)> func)
void UI_but_flag_enable(uiBut *but, int flag)
void UI_block_bounds_set_explicit(uiBlock *block, int minx, int miny, int maxx, int maxy)
Definition interface.cc:673
void UI_block_align_end(uiBlock *block)
void uiLayoutSetContextPointer(uiLayout *layout, blender::StringRef name, PointerRNA *ptr)
void uiLayoutSetActive(uiLayout *layout, bool active)
@ UI_LAYOUT_VERTICAL
uiLayout * UI_block_layout(uiBlock *block, int dir, int type, int x, int y, int size, int em, int padding, const uiStyle *style)
@ UI_LAYOUT_ALIGN_RIGHT
@ UI_LAYOUT_ALIGN_EXPAND
void uiLayoutSetAlignment(uiLayout *layout, char alignment)
@ UI_LAYOUT_PANEL
void uiLayoutSetTooltipFunc(uiLayout *layout, uiButToolTipFunc func, void *arg, uiCopyArgFunc copy_arg, uiFreeArgFunc free_arg)
void UI_block_layout_resolve(uiBlock *block, int *r_x, int *r_y)
void UI_GetThemeColor3fv(int colorid, float col[3])
int UI_GetThemeValueType(int colorid, int spacetype)
ThemeColorID
@ TH_NODE_FRAME
@ TH_NODE_INPUT
@ TH_NODE
@ TH_NODE_FILTER
@ TH_NODE_GROUP
@ TH_GRID
@ TH_NODE_SCRIPT
@ TH_BACK
@ TH_NODE_GEOMETRY
@ TH_NODE_OUTPUT
@ TH_NODE_COLOR
@ TH_WIRE
@ TH_NODE_PATTERN
@ TH_NODE_ATTRIBUTE
@ TH_PANEL_SUB_BACK
@ TH_NODE_DISTORT
@ TH_REDALERT
@ TH_NODE_MATTE
@ TH_NODE_GRID_LEVELS
@ TH_NODE_TEXTURE
@ TH_NODE_VECTOR
@ TH_NODE_CONVERTER
@ TH_SELECT
@ TH_NODE_LAYOUT
@ TH_TEXT
@ TH_NODE_SHADER
@ TH_WIRE_INNER
@ TH_NODE_INTERFACE
@ TH_ACTIVE
void UI_GetThemeColorBlend4f(int colorid1, int colorid2, float fac, float r_col[4])
void UI_GetThemeColorBlendShade4fv(int colorid1, int colorid2, float fac, int offset, float col[4])
void UI_GetThemeColorShadeAlpha4fv(int colorid, int coloffset, int alphaoffset, float col[4])
void UI_GetThemeColor4fv(int colorid, float col[4])
void UI_GetThemeColorShade4fv(int colorid, int offset, float col[4])
void UI_GetThemeColorBlendShade3ubv(int colorid1, int colorid2, float fac, int offset, unsigned char col[3])
void UI_view2d_scrollers_draw(View2D *v2d, const rcti *mask_custom)
Definition view2d.cc:1503
void UI_view2d_view_restore(const bContext *C)
Definition view2d.cc:1162
void UI_view2d_center_get(const View2D *v2d, float *r_x, float *r_y)
Definition view2d.cc:1938
void UI_view2d_view_ortho(const View2D *v2d)
Definition view2d.cc:1095
void UI_view2d_scale_get(const View2D *v2d, float *r_x, float *r_y)
Definition view2d.cc:1911
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:1667
void UI_view2d_center_set(View2D *v2d, float x, float y)
Definition view2d.cc:1948
void UI_view2d_dot_grid_draw(const View2D *v2d, int grid_color_id, float min_step, int grid_subdivisions)
Definition view2d.cc:1287
@ WM_GIZMOMAP_DRAWSTEP_2D
#define NC_WORLD
Definition WM_types.hh:384
#define ND_SHADING
Definition WM_types.hh:474
#define ND_WORLD
Definition WM_types.hh:449
#define NC_SCENE
Definition WM_types.hh:375
#define ND_NODES
Definition WM_types.hh:433
#define ND_MODIFIER
Definition WM_types.hh:459
#define NC_MATERIAL
Definition WM_types.hh:377
#define NC_LAMP
Definition WM_types.hh:379
#define NC_TEXTURE
Definition WM_types.hh:378
#define ND_LIGHTING
Definition WM_types.hh:480
@ WM_OP_INVOKE_DEFAULT
Definition WM_types.hh:238
#define NC_OBJECT
Definition WM_types.hh:376
void ED_annotation_draw_view2d(const bContext *C, bool onlyv2d)
#define U
BMesh const char void * data
unsigned long long int uint64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
const Value * lookup_ptr(const Key &key) const
Definition BLI_map.hh:508
constexpr T & first() const
Definition BLI_span.hh:679
constexpr void copy_from(Span< T > values) const
Definition BLI_span.hh:739
bool is_empty() const
const T * end() const
Definition BLI_array.hh:314
IndexRange index_range() const
Definition BLI_array.hh:349
const T * begin() const
Definition BLI_array.hh:310
bool is() const
void destruct(void *ptr) const
const ComputeContextHash & hash() const
const CPPType * type() const
const void * get() const
const Value * lookup_ptr(const Key &key) const
Definition BLI_map.hh:508
bool add_overwrite(const Key &key, const Value &value)
Definition BLI_map.hh:325
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:295
int64_t size() const
Definition BLI_map.hh:976
constexpr int64_t size() const
Definition BLI_span.hh:493
bool add(const Key &key)
Definition BLI_set.hh:248
constexpr Span drop_back(int64_t n) const
Definition BLI_span.hh:182
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr const T & last(const int64_t n=0) const
Definition BLI_span.hh:325
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
constexpr bool is_empty() const
Definition BLI_span.hh:260
constexpr bool is_empty() const
constexpr bool startswith(StringRef prefix) const
constexpr const char * c_str() const
static VArray ForSingle(T value, const int64_t size)
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)
Span< T > as_span() const
const T & first() const
MutableSpan< float3 > positions_for_write()
void fill_curve_types(CurveType type)
MutableSpan< int > offsets_for_write()
MutableSpan< bool > cyclic_for_write()
std::optional< float > header_center_y
std::optional< bNodePanelExtent > content_extent
std::optional< int > output_node_id
const bNode * output_node() const
bool contains_node_recursively(const bNode &node) const
std::optional< int > input_node_id
Vector< bNodeTreeZone * > child_zones
const bNode * input_node() const
Vector< bNodeTreeZone * > zones
const bNodeTreeZone * get_zone_by_node(const int32_t node_id) const
std::function< DrawNodeLayoutFn > draw
Vector< PanelDeclaration * > panels
std::optional< std::string > translation_context
Vector< ItemDeclaration * > items
const SocketDeclaration * panel_input_decl() const
Span< bke::GeometryComponent::Type > supported_types() const
GeoTreeLog * get_main_tree_log(const bke::bNodeTreeZone *zone) const
void foreach_tree_log(FunctionRef< void(GeoTreeLog &)> callback) const
Map< StringRefNull, NamedAttributeUsage > used_named_attributes
static ContextualGeoTreeLogs get_contextual_tree_logs(const SpaceNode &snode)
ValueLog * find_socket_value_log(const bNodeSocket &query_socket)
std::optional< GreasePencilInfo > grease_pencil_info
Vector< bke::GeometryComponent::Type > component_types
#define SELECT
#define sinf(x)
#define cosf(x)
#define str(s)
static const char * to_string(const Interpolation &interp)
Definition gl_shader.cc:109
uint pos
#define log
#define round
#define output
float length(VecOp< float, D >) RET
#define ID_IS_LINKED(_id)
#define ID_REAL_USERS(id)
#define GS(a)
uint padding(uint offset, uint alignment)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
format
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void * MEM_dupallocN(const void *vmemh)
Definition mallocn.cc:143
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
static ulong * next
static void error(const char *str)
const bNodeZoneType * zone_type_by_node_type(const int node_type)
std::string node_label(const bNodeTree &ntree, const bNode &node)
Definition node.cc:5242
bool node_link_is_selected(const bNodeLink &link)
Definition node.cc:4164
const DataTypeConversions & get_implicit_type_conversions()
std::optional< StringRefNull > node_socket_short_label(const bNodeSocket &sock)
Definition node.cc:5257
bNodeType NodeTypeUndefined
Definition node.cc:122
bNodeInstanceKey node_instance_key(bNodeInstanceKey parent_key, const bNodeTree *ntree, const bNode *node)
Definition node.cc:5139
Span< int > all_zone_node_types()
const bNodeInstanceKey NODE_INSTANCE_KEY_NONE
Definition node.cc:5119
bNodeTree * node_tree_from_id(ID *id)
Definition node.cc:4840
StringRefNull node_socket_label(const bNodeSocket &sock)
Definition node.cc:5268
bool node_link_is_hidden(const bNodeLink &link)
Definition node.cc:4159
std::optional< Bounds< T > > min_max(const std::optional< Bounds< T > > &a, const T &b)
Definition BLI_bounds.hh:55
static bool compare_node_depth(const bNode *a, const bNode *b)
Definition node_draw.cc:259
void tag_update_id(ID *id)
Definition node_draw.cc:194
static rctf calc_node_frame_dimensions(const bContext &C, TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, bNode &node)
static void determine_visible_panels_impl_recursive(const bNode &node, const nodes::PanelDeclaration &panel_decl, const Span< bool > potentially_visible_states, MutableSpan< bool > r_result)
Definition node_draw.cc:664
static bool compositor_is_in_use(const bContext &context)
static bool node_update_basis_socket(const bContext &C, bNodeTree &ntree, bNode &node, const char *panel_label, bNodeSocket *input_socket, bNodeSocket *output_socket, uiBlock &block, const int &locx, int &locy)
Definition node_draw.cc:485
static void add_flat_items_for_panel(bNode &node, const nodes::PanelDeclaration &panel_decl, const Span< bool > panel_visibility, Vector< FlatNodeItem > &r_items)
Definition node_draw.cc:756
float node_socket_calculate_height(const bNodeSocket &socket)
Definition node_edit.cc:118
rctf node_frame_rect_inside(const SpaceNode &snode, const bNode &node)
static bNodeInstanceKey current_node_instance_key(const SpaceNode &snode, const bNode &node)
int node_get_resize_cursor(NodeResizeDirection directions)
static void reroute_node_draw(const bContext &C, TreeDrawContext &tree_draw_ctx, ARegion &region, const SpaceNode &snode, bNodeTree &ntree, const bNode &node, uiBlock &block)
static float get_margin_to_bottom(const Span< FlatNodeItem > items)
Definition node_draw.cc:869
static void node_socket_outline_color_get(const bool selected, const int socket_type, float r_outline_color[4])
static void mark_sockets_collapsed_recursive(bNode &node, const int node_left_x, const nodes::PanelDeclaration &visible_panel_decl, const nodes::PanelDeclaration &panel_decl)
void nodelink_batch_start(SpaceNode &)
Definition drawnode.cc:2111
static const bNode * reroute_node_get_linked_reroute(const bNode &reroute)
void tree_draw_order_update(bNodeTree &ntree)
Definition node_draw.cc:321
static void draw_link_errors(const bContext &C, SpaceNode &snode, const bNodeLink &link, const Span< bke::NodeLinkError > errors, uiBlock &invalid_links_block)
void node_socket_draw(bNodeSocket *sock, const rcti *rect, const float color[4], float scale)
static void update_collapsed_sockets(bNode &node, const int node_left_x)
static std::optional< std::string > create_description_inspection_string(const bNodeSocket &socket)
static std::optional< std::string > create_multi_input_log_inspection_string(const bNodeSocket &socket, TreeDrawContext &tree_draw_ctx)
static void tag_final_panel(bNode &node, const Span< FlatNodeItem > items)
static void draw_nodetree(const bContext &C, ARegion &region, bNodeTree &ntree, bNodeInstanceKey parent_key)
static void frame_node_draw_label(TreeDrawContext &tree_draw_ctx, const bNode &node, const SpaceNode &snode)
static float get_margin_between_elements(const Span< FlatNodeItem > items, const int next_index)
Definition node_draw.cc:892
static void reroute_node_draw_body(const bContext &C, const SpaceNode &snode, const bNodeTree &ntree, const bNode &node, uiBlock &block, const bool selected)
static void node_update_nodetree(const bContext &C, TreeDrawContext &tree_draw_ctx, bNodeTree &ntree, Span< bNode * > nodes, Span< uiBlock * > blocks)
static float node_tree_view_scale(const SpaceNode &snode)
static void determine_potentially_visible_panels(const bNode &node, MutableSpan< bool > r_result)
Definition node_draw.cc:655
static std::optional< std::string > create_log_inspection_string(geo_log::GeoTreeLog *geo_tree_log, const bNodeSocket &socket)
static void frame_node_draw_background(const ARegion &region, const SpaceNode &snode, const bNode &node)
float2 node_from_view(const float2 &co)
Definition node_draw.cc:399
void node_draw_link_bezier(const bContext &C, const View2D &v2d, const SpaceNode &snode, const bNodeLink &link, const int th_col1, const int th_col2, const int th_col3, const bool selected)
Definition drawnode.cc:2375
static void draw_background_color(const SpaceNode &snode)
static void create_inspection_string_for_bundle(const geo_log::BundleValueLog &value_log, fmt::memory_buffer &buf)
static std::optional< std::chrono::nanoseconds > node_get_execution_time(const TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, const bNode &node)
static void create_inspection_string_for_geometry_info(const geo_log::GeometryInfoLog &value_log, fmt::memory_buffer &buf)
void nodelink_batch_end(SpaceNode &snode)
Definition drawnode.cc:2116
static std::optional< std::chrono::nanoseconds > compositor_accumulate_frame_node_execution_time(const TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, const bNode &node)
static void reroute_node_draw_label(TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, const bNode &node, uiBlock &block)
static float get_margin_from_top(const Span< FlatNodeItem > items)
Definition node_draw.cc:847
ImBuf * node_preview_acquire_ibuf(bNodeTree &ntree, NestedTreePreviews &tree_previews, const bNode &node)
std::array< float2, 4 > node_link_bezier_points_dragged(const SpaceNode &snode, const bNodeLink &link)
Definition drawnode.cc:2432
bNodeSocket * node_find_indicated_socket(SpaceNode &snode, ARegion &region, const float2 &cursor, const eNodeSocketInOut in_out)
static void node_draw_panels_background(const bNode &node)
static void find_bounds_by_zone_recursive(const SpaceNode &snode, const bNodeTreeZone &zone, const Span< const bNodeTreeZone * > all_zones, MutableSpan< Vector< float2 > > r_bounds_by_zone)
static void node_draw_hidden(const bContext &C, TreeDrawContext &tree_draw_ctx, const View2D &v2d, const SpaceNode &snode, bNodeTree &ntree, bNode &node, uiBlock &block)
static std::optional< NodeExtraInfoRow > node_get_accessed_attributes_row(TreeDrawContext &tree_draw_ctx, const bNode &node)
void node_socket_color_get(const bContext &C, const bNodeTree &ntree, PointerRNA &node_ptr, const bNodeSocket &sock, float r_color[4])
Array< bNode * > tree_draw_order_calc_nodes_reversed(bNodeTree &ntree)
Definition node_draw.cc:345
static void node_get_compositor_extra_info(TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, const bNode &node, Vector< NodeExtraInfoRow > &rows)
void node_set_cursor(wmWindow &win, ARegion &region, SpaceNode &snode, const float2 &cursor)
void node_draw_link(const bContext &C, const View2D &v2d, const SpaceNode &snode, const bNodeLink &link, const bool selected)
Definition drawnode.cc:2394
static const bNodeSocket * target_for_reroute(const bNodeSocket &reroute_output)
static void node_draw_mute_line(const bContext &C, const View2D &v2d, const SpaceNode &snode, const bNode &node)
static void reroute_node_prepare_for_draw(bNode &node)
static const bNode * find_node_under_cursor(SpaceNode &snode, const float2 &cursor)
static bool is_node_panels_supported(const bNode &node)
Definition node_draw.cc:404
static NodeExtraInfoRow row_from_used_named_attribute(const Map< StringRefNull, geo_log::NamedAttributeUsage > &usage_by_attribute_name)
void node_draw_link_dragged(const bContext &C, const View2D &v2d, const SpaceNode &snode, const bNodeLink &link)
Definition drawnode.cc:2446
static void node_add_unsupported_compositor_operation_error_message_button(const bNode &node, uiBlock &block, const rctf &rect, float &icon_offset)
static std::optional< std::chrono::nanoseconds > compositor_node_get_execution_time(const TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, const bNode &node)
static void add_flat_items_for_separator(Vector< FlatNodeItem > &r_items)
Definition node_draw.cc:741
static void node_toggle_button_cb(bContext *C, void *node_argv, void *op_argv)
static Array< uiBlock * > node_uiblocks_init(const bContext &C, const Span< bNode * > nodes)
Definition node_draw.cc:357
static void add_rect_corner_positions(Vector< float2 > &positions, const rctf &rect)
bool node_is_previewable(const SpaceNode &snode, const bNodeTree &ntree, const bNode &node)
static void node_socket_tooltip_set(uiBlock &block, const int socket_index_in_tree, const float2 location, const float2 size)
static void determine_potentially_visible_panels_recursive(const bNode &node, const nodes::PanelDeclaration &panel_decl, MutableSpan< bool > r_result)
Definition node_draw.cc:634
static bool node_update_basis_buttons(const bContext &C, bNodeTree &ntree, bNode &node, blender::FunctionRef< nodes::DrawNodeLayoutFn > draw_buttons, uiBlock &block, int &dy)
Definition node_draw.cc:410
const ComputeContext * compute_context_for_edittree(const SpaceNode &snode, bke::ComputeContextCache &compute_context_cache)
void draw_nodespace_back_pix(const bContext &C, ARegion &region, SpaceNode &snode, bNodeInstanceKey parent_key)
Definition drawnode.cc:1554
static std::optional< std::string > create_default_value_inspection_string(const bNodeSocket &socket)
static void draw_frame_overlays(const bContext &C, TreeDrawContext &tree_draw_ctx, const ARegion &region, const SpaceNode &snode, const bNodeTree &ntree, Span< uiBlock * > blocks)
static std::optional< std::chrono::nanoseconds > geo_node_get_execution_time(const TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, const bNode &node)
static bool panel_has_only_inactive_inputs(const bNode &node, const nodes::PanelDeclaration &panel_decl)
static std::string named_attribute_tooltip(bContext *, void *argN, const StringRef)
static void node_draw_basis(const bContext &C, TreeDrawContext &tree_draw_ctx, const View2D &v2d, const SpaceNode &snode, bNodeTree &ntree, const bNode &node, uiBlock &block, bNodeInstanceKey key)
static void node_draw_extra_info_panel_back(const bNode &node, const rctf &extra_info_rect)
static void node_draw_extra_info_panel(const bContext &C, TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, const bNode &node, ImBuf *preview, uiBlock &block)
static void count_multi_input_socket_links(bNodeTree &ntree, SpaceNode &snode)
static void node_update_basis_from_declaration(const bContext &C, bNodeTree &ntree, bNode &node, uiBlock &block, const int locx, int &locy)
static void determine_visible_panels(const bNode &node, MutableSpan< bool > r_visibility_states)
Definition node_draw.cc:704
static void node_draw_sockets(const bContext &C, uiBlock &block, const SpaceNode &snode, const bNodeTree &ntree, const bNode &node)
static void node_update_basis(const bContext &C, const TreeDrawContext &, bNodeTree &ntree, bNode &node, uiBlock &block)
static bNodeTree * node_tree_from_ID(ID *id)
Definition node_draw.cc:182
const char * node_socket_get_label(const bNodeSocket *socket, const char *panel_label)
Definition node_draw.cc:456
static void create_inspection_string_for_geometry_socket(fmt::memory_buffer &buf, const nodes::decl::Geometry *socket_decl)
void tree_update(const bContext *C)
Definition node_draw.cc:169
static std::string node_get_execution_time_label(TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, const bNode &node)
static void create_inspection_string_for_default_socket_value(const bNodeSocket &socket, fmt::memory_buffer &buf)
static void node_draw_extra_info_row(const bNode &node, uiBlock &block, const rctf &rect, const int row, const NodeExtraInfoRow &extra_info_row)
static void node_draw_preview(const Scene *scene, ImBuf *preview, const rctf *prv)
static void node_draw_nodetree(const bContext &C, TreeDrawContext &tree_draw_ctx, ARegion &region, SpaceNode &snode, bNodeTree &ntree, Span< bNode * > nodes, Span< uiBlock * > blocks, bNodeInstanceKey parent_key)
static void node_update_basis_from_socket_lists(const bContext &C, bNodeTree &ntree, bNode &node, uiBlock &block, const int locx, int &locy)
static short get_viewer_shortcut_icon(const bNode &node)
Array< bNode * > tree_draw_order_calc_nodes(bNodeTree &ntree)
Definition node_draw.cc:333
static void update_collapsed_sockets_recursive(bNode &node, const int node_left_x, const nodes::PanelDeclaration &panel_decl)
Vector< ui::ContextPathItem > context_path_for_space_node(const bContext &C)
float2 node_to_view(const float2 &co)
Definition node_draw.cc:378
static void add_flat_items_for_socket(bNode &node, const nodes::SocketDeclaration &socket_decl, const nodes::PanelDeclaration *panel_decl, const nodes::SocketDeclaration *prev_socket_decl, Vector< FlatNodeItem > &r_items)
Definition node_draw.cc:711
static void node_update_hidden(bNode &node, uiBlock &block)
void node_select_single(bContext &C, bNode &node)
static std::string node_errors_tooltip_fn(const Span< geo_log::NodeWarning > warnings)
static rctf node_to_rect(const bNode &node)
Definition node_draw.cc:383
static Vector< NodeExtraInfoRow > node_get_extra_info(const bContext &C, TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, const bNode &node)
NestedTreePreviews * get_nested_previews(const bContext &C, SpaceNode &snode)
std::optional< ObjectAndModifier > get_modifier_for_node_editor(const SpaceNode &snode)
static std::optional< std::string > create_dangling_reroute_inspection_string(const bNodeTree &ntree, const bNodeSocket &socket)
static std::optional< NodeExtraInfoRow > node_get_execution_time_label_row(TreeDrawContext &tree_draw_ctx, const SpaceNode &snode, const bNode &node)
static std::optional< std::string > create_declaration_inspection_string(const bNodeSocket &socket)
static nodes::NodeWarningType node_error_highest_priority(Span< geo_log::NodeWarning > warnings)
void node_link_bezier_points_evaluated(const bNodeLink &link, std::array< float2, NODE_LINK_RESOL+1 > &coords)
Definition drawnode.cc:1704
static Vector< FlatNodeItem > make_flat_node_items(bNode &node)
Definition node_draw.cc:806
static const float virtual_node_socket_outline_color[4]
static std::optional< float2 > find_visible_center_of_link(const View2D &v2d, const bNodeLink &link, const float radius, const float region_padding)
static void create_inspection_string_for_field_info(const bNodeSocket &socket, const geo_log::FieldInfoLog &value_log, fmt::memory_buffer &buf)
float2 socket_link_connection_location(const bNode &node, const bNodeSocket &socket, const bNodeLink &link)
Definition drawnode.cc:1644
static void node_draw_socket(const bContext &C, const bNodeTree &ntree, const bNode &node, PointerRNA &node_ptr, uiBlock &block, const bNodeSocket &sock, const float outline_thickness, const bool selected, const float aspect)
static FrameNodeLayout frame_node_layout(const bNode &frame_node)
static void frame_node_draw_outline(const ARegion &region, const SpaceNode &snode, const bNode &node)
void snode_set_context(const bContext &C)
Definition node_edit.cc:726
static void node_socket_add_tooltip_in_node_editor(const bNodeSocket &sock, uiLayout &layout)
static void node_panel_toggle_button_cb(bContext *C, void *panel_state_argv, void *ntree_argv)
NodeResizeDirection node_get_resize_direction(const SpaceNode &snode, const bNode *node, const int x, const int y)
Definition drawnode.cc:199
static const char * node_socket_get_translation_context(const bNodeSocket &socket)
Definition node_draw.cc:238
static void node_draw(const bContext &C, TreeDrawContext &tree_draw_ctx, ARegion &region, const SpaceNode &snode, bNodeTree &ntree, bNode &node, uiBlock &block, bNodeInstanceKey key)
void node_release_preview_ibuf(NestedTreePreviews &tree_previews)
static void node_draw_zones_and_frames(const ARegion &region, const SpaceNode &snode, const bNodeTree &ntree)
static void add_flat_items_for_layout(const bNode &node, const nodes::LayoutDeclaration &layout_decl, Vector< FlatNodeItem > &r_items)
Definition node_draw.cc:746
static void create_inspection_string_for_generic_value(const bNodeSocket &socket, const GPointer value, fmt::memory_buffer &buf)
void node_to_updated_rect(const bNode &node, rctf &r_rect)
Definition node_draw.cc:393
static bool draw_node_details(const SpaceNode &snode)
static std::string node_socket_get_tooltip(const SpaceNode *snode, const bNodeTree &ntree, const bNodeSocket &socket)
static void node_add_error_message_button(const TreeDrawContext &tree_draw_ctx, const bNode &node, uiBlock &block, const rctf &rect, float &icon_offset)
void node_draw_space(const bContext &C, ARegion &region)
static void node_draw_preview_background(rctf *rect)
static void node_draw_panels(bNodeTree &ntree, const bNode &node, uiBlock &block)
static void node_draw_shadow(const SpaceNode &snode, const bNode &node, const float radius, const float alpha)
static uiBlock & invalid_links_uiblock_init(const bContext &C)
static void create_inspection_string_for_closure(const geo_log::ClosureValueLog &value_log, fmt::memory_buffer &buf)
static bool node_undefined_or_unsupported(const bNodeTree &node_tree, const bNode &node)
static void frame_node_draw_overlay(const bContext &C, TreeDrawContext &tree_draw_ctx, const ARegion &region, const SpaceNode &snode, const bNode &node, uiBlock &block)
static void draw_tree_path(const bContext &C, ARegion &region)
static Vector< std::string > lines_of_text(std::string text)
static Set< const bNodeSocket * > find_sockets_on_active_gizmo_paths(const bContext &C, const SpaceNode &snode)
static float get_margin_empty()
Definition node_draw.cc:841
void node_socket_add_tooltip(const bNodeTree &ntree, const bNodeSocket &sock, uiLayout &layout)
static StringRef reroute_node_get_auto_label(TreeDrawContext &tree_draw_ctx, const bNode &src_reroute)
void node_draw_nodesocket(const rctf *rect, const float color_inner[4], const float color_outline[4], float outline_thickness, int shape, float aspect)
Definition drawnode.cc:1826
static int node_get_colorid(TreeDrawContext &tree_draw_ctx, const bNode &node)
static void determine_visible_panels_impl(const bNode &node, const Span< bool > potentially_visible_states, MutableSpan< bool > r_result)
Definition node_draw.cc:687
static void snode_setup_v2d(SpaceNode &snode, ARegion &region, const float2 &center)
bNode * find_geometry_nodes_viewer(const ViewerPath &viewer_path, SpaceNode &snode)
bke::CurvesGeometry fillet_curves_poly(const bke::CurvesGeometry &src_curves, const IndexMask &curve_selection, const VArray< float > &radius, const VArray< int > &counts, bool limit_radius, const bke::AttributeFilter &attribute_filter)
QuaternionBase< float > Quaternion
MatBase< T, NumCol, NumRow > transpose(const MatBase< T, NumRow, NumCol > &mat)
T distance(const T &a, const T &b)
EulerXYZBase< float > EulerXYZ
T midpoint(const T &a, const T &b)
T interpolate(const T &a, const T &b, const FactorT &t)
T round(const T &a)
Euler3Base< T > to_euler(const AxisAngleBase< T, AngleT > &axis_angle, EulerOrder order)
void foreach_socket_on_gizmo_path(const ComputeContext &gizmo_context, const bNode &gizmo_node, const bNodeSocket &gizmo_socket, FunctionRef< void(const ComputeContext &context, const bNodeSocket &socket, const ie::ElemVariant &elem)> fn)
void foreach_active_gizmo(const bContext &C, bke::ComputeContextCache &compute_context_cache, const ForeachGizmoFn fn)
int node_warning_type_icon(const NodeWarningType type)
std::chrono::nanoseconds Nanoseconds
Definition BLI_timeit.hh:21
void template_breadcrumbs(uiLayout &layout, Span< ContextPathItem > context_path)
MatBase< float, 4, 4 > float4x4
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
ColorSceneLinear4f< eAlpha::Premultiplied > ColorGeometry4f
Definition BLI_color.hh:342
ColorTheme4< float > ColorTheme4f
Definition BLI_color.hh:292
VecBase< float, 3 > float3
#define NODE_HEADER_ICON_SIZE
#define NODE_SOCKET_OUTLINE
#define ZONE_ZONE_PADDING
Definition node_draw.cc:117
#define EXTRA_INFO_ROW_HEIGHT
Definition node_draw.cc:118
#define NODE_ZONE_PADDING
Definition node_draw.cc:116
#define NODE_TREE_SCALE_SMALL
#define NODE_MULTI_INPUT_LINK_GAP
#define BASIS_RAD
#define NODE_SOCKSIZE
#define NODE_WIDTH(node)
#define NODE_DYS
#define NODE_ITEM_SPACING_Y
#define NODE_MARGIN_X
#define HIDDEN_RAD
#define NODE_DY
bool RNA_struct_is_a(const StructRNA *type, const StructRNA *srna)
PointerRNA RNA_pointer_create_discrete(ID *id, StructRNA *type, void *data)
#define min(a, b)
Definition sort.cc:36
ARegionRuntimeHandle * runtime
Definition DNA_ID.h:404
int tag
Definition DNA_ID.h:424
char name[66]
Definition DNA_ID.h:415
void * last
void * first
StructRNA * type
Definition RNA_types.hh:52
void * data
Definition RNA_types.hh:53
int lines
Definition BLF_api.hh:481
struct bNodeTree * nodetree
ColorManagedViewSettings view_settings
SceneRuntimeHandle * runtime
ColorManagedDisplaySettings display_settings
SpaceNode_Runtime * runtime
ListBase treepath
struct bNodeTree * edittree
struct ID * id
SpaceNodeOverlay overlay
struct bNodeTree * nodetree
View3DShading shading
ViewerPath viewer_path
bNodeSocketRuntimeHandle * runtime
char description[64]
bNodeSocketTypeHandle * typeinfo
void * default_value
char idname[64]
struct bNodeTree * nodetree
bNodeInstanceKey parent_key
char display_name[64]
float view_center[2]
bNodeTreeRuntimeHandle * runtime
float location[2]
bNodeTypeHandle * typeinfo
int16_t custom1
float width
ListBase inputs
int num_panel_states
float height
struct ID * id
float color[3]
struct bNode * parent
char name[64]
bNodePanelState * panel_states_array
int16_t type_legacy
bNodeRuntimeHandle * runtime
void * storage
char idname[64]
char label[64]
int32_t identifier
ListBase areabase
std::variant< flat_item::Socket, flat_item::Separator, flat_item::PanelHeader, flat_item::PanelContentBegin, flat_item::PanelContentEnd, flat_item::Layout > item
Definition node_draw.cc:626
Map< StringRefNull, geo_log::NamedAttributeUsage > usage_by_attribute
std::optional< int > frame_identifier_to_highlight
std::unique_ptr< bNodeLinkDrag > linkdrag
geo_log::ContextualGeoTreeLogs tree_logs
Definition node_draw.cc:142
Map< bNodeInstanceKey, timeit::Nanoseconds > * compositor_per_node_execution_time
Definition node_draw.cc:150
Array< Vector< NodeExtraInfoRow > > extra_info_rows_per_node
Definition node_draw.cc:161
Map< const bNode *, StringRef > reroute_auto_labels
Definition node_draw.cc:155
const nodes::LayoutDeclaration * decl
Definition node_draw.cc:614
const nodes::PanelDeclaration * decl
Definition node_draw.cc:600
const nodes::PanelDeclaration * panel_decl
Definition node_draw.cc:593
float xmax
float xmin
float ymax
float ymin
int ymin
int xmin
void separator(float factor=1.0f, LayoutSeparatorType type=LayoutSeparatorType::Auto)
uiLayout & row(bool align)
uiFontStyle widget
int xy[2]
Definition WM_types.hh:758
struct wmEvent * eventstate
i
Definition text_draw.cc:230
max
Definition text_draw.cc:251
void WM_cursor_set(wmWindow *win, int curs)
@ WM_CURSOR_NSEW_SCROLL
Definition wm_cursors.hh:52
@ WM_CURSOR_DEFAULT
Definition wm_cursors.hh:15
@ WM_CURSOR_Y_MOVE
Definition wm_cursors.hh:40
@ WM_CURSOR_EDIT
Definition wm_cursors.hh:19
@ WM_CURSOR_X_MOVE
Definition wm_cursors.hh:39
GPUViewport * WM_draw_region_get_viewport(ARegion *region)
Definition wm_draw.cc:912
void WM_main_add_notifier(uint type, void *reference)
wmOperatorStatus WM_operator_name_call(bContext *C, const char *opstring, wmOperatorCallContext context, PointerRNA *properties, const wmEvent *event)
PointerRNA * ptr
Definition wm_files.cc:4227
void WM_gizmomap_draw(wmGizmoMap *gzmap, const bContext *C, const eWM_GizmoFlagMapDrawStep drawstep)
void wmOrtho2_pixelspace(const float x, const float y)
void wmOrtho2_region_pixelspace(const ARegion *region)
bScreen * WM_window_get_active_screen(const wmWindow *win)