Files
test2/source/blender/editors/space_node/drawnode.cc
Jacques Lucke 80441190c6 Nodes: automatically gray out input values that don't affect the output
This patch automatically grays out input values which can't affect the output
currently. It works with inputs of group nodes, geometry nodes modifiers and
node tools.

To achieve this, it analyses the node tree and partially evaluates it to figure
out which group inputs are currently not linked to an output or are disabled by e.g.
some switch node.

Original proposal: https://devtalk.blender.org/t/dynamic-socket-visibility/31874
Related info in blog post:
https://code.blender.org/2023/11/geometry-nodes-workshop-november-2023/#dynamic-socket-visibility

Follow up task for designing a UI that allows hiding sockets: #132706

Limitations:
* The inferencing does not update correctly when a socket starts being
  animated/driven. I haven't found a good way to invalidate the cache in a good
  way reliably yet. It's only a very short term problem though. It fixes itself
  after the next modification of the node tree and is only noticeable when
  animating some specific sockets such as the switch node condition.
* Whether a socket is grayed out is not exposed in the Python API yet. That will
  be done separately.
* Only a partial evaluation is done to determine if an input affects an output.
  There should be no cases where a socket is found to be unused when it can actually
  affect the output. However, there can be cases where a socket is inferenced to be used
  even if it is not due to some complex condition. Depending on the exact circumstances,
  this can either be improved or the condition in the node tree should be simplified.

Pull Request: https://projects.blender.org/blender/blender/pulls/132219
2025-01-21 12:53:24 +01:00

2577 lines
89 KiB
C++

/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup spnode
* \brief lower level node drawing for nodes (boarders, headers etc), also node layout.
*/
#include "BLI_color.hh"
#include "BLI_string.h"
#include "BLI_threads.h"
#include "DNA_node_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "DNA_userdef_types.h"
#include "BKE_context.hh"
#include "BKE_curve.hh"
#include "BKE_image.hh"
#include "BKE_main.hh"
#include "BKE_main_invariants.hh"
#include "BKE_node.hh"
#include "BKE_node_enum.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_scene.hh"
#include "BKE_tracking.h"
#include "BLT_translation.hh"
#include "BIF_glutil.hh"
#include "GPU_batch.hh"
#include "GPU_batch_presets.hh"
#include "GPU_framebuffer.hh"
#include "GPU_immediate.hh"
#include "GPU_matrix.hh"
#include "GPU_shader_shared.hh"
#include "GPU_state.hh"
#include "GPU_uniform_buffer.hh"
#include "DRW_engine.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_prototypes.hh"
#include "ED_node.hh"
#include "ED_space_api.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "UI_resources.hh"
#include "UI_view2d.hh"
#include "IMB_colormanagement.hh"
#include "IMB_imbuf_types.hh"
#include "NOD_composite.hh"
#include "NOD_geometry.hh"
#include "NOD_geometry_nodes_gizmos.hh"
#include "NOD_node_declaration.hh"
#include "NOD_partial_eval.hh"
#include "NOD_socket.hh"
#include "node_intern.hh" /* own include */
namespace blender::ed::space_node {
/* Default flags for uiItemR(). Name is kept short since this is used a lot in this file. */
#define DEFAULT_FLAGS UI_ITEM_R_SPLIT_EMPTY_NAME
/* ****************** SOCKET BUTTON DRAW FUNCTIONS ***************** */
static void node_socket_button_label(bContext * /*C*/,
uiLayout *layout,
PointerRNA * /*ptr*/,
PointerRNA * /*node_ptr*/,
const StringRefNull text)
{
uiItemL(layout, text, ICON_NONE);
}
/* ****************** BUTTON CALLBACKS FOR ALL TREES ***************** */
static void node_buts_value(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
bNode *node = (bNode *)ptr->data;
/* first output stores value */
bNodeSocket *output = (bNodeSocket *)node->outputs.first;
PointerRNA sockptr = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, output);
uiLayout *row = uiLayoutRow(layout, true);
uiItemR(row, &sockptr, "default_value", DEFAULT_FLAGS, "", ICON_NONE);
}
static void node_buts_rgb(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
bNode *node = (bNode *)ptr->data;
/* first output stores value */
bNodeSocket *output = (bNodeSocket *)node->outputs.first;
uiLayout *col;
PointerRNA sockptr = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, output);
col = uiLayoutColumn(layout, false);
uiTemplateColorPicker(col, &sockptr, "default_value", true, false, false, false);
uiItemR(col, &sockptr, "default_value", DEFAULT_FLAGS | UI_ITEM_R_SLIDER, "", ICON_NONE);
}
static void node_buts_mix_rgb(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
uiLayout *col = uiLayoutColumn(layout, false);
uiLayout *row = uiLayoutRow(col, true);
uiItemR(row, ptr, "blend_type", DEFAULT_FLAGS, "", ICON_NONE);
if (ELEM(ntree->type, NTREE_COMPOSIT, NTREE_TEXTURE)) {
uiItemR(row, ptr, "use_alpha", DEFAULT_FLAGS, "", ICON_IMAGE_RGB_ALPHA);
}
uiItemR(col, ptr, "use_clamp", DEFAULT_FLAGS, std::nullopt, ICON_NONE);
}
static void node_buts_time(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiTemplateCurveMapping(layout, ptr, "curve", 's', false, false, false, false);
uiLayout *col = uiLayoutColumn(layout, true);
uiItemR(col, ptr, "frame_start", DEFAULT_FLAGS, IFACE_("Start"), ICON_NONE);
uiItemR(col, ptr, "frame_end", DEFAULT_FLAGS, IFACE_("End"), ICON_NONE);
}
static void node_buts_colorramp(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiTemplateColorRamp(layout, ptr, "color_ramp", false);
}
static void node_buts_curvevec(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiTemplateCurveMapping(layout, ptr, "mapping", 'v', false, false, false, false);
}
static void node_buts_curvefloat(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiTemplateCurveMapping(layout, ptr, "mapping", 0, false, false, false, false);
}
} // namespace blender::ed::space_node
#define SAMPLE_FLT_ISNONE FLT_MAX
/* Bad! 2.5 will do better? ... no it won't! */
static float _sample_col[4] = {SAMPLE_FLT_ISNONE};
void ED_node_sample_set(const float col[4])
{
if (col) {
copy_v4_v4(_sample_col, col);
}
else {
copy_v4_fl(_sample_col, SAMPLE_FLT_ISNONE);
}
}
namespace blender::ed::space_node {
static void node_buts_curvecol(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
bNode *node = (bNode *)ptr->data;
CurveMapping *cumap = (CurveMapping *)node->storage;
if (_sample_col[0] != SAMPLE_FLT_ISNONE) {
cumap->flag |= CUMA_DRAW_SAMPLE;
copy_v3_v3(cumap->sample, _sample_col);
}
else {
cumap->flag &= ~CUMA_DRAW_SAMPLE;
}
/* "Tone" (Standard/Film-like) only used in the Compositor. */
bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
uiTemplateCurveMapping(
layout, ptr, "mapping", 'c', false, false, false, (ntree->type == NTREE_COMPOSIT));
}
static void node_buts_normal(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
bNode *node = (bNode *)ptr->data;
/* first output stores normal */
bNodeSocket *output = (bNodeSocket *)node->outputs.first;
PointerRNA sockptr = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, output);
uiItemR(layout, &sockptr, "default_value", DEFAULT_FLAGS, "", ICON_NONE);
}
static void node_buts_texture(uiLayout *layout, bContext *C, PointerRNA *ptr)
{
bNode *node = (bNode *)ptr->data;
short multi = (node->id && ((Tex *)node->id)->use_nodes &&
(node->type_legacy != CMP_NODE_TEXTURE) &&
(node->type_legacy != TEX_NODE_TEXTURE));
uiTemplateID(layout, C, ptr, "texture", "texture.new", nullptr, nullptr);
if (multi) {
/* Number Drawing not optimal here, better have a list. */
uiItemR(layout, ptr, "node_output", DEFAULT_FLAGS, "", ICON_NONE);
}
}
static void node_buts_math(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "operation", DEFAULT_FLAGS, "", ICON_NONE);
uiItemR(layout, ptr, "use_clamp", DEFAULT_FLAGS, std::nullopt, ICON_NONE);
}
static void node_buts_combsep_color(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "mode", DEFAULT_FLAGS, "", ICON_NONE);
}
NodeResizeDirection node_get_resize_direction(const SpaceNode &snode,
const bNode *node,
const int x,
const int y)
{
const float size = NODE_RESIZE_MARGIN * math::max(snode.runtime->aspect, 1.0f);
if (node->is_frame()) {
NodeFrame *data = (NodeFrame *)node->storage;
/* shrinking frame size is determined by child nodes */
if (!(data->flag & NODE_FRAME_RESIZEABLE)) {
return NODE_RESIZE_NONE;
}
NodeResizeDirection dir = NODE_RESIZE_NONE;
const rctf &bounds = node->runtime->draw_bounds;
if (x > bounds.xmax - size && x <= bounds.xmax && y >= bounds.ymin && y < bounds.ymax) {
dir |= NODE_RESIZE_RIGHT;
}
if (x >= bounds.xmin && x < bounds.xmin + size && y >= bounds.ymin && y < bounds.ymax) {
dir |= NODE_RESIZE_LEFT;
}
if (x >= bounds.xmin && x < bounds.xmax && y >= bounds.ymax - size && y < bounds.ymax) {
dir |= NODE_RESIZE_TOP;
}
if (x >= bounds.xmin && x < bounds.xmax && y >= bounds.ymin && y < bounds.ymin + size) {
dir |= NODE_RESIZE_BOTTOM;
}
return dir;
}
if (node->flag & NODE_HIDDEN) {
/* right part of node */
rctf bounds = node->runtime->draw_bounds;
bounds.xmin = node->runtime->draw_bounds.xmax - 1.0f * U.widget_unit;
if (BLI_rctf_isect_pt(&bounds, x, y)) {
return NODE_RESIZE_RIGHT;
}
return NODE_RESIZE_NONE;
}
const rctf &bounds = node->runtime->draw_bounds;
NodeResizeDirection dir = NODE_RESIZE_NONE;
if (x >= bounds.xmax - size && x < bounds.xmax && y >= bounds.ymin && y < bounds.ymax) {
dir |= NODE_RESIZE_RIGHT;
}
if (x >= bounds.xmin && x < bounds.xmin + size && y >= bounds.ymin && y < bounds.ymax) {
dir |= NODE_RESIZE_LEFT;
}
return dir;
}
/* ****************** BUTTON CALLBACKS FOR COMMON NODES ***************** */
static void node_draw_buttons_group(uiLayout *layout, bContext *C, PointerRNA *ptr)
{
uiTemplateIDBrowse(layout, C, ptr, "node_tree", nullptr, nullptr, nullptr);
}
static void node_buts_frame_ex(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "label_size", DEFAULT_FLAGS, IFACE_("Label Size"), ICON_NONE);
uiItemR(layout, ptr, "shrink", DEFAULT_FLAGS, IFACE_("Shrink"), ICON_NONE);
uiItemR(layout, ptr, "text", DEFAULT_FLAGS, std::nullopt, ICON_NONE);
}
static void node_common_set_butfunc(blender::bke::bNodeType *ntype)
{
switch (ntype->type_legacy) {
case NODE_GROUP:
ntype->draw_buttons = node_draw_buttons_group;
break;
case NODE_FRAME:
ntype->draw_buttons_ex = node_buts_frame_ex;
break;
}
}
/* ****************** BUTTON CALLBACKS FOR SHADER NODES ***************** */
static void node_buts_image_user(uiLayout *layout,
bContext *C,
PointerRNA *ptr,
PointerRNA *imaptr,
PointerRNA *iuserptr,
const bool show_layer_selection,
const bool show_color_management)
{
Image *image = (Image *)imaptr->data;
if (!image) {
return;
}
ImageUser *iuser = (ImageUser *)iuserptr->data;
uiLayout *col = uiLayoutColumn(layout, false);
uiItemR(col, imaptr, "source", DEFAULT_FLAGS, "", ICON_NONE);
const int source = RNA_enum_get(imaptr, "source");
if (source == IMA_SRC_SEQUENCE) {
/* don't use iuser->framenr directly
* because it may not be updated if auto-refresh is off */
Scene *scene = CTX_data_scene(C);
char numstr[32];
const int framenr = BKE_image_user_frame_get(iuser, scene->r.cfra, nullptr);
SNPRINTF(numstr, IFACE_("Frame: %d"), framenr);
uiItemL(layout, numstr, ICON_NONE);
}
if (ELEM(source, IMA_SRC_SEQUENCE, IMA_SRC_MOVIE)) {
col = uiLayoutColumn(layout, true);
uiItemR(col, ptr, "frame_duration", DEFAULT_FLAGS, std::nullopt, ICON_NONE);
uiItemR(col, ptr, "frame_start", DEFAULT_FLAGS, std::nullopt, ICON_NONE);
uiItemR(col, ptr, "frame_offset", DEFAULT_FLAGS, std::nullopt, ICON_NONE);
uiItemR(col, ptr, "use_cyclic", DEFAULT_FLAGS, std::nullopt, ICON_NONE);
uiItemR(col, ptr, "use_auto_refresh", DEFAULT_FLAGS, std::nullopt, ICON_NONE);
}
if (show_layer_selection && RNA_enum_get(imaptr, "type") == IMA_TYPE_MULTILAYER &&
RNA_boolean_get(ptr, "has_layers"))
{
col = uiLayoutColumn(layout, false);
uiItemR(col, ptr, "layer", DEFAULT_FLAGS, std::nullopt, ICON_NONE);
}
if (show_color_management) {
uiLayout *split = uiLayoutSplit(layout, 0.33f, true);
PointerRNA colorspace_settings_ptr = RNA_pointer_get(imaptr, "colorspace_settings");
uiItemL(split, IFACE_("Color Space"), ICON_NONE);
uiItemR(split, &colorspace_settings_ptr, "name", DEFAULT_FLAGS, "", ICON_NONE);
if (image->source != IMA_SRC_GENERATED) {
split = uiLayoutSplit(layout, 0.33f, true);
uiItemL(split, IFACE_("Alpha"), ICON_NONE);
uiItemR(split, imaptr, "alpha_mode", DEFAULT_FLAGS, "", ICON_NONE);
bool is_data = IMB_colormanagement_space_name_is_data(image->colorspace_settings.name);
uiLayoutSetActive(split, !is_data);
}
/* Avoid losing changes image is painted. */
if (BKE_image_is_dirty((Image *)imaptr->data)) {
uiLayoutSetEnabled(split, false);
}
}
}
static void node_shader_buts_tex_image(uiLayout *layout, bContext *C, PointerRNA *ptr)
{
PointerRNA imaptr = RNA_pointer_get(ptr, "image");
PointerRNA iuserptr = RNA_pointer_get(ptr, "image_user");
uiLayoutSetContextPointer(layout, "image_user", &iuserptr);
uiTemplateID(layout, C, ptr, "image", "IMAGE_OT_new", "IMAGE_OT_open", nullptr);
uiItemR(layout, ptr, "interpolation", DEFAULT_FLAGS, "", ICON_NONE);
uiItemR(layout, ptr, "projection", DEFAULT_FLAGS, "", ICON_NONE);
if (RNA_enum_get(ptr, "projection") == SHD_PROJ_BOX) {
uiItemR(layout, ptr, "projection_blend", DEFAULT_FLAGS, IFACE_("Blend"), ICON_NONE);
}
uiItemR(layout, ptr, "extension", DEFAULT_FLAGS, "", ICON_NONE);
/* NOTE: image user properties used directly here, unlike compositor image node,
* which redefines them in the node struct RNA to get proper updates.
*/
node_buts_image_user(layout, C, &iuserptr, &imaptr, &iuserptr, false, true);
}
static void node_shader_buts_tex_image_ex(uiLayout *layout, bContext *C, PointerRNA *ptr)
{
PointerRNA iuserptr = RNA_pointer_get(ptr, "image_user");
uiTemplateImage(layout, C, ptr, "image", &iuserptr, false, false);
}
static void node_shader_buts_tex_environment(uiLayout *layout, bContext *C, PointerRNA *ptr)
{
PointerRNA imaptr = RNA_pointer_get(ptr, "image");
PointerRNA iuserptr = RNA_pointer_get(ptr, "image_user");
uiLayoutSetContextPointer(layout, "image_user", &iuserptr);
uiTemplateID(layout, C, ptr, "image", "IMAGE_OT_new", "IMAGE_OT_open", nullptr);
uiItemR(layout, ptr, "interpolation", DEFAULT_FLAGS, "", ICON_NONE);
uiItemR(layout, ptr, "projection", DEFAULT_FLAGS, "", ICON_NONE);
node_buts_image_user(layout, C, &iuserptr, &imaptr, &iuserptr, false, true);
}
static void node_shader_buts_tex_environment_ex(uiLayout *layout, bContext *C, PointerRNA *ptr)
{
PointerRNA iuserptr = RNA_pointer_get(ptr, "image_user");
uiTemplateImage(layout, C, ptr, "image", &iuserptr, false, false);
uiItemR(layout, ptr, "interpolation", DEFAULT_FLAGS, IFACE_("Interpolation"), ICON_NONE);
uiItemR(layout, ptr, "projection", DEFAULT_FLAGS, IFACE_("Projection"), ICON_NONE);
}
static void node_shader_buts_displacement(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "space", DEFAULT_FLAGS, "", ICON_NONE);
}
static void node_shader_buts_glossy(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "distribution", DEFAULT_FLAGS, "", ICON_NONE);
}
static void node_buts_output_shader(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "target", DEFAULT_FLAGS, "", ICON_NONE);
}
static void node_shader_buts_scatter(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "phase", DEFAULT_FLAGS, "", ICON_NONE);
}
/* only once called */
static void node_shader_set_butfunc(blender::bke::bNodeType *ntype)
{
switch (ntype->type_legacy) {
case SH_NODE_NORMAL:
ntype->draw_buttons = node_buts_normal;
break;
case SH_NODE_CURVE_VEC:
ntype->draw_buttons = node_buts_curvevec;
break;
case SH_NODE_CURVE_RGB:
ntype->draw_buttons = node_buts_curvecol;
break;
case SH_NODE_CURVE_FLOAT:
ntype->draw_buttons = node_buts_curvefloat;
break;
case SH_NODE_VALUE:
ntype->draw_buttons = node_buts_value;
break;
case SH_NODE_RGB:
ntype->draw_buttons = node_buts_rgb;
break;
case SH_NODE_MIX_RGB_LEGACY:
ntype->draw_buttons = node_buts_mix_rgb;
break;
case SH_NODE_VALTORGB:
ntype->draw_buttons = node_buts_colorramp;
break;
case SH_NODE_MATH:
ntype->draw_buttons = node_buts_math;
break;
case SH_NODE_COMBINE_COLOR:
case SH_NODE_SEPARATE_COLOR:
ntype->draw_buttons = node_buts_combsep_color;
break;
case SH_NODE_TEX_IMAGE:
ntype->draw_buttons = node_shader_buts_tex_image;
ntype->draw_buttons_ex = node_shader_buts_tex_image_ex;
break;
case SH_NODE_TEX_ENVIRONMENT:
ntype->draw_buttons = node_shader_buts_tex_environment;
ntype->draw_buttons_ex = node_shader_buts_tex_environment_ex;
break;
case SH_NODE_DISPLACEMENT:
case SH_NODE_VECTOR_DISPLACEMENT:
ntype->draw_buttons = node_shader_buts_displacement;
break;
case SH_NODE_BSDF_GLASS:
case SH_NODE_BSDF_REFRACTION:
ntype->draw_buttons = node_shader_buts_glossy;
break;
case SH_NODE_OUTPUT_MATERIAL:
case SH_NODE_OUTPUT_LIGHT:
case SH_NODE_OUTPUT_WORLD:
ntype->draw_buttons = node_buts_output_shader;
break;
case SH_NODE_VOLUME_SCATTER:
ntype->draw_buttons = node_shader_buts_scatter;
break;
}
}
/* ****************** BUTTON CALLBACKS FOR COMPOSITE NODES ***************** */
static void node_buts_image_views(uiLayout *layout,
bContext * /*C*/,
PointerRNA *ptr,
PointerRNA *imaptr)
{
uiLayout *col;
if (!imaptr->data) {
return;
}
col = uiLayoutColumn(layout, false);
if (RNA_boolean_get(ptr, "has_views")) {
if (RNA_enum_get(ptr, "view") == 0) {
uiItemR(col, ptr, "view", DEFAULT_FLAGS, std::nullopt, ICON_CAMERA_STEREO);
}
else {
uiItemR(col, ptr, "view", DEFAULT_FLAGS, std::nullopt, ICON_SCENE);
}
}
}
static void node_composit_buts_image(uiLayout *layout, bContext *C, PointerRNA *ptr)
{
bNode *node = (bNode *)ptr->data;
PointerRNA iuserptr = RNA_pointer_create(ptr->owner_id, &RNA_ImageUser, node->storage);
uiLayoutSetContextPointer(layout, "image_user", &iuserptr);
uiTemplateID(layout, C, ptr, "image", "IMAGE_OT_new", "IMAGE_OT_open", nullptr);
if (!node->id) {
return;
}
PointerRNA imaptr = RNA_pointer_get(ptr, "image");
node_buts_image_user(layout, C, ptr, &imaptr, &iuserptr, true, true);
node_buts_image_views(layout, C, ptr, &imaptr);
}
static void node_composit_buts_image_ex(uiLayout *layout, bContext *C, PointerRNA *ptr)
{
bNode *node = (bNode *)ptr->data;
PointerRNA iuserptr = RNA_pointer_create(ptr->owner_id, &RNA_ImageUser, node->storage);
uiLayoutSetContextPointer(layout, "image_user", &iuserptr);
uiTemplateImage(layout, C, ptr, "image", &iuserptr, false, true);
}
static void node_composit_buts_huecorrect(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
bNode *node = (bNode *)ptr->data;
CurveMapping *cumap = (CurveMapping *)node->storage;
if (_sample_col[0] != SAMPLE_FLT_ISNONE) {
cumap->flag |= CUMA_DRAW_SAMPLE;
copy_v3_v3(cumap->sample, _sample_col);
}
else {
cumap->flag &= ~CUMA_DRAW_SAMPLE;
}
uiTemplateCurveMapping(layout, ptr, "mapping", 'h', false, false, false, false);
}
static void node_composit_buts_ycc(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "mode", DEFAULT_FLAGS, "", ICON_NONE);
}
static void node_composit_buts_combsep_color(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
bNode *node = (bNode *)ptr->data;
NodeCMPCombSepColor *storage = (NodeCMPCombSepColor *)node->storage;
uiItemR(layout, ptr, "mode", DEFAULT_FLAGS, "", ICON_NONE);
if (storage->mode == CMP_NODE_COMBSEP_COLOR_YCC) {
uiItemR(layout, ptr, "ycc_mode", DEFAULT_FLAGS, "", ICON_NONE);
}
}
static void node_composit_backdrop_boxmask(
SpaceNode *snode, ImBuf *backdrop, bNode *node, int x, int y)
{
NodeBoxMask *boxmask = (NodeBoxMask *)node->storage;
const float backdropWidth = backdrop->x;
const float backdropHeight = backdrop->y;
const float aspect = backdropWidth / backdropHeight;
const float rad = -boxmask->rotation;
const float cosine = cosf(rad);
const float sine = sinf(rad);
const float halveBoxWidth = backdropWidth * (boxmask->width / 2.0f);
const float halveBoxHeight = backdropHeight * (boxmask->height / 2.0f) * aspect;
float cx, cy, x1, x2, x3, x4;
float y1, y2, y3, y4;
cx = x + snode->zoom * backdropWidth * boxmask->x;
cy = y + snode->zoom * backdropHeight * boxmask->y;
x1 = cx - (cosine * halveBoxWidth + sine * halveBoxHeight) * snode->zoom;
x2 = cx - (cosine * -halveBoxWidth + sine * halveBoxHeight) * snode->zoom;
x3 = cx - (cosine * -halveBoxWidth + sine * -halveBoxHeight) * snode->zoom;
x4 = cx - (cosine * halveBoxWidth + sine * -halveBoxHeight) * snode->zoom;
y1 = cy - (-sine * halveBoxWidth + cosine * halveBoxHeight) * snode->zoom;
y2 = cy - (-sine * -halveBoxWidth + cosine * halveBoxHeight) * snode->zoom;
y3 = cy - (-sine * -halveBoxWidth + cosine * -halveBoxHeight) * snode->zoom;
y4 = cy - (-sine * halveBoxWidth + cosine * -halveBoxHeight) * snode->zoom;
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformColor3f(1.0f, 1.0f, 1.0f);
immBegin(GPU_PRIM_LINE_LOOP, 4);
immVertex2f(pos, x1, y1);
immVertex2f(pos, x2, y2);
immVertex2f(pos, x3, y3);
immVertex2f(pos, x4, y4);
immEnd();
immUnbindProgram();
}
static void node_composit_backdrop_ellipsemask(
SpaceNode *snode, ImBuf *backdrop, bNode *node, int x, int y)
{
NodeEllipseMask *ellipsemask = (NodeEllipseMask *)node->storage;
const float backdropWidth = backdrop->x;
const float backdropHeight = backdrop->y;
const float aspect = backdropWidth / backdropHeight;
const float rad = -ellipsemask->rotation;
const float cosine = cosf(rad);
const float sine = sinf(rad);
const float halveBoxWidth = backdropWidth * (ellipsemask->width / 2.0f);
const float halveBoxHeight = backdropHeight * (ellipsemask->height / 2.0f) * aspect;
float cx, cy, x1, x2, x3, x4;
float y1, y2, y3, y4;
cx = x + snode->zoom * backdropWidth * ellipsemask->x;
cy = y + snode->zoom * backdropHeight * ellipsemask->y;
x1 = cx - (cosine * halveBoxWidth + sine * halveBoxHeight) * snode->zoom;
x2 = cx - (cosine * -halveBoxWidth + sine * halveBoxHeight) * snode->zoom;
x3 = cx - (cosine * -halveBoxWidth + sine * -halveBoxHeight) * snode->zoom;
x4 = cx - (cosine * halveBoxWidth + sine * -halveBoxHeight) * snode->zoom;
y1 = cy - (-sine * halveBoxWidth + cosine * halveBoxHeight) * snode->zoom;
y2 = cy - (-sine * -halveBoxWidth + cosine * halveBoxHeight) * snode->zoom;
y3 = cy - (-sine * -halveBoxWidth + cosine * -halveBoxHeight) * snode->zoom;
y4 = cy - (-sine * halveBoxWidth + cosine * -halveBoxHeight) * snode->zoom;
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformColor3f(1.0f, 1.0f, 1.0f);
immBegin(GPU_PRIM_LINE_LOOP, 4);
immVertex2f(pos, x1, y1);
immVertex2f(pos, x2, y2);
immVertex2f(pos, x3, y3);
immVertex2f(pos, x4, y4);
immEnd();
immUnbindProgram();
}
static void node_composit_buts_cryptomatte_legacy(uiLayout *layout,
bContext * /*C*/,
PointerRNA *ptr)
{
uiLayout *col = uiLayoutColumn(layout, true);
uiItemL(col, IFACE_("Matte Objects:"), ICON_NONE);
uiLayout *row = uiLayoutRow(col, true);
uiTemplateCryptoPicker(row, ptr, "add", ICON_ADD);
uiTemplateCryptoPicker(row, ptr, "remove", ICON_REMOVE);
uiItemR(col, ptr, "matte_id", DEFAULT_FLAGS, "", ICON_NONE);
}
static void node_composit_buts_cryptomatte_legacy_ex(uiLayout *layout,
bContext * /*C*/,
PointerRNA * /*ptr*/)
{
uiItemO(layout, IFACE_("Add Crypto Layer"), ICON_ADD, "NODE_OT_cryptomatte_layer_add");
uiItemO(layout, IFACE_("Remove Crypto Layer"), ICON_REMOVE, "NODE_OT_cryptomatte_layer_remove");
}
static void node_composit_buts_cryptomatte(uiLayout *layout, bContext *C, PointerRNA *ptr)
{
bNode *node = (bNode *)ptr->data;
uiLayout *row = uiLayoutRow(layout, true);
uiItemR(row, ptr, "source", DEFAULT_FLAGS | UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
uiLayout *col = uiLayoutColumn(layout, false);
if (node->custom1 == CMP_NODE_CRYPTOMATTE_SOURCE_RENDER) {
uiTemplateID(col, C, ptr, "scene", nullptr, nullptr, nullptr);
}
else {
uiTemplateID(col, C, ptr, "image", nullptr, "IMAGE_OT_open", nullptr);
NodeCryptomatte *crypto = (NodeCryptomatte *)node->storage;
PointerRNA imaptr = RNA_pointer_get(ptr, "image");
PointerRNA iuserptr = RNA_pointer_create((ID *)ptr->owner_id, &RNA_ImageUser, &crypto->iuser);
uiLayoutSetContextPointer(layout, "image_user", &iuserptr);
node_buts_image_user(col, C, ptr, &imaptr, &iuserptr, false, false);
node_buts_image_views(col, C, ptr, &imaptr);
}
col = uiLayoutColumn(layout, true);
uiItemR(col, ptr, "layer_name", UI_ITEM_NONE, "", ICON_NONE);
uiItemL(col, IFACE_("Matte ID:"), ICON_NONE);
row = uiLayoutRow(col, true);
uiItemR(row, ptr, "matte_id", DEFAULT_FLAGS, "", ICON_NONE);
uiTemplateCryptoPicker(row, ptr, "add", ICON_ADD);
uiTemplateCryptoPicker(row, ptr, "remove", ICON_REMOVE);
}
/* only once called */
static void node_composit_set_butfunc(blender::bke::bNodeType *ntype)
{
switch (ntype->type_legacy) {
case CMP_NODE_IMAGE:
ntype->draw_buttons = node_composit_buts_image;
ntype->draw_buttons_ex = node_composit_buts_image_ex;
break;
case CMP_NODE_NORMAL:
ntype->draw_buttons = node_buts_normal;
break;
case CMP_NODE_CURVE_RGB:
ntype->draw_buttons = node_buts_curvecol;
break;
case CMP_NODE_VALUE:
ntype->draw_buttons = node_buts_value;
break;
case CMP_NODE_RGB:
ntype->draw_buttons = node_buts_rgb;
break;
case CMP_NODE_MIX_RGB:
ntype->draw_buttons = node_buts_mix_rgb;
break;
case CMP_NODE_VALTORGB:
ntype->draw_buttons = node_buts_colorramp;
break;
case CMP_NODE_TIME:
ntype->draw_buttons = node_buts_time;
break;
case CMP_NODE_TEXTURE:
ntype->draw_buttons = node_buts_texture;
break;
case CMP_NODE_MATH:
ntype->draw_buttons = node_buts_math;
break;
case CMP_NODE_HUECORRECT:
ntype->draw_buttons = node_composit_buts_huecorrect;
break;
case CMP_NODE_COMBINE_COLOR:
case CMP_NODE_SEPARATE_COLOR:
ntype->draw_buttons = node_composit_buts_combsep_color;
break;
case CMP_NODE_COMBYCCA_LEGACY:
case CMP_NODE_SEPYCCA_LEGACY:
ntype->draw_buttons = node_composit_buts_ycc;
break;
case CMP_NODE_MASK_BOX:
ntype->draw_backdrop = node_composit_backdrop_boxmask;
break;
case CMP_NODE_MASK_ELLIPSE:
ntype->draw_backdrop = node_composit_backdrop_ellipsemask;
break;
case CMP_NODE_CRYPTOMATTE:
ntype->draw_buttons = node_composit_buts_cryptomatte;
break;
case CMP_NODE_CRYPTOMATTE_LEGACY:
ntype->draw_buttons = node_composit_buts_cryptomatte_legacy;
ntype->draw_buttons_ex = node_composit_buts_cryptomatte_legacy_ex;
break;
}
}
/* ****************** BUTTON CALLBACKS FOR TEXTURE NODES ***************** */
static void node_texture_buts_bricks(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiLayout *col;
col = uiLayoutColumn(layout, true);
uiItemR(col, ptr, "offset", DEFAULT_FLAGS | UI_ITEM_R_SLIDER, IFACE_("Offset"), ICON_NONE);
uiItemR(col, ptr, "offset_frequency", DEFAULT_FLAGS, IFACE_("Frequency"), ICON_NONE);
col = uiLayoutColumn(layout, true);
uiItemR(col, ptr, "squash", DEFAULT_FLAGS, IFACE_("Squash"), ICON_NONE);
uiItemR(col, ptr, "squash_frequency", DEFAULT_FLAGS, IFACE_("Frequency"), ICON_NONE);
}
static void node_texture_buts_proc(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
bNode *node = (bNode *)ptr->data;
ID *id = ptr->owner_id;
Tex *tex = (Tex *)node->storage;
uiLayout *col, *row;
PointerRNA tex_ptr = RNA_pointer_create(id, &RNA_Texture, tex);
col = uiLayoutColumn(layout, false);
switch (tex->type) {
case TEX_BLEND:
uiItemR(col, &tex_ptr, "progression", DEFAULT_FLAGS, "", ICON_NONE);
row = uiLayoutRow(col, false);
uiItemR(row,
&tex_ptr,
"use_flip_axis",
DEFAULT_FLAGS | UI_ITEM_R_EXPAND,
std::nullopt,
ICON_NONE);
break;
case TEX_MARBLE:
row = uiLayoutRow(col, false);
uiItemR(
row, &tex_ptr, "marble_type", DEFAULT_FLAGS | UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
row = uiLayoutRow(col, false);
uiItemR(
row, &tex_ptr, "noise_type", DEFAULT_FLAGS | UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
row = uiLayoutRow(col, false);
uiItemR(row, &tex_ptr, "noise_basis", DEFAULT_FLAGS, "", ICON_NONE);
row = uiLayoutRow(col, false);
uiItemR(row,
&tex_ptr,
"noise_basis_2",
DEFAULT_FLAGS | UI_ITEM_R_EXPAND,
std::nullopt,
ICON_NONE);
break;
case TEX_MAGIC:
uiItemR(col, &tex_ptr, "noise_depth", DEFAULT_FLAGS, std::nullopt, ICON_NONE);
break;
case TEX_STUCCI:
row = uiLayoutRow(col, false);
uiItemR(
row, &tex_ptr, "stucci_type", DEFAULT_FLAGS | UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
row = uiLayoutRow(col, false);
uiItemR(
row, &tex_ptr, "noise_type", DEFAULT_FLAGS | UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
uiItemR(col, &tex_ptr, "noise_basis", DEFAULT_FLAGS, "", ICON_NONE);
break;
case TEX_WOOD:
uiItemR(col, &tex_ptr, "noise_basis", DEFAULT_FLAGS, "", ICON_NONE);
uiItemR(col, &tex_ptr, "wood_type", DEFAULT_FLAGS, "", ICON_NONE);
row = uiLayoutRow(col, false);
uiItemR(row,
&tex_ptr,
"noise_basis_2",
DEFAULT_FLAGS | UI_ITEM_R_EXPAND,
std::nullopt,
ICON_NONE);
row = uiLayoutRow(col, false);
uiLayoutSetActive(row, !ELEM(tex->stype, TEX_BAND, TEX_RING));
uiItemR(
row, &tex_ptr, "noise_type", DEFAULT_FLAGS | UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
break;
case TEX_CLOUDS:
uiItemR(col, &tex_ptr, "noise_basis", DEFAULT_FLAGS, "", ICON_NONE);
row = uiLayoutRow(col, false);
uiItemR(
row, &tex_ptr, "cloud_type", DEFAULT_FLAGS | UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
row = uiLayoutRow(col, false);
uiItemR(
row, &tex_ptr, "noise_type", DEFAULT_FLAGS | UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
uiItemR(col,
&tex_ptr,
"noise_depth",
DEFAULT_FLAGS | UI_ITEM_R_EXPAND,
IFACE_("Depth"),
ICON_NONE);
break;
case TEX_DISTNOISE:
uiItemR(col, &tex_ptr, "noise_basis", DEFAULT_FLAGS, "", ICON_NONE);
uiItemR(col, &tex_ptr, "noise_distortion", DEFAULT_FLAGS, "", ICON_NONE);
break;
case TEX_MUSGRAVE:
uiItemR(col, &tex_ptr, "musgrave_type", DEFAULT_FLAGS, "", ICON_NONE);
uiItemR(col, &tex_ptr, "noise_basis", DEFAULT_FLAGS, "", ICON_NONE);
break;
case TEX_VORONOI:
uiItemR(col, &tex_ptr, "distance_metric", DEFAULT_FLAGS, "", ICON_NONE);
if (tex->vn_distm == TEX_MINKOVSKY) {
uiItemR(col, &tex_ptr, "minkovsky_exponent", DEFAULT_FLAGS, std::nullopt, ICON_NONE);
}
uiItemR(col, &tex_ptr, "color_mode", DEFAULT_FLAGS, "", ICON_NONE);
break;
}
}
static void node_texture_buts_image(uiLayout *layout, bContext *C, PointerRNA *ptr)
{
uiTemplateID(layout, C, ptr, "image", "IMAGE_OT_new", "IMAGE_OT_open", nullptr);
}
static void node_texture_buts_image_ex(uiLayout *layout, bContext *C, PointerRNA *ptr)
{
bNode *node = (bNode *)ptr->data;
PointerRNA iuserptr = RNA_pointer_create(ptr->owner_id, &RNA_ImageUser, node->storage);
uiTemplateImage(layout, C, ptr, "image", &iuserptr, false, false);
}
static void node_texture_buts_output(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "filepath", DEFAULT_FLAGS, "", ICON_NONE);
}
static void node_texture_buts_combsep_color(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "mode", DEFAULT_FLAGS, "", ICON_NONE);
}
/* only once called */
static void node_texture_set_butfunc(blender::bke::bNodeType *ntype)
{
if (ntype->type_legacy >= TEX_NODE_PROC && ntype->type_legacy < TEX_NODE_PROC_MAX) {
ntype->draw_buttons = node_texture_buts_proc;
}
else {
switch (ntype->type_legacy) {
case TEX_NODE_MATH:
ntype->draw_buttons = node_buts_math;
break;
case TEX_NODE_MIX_RGB:
ntype->draw_buttons = node_buts_mix_rgb;
break;
case TEX_NODE_VALTORGB:
ntype->draw_buttons = node_buts_colorramp;
break;
case TEX_NODE_CURVE_RGB:
ntype->draw_buttons = node_buts_curvecol;
break;
case TEX_NODE_CURVE_TIME:
ntype->draw_buttons = node_buts_time;
break;
case TEX_NODE_TEXTURE:
ntype->draw_buttons = node_buts_texture;
break;
case TEX_NODE_BRICKS:
ntype->draw_buttons = node_texture_buts_bricks;
break;
case TEX_NODE_IMAGE:
ntype->draw_buttons = node_texture_buts_image;
ntype->draw_buttons_ex = node_texture_buts_image_ex;
break;
case TEX_NODE_OUTPUT:
ntype->draw_buttons = node_texture_buts_output;
break;
case TEX_NODE_COMBINE_COLOR:
case TEX_NODE_SEPARATE_COLOR:
ntype->draw_buttons = node_texture_buts_combsep_color;
break;
}
}
}
/* -------------------------------------------------------------------- */
/** \name Init Draw Callbacks For All Tree Types
*
* Only called on node initialization, once.
* \{ */
static void node_property_update_default(Main *bmain, Scene * /*scene*/, PointerRNA *ptr)
{
bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
bNode *node = (bNode *)ptr->data;
BKE_ntree_update_tag_node_property(ntree, node);
BKE_main_ensure_invariants(*bmain);
}
static void node_socket_template_properties_update(blender::bke::bNodeType *ntype,
blender::bke::bNodeSocketTemplate *stemp)
{
StructRNA *srna = ntype->rna_ext.srna;
PropertyRNA *prop = RNA_struct_type_find_property(srna, stemp->identifier);
if (prop) {
RNA_def_property_update_runtime(prop, node_property_update_default);
}
}
static void node_template_properties_update(blender::bke::bNodeType *ntype)
{
blender::bke::bNodeSocketTemplate *stemp;
if (ntype->inputs) {
for (stemp = ntype->inputs; stemp->type >= 0; stemp++) {
node_socket_template_properties_update(ntype, stemp);
}
}
if (ntype->outputs) {
for (stemp = ntype->outputs; stemp->type >= 0; stemp++) {
node_socket_template_properties_update(ntype, stemp);
}
}
}
static void node_socket_undefined_draw(bContext * /*C*/,
uiLayout *layout,
PointerRNA * /*ptr*/,
PointerRNA * /*node_ptr*/,
StringRefNull /*text*/)
{
uiItemL(layout, IFACE_("Undefined Socket Type"), ICON_ERROR);
}
static void node_socket_undefined_draw_color(bContext * /*C*/,
PointerRNA * /*ptr*/,
PointerRNA * /*node_ptr*/,
float *r_color)
{
r_color[0] = 1.0f;
r_color[1] = 0.0f;
r_color[2] = 0.0f;
r_color[3] = 1.0f;
}
static void node_socket_undefined_draw_color_simple(const bke::bNodeSocketType * /*type*/,
float *r_color)
{
r_color[0] = 1.0f;
r_color[1] = 0.0f;
r_color[2] = 0.0f;
r_color[3] = 1.0f;
}
static void node_socket_undefined_interface_draw(ID * /*id*/,
bNodeTreeInterfaceSocket * /*interface_socket*/,
bContext * /*C*/,
uiLayout *layout)
{
uiItemL(layout, IFACE_("Undefined Socket Type"), ICON_ERROR);
}
/** \} */
} // namespace blender::ed::space_node
void ED_node_init_butfuncs()
{
using namespace blender::ed::space_node;
/* Fallback types for undefined tree, nodes, sockets
* Defined in blenkernel, but not registered in type hashes.
*/
using blender::bke::NodeSocketTypeUndefined;
using blender::bke::NodeTypeUndefined;
NodeTypeUndefined.draw_buttons = nullptr;
NodeTypeUndefined.draw_buttons_ex = nullptr;
NodeSocketTypeUndefined.draw = node_socket_undefined_draw;
NodeSocketTypeUndefined.draw_color = node_socket_undefined_draw_color;
NodeSocketTypeUndefined.draw_color_simple = node_socket_undefined_draw_color_simple;
NodeSocketTypeUndefined.interface_draw = node_socket_undefined_interface_draw;
/* node type ui functions */
for (blender::bke::bNodeType *ntype : blender::bke::node_types_get()) {
node_common_set_butfunc(ntype);
node_composit_set_butfunc(ntype);
node_shader_set_butfunc(ntype);
node_texture_set_butfunc(ntype);
/* define update callbacks for socket properties */
node_template_properties_update(ntype);
}
}
void ED_init_custom_node_type(blender::bke::bNodeType * /*ntype*/) {}
void ED_init_custom_node_socket_type(blender::bke::bNodeSocketType *stype)
{
stype->draw = blender::ed::space_node::node_socket_button_label;
}
namespace blender::ed::space_node {
static const float virtual_node_socket_color[4] = {0.2, 0.2, 0.2, 1.0};
/* maps standard socket integer type to a color */
static const float std_node_socket_colors[][4] = {
{0.63, 0.63, 0.63, 1.0}, /* SOCK_FLOAT */
{0.39, 0.39, 0.78, 1.0}, /* SOCK_VECTOR */
{0.78, 0.78, 0.16, 1.0}, /* SOCK_RGBA */
{0.39, 0.78, 0.39, 1.0}, /* SOCK_SHADER */
{0.80, 0.65, 0.84, 1.0}, /* SOCK_BOOLEAN */
{0.0, 0.0, 0.0, 0.0}, /* UNUSED */
{0.35, 0.55, 0.36, 1.0}, /* SOCK_INT */
{0.44, 0.70, 1.00, 1.0}, /* SOCK_STRING */
{0.93, 0.62, 0.36, 1.0}, /* SOCK_OBJECT */
{0.39, 0.22, 0.39, 1.0}, /* SOCK_IMAGE */
{0.00, 0.84, 0.64, 1.0}, /* SOCK_GEOMETRY */
{0.96, 0.96, 0.96, 1.0}, /* SOCK_COLLECTION */
{0.62, 0.31, 0.64, 1.0}, /* SOCK_TEXTURE */
{0.92, 0.46, 0.51, 1.0}, /* SOCK_MATERIAL */
{0.65, 0.39, 0.78, 1.0}, /* SOCK_ROTATION */
{0.40, 0.40, 0.40, 1.0}, /* SOCK_MENU */
{0.72, 0.20, 0.52, 1.0}, /* SOCK_MATRIX */
};
/* Callback for colors that does not depend on the socket pointer argument to get the type. */
template<int socket_type>
void std_node_socket_color_fn(bContext * /*C*/,
PointerRNA * /*ptr*/,
PointerRNA * /*node_ptr*/,
float *r_color)
{
copy_v4_v4(r_color, std_node_socket_colors[socket_type]);
};
static void std_node_socket_color_simple_fn(const bke::bNodeSocketType *type, float *r_color)
{
copy_v4_v4(r_color, std_node_socket_colors[type->type]);
};
using SocketColorFn = void (*)(bContext *C, PointerRNA *ptr, PointerRNA *node_ptr, float *r_color);
/* Callbacks for all built-in socket types. */
static const SocketColorFn std_node_socket_color_funcs[] = {
std_node_socket_color_fn<SOCK_FLOAT>,
std_node_socket_color_fn<SOCK_VECTOR>,
std_node_socket_color_fn<SOCK_RGBA>,
std_node_socket_color_fn<SOCK_SHADER>,
std_node_socket_color_fn<SOCK_BOOLEAN>,
nullptr /* UNUSED. */,
std_node_socket_color_fn<SOCK_INT>,
std_node_socket_color_fn<SOCK_STRING>,
std_node_socket_color_fn<SOCK_OBJECT>,
std_node_socket_color_fn<SOCK_IMAGE>,
std_node_socket_color_fn<SOCK_GEOMETRY>,
std_node_socket_color_fn<SOCK_COLLECTION>,
std_node_socket_color_fn<SOCK_TEXTURE>,
std_node_socket_color_fn<SOCK_MATERIAL>,
std_node_socket_color_fn<SOCK_ROTATION>,
std_node_socket_color_fn<SOCK_MENU>,
std_node_socket_color_fn<SOCK_MATRIX>,
};
/* draw function for file output node sockets,
* displays only sub-path and format, no value button */
static void node_file_output_socket_draw(bContext *C,
uiLayout *layout,
PointerRNA *ptr,
PointerRNA *node_ptr)
{
bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
bNodeSocket *sock = (bNodeSocket *)ptr->data;
uiLayout *row;
PointerRNA inputptr;
row = uiLayoutRow(layout, false);
PointerRNA imfptr = RNA_pointer_get(node_ptr, "format");
int imtype = RNA_enum_get(&imfptr, "file_format");
if (imtype == R_IMF_IMTYPE_MULTILAYER) {
NodeImageMultiFileSocket *input = (NodeImageMultiFileSocket *)sock->storage;
inputptr = RNA_pointer_create(&ntree->id, &RNA_NodeOutputFileSlotLayer, input);
uiItemL(row, input->layer, ICON_NONE);
}
else {
NodeImageMultiFileSocket *input = (NodeImageMultiFileSocket *)sock->storage;
uiBlock *block;
inputptr = RNA_pointer_create(&ntree->id, &RNA_NodeOutputFileSlotFile, input);
uiItemL(row, input->path, ICON_NONE);
if (!RNA_boolean_get(&inputptr, "use_node_format")) {
imfptr = RNA_pointer_get(&inputptr, "format");
}
const char *imtype_name;
PropertyRNA *imtype_prop = RNA_struct_find_property(&imfptr, "file_format");
RNA_property_enum_name((bContext *)C,
&imfptr,
imtype_prop,
RNA_property_enum_get(&imfptr, imtype_prop),
&imtype_name);
block = uiLayoutGetBlock(row);
UI_block_emboss_set(block, UI_EMBOSS_PULLDOWN);
uiItemL(row, imtype_name, ICON_NONE);
UI_block_emboss_set(block, UI_EMBOSS_NONE);
}
}
static bool socket_needs_attribute_search(bNode &node, bNodeSocket &socket)
{
const nodes::NodeDeclaration *node_decl = node.declaration();
if (node_decl == nullptr) {
return false;
}
if (node_decl->skip_updating_sockets) {
return false;
}
if (socket.in_out == SOCK_OUT) {
return false;
}
const int socket_index = BLI_findindex(&node.inputs, &socket);
return node_decl->inputs[socket_index]->is_attribute_name;
}
static void draw_gizmo_pin_icon(uiLayout *layout, PointerRNA *socket_ptr)
{
uiItemR(layout, socket_ptr, "pin_gizmo", UI_ITEM_NONE, "", ICON_GIZMO);
}
static void draw_node_socket_name_editable(uiLayout *layout,
bNodeSocket *sock,
const StringRef text)
{
if (sock->runtime->declaration) {
if (sock->runtime->declaration->socket_name_rna) {
uiLayoutSetEmboss(layout, UI_EMBOSS_NONE);
uiItemR(layout,
const_cast<PointerRNA *>(&sock->runtime->declaration->socket_name_rna->owner),
sock->runtime->declaration->socket_name_rna->property_name,
UI_ITEM_NONE,
"",
ICON_NONE);
return;
}
}
uiItemL(layout, text, ICON_NONE);
}
static void draw_node_socket_without_value(uiLayout *layout,
bNodeSocket *sock,
const StringRef text)
{
draw_node_socket_name_editable(layout, sock, text);
}
static void std_node_socket_draw(
bContext *C, uiLayout *layout, PointerRNA *ptr, PointerRNA *node_ptr, StringRefNull text)
{
bNode *node = (bNode *)node_ptr->data;
bNodeSocket *sock = (bNodeSocket *)ptr->data;
bNodeTree *tree = reinterpret_cast<bNodeTree *>(ptr->owner_id);
int type = sock->typeinfo->type;
// int subtype = sock->typeinfo->subtype;
if (sock->is_input() && !sock->affects_node_output()) {
uiLayoutSetActive(layout, false);
}
/* XXX not nice, eventually give this node its own socket type ... */
if (node->type_legacy == CMP_NODE_OUTPUT_FILE) {
node_file_output_socket_draw(C, layout, ptr, node_ptr);
return;
}
const bool has_gizmo = tree->runtime->gizmo_propagation ?
tree->runtime->gizmo_propagation->gizmo_endpoint_sockets.contains(
sock) :
false;
if (has_gizmo) {
if (sock->in_out == SOCK_OUT && ELEM(node->type_legacy,
SH_NODE_VALUE,
FN_NODE_INPUT_VECTOR,
FN_NODE_INPUT_INT,
FN_NODE_INPUT_BOOL,
FN_NODE_INPUT_ROTATION,
NODE_GROUP_INPUT))
{
if (node->is_group_input()) {
uiLayout *row = uiLayoutRow(layout, false);
uiLayoutSetAlignment(row, UI_LAYOUT_ALIGN_RIGHT);
node_socket_button_label(C, row, ptr, node_ptr, text);
uiItemL(row, "", ICON_GIZMO);
}
else if (nodes::partial_eval::is_supported_value_node(*node)) {
uiLayout *row = uiLayoutRow(layout, false);
uiLayoutSetAlignment(row, UI_LAYOUT_ALIGN_RIGHT);
node_socket_button_label(C, row, ptr, node_ptr, text);
draw_gizmo_pin_icon(row, ptr);
}
return;
}
if (sock->in_out == SOCK_IN && sock->index() == 0 &&
nodes::gizmos::is_builtin_gizmo_node(*node))
{
uiLayout *row = uiLayoutRow(layout, false);
node_socket_button_label(C, row, ptr, node_ptr, text);
draw_gizmo_pin_icon(row, ptr);
return;
}
}
if ((sock->in_out == SOCK_OUT) || (sock->flag & SOCK_HIDE_VALUE) || sock->is_logically_linked())
{
draw_node_socket_without_value(layout, sock, text);
return;
}
const StringRefNull label = text;
text = (sock->flag & SOCK_HIDE_LABEL) ? "" : text;
/* Some socket types draw the gizmo icon in a special way to look better. All others use a
* fallback default code path. */
bool gizmo_handled = false;
switch (type) {
case SOCK_FLOAT:
case SOCK_INT:
case SOCK_BOOLEAN:
uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, ICON_NONE);
break;
case SOCK_VECTOR:
if (sock->flag & SOCK_COMPACT) {
uiTemplateComponentMenu(layout, ptr, "default_value", text);
}
else {
if (sock->typeinfo->subtype == PROP_DIRECTION) {
uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, "", ICON_NONE);
}
else {
uiLayout *column = uiLayoutColumn(layout, false);
{
uiLayout *row = uiLayoutRow(column, true);
draw_node_socket_name_editable(row, sock, text);
if (has_gizmo) {
draw_gizmo_pin_icon(row, ptr);
gizmo_handled = true;
}
}
uiItemR(column, ptr, "default_value", DEFAULT_FLAGS, "", ICON_NONE);
}
}
break;
case SOCK_ROTATION: {
uiLayout *column = uiLayoutColumn(layout, false);
{
uiLayout *row = uiLayoutRow(column, true);
draw_node_socket_name_editable(row, sock, text);
if (has_gizmo) {
draw_gizmo_pin_icon(row, ptr);
gizmo_handled = true;
}
}
uiItemR(column, ptr, "default_value", DEFAULT_FLAGS, "", ICON_NONE);
break;
}
case SOCK_MATRIX: {
draw_node_socket_name_editable(layout, sock, text);
break;
}
case SOCK_RGBA: {
if (text.is_empty()) {
uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, "", ICON_NONE);
}
else {
uiLayout *row = uiLayoutSplit(layout, 0.4f, false);
uiItemL(row, text, ICON_NONE);
uiItemR(row, ptr, "default_value", DEFAULT_FLAGS, "", ICON_NONE);
}
break;
}
case SOCK_STRING: {
if (socket_needs_attribute_search(*node, *sock)) {
if (text.is_empty()) {
node_geometry_add_attribute_search_button(*C, *node, *ptr, *layout, label);
}
else {
uiLayout *row = uiLayoutSplit(layout, 0.4f, false);
uiItemL(row, text, ICON_NONE);
node_geometry_add_attribute_search_button(*C, *node, *ptr, *row);
}
}
else {
if (text.is_empty()) {
uiItemFullR(layout,
ptr,
RNA_struct_find_property(ptr, "default_value"),
-1,
0,
UI_ITEM_NONE,
"",
ICON_NONE,
label);
}
else {
uiLayout *row = uiLayoutSplit(layout, 0.4f, false);
uiItemL(row, text, ICON_NONE);
uiItemR(row, ptr, "default_value", DEFAULT_FLAGS, "", ICON_NONE);
}
}
break;
}
case SOCK_MENU: {
const bNodeSocketValueMenu *default_value =
sock->default_value_typed<bNodeSocketValueMenu>();
if (default_value->enum_items) {
if (default_value->enum_items->items.is_empty()) {
uiLayout *row = uiLayoutSplit(layout, 0.4f, false);
uiItemL(row, text, ICON_NONE);
uiItemL(row, IFACE_("No Items"), ICON_NONE);
}
else {
uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, "", ICON_NONE);
}
}
else if (default_value->has_conflict()) {
uiItemL(layout, IFACE_("Menu Error"), ICON_ERROR);
}
else {
uiItemL(layout, IFACE_("Menu Undefined"), ICON_QUESTION);
}
break;
}
case SOCK_OBJECT: {
uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, ICON_NONE);
break;
}
case SOCK_IMAGE: {
const bNodeTree *node_tree = (const bNodeTree *)node_ptr->owner_id;
if (node_tree->type == NTREE_GEOMETRY) {
if (text.is_empty()) {
uiTemplateID(layout, C, ptr, "default_value", "image.new", "image.open", nullptr);
}
else {
/* 0.3 split ratio is inconsistent, but use it here because the "New" button is large. */
uiLayout *row = uiLayoutSplit(layout, 0.3f, false);
uiItemL(row, text, ICON_NONE);
uiTemplateID(row, C, ptr, "default_value", "image.new", "image.open", nullptr);
}
}
else {
uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, ICON_NONE);
}
break;
}
case SOCK_COLLECTION: {
uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, ICON_NONE);
break;
}
case SOCK_TEXTURE: {
if (text.is_empty()) {
uiTemplateID(layout, C, ptr, "default_value", "texture.new", nullptr, nullptr);
}
else {
/* 0.3 split ratio is inconsistent, but use it here because the "New" button is large. */
uiLayout *row = uiLayoutSplit(layout, 0.3f, false);
uiItemL(row, text, ICON_NONE);
uiTemplateID(row, C, ptr, "default_value", "texture.new", nullptr, nullptr);
}
break;
}
case SOCK_MATERIAL: {
uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, ICON_NONE);
break;
}
default:
draw_node_socket_without_value(layout, sock, text);
break;
}
if (has_gizmo && !gizmo_handled) {
draw_gizmo_pin_icon(layout, ptr);
}
}
static void std_node_socket_interface_draw(ID *id,
bNodeTreeInterfaceSocket *interface_socket,
bContext * /*C*/,
uiLayout *layout)
{
PointerRNA ptr = RNA_pointer_create(id, &RNA_NodeTreeInterfaceSocket, interface_socket);
const bke::bNodeSocketType *typeinfo = interface_socket->socket_typeinfo();
BLI_assert(typeinfo != nullptr);
eNodeSocketDatatype type = eNodeSocketDatatype(typeinfo->type);
uiLayout *col = uiLayoutColumn(layout, false);
switch (type) {
case SOCK_FLOAT: {
uiItemR(col, &ptr, "subtype", DEFAULT_FLAGS, IFACE_("Subtype"), ICON_NONE);
uiItemR(col, &ptr, "default_value", DEFAULT_FLAGS, IFACE_("Default"), ICON_NONE);
uiLayout *sub = uiLayoutColumn(col, true);
uiItemR(sub, &ptr, "min_value", DEFAULT_FLAGS, IFACE_("Min"), ICON_NONE);
uiItemR(sub, &ptr, "max_value", DEFAULT_FLAGS, IFACE_("Max"), ICON_NONE);
break;
}
case SOCK_INT: {
uiItemR(col, &ptr, "subtype", DEFAULT_FLAGS, IFACE_("Subtype"), ICON_NONE);
uiItemR(col, &ptr, "default_value", DEFAULT_FLAGS, IFACE_("Default"), ICON_NONE);
uiLayout *sub = uiLayoutColumn(col, true);
uiItemR(sub, &ptr, "min_value", DEFAULT_FLAGS, IFACE_("Min"), ICON_NONE);
uiItemR(sub, &ptr, "max_value", DEFAULT_FLAGS, IFACE_("Max"), ICON_NONE);
break;
}
case SOCK_VECTOR: {
uiItemR(col, &ptr, "subtype", DEFAULT_FLAGS, IFACE_("Subtype"), ICON_NONE);
uiItemR(col, &ptr, "default_value", UI_ITEM_R_EXPAND, IFACE_("Default"), ICON_NONE);
uiLayout *sub = uiLayoutColumn(col, true);
uiItemR(sub, &ptr, "min_value", DEFAULT_FLAGS, IFACE_("Min"), ICON_NONE);
uiItemR(sub, &ptr, "max_value", DEFAULT_FLAGS, IFACE_("Max"), ICON_NONE);
break;
}
case SOCK_STRING: {
uiItemR(col, &ptr, "subtype", DEFAULT_FLAGS, IFACE_("Subtype"), ICON_NONE);
uiItemR(col, &ptr, "default_value", DEFAULT_FLAGS, IFACE_("Default"), ICON_NONE);
break;
}
case SOCK_BOOLEAN:
case SOCK_ROTATION:
case SOCK_RGBA:
case SOCK_OBJECT:
case SOCK_COLLECTION:
case SOCK_IMAGE:
case SOCK_TEXTURE:
case SOCK_MATERIAL: {
uiItemR(col, &ptr, "default_value", DEFAULT_FLAGS, IFACE_("Default"), ICON_NONE);
break;
}
case SOCK_MENU: {
uiItemR(col, &ptr, "default_value", DEFAULT_FLAGS, IFACE_("Default"), ICON_NONE);
break;
}
case SOCK_SHADER:
case SOCK_GEOMETRY:
case SOCK_MATRIX:
break;
case SOCK_CUSTOM:
BLI_assert_unreachable();
break;
}
col = uiLayoutColumn(layout, false);
const bNodeTree *node_tree = reinterpret_cast<const bNodeTree *>(id);
if (interface_socket->flag & NODE_INTERFACE_SOCKET_INPUT && node_tree->type == NTREE_GEOMETRY) {
if (ELEM(type, SOCK_INT, SOCK_VECTOR, SOCK_MATRIX)) {
uiItemR(col, &ptr, "default_input", DEFAULT_FLAGS, std::nullopt, ICON_NONE);
}
}
{
uiLayout *sub = uiLayoutColumn(col, false);
uiLayoutSetActive(sub, interface_socket->default_input == NODE_INPUT_DEFAULT_VALUE);
uiItemR(sub, &ptr, "hide_value", DEFAULT_FLAGS, std::nullopt, ICON_NONE);
}
if (interface_socket->flag & NODE_INTERFACE_SOCKET_INPUT && node_tree->type == NTREE_GEOMETRY) {
if (type == SOCK_BOOLEAN) {
uiItemR(col, &ptr, "layer_selection_field", DEFAULT_FLAGS, std::nullopt, ICON_NONE);
}
uiLayout *sub = uiLayoutColumn(col, false);
uiLayoutSetActive(sub, !is_layer_selection_field(*interface_socket));
uiItemR(sub, &ptr, "hide_in_modifier", DEFAULT_FLAGS, std::nullopt, ICON_NONE);
if (nodes::socket_type_supports_fields(type)) {
uiLayout *sub_sub = uiLayoutColumn(col, false);
uiLayoutSetActive(sub_sub,
(interface_socket->default_input == NODE_INPUT_DEFAULT_VALUE) &&
!is_layer_selection_field(*interface_socket));
uiItemR(sub_sub, &ptr, "force_non_field", DEFAULT_FLAGS, std::nullopt, ICON_NONE);
}
}
}
static void node_socket_virtual_draw_color(bContext * /*C*/,
PointerRNA * /*ptr*/,
PointerRNA * /*node_ptr*/,
float *r_color)
{
copy_v4_v4(r_color, virtual_node_socket_color);
}
static void node_socket_virtual_draw_color_simple(const bke::bNodeSocketType * /*type*/,
float *r_color)
{
copy_v4_v4(r_color, virtual_node_socket_color);
}
} // namespace blender::ed::space_node
void ED_init_standard_node_socket_type(blender::bke::bNodeSocketType *stype)
{
using namespace blender::ed::space_node;
stype->draw = std_node_socket_draw;
stype->draw_color = std_node_socket_color_funcs[stype->type];
stype->draw_color_simple = std_node_socket_color_simple_fn;
stype->interface_draw = std_node_socket_interface_draw;
}
void ED_init_node_socket_type_virtual(blender::bke::bNodeSocketType *stype)
{
using namespace blender::ed::space_node;
stype->draw = node_socket_button_label;
stype->draw_color = node_socket_virtual_draw_color;
stype->draw_color_simple = node_socket_virtual_draw_color_simple;
}
void ED_node_type_draw_color(const char *idname, float *r_color)
{
using namespace blender::ed::space_node;
const blender::bke::bNodeSocketType *typeinfo = blender::bke::node_socket_type_find(idname);
if (!typeinfo || typeinfo->type == SOCK_CUSTOM) {
r_color[0] = 0.0f;
r_color[1] = 0.0f;
r_color[2] = 0.0f;
r_color[3] = 0.0f;
return;
}
BLI_assert(typeinfo->type < ARRAY_SIZE(std_node_socket_colors));
copy_v4_v4(r_color, std_node_socket_colors[typeinfo->type]);
}
namespace blender::ed::space_node {
/* ************** Generic drawing ************** */
void draw_nodespace_back_pix(const bContext &C,
ARegion &region,
SpaceNode &snode,
bNodeInstanceKey parent_key)
{
Main *bmain = CTX_data_main(&C);
bNodeInstanceKey active_viewer_key = (snode.nodetree ? snode.nodetree->active_viewer_key :
bke::NODE_INSTANCE_KEY_NONE);
GPU_matrix_push_projection();
GPU_matrix_push();
wmOrtho2_region_pixelspace(&region);
GPU_matrix_identity_set();
ED_region_draw_cb_draw(&C, &region, REGION_DRAW_BACKDROP);
GPU_matrix_pop_projection();
GPU_matrix_pop();
if (!(snode.flag & SNODE_BACKDRAW) || !ED_node_is_compositor(&snode)) {
return;
}
if (parent_key.value != active_viewer_key.value) {
return;
}
GPU_matrix_push_projection();
GPU_matrix_push();
/* The draw manager is used to draw the backdrop image. */
GPUFrameBuffer *old_fb = GPU_framebuffer_active_get();
GPU_framebuffer_restore();
BLI_thread_lock(LOCK_DRAW_IMAGE);
DRW_draw_view(&C);
BLI_thread_unlock(LOCK_DRAW_IMAGE);
GPU_framebuffer_bind_no_srgb(old_fb);
/* Draw manager changes the depth state. Set it back to NONE. Without this the
* node preview images aren't drawn correctly. */
GPU_depth_test(GPU_DEPTH_NONE);
void *lock;
Image *ima = BKE_image_ensure_viewer(bmain, IMA_TYPE_COMPOSITE, "Viewer Node");
ImBuf *ibuf = BKE_image_acquire_ibuf(ima, nullptr, &lock);
if (ibuf) {
/* somehow the offset has to be calculated inverse */
wmOrtho2_region_pixelspace(&region);
const float offset_x = snode.xof + ima->runtime.backdrop_offset[0] * snode.zoom;
const float offset_y = snode.yof + ima->runtime.backdrop_offset[1] * snode.zoom;
const float x = (region.winx - snode.zoom * ibuf->x) / 2 + offset_x;
const float y = (region.winy - snode.zoom * ibuf->y) / 2 + offset_y;
/** \note draw selected info on backdrop
*/
if (snode.edittree) {
bNode *node = (bNode *)snode.edittree->nodes.first;
const rctf *viewer_border = &snode.nodetree->viewer_border;
while (node) {
if (node->flag & NODE_SELECT) {
if (node->typeinfo->draw_backdrop) {
node->typeinfo->draw_backdrop(&snode, ibuf, node, x, y);
}
}
node = node->next;
}
if ((snode.nodetree->flag & NTREE_VIEWER_BORDER) &&
viewer_border->xmin < viewer_border->xmax && viewer_border->ymin < viewer_border->ymax)
{
rcti pixel_border;
BLI_rcti_init(&pixel_border,
x + snode.zoom * viewer_border->xmin * ibuf->x,
x + snode.zoom * viewer_border->xmax * ibuf->x,
y + snode.zoom * viewer_border->ymin * ibuf->y,
y + snode.zoom * viewer_border->ymax * ibuf->y);
uint pos = GPU_vertformat_attr_add(
immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformThemeColor(TH_ACTIVE);
immDrawBorderCorners(pos, &pixel_border, 1.0f, 1.0f);
immUnbindProgram();
}
}
}
BKE_image_release_ibuf(ima, ibuf, lock);
GPU_matrix_pop_projection();
GPU_matrix_pop();
}
static float2 socket_link_connection_location(const bNode &node,
const bNodeSocket &socket,
const bNodeLink &link)
{
const float2 socket_location = socket.runtime->location;
if (socket.is_multi_input() && socket.is_input() && !(node.flag & NODE_HIDDEN)) {
/* For internal link case, handle number of links as at least 1. */
const int clamped_total_inputs = math::max<int>(1, socket.runtime->total_inputs);
return node_link_calculate_multi_input_position(
socket_location, link.multi_input_sort_id, clamped_total_inputs);
}
return socket_location;
}
static void calculate_inner_link_bezier_points(std::array<float2, 4> &points)
{
const int curving = UI_GetThemeValueType(TH_NODE_CURVING, SPACE_NODE);
if (curving == 0) {
/* Straight line: align all points. */
points[1] = math::interpolate(points[0], points[3], 1.0f / 3.0f);
points[2] = math::interpolate(points[0], points[3], 2.0f / 3.0f);
}
else {
const float dist_x = math::distance(points[0].x, points[3].x);
const float dist_y = math::distance(points[0].y, points[3].y);
/* Reduce the handle offset when the link endpoints are close to horizontal. */
const float slope = math::safe_divide(dist_y, dist_x);
const float clamp_factor = math::min(1.0f, slope * (4.5f - 0.25f * float(curving)));
const float handle_offset = curving * 0.1f * dist_x * clamp_factor;
points[1].x = points[0].x + handle_offset;
points[1].y = points[0].y;
points[2].x = points[3].x - handle_offset;
points[2].y = points[3].y;
}
}
static std::array<float2, 4> node_link_bezier_points(const bNodeLink &link)
{
std::array<float2, 4> points;
points[0] = socket_link_connection_location(*link.fromnode, *link.fromsock, link);
points[3] = socket_link_connection_location(*link.tonode, *link.tosock, link);
calculate_inner_link_bezier_points(points);
return points;
}
static bool node_link_draw_is_visible(const View2D &v2d, const std::array<float2, 4> &points)
{
if (min_ffff(points[0].x, points[1].x, points[2].x, points[3].x) > v2d.cur.xmax) {
return false;
}
if (max_ffff(points[0].x, points[1].x, points[2].x, points[3].x) < v2d.cur.xmin) {
return false;
}
return true;
}
void node_link_bezier_points_evaluated(const bNodeLink &link,
std::array<float2, NODE_LINK_RESOL + 1> &coords)
{
const std::array<float2, 4> points = node_link_bezier_points(link);
/* The extra +1 in size is required by these functions and would be removed ideally. */
BKE_curve_forward_diff_bezier(points[0].x,
points[1].x,
points[2].x,
points[3].x,
&coords[0].x,
NODE_LINK_RESOL,
sizeof(float2));
BKE_curve_forward_diff_bezier(points[0].y,
points[1].y,
points[2].y,
points[3].y,
&coords[0].y,
NODE_LINK_RESOL,
sizeof(float2));
}
/* -------------------------------------------------------------------- */
/** \name Node Socket Drawing
* \{ */
/* Keep in sync with node socket shader. */
#define MAX_SOCKET_PARAMETERS 4
#define MAX_SOCKET_INSTANCE 32
struct GBatchNodesocket {
gpu::Batch *batch;
Vector<NodeSocketShaderParameters, MAX_SOCKET_INSTANCE> params;
bool enabled;
};
static GBatchNodesocket &g_batch_nodesocket()
{
static GBatchNodesocket nodesocket_batch;
return nodesocket_batch;
}
static gpu::Batch *nodesocket_batch_init()
{
if (g_batch_nodesocket().batch == nullptr) {
GPUIndexBufBuilder ibuf;
GPU_indexbuf_init(&ibuf, GPU_PRIM_TRIS, 2, 4);
/* Quad to draw the node socket in. */
GPU_indexbuf_add_tri_verts(&ibuf, 0, 1, 2);
GPU_indexbuf_add_tri_verts(&ibuf, 2, 1, 3);
g_batch_nodesocket().batch = GPU_batch_create_ex(
GPU_PRIM_TRIS, nullptr, GPU_indexbuf_build(&ibuf), GPU_BATCH_OWNS_INDEX);
gpu_batch_presets_register(g_batch_nodesocket().batch);
}
return g_batch_nodesocket().batch;
}
static void nodesocket_cache_flush()
{
if (g_batch_nodesocket().params.is_empty()) {
return;
}
gpu::Batch *batch = nodesocket_batch_init();
if (g_batch_nodesocket().params.size() == 1) {
/* draw single */
GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_NODE_SOCKET);
GPU_batch_uniform_4fv_array(
batch,
"parameters",
4,
reinterpret_cast<const float(*)[4]>(g_batch_nodesocket().params.data()));
GPU_batch_draw(batch);
}
else {
GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_NODE_SOCKET_INST);
GPU_batch_uniform_4fv_array(
batch,
"parameters",
MAX_SOCKET_PARAMETERS * MAX_SOCKET_INSTANCE,
reinterpret_cast<const float(*)[4]>(g_batch_nodesocket().params.data()));
GPU_batch_draw_instance_range(batch, 0, g_batch_nodesocket().params.size());
}
g_batch_nodesocket().params.clear();
}
void nodesocket_batch_start()
{
BLI_assert(g_batch_nodesocket().enabled == false);
g_batch_nodesocket().enabled = true;
}
void nodesocket_batch_end()
{
BLI_assert(g_batch_nodesocket().enabled == true);
g_batch_nodesocket().enabled = false;
GPU_blend(GPU_BLEND_ALPHA);
nodesocket_cache_flush();
GPU_blend(GPU_BLEND_NONE);
}
static void draw_node_socket_batch(const NodeSocketShaderParameters &socket_params)
{
if (g_batch_nodesocket().enabled) {
g_batch_nodesocket().params.append(socket_params);
if (g_batch_nodesocket().params.size() >= MAX_SOCKET_INSTANCE) {
nodesocket_cache_flush();
}
}
else {
/* Draw single instead of batch. */
gpu::Batch *batch = nodesocket_batch_init();
GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_NODE_SOCKET);
GPU_batch_uniform_4fv_array(
batch, "parameters", MAX_SOCKET_PARAMETERS, (const float(*)[4])(&socket_params));
GPU_batch_draw(batch);
}
}
void node_draw_nodesocket(const rctf *rect,
const float color_inner[4],
const float color_outline[4],
const float outline_thickness,
const int shape,
const float aspect)
{
/* WATCH: This is assuming the ModelViewProjectionMatrix is area pixel space.
* If it has been scaled, then it's no longer valid. */
BLI_assert((color_inner != nullptr) && (color_outline != nullptr));
NodeSocketShaderParameters socket_params = {};
socket_params.rect[0] = rect->xmin;
socket_params.rect[1] = rect->xmax;
socket_params.rect[2] = rect->ymin;
socket_params.rect[3] = rect->ymax;
socket_params.color_inner[0] = color_inner[0];
socket_params.color_inner[1] = color_inner[1];
socket_params.color_inner[2] = color_inner[2];
socket_params.color_inner[3] = color_inner[3];
socket_params.color_outline[0] = color_outline[0];
socket_params.color_outline[1] = color_outline[1];
socket_params.color_outline[2] = color_outline[2];
socket_params.color_outline[3] = color_outline[3];
socket_params.outline_thickness = outline_thickness;
socket_params.outline_offset = 0.0;
socket_params.shape = float(shape) + 0.1f;
socket_params.aspect = aspect;
GPU_blend(GPU_BLEND_ALPHA);
draw_node_socket_batch(socket_params);
GPU_blend(GPU_BLEND_NONE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Node Link Drawing
* \{ */
#define NODELINK_GROUP_SIZE 256
#define LINK_RESOL 24
#define LINK_WIDTH 2.5f
#define ARROW_SIZE (7 * UI_SCALE_FAC)
/* Reroute arrow shape and mute bar. These are expanded here and shrunk in the GLSL code.
* See: `gpu_shader_2D_nodelink_vert.glsl`. */
static float arrow_verts[3][2] = {{-1.0f, 1.0f}, {0.0f, 0.0f}, {-1.0f, -1.0f}};
static float arrow_expand_axis[3][2] = {{0.7071f, 0.7071f}, {M_SQRT2, 0.0f}, {0.7071f, -0.7071f}};
static float mute_verts[3][2] = {{0.7071f, 1.0f}, {0.7071f, 0.0f}, {0.7071f, -1.0f}};
static float mute_expand_axis[3][2] = {{1.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, -0.0f}};
/* Is zero initialized because it is static data. */
static struct {
gpu::Batch *batch; /* for batching line together */
gpu::Batch *batch_single; /* for single line */
gpu::VertBuf *inst_vbo;
uint p0_id, p1_id, p2_id, p3_id;
uint colid_id, muted_id, start_color_id, end_color_id;
uint dim_factor_id;
uint thickness_id;
uint dash_params_id;
uint has_back_link_id;
GPUVertBufRaw p0_step, p1_step, p2_step, p3_step;
GPUVertBufRaw colid_step, muted_step, start_color_step, end_color_step;
GPUVertBufRaw dim_factor_step;
GPUVertBufRaw thickness_step;
GPUVertBufRaw dash_params_step;
GPUVertBufRaw has_back_link_step;
uint count;
bool enabled;
} g_batch_link;
static void nodelink_batch_reset()
{
GPU_vertbuf_attr_get_raw_data(g_batch_link.inst_vbo, g_batch_link.p0_id, &g_batch_link.p0_step);
GPU_vertbuf_attr_get_raw_data(g_batch_link.inst_vbo, g_batch_link.p1_id, &g_batch_link.p1_step);
GPU_vertbuf_attr_get_raw_data(g_batch_link.inst_vbo, g_batch_link.p2_id, &g_batch_link.p2_step);
GPU_vertbuf_attr_get_raw_data(g_batch_link.inst_vbo, g_batch_link.p3_id, &g_batch_link.p3_step);
GPU_vertbuf_attr_get_raw_data(
g_batch_link.inst_vbo, g_batch_link.colid_id, &g_batch_link.colid_step);
GPU_vertbuf_attr_get_raw_data(
g_batch_link.inst_vbo, g_batch_link.muted_id, &g_batch_link.muted_step);
GPU_vertbuf_attr_get_raw_data(
g_batch_link.inst_vbo, g_batch_link.dim_factor_id, &g_batch_link.dim_factor_step);
GPU_vertbuf_attr_get_raw_data(
g_batch_link.inst_vbo, g_batch_link.thickness_id, &g_batch_link.thickness_step);
GPU_vertbuf_attr_get_raw_data(
g_batch_link.inst_vbo, g_batch_link.dash_params_id, &g_batch_link.dash_params_step);
GPU_vertbuf_attr_get_raw_data(
g_batch_link.inst_vbo, g_batch_link.has_back_link_id, &g_batch_link.has_back_link_step);
GPU_vertbuf_attr_get_raw_data(
g_batch_link.inst_vbo, g_batch_link.start_color_id, &g_batch_link.start_color_step);
GPU_vertbuf_attr_get_raw_data(
g_batch_link.inst_vbo, g_batch_link.end_color_id, &g_batch_link.end_color_step);
g_batch_link.count = 0;
}
static void set_nodelink_vertex(gpu::VertBuf *vbo,
uint uv_id,
uint pos_id,
uint exp_id,
uint v,
const uchar uv[2],
const float pos[2],
const float exp[2])
{
GPU_vertbuf_attr_set(vbo, uv_id, v, uv);
GPU_vertbuf_attr_set(vbo, pos_id, v, pos);
GPU_vertbuf_attr_set(vbo, exp_id, v, exp);
}
static void nodelink_batch_init()
{
GPUVertFormat format = {0};
uint uv_id = GPU_vertformat_attr_add(&format, "uv", GPU_COMP_U8, 2, GPU_FETCH_INT_TO_FLOAT_UNIT);
uint pos_id = GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
uint expand_id = GPU_vertformat_attr_add(&format, "expand", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format_ex(format, GPU_USAGE_STATIC);
int vcount = LINK_RESOL * 2; /* curve */
vcount += 2; /* restart strip */
vcount += 3 * 2; /* arrow */
vcount += 2; /* restart strip */
vcount += 3 * 2; /* mute */
vcount *= 2; /* shadow */
vcount += 2; /* restart strip */
GPU_vertbuf_data_alloc(*vbo, vcount);
int v = 0;
for (int k = 0; k < 2; k++) {
uchar uv[2] = {0, 0};
float pos[2] = {0.0f, 0.0f};
float exp[2] = {0.0f, 1.0f};
/* restart */
if (k == 1) {
set_nodelink_vertex(vbo, uv_id, pos_id, expand_id, v++, uv, pos, exp);
}
/* curve strip */
for (int i = 0; i < LINK_RESOL; i++) {
uv[0] = 255 * (i / float(LINK_RESOL - 1));
uv[1] = 0;
set_nodelink_vertex(vbo, uv_id, pos_id, expand_id, v++, uv, pos, exp);
uv[1] = 255;
set_nodelink_vertex(vbo, uv_id, pos_id, expand_id, v++, uv, pos, exp);
}
/* restart */
set_nodelink_vertex(vbo, uv_id, pos_id, expand_id, v++, uv, pos, exp);
uv[0] = 127;
uv[1] = 0;
copy_v2_v2(pos, arrow_verts[0]);
copy_v2_v2(exp, arrow_expand_axis[0]);
set_nodelink_vertex(vbo, uv_id, pos_id, expand_id, v++, uv, pos, exp);
/* arrow */
for (int i = 0; i < 3; i++) {
uv[1] = 0;
copy_v2_v2(pos, arrow_verts[i]);
copy_v2_v2(exp, arrow_expand_axis[i]);
set_nodelink_vertex(vbo, uv_id, pos_id, expand_id, v++, uv, pos, exp);
uv[1] = 255;
set_nodelink_vertex(vbo, uv_id, pos_id, expand_id, v++, uv, pos, exp);
}
/* restart */
set_nodelink_vertex(vbo, uv_id, pos_id, expand_id, v++, uv, pos, exp);
uv[0] = 127;
uv[1] = 0;
copy_v2_v2(pos, mute_verts[0]);
copy_v2_v2(exp, mute_expand_axis[0]);
set_nodelink_vertex(vbo, uv_id, pos_id, expand_id, v++, uv, pos, exp);
/* bar */
for (int i = 0; i < 3; ++i) {
uv[1] = 0;
copy_v2_v2(pos, mute_verts[i]);
copy_v2_v2(exp, mute_expand_axis[i]);
set_nodelink_vertex(vbo, uv_id, pos_id, expand_id, v++, uv, pos, exp);
uv[1] = 255;
set_nodelink_vertex(vbo, uv_id, pos_id, expand_id, v++, uv, pos, exp);
}
/* restart */
if (k == 0) {
set_nodelink_vertex(vbo, uv_id, pos_id, expand_id, v++, uv, pos, exp);
}
}
g_batch_link.batch = GPU_batch_create_ex(GPU_PRIM_TRI_STRIP, vbo, nullptr, GPU_BATCH_OWNS_VBO);
gpu_batch_presets_register(g_batch_link.batch);
g_batch_link.batch_single = GPU_batch_create_ex(
GPU_PRIM_TRI_STRIP, vbo, nullptr, GPU_BATCH_INVALID);
gpu_batch_presets_register(g_batch_link.batch_single);
/* Instances data */
GPUVertFormat format_inst = {0};
g_batch_link.p0_id = GPU_vertformat_attr_add(
&format_inst, "P0", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
g_batch_link.p1_id = GPU_vertformat_attr_add(
&format_inst, "P1", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
g_batch_link.p2_id = GPU_vertformat_attr_add(
&format_inst, "P2", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
g_batch_link.p3_id = GPU_vertformat_attr_add(
&format_inst, "P3", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
g_batch_link.colid_id = GPU_vertformat_attr_add(
&format_inst, "colid_doarrow", GPU_COMP_U8, 4, GPU_FETCH_INT);
g_batch_link.start_color_id = GPU_vertformat_attr_add(
&format_inst, "start_color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
g_batch_link.end_color_id = GPU_vertformat_attr_add(
&format_inst, "end_color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
g_batch_link.muted_id = GPU_vertformat_attr_add(
&format_inst, "domuted", GPU_COMP_U8, 2, GPU_FETCH_INT);
g_batch_link.dim_factor_id = GPU_vertformat_attr_add(
&format_inst, "dim_factor", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
g_batch_link.thickness_id = GPU_vertformat_attr_add(
&format_inst, "thickness", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
g_batch_link.dash_params_id = GPU_vertformat_attr_add(
&format_inst, "dash_params", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
g_batch_link.has_back_link_id = GPU_vertformat_attr_add(
&format_inst, "has_back_link", GPU_COMP_I32, 1, GPU_FETCH_INT);
g_batch_link.inst_vbo = GPU_vertbuf_create_with_format_ex(format_inst, GPU_USAGE_STREAM);
/* Alloc max count but only draw the range we need. */
GPU_vertbuf_data_alloc(*g_batch_link.inst_vbo, NODELINK_GROUP_SIZE);
GPU_batch_instbuf_set(g_batch_link.batch, g_batch_link.inst_vbo, true);
nodelink_batch_reset();
}
static char nodelink_get_color_id(int th_col)
{
switch (th_col) {
case TH_WIRE:
return 1;
case TH_WIRE_INNER:
return 2;
case TH_ACTIVE:
return 3;
case TH_EDGE_SELECT:
return 4;
case TH_REDALERT:
return 5;
}
return 0;
}
static void nodelink_batch_draw(const SpaceNode &snode)
{
if (g_batch_link.count == 0) {
return;
}
GPU_blend(GPU_BLEND_ALPHA);
NodeLinkInstanceData node_link_data;
UI_GetThemeColor4fv(TH_WIRE_INNER, node_link_data.colors[nodelink_get_color_id(TH_WIRE_INNER)]);
UI_GetThemeColor4fv(TH_WIRE, node_link_data.colors[nodelink_get_color_id(TH_WIRE)]);
UI_GetThemeColor4fv(TH_ACTIVE, node_link_data.colors[nodelink_get_color_id(TH_ACTIVE)]);
UI_GetThemeColor4fv(TH_EDGE_SELECT,
node_link_data.colors[nodelink_get_color_id(TH_EDGE_SELECT)]);
UI_GetThemeColor4fv(TH_REDALERT, node_link_data.colors[nodelink_get_color_id(TH_REDALERT)]);
node_link_data.aspect = snode.runtime->aspect;
node_link_data.arrowSize = ARROW_SIZE;
GPUUniformBuf *ubo = GPU_uniformbuf_create_ex(sizeof(node_link_data), &node_link_data, __func__);
GPU_vertbuf_data_len_set(*g_batch_link.inst_vbo, g_batch_link.count);
GPU_vertbuf_use(g_batch_link.inst_vbo); /* force update. */
GPU_batch_program_set_builtin(g_batch_link.batch, GPU_SHADER_2D_NODELINK_INST);
GPU_batch_uniformbuf_bind(g_batch_link.batch, "node_link_data", ubo);
GPU_batch_draw(g_batch_link.batch);
GPU_uniformbuf_unbind(ubo);
GPU_uniformbuf_free(ubo);
nodelink_batch_reset();
GPU_blend(GPU_BLEND_NONE);
}
void nodelink_batch_start(SpaceNode & /*snode*/)
{
g_batch_link.enabled = true;
}
void nodelink_batch_end(SpaceNode &snode)
{
nodelink_batch_draw(snode);
g_batch_link.enabled = false;
}
struct NodeLinkDrawConfig {
int th_col1;
int th_col2;
int th_col3;
ColorTheme4f start_color;
ColorTheme4f end_color;
ColorTheme4f outline_color;
bool drawarrow;
bool drawmuted;
bool highlighted;
bool has_back_link;
float dim_factor;
float thickness;
float dash_length;
float dash_factor;
float dash_alpha;
};
static void nodelink_batch_add_link(const SpaceNode &snode,
const std::array<float2, 4> &points,
const NodeLinkDrawConfig &draw_config)
{
/* Only allow these colors. If more is needed, you need to modify the shader accordingly. */
BLI_assert(
ELEM(draw_config.th_col1, TH_WIRE_INNER, TH_WIRE, TH_ACTIVE, TH_EDGE_SELECT, TH_REDALERT));
BLI_assert(
ELEM(draw_config.th_col2, TH_WIRE_INNER, TH_WIRE, TH_ACTIVE, TH_EDGE_SELECT, TH_REDALERT));
BLI_assert(ELEM(draw_config.th_col3, TH_WIRE, TH_REDALERT, -1));
g_batch_link.count++;
copy_v2_v2((float *)GPU_vertbuf_raw_step(&g_batch_link.p0_step), points[0]);
copy_v2_v2((float *)GPU_vertbuf_raw_step(&g_batch_link.p1_step), points[1]);
copy_v2_v2((float *)GPU_vertbuf_raw_step(&g_batch_link.p2_step), points[2]);
copy_v2_v2((float *)GPU_vertbuf_raw_step(&g_batch_link.p3_step), points[3]);
char *colid = (char *)GPU_vertbuf_raw_step(&g_batch_link.colid_step);
colid[0] = nodelink_get_color_id(draw_config.th_col1);
colid[1] = nodelink_get_color_id(draw_config.th_col2);
colid[2] = nodelink_get_color_id(draw_config.th_col3);
colid[3] = draw_config.drawarrow;
copy_v4_v4((float *)GPU_vertbuf_raw_step(&g_batch_link.start_color_step),
draw_config.start_color);
copy_v4_v4((float *)GPU_vertbuf_raw_step(&g_batch_link.end_color_step), draw_config.end_color);
char *muted = (char *)GPU_vertbuf_raw_step(&g_batch_link.muted_step);
muted[0] = draw_config.drawmuted;
*(float *)GPU_vertbuf_raw_step(&g_batch_link.dim_factor_step) = draw_config.dim_factor;
*(float *)GPU_vertbuf_raw_step(&g_batch_link.thickness_step) = draw_config.thickness;
float3 dash_params(draw_config.dash_length, draw_config.dash_factor, draw_config.dash_alpha);
copy_v3_v3((float *)GPU_vertbuf_raw_step(&g_batch_link.dash_params_step), dash_params);
*(int *)GPU_vertbuf_raw_step(&g_batch_link.has_back_link_step) = draw_config.has_back_link;
if (g_batch_link.count == NODELINK_GROUP_SIZE) {
nodelink_batch_draw(snode);
}
}
static void node_draw_link_end_marker(const float2 center,
const float radius,
const ColorTheme4f &color)
{
rctf rect;
BLI_rctf_init(&rect, center.x - radius, center.x + radius, center.y - radius, center.y + radius);
UI_draw_roundbox_corner_set(UI_CNR_ALL);
UI_draw_roundbox_4fv(&rect, true, radius, color);
/* Round-box disables alpha. Re-enable it for node links that are drawn after this one. */
GPU_blend(GPU_BLEND_ALPHA);
}
static void node_draw_link_end_markers(const bNodeLink &link,
const NodeLinkDrawConfig &draw_config,
const std::array<float2, 4> &points,
const bool outline)
{
const float radius = (outline ? 0.65f : 0.45f) * NODE_SOCKSIZE;
if (link.fromsock) {
node_draw_link_end_marker(
points[0], radius, outline ? draw_config.outline_color : draw_config.start_color);
}
if (link.tosock) {
node_draw_link_end_marker(
points[3], radius, outline ? draw_config.outline_color : draw_config.end_color);
}
}
static bool node_link_is_field_link(const SpaceNode &snode, const bNodeLink &link)
{
const bNodeTree &tree = *snode.edittree;
if (tree.type != NTREE_GEOMETRY) {
return false;
}
const Span<bke::FieldSocketState> field_states = tree.runtime->field_states;
if (link.fromsock &&
field_states[link.fromsock->index_in_tree()] == bke::FieldSocketState::IsField)
{
return true;
}
return false;
}
static bool node_link_is_gizmo_link(const SpaceNode &snode, const bNodeLink &link)
{
if (snode.edittree->type != NTREE_GEOMETRY) {
return false;
}
if (!link.fromsock || !link.tosock) {
return false;
}
const bNodeTree &tree = *snode.edittree;
return tree.runtime->sockets_on_active_gizmo_paths.contains(link.fromsock) &&
tree.runtime->sockets_on_active_gizmo_paths.contains(link.tosock);
}
static NodeLinkDrawConfig nodelink_get_draw_config(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)
{
NodeLinkDrawConfig draw_config;
draw_config.th_col1 = th_col1;
draw_config.th_col2 = th_col2;
draw_config.th_col3 = th_col3;
draw_config.dim_factor = selected ? 1.0f : node_link_dim_factor(v2d, link);
bTheme *btheme = UI_GetTheme();
draw_config.dash_alpha = btheme->space_node.dash_alpha;
const bool field_link = node_link_is_field_link(snode, link);
const bool gizmo_link = node_link_is_gizmo_link(snode, link);
draw_config.dash_factor = field_link ? 0.75f : 1.0f;
draw_config.dash_length = 10.0f * UI_SCALE_FAC;
const float scale = UI_view2d_scale_get_x(&v2d);
/* Clamp the thickness to make the links more readable when zooming out. */
draw_config.thickness = LINK_WIDTH * max_ff(UI_SCALE_FAC * scale, 1.0f) *
(field_link ? 0.7f : 1.0f);
draw_config.has_back_link = gizmo_link;
draw_config.highlighted = link.flag & NODE_LINK_TEMP_HIGHLIGHT;
draw_config.drawarrow = ((link.tonode && link.tonode->is_reroute()) &&
(link.fromnode && link.fromnode->is_reroute()));
draw_config.drawmuted = (link.flag & NODE_LINK_MUTED);
UI_GetThemeColor4fv(th_col3, draw_config.outline_color);
if (snode.overlay.flag & SN_OVERLAY_SHOW_OVERLAYS &&
snode.overlay.flag & SN_OVERLAY_SHOW_WIRE_COLORS)
{
const bNodeTree &node_tree = *snode.edittree;
PointerRNA from_node_ptr = RNA_pointer_create(
&const_cast<ID &>(node_tree.id), &RNA_Node, link.fromnode);
PointerRNA to_node_ptr = RNA_pointer_create(
&const_cast<ID &>(node_tree.id), &RNA_Node, link.tonode);
if (link.fromsock) {
node_socket_color_get(C, node_tree, from_node_ptr, *link.fromsock, draw_config.start_color);
}
else {
node_socket_color_get(C, node_tree, to_node_ptr, *link.tosock, draw_config.start_color);
}
if (link.tosock) {
node_socket_color_get(C, node_tree, to_node_ptr, *link.tosock, draw_config.end_color);
}
else {
node_socket_color_get(C, node_tree, from_node_ptr, *link.fromsock, draw_config.end_color);
}
}
else {
UI_GetThemeColor4fv(th_col1, draw_config.start_color);
UI_GetThemeColor4fv(th_col2, draw_config.end_color);
}
/* Highlight links connected to selected nodes. */
if (selected) {
ColorTheme4f color_selected;
UI_GetThemeColor4fv(TH_EDGE_SELECT, color_selected);
const float alpha = color_selected.a;
/* Interpolate color if highlight color is not fully transparent. */
if (alpha != 0.0) {
if (link.fromsock) {
interp_v3_v3v3(draw_config.start_color, draw_config.start_color, color_selected, alpha);
}
if (link.tosock) {
interp_v3_v3v3(draw_config.end_color, draw_config.end_color, color_selected, alpha);
}
}
}
if (draw_config.highlighted) {
ColorTheme4f link_preselection_highlight_color;
UI_GetThemeColor4fv(TH_SELECT, link_preselection_highlight_color);
/* Multi sockets can only be inputs. So we only have to highlight the end of the link. */
copy_v4_v4(draw_config.end_color, link_preselection_highlight_color);
}
return draw_config;
}
static void node_draw_link_bezier_ex(const SpaceNode &snode,
const NodeLinkDrawConfig &draw_config,
const std::array<float2, 4> &points)
{
if (g_batch_link.batch == nullptr) {
nodelink_batch_init();
}
if (g_batch_link.enabled && !draw_config.highlighted) {
/* Add link to batch. */
nodelink_batch_add_link(snode, points, draw_config);
}
else {
NodeLinkData node_link_data;
for (const int i : IndexRange(points.size())) {
copy_v2_v2(node_link_data.bezierPts[i], points[i]);
}
copy_v4_v4(node_link_data.colors[0], draw_config.outline_color);
copy_v4_v4(node_link_data.colors[1], draw_config.start_color);
copy_v4_v4(node_link_data.colors[2], draw_config.end_color);
node_link_data.doArrow = draw_config.drawarrow;
node_link_data.doMuted = draw_config.drawmuted;
node_link_data.dim_factor = draw_config.dim_factor;
node_link_data.thickness = draw_config.thickness;
node_link_data.dash_params[0] = draw_config.dash_length;
node_link_data.dash_params[1] = draw_config.dash_factor;
node_link_data.dash_params[2] = draw_config.dash_alpha;
node_link_data.has_back_link = draw_config.has_back_link;
node_link_data.aspect = snode.runtime->aspect;
node_link_data.arrowSize = ARROW_SIZE;
gpu::Batch *batch = g_batch_link.batch_single;
GPUUniformBuf *ubo = GPU_uniformbuf_create_ex(sizeof(NodeLinkData), &node_link_data, __func__);
GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_NODELINK);
GPU_batch_uniformbuf_bind(batch, "node_link_data", ubo);
GPU_batch_draw(batch);
GPU_uniformbuf_unbind(ubo);
GPU_uniformbuf_free(ubo);
}
}
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)
{
const std::array<float2, 4> points = node_link_bezier_points(link);
if (!node_link_draw_is_visible(v2d, points)) {
return;
}
const NodeLinkDrawConfig draw_config = nodelink_get_draw_config(
C, v2d, snode, link, th_col1, th_col2, th_col3, selected);
node_draw_link_bezier_ex(snode, draw_config, points);
}
void node_draw_link(const bContext &C,
const View2D &v2d,
const SpaceNode &snode,
const bNodeLink &link,
const bool selected)
{
int th_col1 = TH_WIRE_INNER, th_col2 = TH_WIRE_INNER, th_col3 = TH_WIRE;
if (link.fromsock == nullptr && link.tosock == nullptr) {
return;
}
/* going to give issues once... */
if (link.tosock->flag & SOCK_UNAVAIL) {
return;
}
if (link.fromsock->flag & SOCK_UNAVAIL) {
return;
}
if (link.flag & NODE_LINK_VALID) {
/* special indicated link, on drop-node */
if (link.flag & NODE_LINK_INSERT_TARGET && !(link.flag & NODE_LINK_INSERT_TARGET_INVALID)) {
th_col1 = th_col2 = TH_ACTIVE;
}
else if (link.flag & NODE_LINK_MUTED) {
th_col1 = th_col2 = TH_REDALERT;
}
}
else {
/* Invalid link. */
th_col1 = th_col2 = th_col3 = TH_REDALERT;
// th_col3 = -1; /* no shadow */
}
node_draw_link_bezier(C, v2d, snode, link, th_col1, th_col2, th_col3, selected);
}
std::array<float2, 4> node_link_bezier_points_dragged(const SpaceNode &snode,
const bNodeLink &link)
{
const float2 cursor = snode.runtime->cursor * UI_SCALE_FAC;
std::array<float2, 4> points;
points[0] = link.fromsock ?
socket_link_connection_location(*link.fromnode, *link.fromsock, link) :
cursor;
points[3] = link.tosock ? socket_link_connection_location(*link.tonode, *link.tosock, link) :
cursor;
calculate_inner_link_bezier_points(points);
return points;
}
void node_draw_link_dragged(const bContext &C,
const View2D &v2d,
const SpaceNode &snode,
const bNodeLink &link)
{
if (link.fromsock == nullptr && link.tosock == nullptr) {
return;
}
const std::array<float2, 4> points = node_link_bezier_points_dragged(snode, link);
const NodeLinkDrawConfig draw_config = nodelink_get_draw_config(
C, v2d, snode, link, TH_ACTIVE, TH_ACTIVE, TH_WIRE, true);
/* End marker outline. */
node_draw_link_end_markers(link, draw_config, points, true);
/* Link. */
node_draw_link_bezier_ex(snode, draw_config, points);
/* End marker fill. */
node_draw_link_end_markers(link, draw_config, points, false);
}
} // namespace blender::ed::space_node