From e4ad58114b9d56fe838396a97fe09aff32c79c6a Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 24 Oct 2023 14:04:18 +0200 Subject: [PATCH] Nodes: support looking up node sockets by identifier and name The main goal of this patch is to turn `node.inputs[...]` and `node.outputs[...]` into the main way to lookup sockets on a node. Currently, it's used for that sometimes, but often it is not, because it does not work in all cases. More specifically, it does not work when a node has multiple sockets with the same name but different identifiers, which is relatively common. This patch proposes to make the string lookup more convenient, more useful and less prone to breaking after changes in Blender. This is achieved by changing how the lookup works: * First, it tries to find an available socket with an identifier that matches the given key. This is checked first, it makes sure that every socket can be accessed by its identifier. The identifier matching is currently disabled in some nodes where we want to change identifiers soonish. * If that didn't work, it tries to find an available socket with a matching name. This is often convenient because it generally matches what can be seen in the UI. Furthermore, it's also necessary to avoid breakage with old usages of this lookup function. * If both options above didn't work, it checks whether the given key matches any socket name/identifier that the node used to have in the past, but has been renamed for whatever reason. This mainly exists to avoid breaking scripts by certain kinds of changes in the future. This is not included in this patch. Note, previously, string lookup would also find unavailable sockets. This is disabled now, because the way we handle unavailable sockets internally is likely to change. Therefor, any use of unavailable sockets might break (unavailable != hidden). For the time being, it's still possible to find unavailable sockets by iterating over all sockets. This change has a small chance to break existing scripts because the behavior changes for some inputs in some nodes. However, for the cases where the function is used in practice, I don't really expect any breakage. This patch also allows us to give a better answer to reports like #113106. Pull Request: https://projects.blender.org/blender/blender/pulls/113984 --- .../blender/makesrna/intern/rna_nodetree.cc | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index eca3dffbd71..f670f9e8c20 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -2021,6 +2021,82 @@ static void rna_Node_internal_links_begin(CollectionPropertyIterator *iter, Poin rna_iterator_array_begin(iter, begin, sizeof(bNodeLink), len, false, nullptr); } +/** + * Forbid identifier lookup in nodes whose identifiers are likely to change soon because + * dynamically typed sockets are joined into one. + */ +static bool allow_identifier_lookup(const bNode &node) +{ + switch (node.type) { + case GEO_NODE_SWITCH: + case GEO_NODE_ACCUMULATE_FIELD: + case GEO_NODE_CAPTURE_ATTRIBUTE: + case GEO_NODE_ATTRIBUTE_STATISTIC: + case GEO_NODE_BLUR_ATTRIBUTE: + case GEO_NODE_SAMPLE_CURVE: + case GEO_NODE_EVALUATE_AT_INDEX: + case GEO_NODE_EVALUATE_ON_DOMAIN: + case GEO_NODE_INPUT_NAMED_ATTRIBUTE: + case GEO_NODE_RAYCAST: + case GEO_NODE_SAMPLE_INDEX: + case GEO_NODE_SAMPLE_NEAREST_SURFACE: + case FN_NODE_RANDOM_VALUE: + case GEO_NODE_SAMPLE_UV_SURFACE: + case GEO_NODE_STORE_NAMED_ATTRIBUTE: + case GEO_NODE_VIEWER: + case SH_NODE_MIX: + case FN_NODE_COMPARE: + case SH_NODE_MAP_RANGE: + return false; + default: + return true; + } +} + +static bNodeSocket *find_socket_by_key(bNode &node, + const eNodeSocketInOut in_out, + const blender::StringRef key) +{ + ListBase *sockets = in_out == SOCK_IN ? &node.inputs : &node.outputs; + if (allow_identifier_lookup(node)) { + LISTBASE_FOREACH (bNodeSocket *, socket, sockets) { + if (socket->is_available()) { + if (socket->identifier == key) { + return socket; + } + } + } + } + LISTBASE_FOREACH (bNodeSocket *, socket, sockets) { + if (socket->is_available()) { + if (socket->name == key) { + return socket; + } + } + } + return nullptr; +} + +static int rna_NodeInputs_lookup_string(PointerRNA *ptr, const char *key, PointerRNA *r_ptr) +{ + bNode *node = static_cast(ptr->data); + if (bNodeSocket *socket = find_socket_by_key(*node, SOCK_IN, key)) { + *r_ptr = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, socket); + return true; + } + return false; +} + +static int rna_NodeOutputs_lookup_string(PointerRNA *ptr, const char *key, PointerRNA *r_ptr) +{ + bNode *node = static_cast(ptr->data); + if (bNodeSocket *socket = find_socket_by_key(*node, SOCK_OUT, key)) { + *r_ptr = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, socket); + return true; + } + return false; +} + static bool rna_Node_parent_poll(PointerRNA *ptr, PointerRNA value) { bNode *node = static_cast(ptr->data); @@ -9630,6 +9706,15 @@ static void rna_def_node(BlenderRNA *brna) prop = RNA_def_property(srna, "inputs", PROP_COLLECTION, PROP_NONE); RNA_def_property_collection_sdna(prop, nullptr, "inputs", nullptr); + RNA_def_property_collection_funcs(prop, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + "rna_NodeInputs_lookup_string", + nullptr); RNA_def_property_struct_type(prop, "NodeSocket"); RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Inputs", ""); @@ -9637,6 +9722,15 @@ static void rna_def_node(BlenderRNA *brna) prop = RNA_def_property(srna, "outputs", PROP_COLLECTION, PROP_NONE); RNA_def_property_collection_sdna(prop, nullptr, "outputs", nullptr); + RNA_def_property_collection_funcs(prop, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + "rna_NodeOutputs_lookup_string", + nullptr); RNA_def_property_struct_type(prop, "NodeSocket"); RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Outputs", "");