From 6f3b967ac030b2fd4b7b6fcd934bf13013d58b5c Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 9 Mar 2023 12:06:01 -0500 Subject: [PATCH 1/3] Nodes: Add dropdown to select group socket subtype Previously the only way to control the subtype was to remove the group input or output and create it again. This commit adds a dropdown to change an existing socket, for supported socket types. Based on a patch by Angus Stanton: https://developer.blender.org/D15715 --- scripts/startup/bl_ui/space_node.py | 17 ++ source/blender/blenkernel/BKE_node.h | 3 + source/blender/blenkernel/intern/node.cc | 10 ++ .../editors/interface/interface_layout.cc | 7 +- .../blender/editors/space_node/node_edit.cc | 153 +++++++++++++++++- .../blender/editors/space_node/node_intern.hh | 1 + source/blender/editors/space_node/node_ops.cc | 1 + source/blender/makesrna/intern/rna_nodetree.c | 11 ++ source/blender/nodes/intern/node_socket.cc | 2 + 9 files changed, 201 insertions(+), 4 deletions(-) diff --git a/scripts/startup/bl_ui/space_node.py b/scripts/startup/bl_ui/space_node.py index b63a84cfc81..9821e49780c 100644 --- a/scripts/startup/bl_ui/space_node.py +++ b/scripts/startup/bl_ui/space_node.py @@ -889,6 +889,23 @@ class NodeTreeInterfacePanel(Panel): ) props.in_out = in_out + with context.temp_override(interface_socket=active_socket): + if bpy.ops.node.tree_socket_change_subtype.poll(): + layout_row = layout.row(align=True) + layout_split = layout_row.split(factor=0.4, align=True) + + label_column = layout_split.column(align=True) + label_column.alignment = 'RIGHT' + label_column.label(text="Subtype") + property_row = layout_split.row(align=True) + + property_row.context_pointer_set("interface_socket", active_socket) + props = property_row.operator_menu_enum( + "node.tree_socket_change_subtype", + "socket_subtype", + text=active_socket.bl_subtype_label if active_socket.bl_subtype_label else active_socket.bl_idname + ) + layout.use_property_split = True layout.use_property_decorate = False diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 28aa407f633..5dd04538224 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -158,6 +158,8 @@ typedef struct bNodeSocketType { char idname[64]; /* Type label */ char label[64]; + /* Subtype label */ + char subtype_label[64]; void (*draw)(struct bContext *C, struct uiLayout *layout, @@ -639,6 +641,7 @@ 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); +const char *nodeSocketSubTypeLabel(int subtype); /* Helper macros for iterating over node types. */ #define NODE_SOCKET_TYPES_BEGIN(stype) \ diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 82589e65e97..4ecd2c23b1c 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -68,6 +68,7 @@ #include "RNA_access.h" #include "RNA_define.h" +#include "RNA_enum_types.h" #include "RNA_prototypes.h" #include "NOD_common.h" @@ -1466,6 +1467,15 @@ const char *nodeSocketTypeLabel(const bNodeSocketType *stype) return stype->label[0] != '\0' ? stype->label : RNA_struct_ui_name(stype->ext_socket.srna); } +const char *nodeSocketSubTypeLabel(int subtype) +{ + const char *name; + if (RNA_enum_name(rna_enum_property_subtype_items, subtype, &name)) { + return name; + } + return ""; +} + bNodeSocket *nodeFindSocket(const bNode *node, const eNodeSocketInOut in_out, const char *identifier) diff --git a/source/blender/editors/interface/interface_layout.cc b/source/blender/editors/interface/interface_layout.cc index 1a4def7bed7..74bc15e92be 100644 --- a/source/blender/editors/interface/interface_layout.cc +++ b/source/blender/editors/interface/interface_layout.cc @@ -1658,8 +1658,11 @@ void uiItemsFullEnumO(uiLayout *layout, #endif } else { - RNA_property_enum_items_gettexted( - static_cast(block->evil_C), &ptr, prop, &item_array, &totitem, &free); + bContext *C = static_cast(block->evil_C); + bContextStore *previous_ctx = CTX_store_get(C); + CTX_store_set(C, layout->context); + RNA_property_enum_items_gettexted(C, &ptr, prop, &item_array, &totitem, &free); + CTX_store_set(C, previous_ctx); } /* add items */ diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index 79473957f33..b8f36968c47 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -30,6 +30,7 @@ #include "BKE_scene.h" #include "BKE_workspace.h" +#include "BLI_set.hh" #include "BLT_translation.h" #include "DEG_depsgraph.h" @@ -2120,7 +2121,7 @@ void NODE_OT_node_copy_color(wmOperatorType *ot) /** \name Node-Tree Add Interface Socket Operator * \{ */ -static bNodeSocket *ntree_get_active_interface_socket(ListBase *lb) +static bNodeSocket *ntree_get_active_interface_socket(const ListBase *lb) { LISTBASE_FOREACH (bNodeSocket *, socket, lb) { if (socket->flag & SELECT) { @@ -2265,7 +2266,6 @@ static int ntree_socket_change_type_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } - /* Don't handle sub-types for now. */ nodeModifySocketType(ntree, nullptr, iosock, socket_type->idname); /* Need the extra update here because the loop above does not check for valid links in the node @@ -2344,6 +2344,155 @@ void NODE_OT_tree_socket_change_type(wmOperatorType *ot) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Node-Tree Change Interface Socket Subtype Operator + * \{ */ + +static int ntree_socket_change_subtype_exec(bContext *C, wmOperator *op) +{ + Main *main = CTX_data_main(C); + const int socket_subtype = RNA_enum_get(op->ptr, "socket_subtype"); + + PointerRNA io_socket_ptr = CTX_data_pointer_get_type( + C, "interface_socket", &RNA_NodeSocketInterface); + bNodeSocket *io_socket = static_cast(io_socket_ptr.data); + if (!io_socket) { + return OPERATOR_CANCELLED; + } + + bNodeTree &node_tree = *reinterpret_cast(io_socket_ptr.owner_id); + + ListBase *sockets; + if (node_tree.interface_inputs().contains(io_socket)) { + sockets = &node_tree.inputs; + } + else if (node_tree.interface_outputs().contains(io_socket)) { + sockets = &node_tree.outputs; + } + else { + /* The interface socket should be in the inputs or outputs. */ + BLI_assert_unreachable(); + return OPERATOR_CANCELLED; + } + + nodeModifySocketTypeStatic(&node_tree, nullptr, io_socket, io_socket->type, socket_subtype); + + /* Deactivate sockets. */ + LISTBASE_FOREACH (bNodeSocket *, socket_iter, sockets) { + socket_iter->flag &= ~SELECT; + } + /* Make the new socket active. */ + io_socket->flag |= SELECT; + + BKE_ntree_update_tag_interface(&node_tree); + ED_node_tree_propagate_change(C, main, &node_tree); + + return OPERATOR_FINISHED; +} + +static Set socket_type_get_subtypes(const eNodeSocketDatatype type) +{ + switch (type) { + case SOCK_FLOAT: + return {PROP_PERCENTAGE, + PROP_FACTOR, + PROP_ANGLE, + PROP_TIME, + PROP_TIME_ABSOLUTE, + PROP_DISTANCE, + PROP_NONE}; + case SOCK_INT: + return {PROP_PERCENTAGE, PROP_FACTOR, PROP_NONE}; + case SOCK_VECTOR: + return {PROP_TRANSLATION, + /* Direction doesn't seem to work. */ + // PROP_DIRECTION, + PROP_VELOCITY, + PROP_ACCELERATION, + PROP_EULER, + PROP_XYZ, + PROP_NONE}; + default: + return {}; + } +} + +static const EnumPropertyItem *socket_change_subtype_itemf(bContext *C, + PointerRNA * /*ptr*/, + PropertyRNA * /*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; + } + + PointerRNA active_socket_ptr = CTX_data_pointer_get_type( + C, "interface_socket", &RNA_NodeSocketInterface); + const bNodeSocket *active_socket = static_cast(active_socket_ptr.data); + if (!active_socket) { + return DummyRNA_NULL_items; + } + + const Set subtypes = socket_type_get_subtypes(eNodeSocketDatatype(active_socket->type)); + if (subtypes.is_empty()) { + return DummyRNA_NULL_items; + } + + EnumPropertyItem *items = NULL; + int items_count = 0; + for (const EnumPropertyItem *item = rna_enum_property_subtype_items; item->name != NULL; + item++) { + if (subtypes.contains(item->value)) { + RNA_enum_item_add(&items, &items_count, item); + } + } + + if (items_count == 0) { + return DummyRNA_NULL_items; + } + + RNA_enum_item_end(&items, &items_count); + *r_free = true; + return items; +} + +static bool ntree_socket_change_subtype_poll(bContext *C) +{ + if (!ED_operator_node_editable(C)) { + return false; + } + PointerRNA io_socket_ptr = CTX_data_pointer_get_type( + C, "interface_socket", &RNA_NodeSocketInterface); + const bNodeSocket *io_socket = static_cast(io_socket_ptr.data); + if (!io_socket) { + return false; + } + return !socket_type_get_subtypes(eNodeSocketDatatype(io_socket->type)).is_empty(); +} + +void NODE_OT_tree_socket_change_subtype(wmOperatorType *ot) +{ + ot->name = "Change Node Tree Socket Subtype"; + ot->description = "Change the subtype of a socket of the active node tree"; + ot->idname = "NODE_OT_tree_socket_change_subtype"; + + ot->invoke = WM_menu_invoke; + ot->exec = ntree_socket_change_subtype_exec; + ot->poll = ntree_socket_change_subtype_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum( + ot->srna, "socket_subtype", DummyRNA_DEFAULT_items, 0, "Socket Subtype", ""); + RNA_def_enum_funcs(ot->prop, socket_change_subtype_itemf); +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Node-Tree Move Interface Socket Operator * \{ */ diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh index 11e64bb2f4c..b421a3e6d8f 100644 --- a/source/blender/editors/space_node/node_intern.hh +++ b/source/blender/editors/space_node/node_intern.hh @@ -363,6 +363,7 @@ void NODE_OT_clipboard_paste(wmOperatorType *ot); void NODE_OT_tree_socket_add(wmOperatorType *ot); void NODE_OT_tree_socket_remove(wmOperatorType *ot); void NODE_OT_tree_socket_change_type(wmOperatorType *ot); +void NODE_OT_tree_socket_change_subtype(wmOperatorType *ot); void NODE_OT_tree_socket_move(wmOperatorType *ot); void NODE_OT_shader_script_update(wmOperatorType *ot); diff --git a/source/blender/editors/space_node/node_ops.cc b/source/blender/editors/space_node/node_ops.cc index 026f375f467..4aae833e006 100644 --- a/source/blender/editors/space_node/node_ops.cc +++ b/source/blender/editors/space_node/node_ops.cc @@ -108,6 +108,7 @@ void node_operatortypes() 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_change_subtype); WM_operatortype_append(NODE_OT_tree_socket_move); WM_operatortype_append(NODE_OT_cryptomatte_layer_add); diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index bf1a9963f87..59a9942250a 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -11162,6 +11162,12 @@ static void rna_def_node_socket(BlenderRNA *brna) 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"); + prop = RNA_def_property(srna, "bl_subtype_label", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "typeinfo->subtype_label"); + RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL); + RNA_def_property_ui_text( + prop, "Subtype Label", "Label to display for the socket subtype in the UI"); + /* draw socket */ func = RNA_def_function(srna, "draw", NULL); RNA_def_function_ui_description(func, "Draw socket"); @@ -11279,6 +11285,11 @@ static void rna_def_node_socket_interface(BlenderRNA *brna) 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"); + prop = RNA_def_property(srna, "bl_subtype_label", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "typeinfo->subtype_label"); + RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL); + RNA_def_property_ui_text(prop, "Subtype Label", "Label to display for the socket subtype 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); diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc index 7d6499a496f..ee083e458ce 100644 --- a/source/blender/nodes/intern/node_socket.cc +++ b/source/blender/nodes/intern/node_socket.cc @@ -537,6 +537,7 @@ 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); + const char *socket_subtype_label = nodeSocketSubTypeLabel(subtype); bNodeSocketType *stype; StructRNA *srna; @@ -544,6 +545,7 @@ static bNodeSocketType *make_standard_socket_type(int type, int subtype) 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)); + BLI_strncpy(stype->subtype_label, socket_subtype_label, sizeof(stype->subtype_label)); /* set the RNA type * uses the exact same identifier as the socket type idname */ -- 2.30.2 From 7bcda420aeeb60e9184bf3b93f95c91836729a95 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 15 Mar 2023 09:59:05 -0400 Subject: [PATCH 2/3] Keep UI data when changing socket subtypes --- source/blender/blenkernel/intern/node.cc | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 1d90a67ea87..b33289edb65 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -1683,9 +1683,14 @@ void nodeModifySocketType(bNodeTree *ntree, } if (sock->default_value) { - socket_id_user_decrement(sock); - MEM_freeN(sock->default_value); - sock->default_value = nullptr; + if (sock->type != socktype->type) { + /* Only reallocate the default value if the type changed so that UI data like min and max + * isn't removed. This assumes that the default value is stored in the same format for all + * socket types with the same #eNodeSocketDatatype. */ + socket_id_user_decrement(sock); + MEM_freeN(sock->default_value); + sock->default_value = nullptr; + } } BLI_strncpy(sock->idname, idname, sizeof(sock->idname)); -- 2.30.2 From 82bea403a581b6b0d1f7978f13808beac5adefe3 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 3 Apr 2023 11:47:09 -0400 Subject: [PATCH 3/3] Update subtype stored in default value when not freeing --- source/blender/blenkernel/BKE_node_runtime.hh | 5 +++ source/blender/blenkernel/intern/node.cc | 33 +++++++++++++++++++ source/blender/makesdna/DNA_node_types.h | 1 + 3 files changed, 39 insertions(+) diff --git a/source/blender/blenkernel/BKE_node_runtime.hh b/source/blender/blenkernel/BKE_node_runtime.hh index 10f4d1ab4ec..17f8665c1b9 100644 --- a/source/blender/blenkernel/BKE_node_runtime.hh +++ b/source/blender/blenkernel/BKE_node_runtime.hh @@ -778,6 +778,11 @@ inline const bNodeSocket *bNodeSocket::internal_link_input() const return this->runtime->internal_link_input; } +template T *bNodeSocket::default_value_typed() +{ + return static_cast(this->default_value); +} + template const T *bNodeSocket::default_value_typed() const { return static_cast(this->default_value); diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 198d3409b42..e613af558f8 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -1691,6 +1691,39 @@ void nodeModifySocketType(bNodeTree *ntree, MEM_freeN(sock->default_value); sock->default_value = nullptr; } + else { + /* Update the socket subtype when the storage isn't freed and recreated. */ + switch (eNodeSocketDatatype(sock->type)) { + case SOCK_FLOAT: { + sock->default_value_typed()->subtype = socktype->subtype; + break; + } + case SOCK_VECTOR: { + sock->default_value_typed()->subtype = socktype->subtype; + break; + } + case SOCK_INT: { + sock->default_value_typed()->subtype = socktype->subtype; + break; + } + case SOCK_STRING: { + sock->default_value_typed()->subtype = socktype->subtype; + break; + } + case SOCK_RGBA: + case SOCK_SHADER: + case SOCK_BOOLEAN: + case SOCK_CUSTOM: + case __SOCK_MESH: + case SOCK_OBJECT: + case SOCK_IMAGE: + case SOCK_GEOMETRY: + case SOCK_COLLECTION: + case SOCK_TEXTURE: + case SOCK_MATERIAL: + break; + } + } } BLI_strncpy(sock->idname, idname, sizeof(sock->idname)); diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index ca5ada75e22..8245637943a 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -176,6 +176,7 @@ typedef struct bNodeSocket { bool is_output() const; /** Utility to access the value of the socket. */ + template T *default_value_typed(); template const T *default_value_typed() const; /* The following methods are only available when #bNodeTree.ensure_topology_cache has been -- 2.30.2