Nodes: Adds button to groups to change type of sockets.

The menu lists all socket types that are valid for the node tree.
Changing a socket type updates all instances of the group and keeps
existing links to the socket.
If changing the socket type leads to incorrect node connections the
links are flagged as invalid (red) and ignored but not removed. This is
so users don't lose information and can then fix resulting issues.
For example: Changing a Color socket to a Shader socket can cause an
invalid Shader-to-Color connection.

Implementation details:
The new `NODE_OT_tree_socket_change_type` operator uses the generic
`rna_node_socket_type_itemf` function to list all eligible socket types.
It uses the tree type's `valid_socket_type` callback to test for valid
types. In addition it also checks the subtype, because multiple RNA
types are registered for the same base type. The `valid_socket_type`
callback has been modified slightly to accept full socket types instead
of just the base type enum, so that custom (python) socket types can be
used by this operator.

The `nodeModifySocketType` function is now called when group nodes
encounter a socket type mismatch, instead of replacing the socket
entirely. This ensures that links are kept to/from group nodes as well
as group input/output nodes. The `nodeModifySocketType` function now
also takes a full `bNodeSocketType` instead of just the base and subtype
enum (a shortcut `nodeModifySocketTypeStatic` exists for when only
static types are used).

Differential Revision: https://developer.blender.org/D10912
This commit is contained in:
Lukas Tönne 2021-07-06 18:36:11 +01:00
parent 933eddc9a1
commit 586cf8b190
Notes: blender-bot 2023-02-14 09:34:18 +01:00
Referenced by issue #87049, Change data-type for input/output sockets for node groups
14 changed files with 273 additions and 45 deletions

View File

@ -141,7 +141,10 @@ typedef void *SocketGetCPPValueFunction;
* Defines the appearance and behavior of a socket in the UI.
*/
typedef struct bNodeSocketType {
char idname[64]; /* identifier name */
/* Identifier name */
char idname[64];
/* Type label */
char label[64];
void (*draw)(struct bContext *C,
struct uiLayout *layout,
@ -412,7 +415,7 @@ typedef struct bNodeTreeType {
void (*node_add_init)(struct bNodeTree *ntree, struct bNode *bnode);
/* Check if the socket type is valid for this tree type. */
bool (*valid_socket_type)(enum eNodeSocketDatatype socket_type, struct bNodeTreeType *ntreetype);
bool (*valid_socket_type)(struct bNodeTreeType *ntreetype, struct bNodeSocketType *socket_type);
/* RNA integration */
ExtensionRNA rna_ext;
@ -554,8 +557,12 @@ void nodeRegisterSocketType(struct bNodeSocketType *stype);
void nodeUnregisterSocketType(struct bNodeSocketType *stype);
bool nodeSocketIsRegistered(struct bNodeSocket *sock);
struct GHashIterator *nodeSocketTypeGetIterator(void);
const char *nodeSocketTypeLabel(const bNodeSocketType *stype);
bool nodeIsStaticSocketType(const struct bNodeSocketType *stype);
const char *nodeStaticSocketType(int type, int subtype);
const char *nodeStaticSocketInterfaceType(int type, int subtype);
const char *nodeStaticSocketLabel(int type, int subtype);
/* helper macros for iterating over node types */
#define NODE_SOCKET_TYPES_BEGIN(stype) \
@ -605,7 +612,11 @@ struct bNodeSocket *nodeInsertStaticSocket(struct bNodeTree *ntree,
const char *name);
void nodeRemoveSocket(struct bNodeTree *ntree, struct bNode *node, struct bNodeSocket *sock);
void nodeRemoveAllSockets(struct bNodeTree *ntree, struct bNode *node);
void nodeModifySocketType(
void nodeModifySocketType(struct bNodeTree *ntree,
struct bNode *node,
struct bNodeSocket *sock,
const char *idname);
void nodeModifySocketTypeStatic(
struct bNodeTree *ntree, struct bNode *node, struct bNodeSocket *sock, int type, int subtype);
struct bNode *nodeAddNode(const struct bContext *C, struct bNodeTree *ntree, const char *idname);

View File

@ -1425,6 +1425,12 @@ GHashIterator *nodeSocketTypeGetIterator(void)
return BLI_ghashIterator_new(nodesockettypes_hash);
}
const char *nodeSocketTypeLabel(const bNodeSocketType *stype)
{
/* Use socket type name as a fallback if label is undefined. */
return stype->label[0] != '\0' ? stype->label : RNA_struct_ui_name(stype->ext_socket.srna);
}
struct bNodeSocket *nodeFindSocket(const bNode *node,
eNodeSocketInOut in_out,
const char *identifier)
@ -1585,13 +1591,15 @@ static void socket_id_user_decrement(bNodeSocket *sock)
}
}
void nodeModifySocketType(
bNodeTree *ntree, bNode *UNUSED(node), bNodeSocket *sock, int type, int subtype)
void nodeModifySocketType(bNodeTree *ntree,
bNode *UNUSED(node),
bNodeSocket *sock,
const char *idname)
{
const char *idname = nodeStaticSocketType(type, subtype);
bNodeSocketType *socktype = nodeSocketTypeFind(idname);
if (!idname) {
CLOG_ERROR(&LOG, "static node socket type %d undefined", type);
if (!socktype) {
CLOG_ERROR(&LOG, "node socket type %s undefined", idname);
return;
}
@ -1601,9 +1609,21 @@ void nodeModifySocketType(
sock->default_value = nullptr;
}
sock->type = type;
BLI_strncpy(sock->idname, idname, sizeof(sock->idname));
node_socket_set_typeinfo(ntree, sock, nodeSocketTypeFind(idname));
node_socket_set_typeinfo(ntree, sock, socktype);
}
void nodeModifySocketTypeStatic(
bNodeTree *ntree, bNode *node, bNodeSocket *sock, int type, int subtype)
{
const char *idname = nodeStaticSocketType(type, subtype);
if (!idname) {
CLOG_ERROR(&LOG, "static node socket type %d undefined", type);
return;
}
nodeModifySocketType(ntree, node, sock, idname);
}
bNodeSocket *nodeAddSocket(bNodeTree *ntree,
@ -1647,6 +1667,15 @@ bNodeSocket *nodeInsertSocket(bNodeTree *ntree,
return sock;
}
bool nodeIsStaticSocketType(const struct bNodeSocketType *stype)
{
/*
* Cannot rely on type==SOCK_CUSTOM here, because type is 0 by default
* and can be changed on custom sockets.
*/
return RNA_struct_is_a(stype->ext_socket.srna, &RNA_NodeSocketStandard);
}
const char *nodeStaticSocketType(int type, int subtype)
{
switch (type) {
@ -1801,6 +1830,39 @@ const char *nodeStaticSocketInterfaceType(int type, int subtype)
return nullptr;
}
const char *nodeStaticSocketLabel(int type, int UNUSED(subtype))
{
switch (type) {
case SOCK_FLOAT:
return "Float";
case SOCK_INT:
return "Integer";
case SOCK_BOOLEAN:
return "Boolean";
case SOCK_VECTOR:
return "Vector";
case SOCK_RGBA:
return "Color";
case SOCK_STRING:
return "String";
case SOCK_SHADER:
return "Shader";
case SOCK_OBJECT:
return "Object";
case SOCK_IMAGE:
return "Image";
case SOCK_GEOMETRY:
return "Geometry";
case SOCK_COLLECTION:
return "Collection";
case SOCK_TEXTURE:
return "Texture";
case SOCK_MATERIAL:
return "Material";
}
return nullptr;
}
bNodeSocket *nodeAddStaticSocket(bNodeTree *ntree,
bNode *node,
eNodeSocketInOut in_out,

View File

@ -151,10 +151,37 @@ static void draw_socket_list(const bContext *C,
bNodeSocket *socket = node_tree_find_active_socket(ntree, in_out);
if (socket != NULL) {
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
PointerRNA socket_ptr;
RNA_pointer_create((ID *)ntree, &RNA_NodeSocketInterface, socket, &socket_ptr);
{
/* Mimicking property split */
uiLayoutSetPropSep(layout, false);
uiLayoutSetPropDecorate(layout, false);
uiLayout *layout_row = uiLayoutRow(layout, true);
uiLayout *layout_split = uiLayoutSplit(layout_row, 0.4f, true);
uiLayout *label_column = uiLayoutColumn(layout_split, true);
uiLayoutSetAlignment(label_column, UI_LAYOUT_ALIGN_RIGHT);
/* Menu to change the socket type. */
uiItemL(label_column, "Type", ICON_NONE);
uiLayout *property_row = uiLayoutRow(layout_split, true);
PointerRNA props_ptr;
uiItemMenuEnumFullO(property_row,
(bContext *)C,
"NODE_OT_tree_socket_change_type",
"socket_type",
nodeSocketTypeLabel(socket->typeinfo),
ICON_NONE,
&props_ptr);
RNA_enum_set(&props_ptr, "in_out", in_out);
}
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
uiItemR(layout, &socket_ptr, "name", 0, NULL, ICON_NONE);
/* Display descriptions only for Geometry Nodes, since it's only used in the modifier panel. */

View File

@ -2414,6 +2414,109 @@ void NODE_OT_tree_socket_remove(wmOperatorType *ot)
RNA_def_enum(ot->srna, "in_out", rna_enum_node_socket_in_out_items, SOCK_IN, "Socket Type", "");
}
/********************** Change interface socket type operator *********************/
static int ntree_socket_change_type_exec(bContext *C, wmOperator *op)
{
SpaceNode *snode = CTX_wm_space_node(C);
bNodeTree *ntree = snode->edittree;
const eNodeSocketInOut in_out = (eNodeSocketInOut)RNA_enum_get(op->ptr, "in_out");
const bNodeSocketType *socket_type = rna_node_socket_type_from_enum(
RNA_enum_get(op->ptr, "socket_type"));
ListBase *sockets = (in_out == SOCK_IN) ? &ntree->inputs : &ntree->outputs;
Main *main = CTX_data_main(C);
bNodeSocket *iosock = ntree_get_active_interface_socket(sockets);
if (iosock == NULL) {
return OPERATOR_CANCELLED;
}
/* The type remains the same, so we don't need to change anything. */
if (iosock->typeinfo == socket_type) {
return OPERATOR_FINISHED;
}
/* Don't handle subtypes for now. */
nodeModifySocketType(ntree, NULL, iosock, socket_type->idname);
/* Need the extra update here because the loop above does not check for valid links in the node
* group we're currently editing. */
ntree->update |= NTREE_UPDATE_GROUP | NTREE_UPDATE_LINKS;
/* Deactivate sockets. */
LISTBASE_FOREACH (bNodeSocket *, socket_iter, sockets) {
socket_iter->flag &= ~SELECT;
}
/* Make the new socket active. */
iosock->flag |= SELECT;
ntreeUpdateTree(main, ntree);
snode_notify(C, snode);
snode_dag_update(C, snode);
WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, NULL);
return OPERATOR_FINISHED;
}
static bool socket_change_poll_type(void *userdata, bNodeSocketType *socket_type)
{
/* Check if the node tree supports the socket type. */
bNodeTreeType *ntreetype = (bNodeTreeType *)userdata;
if (ntreetype->valid_socket_type && !ntreetype->valid_socket_type(ntreetype, socket_type)) {
return false;
}
/* Only use basic socket types for this enum. */
if (socket_type->subtype != PROP_NONE) {
return false;
}
return true;
}
static const EnumPropertyItem *socket_change_type_itemf(bContext *C,
PointerRNA *UNUSED(ptr),
PropertyRNA *UNUSED(prop),
bool *r_free)
{
if (!C) {
return DummyRNA_NULL_items;
}
SpaceNode *snode = CTX_wm_space_node(C);
if (!snode || !snode->edittree) {
return DummyRNA_NULL_items;
}
return rna_node_socket_type_itemf(snode->edittree->typeinfo, socket_change_poll_type, r_free);
}
void NODE_OT_tree_socket_change_type(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Change Node Tree Interface Socket Type";
ot->description = "Change the type of a socket of the current node tree";
ot->idname = "NODE_OT_tree_socket_change_type";
/* api callbacks */
ot->invoke = WM_menu_invoke;
ot->exec = ntree_socket_change_type_exec;
ot->poll = ED_operator_node_editable;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_enum(ot->srna, "in_out", rna_enum_node_socket_in_out_items, SOCK_IN, "Socket Type", "");
prop = RNA_def_enum(ot->srna, "socket_type", DummyRNA_DEFAULT_items, 0, "Socket Type", "");
RNA_def_enum_funcs(prop, socket_change_type_itemf);
ot->prop = prop;
}
/********************** Move interface socket operator *********************/
static const EnumPropertyItem move_direction_items[] = {

View File

@ -296,6 +296,7 @@ void NODE_OT_clipboard_paste(struct wmOperatorType *ot);
void NODE_OT_tree_socket_add(struct wmOperatorType *ot);
void NODE_OT_tree_socket_remove(struct wmOperatorType *ot);
void NODE_OT_tree_socket_change_type(struct wmOperatorType *ot);
void NODE_OT_tree_socket_move(struct wmOperatorType *ot);
void NODE_OT_shader_script_update(struct wmOperatorType *ot);

View File

@ -119,6 +119,7 @@ void node_operatortypes(void)
WM_operatortype_append(NODE_OT_tree_socket_add);
WM_operatortype_append(NODE_OT_tree_socket_remove);
WM_operatortype_append(NODE_OT_tree_socket_change_type);
WM_operatortype_append(NODE_OT_tree_socket_move);
WM_operatortype_append(NODE_OT_cryptomatte_layer_add);

View File

@ -450,6 +450,7 @@ extern StructRNA RNA_NodeOutputFileSlotFile;
extern StructRNA RNA_NodeOutputFileSlotLayer;
extern StructRNA RNA_NodeSocket;
extern StructRNA RNA_NodeSocketInterface;
extern StructRNA RNA_NodeSocketStandard;
extern StructRNA RNA_NodeTree;
extern StructRNA RNA_NoiseGpencilModifier;
extern StructRNA RNA_NoiseTexture;

View File

@ -799,7 +799,7 @@ const EnumPropertyItem *rna_node_socket_type_itemf(void *data,
tmp.value = i;
tmp.identifier = stype->idname;
tmp.icon = RNA_struct_ui_icon(srna);
tmp.name = RNA_struct_ui_name(srna);
tmp.name = nodeSocketTypeLabel(stype);
tmp.description = RNA_struct_ui_description(srna);
RNA_enum_item_add(&item, &totitem, &tmp);
@ -1025,8 +1025,7 @@ static void rna_NodeTree_get_from_context(
RNA_parameter_list_free(&list);
}
static bool rna_NodeTree_valid_socket_type(eNodeSocketDatatype socket_type,
bNodeTreeType *ntreetype)
static bool rna_NodeTree_valid_socket_type(bNodeTreeType *ntreetype, bNodeSocketType *socket_type)
{
extern FunctionRNA rna_NodeTree_valid_socket_type_func;
@ -1040,7 +1039,7 @@ static bool rna_NodeTree_valid_socket_type(eNodeSocketDatatype socket_type,
func = &rna_NodeTree_valid_socket_type_func;
RNA_parameter_list_create(&list, &ptr, func);
RNA_parameter_set_lookup(&list, "type", &socket_type);
RNA_parameter_set_lookup(&list, "idname", &socket_type->idname);
ntreetype->rna_ext.call(NULL, &ptr, func, &list);
RNA_parameter_get_lookup(&list, "valid", &ret);
@ -2866,7 +2865,7 @@ static void rna_NodeSocket_type_set(PointerRNA *ptr, int value)
bNodeSocket *sock = (bNodeSocket *)ptr->data;
bNode *node;
nodeFindNode(ntree, sock, &node, NULL);
nodeModifySocketType(ntree, node, sock, value, 0);
nodeModifySocketTypeStatic(ntree, node, sock, value, 0);
}
static void rna_NodeSocket_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr)
@ -10176,7 +10175,7 @@ static void rna_def_node_socket(BlenderRNA *brna)
RNA_def_struct_ui_text(srna, "Node Socket", "Input or output socket of a node");
RNA_def_struct_sdna(srna, "bNodeSocket");
RNA_def_struct_refine_func(srna, "rna_NodeSocket_refine");
RNA_def_struct_ui_icon(srna, ICON_PLUGIN);
RNA_def_struct_ui_icon(srna, ICON_NONE);
RNA_def_struct_path_func(srna, "rna_NodeSocket_path");
RNA_def_struct_register_funcs(
srna, "rna_NodeSocket_register", "rna_NodeSocket_unregister", NULL);
@ -10281,6 +10280,11 @@ static void rna_def_node_socket(BlenderRNA *brna)
RNA_def_property_flag(prop, PROP_REGISTER);
RNA_def_property_ui_text(prop, "ID Name", "");
prop = RNA_def_property(srna, "bl_label", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "typeinfo->label");
RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL);
RNA_def_property_ui_text(prop, "Type Label", "Label to display for the socket type in the UI");
/* draw socket */
func = RNA_def_function(srna, "draw", NULL);
RNA_def_function_ui_description(func, "Draw socket");
@ -10369,6 +10373,11 @@ static void rna_def_node_socket_interface(BlenderRNA *brna)
RNA_def_property_flag(prop, PROP_REGISTER);
RNA_def_property_ui_text(prop, "ID Name", "");
prop = RNA_def_property(srna, "bl_label", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "typeinfo->label");
RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL);
RNA_def_property_ui_text(prop, "Type Label", "Label to display for the socket type in the UI");
func = RNA_def_function(srna, "draw", NULL);
RNA_def_function_ui_description(func, "Draw template settings");
RNA_def_function_flag(func, FUNC_REGISTER_OPTIONAL);
@ -11879,11 +11888,12 @@ static void rna_def_nodetree(BlenderRNA *brna)
func, "result_3", "ID", "From ID", "Original ID data-block selected from the context");
RNA_def_function_output(func, parm);
/* Check for support of a socket type. */
/* Check for support of a socket type with a type identifier. */
func = RNA_def_function(srna, "valid_socket_type", NULL);
RNA_def_function_ui_description(func, "Check if the socket type is valid for the node tree");
RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_REGISTER_OPTIONAL);
parm = RNA_def_enum(func, "type", node_socket_type_items, 0, "", "");
parm = RNA_def_string(
func, "idname", "NodeSocket", MAX_NAME, "Socket Type", "Identifier of the socket type");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
RNA_def_function_return(func, RNA_def_boolean(func, "valid", false, "", ""));
}

View File

@ -205,10 +205,11 @@ static void composite_node_add_init(bNodeTree *UNUSED(bnodetree), bNode *bnode)
}
}
static bool composite_node_tree_socket_type_valid(eNodeSocketDatatype socket_type,
bNodeTreeType *UNUSED(ntreetype))
static bool composite_node_tree_socket_type_valid(bNodeTreeType *UNUSED(ntreetype),
bNodeSocketType *socket_type)
{
return ELEM(socket_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA);
return nodeIsStaticSocketType(socket_type) &&
ELEM(socket_type->type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA);
}
bNodeTreeType *ntreeType_Composite;

View File

@ -95,21 +95,21 @@ static bool geometry_node_tree_validate_link(bNodeTree *UNUSED(ntree), bNodeLink
return (link->tosock->type == link->fromsock->type);
}
static bool geometry_node_tree_socket_type_valid(eNodeSocketDatatype socket_type,
bNodeTreeType *UNUSED(ntreetype))
static bool geometry_node_tree_socket_type_valid(bNodeTreeType *UNUSED(ntreetype),
bNodeSocketType *socket_type)
{
return ELEM(socket_type,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_BOOLEAN,
SOCK_INT,
SOCK_STRING,
SOCK_OBJECT,
SOCK_GEOMETRY,
SOCK_COLLECTION,
SOCK_TEXTURE,
SOCK_MATERIAL);
return nodeIsStaticSocketType(socket_type) && ELEM(socket_type->type,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_BOOLEAN,
SOCK_INT,
SOCK_STRING,
SOCK_OBJECT,
SOCK_GEOMETRY,
SOCK_COLLECTION,
SOCK_TEXTURE,
SOCK_MATERIAL);
}
void register_node_tree_type_geo(void)

View File

@ -127,7 +127,7 @@ static bNodeSocket *group_verify_socket(
bNodeSocket *sock;
for (sock = verify_lb->first; sock; sock = sock->next) {
if (sock->typeinfo == iosock->typeinfo && STREQ(sock->identifier, iosock->identifier)) {
if (STREQ(sock->identifier, iosock->identifier)) {
break;
}
}
@ -137,6 +137,13 @@ static bNodeSocket *group_verify_socket(
const int mask = SOCK_HIDE_VALUE;
sock->flag = (sock->flag & ~mask) | (iosock->flag & mask);
/* Update socket type if necessary */
if (sock->typeinfo != iosock->typeinfo) {
nodeModifySocketType(ntree, gnode, sock, iosock->idname);
/* Flag the tree to make sure link validity is updated after type changes. */
ntree->update |= NTREE_UPDATE_LINKS;
}
if (iosock->typeinfo->interface_verify_socket) {
iosock->typeinfo->interface_verify_socket(ntree, iosock, gnode, sock, "interface");
}

View File

@ -117,7 +117,7 @@ static bNodeSocket *verify_socket_template(bNodeTree *ntree,
}
if (sock) {
if (sock->type != stemp->type) {
nodeModifySocketType(ntree, node, sock, stemp->type, stemp->subtype);
nodeModifySocketTypeStatic(ntree, node, sock, stemp->type, stemp->subtype);
}
sock->flag |= stemp->flag;
}
@ -533,12 +533,14 @@ static bNodeSocketType *make_standard_socket_type(int type, int subtype)
{
const char *socket_idname = nodeStaticSocketType(type, subtype);
const char *interface_idname = nodeStaticSocketInterfaceType(type, subtype);
const char *socket_label = nodeStaticSocketLabel(type, subtype);
bNodeSocketType *stype;
StructRNA *srna;
stype = (bNodeSocketType *)MEM_callocN(sizeof(bNodeSocketType), "node socket C type");
stype->free_self = (void (*)(bNodeSocketType * stype)) MEM_freeN;
BLI_strncpy(stype->idname, socket_idname, sizeof(stype->idname));
BLI_strncpy(stype->label, socket_label, sizeof(stype->label));
/* set the RNA type
* uses the exact same identifier as the socket type idname */

View File

@ -184,10 +184,11 @@ static bool shader_validate_link(bNodeTree *UNUSED(ntree), bNodeLink *link)
return true;
}
static bool shader_node_tree_socket_type_valid(eNodeSocketDatatype socket_type,
bNodeTreeType *UNUSED(ntreetype))
static bool shader_node_tree_socket_type_valid(bNodeTreeType *UNUSED(ntreetype),
bNodeSocketType *socket_type)
{
return ELEM(socket_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_SHADER);
return nodeIsStaticSocketType(socket_type) &&
ELEM(socket_type->type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_SHADER);
}
bNodeTreeType *ntreeType_Shader;

View File

@ -152,10 +152,11 @@ static void update(bNodeTree *ntree)
}
}
static bool texture_node_tree_socket_type_valid(eNodeSocketDatatype socket_type,
bNodeTreeType *UNUSED(ntreetype))
static bool texture_node_tree_socket_type_valid(bNodeTreeType *UNUSED(ntreetype),
bNodeSocketType *socket_type)
{
return ELEM(socket_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA);
return nodeIsStaticSocketType(socket_type) &&
ELEM(socket_type->type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA);
}
bNodeTreeType *ntreeType_Texture;