From f7f18cd0c74871671e32229c291c9176e9d68f7f Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 16 Jul 2025 08:31:59 +0200 Subject: [PATCH] Nodes: initial support for built-in menu sockets So far, only node group were able to have menu input sockets. Built-in nodes did not support them. Currently, all menus of built-in nodes are stored on the node instead of on the sockets. This limits their flexibility because it's not possible to expose these inputs. This patch adds initial support for having menu inputs in built-in nodes. For testing purposes, it also changes a couple built-in nodes to use an input socket instead of a node property: Points to Volume, Transform Geometry, Triangulate, Volume to Mesh and Match String. ### Compatibility Forward and backward compatibility is maintained where possible (it's not possible when the menu input is linked in 5.0). The overall compatibility approach is the same as what was done for the compositor with two differences: there are no wrapper RNA properties (not necessary for 5.0, those were removed for the compositor already too), no need to version animation (animation on the menu properties was already disabled). This also makes menu sockets not animatable in general which is kind of brittle (e.g. doesn't properly update when the menu definition changes). To animate a menu it's better to animate an integer and to drive an index switch with it. ### Which nodes to update? Many existing menu properties can become sockets, but it's currently not the intention to convert all of them. In some cases, converting them might restrict future improvements too much. This mainly affects Math nodes. Other existing nodes should be updated but are a bit more tricky to update for different reasons: * We don't support dynamic output visibility yet. This is something I'll need to look into at some point. * They are shared with shader/compositor nodes, which may be more limited in what can become a socket. * There may be performance implications unless extra special cases are implemented, especially for multi-function nodes. * Some nodes use socket renaming instead of dynamic socket visibility which isn't something we support more generally yet. ### Implementation The core implementation is fairly straight forward. The heavy lifting is done by the existing socket visibility inferencing. There is a new simple API that allows individual nodes to implement custom input-usage-rules based on other inputs in a decentralized way. In most cases, the nodes to update just have a single menu, so there is a new node-declaration utility that links a socket to a specific value of the menu input. This internally handles the usage inferencing as well as making the socket available when using link-drag-search. In the modified nodes, I also had to explicitly set the "main input" now which is used when inserting the node in a link. The automatic behavior doesn't work currently when the first input is a menu. This is something we'll have to solve more generally at some point but is out of scope for this patch. Pull Request: https://projects.blender.org/blender/blender/pulls/140705 --- .../blender/blenkernel/BKE_blender_version.h | 2 +- source/blender/blenkernel/intern/node.cc | 38 +++++ .../blenkernel/intern/node_socket_value.cc | 3 + .../blenkernel/intern/node_tree_update.cc | 19 +++ .../blenloader/intern/versioning_500.cc | 82 ++++++++++ source/blender/makesdna/DNA_node_types.h | 2 - .../makesrna/intern/rna_node_socket.cc | 1 + source/blender/nodes/NOD_geometry_exec.hh | 10 +- source/blender/nodes/NOD_node_declaration.hh | 21 +++ .../blender/nodes/NOD_socket_declarations.hh | 7 + .../nodes/NOD_socket_usage_inference.hh | 79 ++++++++++ .../nodes/NOD_socket_usage_inference_fwd.hh | 2 + .../function/nodes/node_fn_match_string.cc | 91 +++-------- .../nodes/node_geo_points_to_volume.cc | 104 ++++-------- .../nodes/node_geo_transform_geometry.cc | 84 +++------- .../geometry/nodes/node_geo_triangulate.cc | 148 +++++++----------- .../geometry/nodes/node_geo_volume_to_mesh.cc | 103 ++++-------- .../blender/nodes/intern/node_declaration.cc | 52 ++++++ .../nodes/intern/node_socket_declarations.cc | 27 +++- .../nodes/intern/socket_usage_inference.cc | 113 ++++++------- 20 files changed, 558 insertions(+), 430 deletions(-) diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 928acd882dd..54d66cd7d9e 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -27,7 +27,7 @@ /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 37 +#define BLENDER_FILE_SUBVERSION 38 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index d5310def75e..8010ee916b0 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -675,6 +675,43 @@ static void update_node_location_legacy(bNodeTree &ntree) } } +static void write_legacy_properties(bNodeTree &ntree) +{ + switch (ntree.type) { + case NTREE_GEOMETRY: { + for (bNode *node : ntree.all_nodes()) { + if (node->type_legacy == GEO_NODE_TRANSFORM_GEOMETRY) { + const bNodeSocket *socket = node_find_socket(*node, SOCK_IN, "Mode"); + node->custom1 = socket->default_value_typed()->value; + } + else if (node->type_legacy == GEO_NODE_POINTS_TO_VOLUME) { + auto &storage = *static_cast(node->storage); + const bNodeSocket *socket = node_find_socket(*node, SOCK_IN, "Resolution Mode"); + storage.resolution_mode = socket->default_value_typed()->value; + } + else if (node->type_legacy == GEO_NODE_TRIANGULATE) { + const bNodeSocket *quad_method_socket = node_find_socket(*node, SOCK_IN, "Quad Method"); + const bNodeSocket *ngon_method_socket = node_find_socket(*node, SOCK_IN, "N-gon Method"); + node->custom1 = quad_method_socket->default_value_typed()->value; + node->custom2 = ngon_method_socket->default_value_typed()->value; + } + else if (node->type_legacy == GEO_NODE_VOLUME_TO_MESH) { + auto &storage = *static_cast(node->storage); + const bNodeSocket *socket = node_find_socket(*node, SOCK_IN, "Resolution Mode"); + storage.resolution_mode = socket->default_value_typed()->value; + } + else if (STREQ(node->idname, "FunctionNodeMatchString")) { + const bNodeSocket *socket = node_find_socket(*node, SOCK_IN, "Operation"); + node->custom1 = socket->default_value_typed()->value; + } + } + break; + } + default: + break; + } +} + } // namespace forward_compat static void write_node_socket_default_value(BlendWriter *writer, const bNodeSocket *sock) @@ -841,6 +878,7 @@ void node_tree_blend_write(BlendWriter *writer, bNodeTree *ntree) if (!BLO_write_is_undo(writer)) { forward_compat::update_node_location_legacy(*ntree); + forward_compat::write_legacy_properties(*ntree); } for (bNode *node : ntree->all_nodes()) { diff --git a/source/blender/blenkernel/intern/node_socket_value.cc b/source/blender/blenkernel/intern/node_socket_value.cc index 9ddb64efe5b..a739ba34d54 100644 --- a/source/blender/blenkernel/intern/node_socket_value.cc +++ b/source/blender/blenkernel/intern/node_socket_value.cc @@ -401,6 +401,9 @@ bool SocketValueVariant::valid_for_socket(eNodeSocketDatatype socket_type) const if (kind_ == Kind::None) { return false; } + if (socket_type == SOCK_MENU) { + return socket_type_ == SOCK_INT; + } return socket_type_ == socket_type; } diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc index dc4de7b956d..25b6fcdfc17 100644 --- a/source/blender/blenkernel/intern/node_tree_update.cc +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -38,6 +38,7 @@ #include "NOD_geometry_nodes_lazy_function.hh" #include "NOD_node_declaration.hh" #include "NOD_socket.hh" +#include "NOD_socket_declarations.hh" #include "NOD_texture.h" #include "DEG_depsgraph_build.hh" @@ -1031,6 +1032,24 @@ class NodeTreeMainUpdater { } locally_defined_enums.append(&enum_input); } + else { + for (bNodeSocket *input_socket : node->input_sockets()) { + if (!input_socket->is_available()) { + continue; + } + if (input_socket->type != SOCK_MENU) { + continue; + } + const auto *socket_decl = dynamic_cast( + input_socket->runtime->declaration); + if (!socket_decl) { + continue; + } + this->set_enum_ptr(*input_socket->default_value_typed(), + socket_decl->items.get()); + locally_defined_enums.append(input_socket); + } + } /* Clear current enum references. */ for (bNodeSocket *socket : node->input_sockets()) { diff --git a/source/blender/blenloader/intern/versioning_500.cc b/source/blender/blenloader/intern/versioning_500.cc index cfcc486c35e..b8256c7a34c 100644 --- a/source/blender/blenloader/intern/versioning_500.cc +++ b/source/blender/blenloader/intern/versioning_500.cc @@ -502,6 +502,62 @@ static void do_version_normal_node_dot_product(bNodeTree *node_tree, bNode *node } } +static void do_version_transform_geometry_options_to_inputs(bNodeTree &ntree, bNode &node) +{ + if (blender::bke::node_find_socket(node, SOCK_IN, "Mode")) { + return; + } + bNodeSocket &socket = version_node_add_socket(ntree, node, SOCK_IN, "NodeSocketMenu", "Mode"); + socket.default_value_typed()->value = node.custom1; +} + +static void do_version_points_to_volume_options_to_inputs(bNodeTree &ntree, bNode &node) +{ + if (blender::bke::node_find_socket(node, SOCK_IN, "Resolution Mode")) { + return; + } + const NodeGeometryPointsToVolume &storage = *static_cast( + node.storage); + bNodeSocket &socket = version_node_add_socket( + ntree, node, SOCK_IN, "NodeSocketMenu", "Resolution Mode"); + socket.default_value_typed()->value = storage.resolution_mode; +} + +static void do_version_triangulate_options_to_inputs(bNodeTree &ntree, bNode &node) +{ + if (!blender::bke::node_find_socket(node, SOCK_IN, "Quad Method")) { + bNodeSocket &socket = version_node_add_socket( + ntree, node, SOCK_IN, "NodeSocketMenu", "Quad Method"); + socket.default_value_typed()->value = node.custom1; + } + if (!blender::bke::node_find_socket(node, SOCK_IN, "N-gon Method")) { + bNodeSocket &socket = version_node_add_socket( + ntree, node, SOCK_IN, "NodeSocketMenu", "N-gon Method"); + socket.default_value_typed()->value = node.custom2; + } +} + +static void do_version_volume_to_mesh_options_to_inputs(bNodeTree &ntree, bNode &node) +{ + if (blender::bke::node_find_socket(node, SOCK_IN, "Resolution Mode")) { + return; + } + const NodeGeometryVolumeToMesh &storage = *static_cast(node.storage); + bNodeSocket &socket = version_node_add_socket( + ntree, node, SOCK_IN, "NodeSocketMenu", "Resolution Mode"); + socket.default_value_typed()->value = storage.resolution_mode; +} + +static void do_version_match_string_options_to_inputs(bNodeTree &ntree, bNode &node) +{ + if (blender::bke::node_find_socket(node, SOCK_IN, "Operation")) { + return; + } + bNodeSocket &socket = version_node_add_socket( + ntree, node, SOCK_IN, "NodeSocketMenu", "Operation"); + socket.default_value_typed()->value = node.custom1; +} + static void version_seq_text_from_legacy(Main *bmain) { LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { @@ -1368,6 +1424,32 @@ void blo_do_versions_500(FileData * /*fd*/, Library * /*lib*/, Main *bmain) } FOREACH_NODETREE_END; } + + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 38)) { + FOREACH_NODETREE_BEGIN (bmain, node_tree, id) { + if (node_tree->type == NTREE_GEOMETRY) { + LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) { + if (node->type_legacy == GEO_NODE_TRANSFORM_GEOMETRY) { + do_version_transform_geometry_options_to_inputs(*node_tree, *node); + } + else if (node->type_legacy == GEO_NODE_POINTS_TO_VOLUME) { + do_version_points_to_volume_options_to_inputs(*node_tree, *node); + } + else if (node->type_legacy == GEO_NODE_TRIANGULATE) { + do_version_triangulate_options_to_inputs(*node_tree, *node); + } + else if (node->type_legacy == GEO_NODE_VOLUME_TO_MESH) { + do_version_volume_to_mesh_options_to_inputs(*node_tree, *node); + } + else if (STREQ(node->idname, "FunctionNodeMatchString")) { + do_version_match_string_options_to_inputs(*node_tree, *node); + } + } + } + } + FOREACH_NODETREE_END; + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index a3508a60ddd..e422d0ef961 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1757,8 +1757,6 @@ typedef struct NodeGeometryObjectInfo { typedef struct NodeGeometryPointsToVolume { /** #GeometryNodePointsToVolumeResolutionMode */ uint8_t resolution_mode; - /** #GeometryNodeAttributeInputMode */ - uint8_t input_type_radius; } NodeGeometryPointsToVolume; typedef struct NodeGeometryCollectionInfo { diff --git a/source/blender/makesrna/intern/rna_node_socket.cc b/source/blender/makesrna/intern/rna_node_socket.cc index 8db4fb0688c..420d59e6339 100644 --- a/source/blender/makesrna/intern/rna_node_socket.cc +++ b/source/blender/makesrna/intern/rna_node_socket.cc @@ -1503,6 +1503,7 @@ static void rna_def_node_socket_menu(BlenderRNA *brna, const char *identifier) RNA_def_property_enum_default_func(prop, "rna_NodeSocketStandard_menu_default"); RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketStandard_value_update"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); RNA_def_struct_sdna_from(srna, "bNodeSocket", nullptr); diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index 5de15634434..3cddc7dbace 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -119,7 +119,10 @@ class GeoNodeExecParams { */ template T extract_input(StringRef identifier) { - if constexpr (stored_as_SocketValueVariant_v) { + if constexpr (std::is_enum_v) { + return T(this->extract_input(identifier)); + } + else if constexpr (stored_as_SocketValueVariant_v) { SocketValueVariant value_variant = this->extract_input(identifier); return value_variant.extract(); } @@ -148,7 +151,10 @@ class GeoNodeExecParams { */ template T get_input(StringRef identifier) const { - if constexpr (stored_as_SocketValueVariant_v) { + if constexpr (std::is_enum_v) { + return T(this->get_input(identifier)); + } + else if constexpr (stored_as_SocketValueVariant_v) { auto value_variant = this->get_input(identifier); return value_variant.extract(); } diff --git a/source/blender/nodes/NOD_node_declaration.hh b/source/blender/nodes/NOD_node_declaration.hh index e4b4fd6a656..71c2f583c95 100644 --- a/source/blender/nodes/NOD_node_declaration.hh +++ b/source/blender/nodes/NOD_node_declaration.hh @@ -19,6 +19,8 @@ #include "RNA_types.hh" +#include "NOD_socket_usage_inference_fwd.hh" + struct bContext; struct bNode; struct uiLayout; @@ -190,6 +192,8 @@ struct CustomSocketDrawParams { }; using CustomSocketDrawFn = std::function; +using InputSocketUsageInferenceFn = std::function( + const socket_usage_inference::InputSocketUsageParams ¶ms)>; /** * Describes a single input or output socket. This is subclassed for different socket types. @@ -252,6 +256,11 @@ class SocketDeclaration : public ItemDeclaration { * Draw function that overrides how the socket is drawn for a specific node. */ std::unique_ptr custom_draw_fn; + /** + * Determines whether this input socket is used based on other input values and based on which + * outputs are used. + */ + std::unique_ptr usage_inference_fn; friend NodeDeclarationBuilder; friend class BaseSocketDeclarationBuilder; @@ -407,6 +416,18 @@ class BaseSocketDeclarationBuilder { */ BaseSocketDeclarationBuilder &custom_draw(CustomSocketDrawFn fn); + /** + * Provide a function that determines whether this input socket is used based on other input + * values and based on which outputs are used. + */ + BaseSocketDeclarationBuilder &usage_inference(InputSocketUsageInferenceFn fn); + + /** + * Utility method for the case when the node has a single menu input and this socket is only used + * when the menu input has a specific value. + */ + BaseSocketDeclarationBuilder &usage_by_single_menu(const int menu_value); + /** * Puts this socket on the same row as the previous socket. This only works when one of them is * an input and the other is an output. diff --git a/source/blender/nodes/NOD_socket_declarations.hh b/source/blender/nodes/NOD_socket_declarations.hh index dbffdf5bcae..89c4d9570b9 100644 --- a/source/blender/nodes/NOD_socket_declarations.hh +++ b/source/blender/nodes/NOD_socket_declarations.hh @@ -8,7 +8,10 @@ #include "RNA_types.hh" +#include "BKE_node_enum.hh" + #include "BLI_color.hh" +#include "BLI_implicit_sharing_ptr.hh" #include "BLI_math_euler_types.hh" #include "BLI_math_vector_types.hh" @@ -227,6 +230,7 @@ class Menu : public SocketDeclaration { int32_t default_value; bool is_expanded = false; + ImplicitSharingPtr items; friend MenuBuilder; @@ -244,6 +248,9 @@ class MenuBuilder : public SocketDeclarationBuilder { /** Draw the menu items next to each other instead of as a drop-down menu. */ MenuBuilder &expanded(bool value = true); + + /** Set the available items in the menu. The items array must have static lifetime. */ + MenuBuilder &static_items(const EnumPropertyItem *items); }; class BundleBuilder; diff --git a/source/blender/nodes/NOD_socket_usage_inference.hh b/source/blender/nodes/NOD_socket_usage_inference.hh index 0a09615594f..3054e0d8bad 100644 --- a/source/blender/nodes/NOD_socket_usage_inference.hh +++ b/source/blender/nodes/NOD_socket_usage_inference.hh @@ -7,6 +7,8 @@ #include "BLI_array.hh" #include "BLI_generic_pointer.hh" +#include "BKE_node.hh" + #include "NOD_geometry_nodes_execute.hh" #include "NOD_socket_usage_inference_fwd.hh" @@ -16,6 +18,83 @@ struct IDProperty; namespace blender::nodes::socket_usage_inference { +struct SocketUsageInferencer; + +/** + * During socket usage inferencing, some socket values are computed. This class represents such a + * computed value. Not all possible values can be presented here, only "basic" once (like int, but + * not int-field). A value can also be unknown if it can't be determined statically. + */ +class InferenceValue { + private: + /** + * Non-owning pointer to a value of type #bNodeSocketType.base_cpp_type of the corresponding + * socket. If this is null, the value is assumed to be unknown (aka, it can't be determined + * statically). + */ + const void *value_ = nullptr; + + public: + explicit InferenceValue(const void *value) : value_(value) {} + + static InferenceValue Unknown() + { + return InferenceValue(nullptr); + } + + bool is_unknown() const + { + return value_ == nullptr; + } + + const void *data() const + { + return value_; + } + + template T get_known() const + { + BLI_assert(!this->is_unknown()); + return *static_cast(this->value_); + } + + template std::optional get() const + { + if (this->is_unknown()) { + return std::nullopt; + } + return this->get_known(); + } +}; + +class InputSocketUsageParams { + private: + SocketUsageInferencer &inferencer_; + const ComputeContext *compute_context_ = nullptr; + + public: + const bNodeTree &tree; + const bNode &node; + const bNodeSocket &socket; + + InputSocketUsageParams(SocketUsageInferencer &inferencer, + const ComputeContext *compute_context, + const bNodeTree &tree, + const bNode &node, + const bNodeSocket &socket); + + /** + * Get an the statically known input value for the given socket identifier. The value may be + * unknown, in which case null is returned. + */ + InferenceValue get_input(StringRef identifier) const; + + /** + * Utility for the case when the socket depends on a specific menu input to have a certain value. + */ + bool menu_input_may_be(StringRef identifier, int enum_value) const; +}; + /** * Get a boolean value for each input socket in the given tree that indicates whether that input is * used. It is assumed that all output sockets in the tree are used. diff --git a/source/blender/nodes/NOD_socket_usage_inference_fwd.hh b/source/blender/nodes/NOD_socket_usage_inference_fwd.hh index d6b98486dea..3320b999708 100644 --- a/source/blender/nodes/NOD_socket_usage_inference_fwd.hh +++ b/source/blender/nodes/NOD_socket_usage_inference_fwd.hh @@ -6,6 +6,8 @@ namespace blender::nodes::socket_usage_inference { +class InputSocketUsageParams; + struct SocketUsage { bool is_used = true; bool is_visible = true; diff --git a/source/blender/nodes/function/nodes/node_fn_match_string.cc b/source/blender/nodes/function/nodes/node_fn_match_string.cc index 1f45ade9470..3d800f3db45 100644 --- a/source/blender/nodes/function/nodes/node_fn_match_string.cc +++ b/source/blender/nodes/function/nodes/node_fn_match_string.cc @@ -8,11 +8,8 @@ #include "UI_interface_layout.hh" #include "UI_resources.hh" -#include "RNA_enum_types.hh" - #include "node_function_util.hh" -#include "NOD_rna_define.hh" #include "NOD_socket_search_link.hh" namespace blender::nodes::node_fn_match_string_cc { @@ -40,63 +37,33 @@ const EnumPropertyItem rna_enum_node_match_string_items[] = { static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("String").hide_label(); + b.add_input("Operation").static_items(rna_enum_node_match_string_items); + b.add_input("String").hide_label().is_default_link_socket(); b.add_input("Key").hide_label().description( "The string to find in the input string"); b.add_output("Result"); } -static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) -{ - layout->prop(ptr, "operation", UI_ITEM_NONE, "", ICON_NONE); -} - -static void node_init(bNodeTree * /*tree*/, bNode *node) -{ - node->custom1 = int(MatchStringOperation::StartsWith); -} - -static const mf::MultiFunction *get_multi_function(const bNode &bnode) -{ - const MatchStringOperation operation = MatchStringOperation(bnode.custom1); - - switch (operation) { - case MatchStringOperation::StartsWith: { - static auto fn = mf::build::SI2_SO( - "Starts With", [](const std::string &a, const std::string &b) { - const StringRef strref_a(a); - const StringRef strref_b(b); - return strref_a.startswith(strref_b); - }); - return &fn; - } - case MatchStringOperation::EndsWith: { - static auto fn = mf::build::SI2_SO( - "Ends With", [](const std::string &a, const std::string &b) { - const StringRef strref_a(a); - const StringRef strref_b(b); - return strref_a.endswith(strref_b); - }); - return &fn; - } - case MatchStringOperation::Contains: { - static auto fn = mf::build::SI2_SO( - "Contains", [](const std::string &a, const std::string &b) { - const StringRef strref_a(a); - const StringRef strref_b(b); - return strref_a.find(strref_b) != StringRef::not_found; - }); - return &fn; - } - } - BLI_assert_unreachable(); - return nullptr; -} - static void node_build_multi_function(NodeMultiFunctionBuilder &builder) { - const mf::MultiFunction *fn = get_multi_function(builder.node()); - builder.set_matching_fn(fn); + static auto fn = mf::build::SI3_SO( + "Starts With", [](const int mode, const std::string &a, const std::string &b) { + const StringRef strref_a(a); + const StringRef strref_b(b); + switch (MatchStringOperation(mode)) { + case MatchStringOperation::StartsWith: { + return strref_a.startswith(strref_b); + } + case MatchStringOperation::EndsWith: { + return strref_a.endswith(strref_b); + } + case MatchStringOperation::Contains: { + return strref_a.find(strref_b) != StringRef::not_found; + } + } + return false; + }); + builder.set_matching_fn(&fn); } static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) @@ -113,8 +80,10 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) MatchStringOperation operation = MatchStringOperation(item->value); params.add_item(IFACE_(item->name), [operation](LinkSearchOpParams ¶ms) { bNode &node = params.add_node("FunctionNodeMatchString"); - node.custom1 = int8_t(operation); params.update_and_connect_available_socket(node, "String"); + bke::node_find_socket(node, SOCK_IN, "Operation") + ->default_value_typed() + ->value = int(operation); }); } } @@ -142,17 +111,6 @@ static void node_label(const bNodeTree * /*tree*/, BLI_strncpy_utf8(label, IFACE_(name), label_maxncpy); } -static void node_rna(StructRNA *srna) -{ - RNA_def_node_enum(srna, - "operation", - "Operation", - "", - rna_enum_node_match_string_items, - NOD_inline_enum_accessors(custom1), - int(MatchStringOperation::StartsWith)); -} - static void node_register() { static blender::bke::bNodeType ntype; @@ -163,12 +121,9 @@ static void node_register() ntype.declare = node_declare; ntype.labelfunc = node_label; ntype.gather_link_search_ops = node_gather_link_searches; - ntype.initfunc = node_init; - ntype.draw_buttons = node_layout; ntype.build_multi_function = node_build_multi_function; blender::bke::node_register_type(ntype); - node_rna(ntype.rna_ext.srna); } NOD_REGISTER_NODE(node_register) diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc index a3d2f2f4187..4cb05316101 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc @@ -17,11 +17,6 @@ #include "BKE_lib_id.hh" #include "BKE_volume.hh" -#include "NOD_rna_define.hh" - -#include "UI_interface_layout.hh" -#include "UI_resources.hh" - namespace blender::nodes::node_geo_points_to_volume_cc { #ifdef WITH_OPENVDB @@ -74,7 +69,6 @@ static float compute_voxel_size_from_amount(const float voxel_amount, * The grid class should be either openvdb::GRID_FOG_VOLUME or openvdb::GRID_LEVEL_SET. */ static void initialize_volume_component_from_points(GeoNodeExecParams ¶ms, - const NodeGeometryPointsToVolume &storage, GeometrySet &r_geometry_set) { Vector positions; @@ -95,11 +89,14 @@ static void initialize_volume_component_from_points(GeoNodeExecParams ¶ms, return; } + const auto resolution_mode = params.get_input( + "Resolution Mode"); + float voxel_size = 0.0f; - if (storage.resolution_mode == GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE) { + if (resolution_mode == GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE) { voxel_size = params.get_input("Voxel Size"); } - else if (storage.resolution_mode == GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT) { + else if (resolution_mode == GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT) { const float voxel_amount = params.get_input("Voxel Amount"); const float max_radius = *std::max_element(radii.begin(), radii.end()); voxel_size = compute_voxel_size_from_amount(voxel_amount, positions, max_radius); @@ -126,52 +123,48 @@ static void initialize_volume_component_from_points(GeoNodeExecParams ¶ms, NODE_STORAGE_FUNCS(NodeGeometryPointsToVolume) +static EnumPropertyItem resolution_mode_items[] = { + {GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT, + "VOXEL_AMOUNT", + 0, + "Amount", + "Specify the approximate number of voxels along the diagonal"}, + {GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE, + "VOXEL_SIZE", + 0, + "Size", + "Specify the voxel side length"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Points"); + b.add_input("Resolution Mode") + .static_items(resolution_mode_items) + .description("How the voxel size is specified"); + b.add_input("Points").is_default_link_socket(); b.add_input("Density").default_value(1.0f).min(0.0f); - auto &voxel_size = b.add_input("Voxel Size") - .default_value(0.3f) - .min(0.01f) - .subtype(PROP_DISTANCE) - .make_available([](bNode &node) { - node_storage(node).resolution_mode = - GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE; - }); - auto &voxel_amount = b.add_input("Voxel Amount") - .default_value(64.0f) - .min(0.0f) - .make_available([](bNode &node) { - node_storage(node).resolution_mode = - GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT; - }); + b.add_input("Voxel Size") + .default_value(0.3f) + .min(0.01f) + .subtype(PROP_DISTANCE) + .usage_by_single_menu(GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE); + b.add_input("Voxel Amount") + .default_value(64.0f) + .min(0.0f) + .usage_by_single_menu(GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT); b.add_input("Radius") .default_value(0.5f) .min(0.0f) .subtype(PROP_DISTANCE) .field_on_all(); b.add_output("Volume").translation_context(BLT_I18NCONTEXT_ID_ID); - - const bNode *node = b.node_or_null(); - if (node != nullptr) { - const NodeGeometryPointsToVolume &data = node_storage(*node); - voxel_size.available(data.resolution_mode == GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE); - voxel_amount.available(data.resolution_mode == - GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT); - } -} - -static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) -{ - layout->use_property_split_set(true); - layout->use_property_decorate_set(false); - layout->prop(ptr, "resolution_mode", UI_ITEM_NONE, IFACE_("Resolution"), ICON_NONE); } static void node_init(bNodeTree * /*tree*/, bNode *node) { + /* Still used for forward compatibility. */ NodeGeometryPointsToVolume *data = MEM_callocN(__func__); - data->resolution_mode = GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT; node->storage = data; } @@ -179,9 +172,8 @@ static void node_geo_exec(GeoNodeExecParams params) { #ifdef WITH_OPENVDB GeometrySet geometry_set = params.extract_input("Points"); - const NodeGeometryPointsToVolume &storage = node_storage(params.node()); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - initialize_volume_component_from_points(params, storage, geometry_set); + initialize_volume_component_from_points(params, geometry_set); }); params.set_output("Volume", std::move(geometry_set)); #else @@ -189,31 +181,6 @@ static void node_geo_exec(GeoNodeExecParams params) #endif } -static void node_rna(StructRNA *srna) -{ - static EnumPropertyItem resolution_mode_items[] = { - {GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT, - "VOXEL_AMOUNT", - 0, - "Amount", - "Specify the approximate number of voxels along the diagonal"}, - {GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE, - "VOXEL_SIZE", - 0, - "Size", - "Specify the voxel side length"}, - {0, nullptr, 0, nullptr, nullptr}, - }; - - RNA_def_node_enum(srna, - "resolution_mode", - "Resolution Mode", - "How the voxel size is specified", - resolution_mode_items, - NOD_storage_enum_accessors(resolution_mode), - GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT); -} - static void node_register() { static blender::bke::bNodeType ntype; @@ -229,10 +196,7 @@ static void node_register() ntype.initfunc = node_init; ntype.declare = node_declare; ntype.geometry_node_execute = node_geo_exec; - ntype.draw_buttons = node_layout; blender::bke::node_register_type(ntype); - - node_rna(ntype.rna_ext.srna); } NOD_REGISTER_NODE(node_register) diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_transform_geometry.cc index f46d986818f..9526d3462e6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_transform_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_transform_geometry.cc @@ -5,52 +5,41 @@ #include "BLI_math_matrix.hh" #include "BLI_math_rotation.hh" -#include "NOD_rna_define.hh" - #include "GEO_transform.hh" -#include "UI_interface_layout.hh" -#include "UI_resources.hh" - #include "node_geometry_util.hh" namespace blender::nodes::node_geo_transform_geometry_cc { +static EnumPropertyItem mode_items[] = { + {GEO_NODE_TRANSFORM_MODE_COMPONENTS, + "COMPONENTS", + 0, + "Components", + "Provide separate location, rotation and scale"}, + {GEO_NODE_TRANSFORM_MODE_MATRIX, "MATRIX", 0, "Matrix", "Use a transformation matrix"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + static void node_declare(NodeDeclarationBuilder &b) { b.use_custom_socket_order(); b.allow_any_socket_order(); - b.add_default_layout(); - auto enable_components = [](bNode &node) { node.custom1 = GEO_NODE_TRANSFORM_MODE_COMPONENTS; }; - auto enable_matrix = [](bNode &node) { node.custom1 = GEO_NODE_TRANSFORM_MODE_MATRIX; }; - b.add_input("Geometry"); + b.add_input("Mode") + .static_items(mode_items) + .description("How the transformation is specified"); + b.add_input("Geometry").is_default_link_socket(); b.add_output("Geometry").propagate_all().align_with_previous(); - auto &translation = b.add_input("Translation") - .subtype(PROP_TRANSLATION) - .make_available(enable_components); - auto &rotation = b.add_input("Rotation").make_available(enable_components); - auto &scale = - b.add_input("Scale").default_value({1, 1, 1}).subtype(PROP_XYZ).make_available( - enable_components); - auto &transform = b.add_input("Transform").make_available(enable_matrix); - - const bNode *node = b.node_or_null(); - if (node != nullptr) { - const bool use_matrix = node->custom1 == GEO_NODE_TRANSFORM_MODE_MATRIX; - - translation.available(!use_matrix); - rotation.available(!use_matrix); - scale.available(!use_matrix); - transform.available(use_matrix); - } -} - -static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) -{ - layout->use_property_split_set(true); - layout->use_property_decorate_set(false); - layout->prop(ptr, "mode", UI_ITEM_NONE, "", ICON_NONE); + b.add_input("Translation") + .subtype(PROP_TRANSLATION) + .usage_by_single_menu(GEO_NODE_TRANSFORM_MODE_COMPONENTS); + b.add_input("Rotation").usage_by_single_menu(GEO_NODE_TRANSFORM_MODE_COMPONENTS); + b.add_input("Scale") + .default_value({1, 1, 1}) + .subtype(PROP_XYZ) + .usage_by_single_menu(GEO_NODE_TRANSFORM_MODE_COMPONENTS); + b.add_input("Transform").usage_by_single_menu(GEO_NODE_TRANSFORM_MODE_MATRIX); } static bool use_translate(const math::Quaternion &rotation, const float3 scale) @@ -81,10 +70,10 @@ static void report_errors(GeoNodeExecParams ¶ms, static void node_geo_exec(GeoNodeExecParams params) { - const bool use_matrix = params.node().custom1 == GEO_NODE_TRANSFORM_MODE_MATRIX; + const auto mode = params.get_input("Mode"); GeometrySet geometry_set = params.extract_input("Geometry"); - if (use_matrix) { + if (mode == GEO_NODE_TRANSFORM_MODE_MATRIX) { const float4x4 transform = params.extract_input("Transform"); if (auto errors = geometry::transform_geometry(geometry_set, transform)) { report_errors(params, *errors); @@ -111,26 +100,6 @@ static void node_geo_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } -static void node_rna(StructRNA *srna) -{ - static EnumPropertyItem mode_items[] = { - {GEO_NODE_TRANSFORM_MODE_COMPONENTS, - "COMPONENTS", - 0, - "Components", - "Provide separate location, rotation and scale"}, - {GEO_NODE_TRANSFORM_MODE_MATRIX, "MATRIX", 0, "Matrix", "Use a transformation matrix"}, - {0, nullptr, 0, nullptr, nullptr}, - }; - - RNA_def_node_enum(srna, - "mode", - "Mode", - "How the transformation is specified", - mode_items, - NOD_inline_enum_accessors(custom1)); -} - static void register_node() { static blender::bke::bNodeType ntype; @@ -141,10 +110,7 @@ static void register_node() ntype.nclass = NODE_CLASS_GEOMETRY; ntype.declare = node_declare; ntype.geometry_node_execute = node_geo_exec; - ntype.draw_buttons = node_layout; blender::bke::node_register_type(ntype); - - node_rna(ntype.rna_ext.srna); } NOD_REGISTER_NODE(register_node) diff --git a/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc b/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc index 117c16307e2..8c45db5773a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc @@ -4,49 +4,85 @@ #include "DNA_mesh_types.h" -#include "NOD_rna_define.hh" - #include "GEO_mesh_triangulate.hh" -#include "UI_interface_layout.hh" -#include "UI_resources.hh" - #include "GEO_randomize.hh" #include "node_geometry_util.hh" namespace blender::nodes::node_geo_triangulate_cc { +static const EnumPropertyItem rna_node_geometry_triangulate_quad_method_items[] = { + {int(geometry::TriangulateQuadMode::Beauty), + "BEAUTY", + 0, + "Beauty", + "Split the quads in nice triangles, slower method"}, + {int(geometry::TriangulateQuadMode::Fixed), + "FIXED", + 0, + "Fixed", + "Split the quads on the first and third vertices"}, + {int(geometry::TriangulateQuadMode::Alternate), + "FIXED_ALTERNATE", + 0, + "Fixed Alternate", + "Split the quads on the 2nd and 4th vertices"}, + {int(geometry::TriangulateQuadMode::ShortEdge), + "SHORTEST_DIAGONAL", + 0, + "Shortest Diagonal", + "Split the quads along their shortest diagonal"}, + {int(geometry::TriangulateQuadMode::LongEdge), + "LONGEST_DIAGONAL", + 0, + "Longest Diagonal", + "Split the quads along their longest diagonal"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static const EnumPropertyItem rna_node_geometry_triangulate_ngon_method_items[] = { + {int(geometry::TriangulateNGonMode::Beauty), + "BEAUTY", + 0, + "Beauty", + "Arrange the new triangles evenly (slow)"}, + {int(geometry::TriangulateNGonMode::EarClip), + "CLIP", + 0, + "Clip", + "Split the polygons with an ear clipping algorithm"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + static void node_declare(NodeDeclarationBuilder &b) { b.use_custom_socket_order(); b.allow_any_socket_order(); - b.add_default_layout(); - b.add_input("Mesh").supported_type(GeometryComponent::Type::Mesh); + + b.add_input("Quad Method") + .static_items(rna_node_geometry_triangulate_quad_method_items) + .default_value(int(geometry::TriangulateQuadMode::ShortEdge)) + .description("Method for splitting the quads into triangles"); + b.add_input("N-gon Method") + .default_value(int(geometry::TriangulateNGonMode::Beauty)) + .static_items(rna_node_geometry_triangulate_ngon_method_items) + .description("Method for splitting the n-gons into triangles"); + b.add_input("Mesh") + .supported_type(GeometryComponent::Type::Mesh) + .is_default_link_socket(); b.add_output("Mesh").propagate_all().align_with_previous(); b.add_input("Selection").default_value(true).field_on_all().hide_value(); } -static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) -{ - layout->prop(ptr, "quad_method", UI_ITEM_NONE, "", ICON_NONE); - layout->prop(ptr, "ngon_method", UI_ITEM_NONE, "", ICON_NONE); -} - -static void geo_triangulate_init(bNodeTree * /*tree*/, bNode *node) -{ - node->custom1 = int(geometry::TriangulateQuadMode::ShortEdge); - node->custom2 = int(geometry::TriangulateNGonMode::Beauty); -} - static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input("Mesh"); Field selection_field = params.extract_input>("Selection"); const AttributeFilter &attribute_filter = params.get_attribute_filter("Mesh"); - geometry::TriangulateNGonMode ngon_method = geometry::TriangulateNGonMode(params.node().custom2); - geometry::TriangulateQuadMode quad_method = geometry::TriangulateQuadMode(params.node().custom1); + const auto ngon_method = params.extract_input("N-gon Method"); + const auto quad_method = params.extract_input("Quad Method"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { const Mesh *src_mesh = geometry_set.get_mesh(); @@ -87,72 +123,6 @@ static void node_geo_exec(GeoNodeExecParams params) params.set_output("Mesh", std::move(geometry_set)); } -static void node_rna(StructRNA *srna) -{ - static const EnumPropertyItem rna_node_geometry_triangulate_quad_method_items[] = { - {int(geometry::TriangulateQuadMode::Beauty), - "BEAUTY", - 0, - "Beauty", - "Split the quads in nice triangles, slower method"}, - {int(geometry::TriangulateQuadMode::Fixed), - "FIXED", - 0, - "Fixed", - "Split the quads on the first and third vertices"}, - {int(geometry::TriangulateQuadMode::Alternate), - "FIXED_ALTERNATE", - 0, - "Fixed Alternate", - "Split the quads on the 2nd and 4th vertices"}, - {int(geometry::TriangulateQuadMode::ShortEdge), - "SHORTEST_DIAGONAL", - 0, - "Shortest Diagonal", - "Split the quads along their shortest diagonal"}, - {int(geometry::TriangulateQuadMode::LongEdge), - "LONGEST_DIAGONAL", - 0, - "Longest Diagonal", - "Split the quads along their longest diagonal"}, - {0, nullptr, 0, nullptr, nullptr}, - }; - - static const EnumPropertyItem rna_node_geometry_triangulate_ngon_method_items[] = { - {int(geometry::TriangulateNGonMode::Beauty), - "BEAUTY", - 0, - "Beauty", - "Arrange the new triangles evenly (slow)"}, - {int(geometry::TriangulateNGonMode::EarClip), - "CLIP", - 0, - "Clip", - "Split the polygons with an ear clipping algorithm"}, - {0, nullptr, 0, nullptr, nullptr}, - }; - - RNA_def_node_enum(srna, - "quad_method", - "Quad Method", - "Method for splitting the quads into triangles", - rna_node_geometry_triangulate_quad_method_items, - NOD_inline_enum_accessors(custom1), - GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT, - nullptr, - true); - - RNA_def_node_enum(srna, - "ngon_method", - "N-gon Method", - "Method for splitting the n-gons into triangles", - rna_node_geometry_triangulate_ngon_method_items, - NOD_inline_enum_accessors(custom2), - GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT, - nullptr, - true); -} - static void node_register() { static blender::bke::bNodeType ntype; @@ -163,12 +133,8 @@ static void node_register() ntype.enum_name_legacy = "TRIANGULATE"; ntype.nclass = NODE_CLASS_GEOMETRY; ntype.declare = node_declare; - ntype.initfunc = geo_triangulate_init; ntype.geometry_node_execute = node_geo_exec; - ntype.draw_buttons = node_layout; blender::bke::node_register_type(ntype); - - node_rna(ntype.rna_ext.srna); } NOD_REGISTER_NODE(node_register) diff --git a/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc index f4137ea98b3..dcc7a7cbcab 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc @@ -15,63 +15,56 @@ #include "BKE_volume_grid.hh" #include "BKE_volume_to_mesh.hh" -#include "NOD_rna_define.hh" - -#include "UI_interface_layout.hh" -#include "UI_resources.hh" - #include "GEO_randomize.hh" namespace blender::nodes::node_geo_volume_to_mesh_cc { NODE_STORAGE_FUNCS(NodeGeometryVolumeToMesh) +static EnumPropertyItem resolution_mode_items[] = { + {VOLUME_TO_MESH_RESOLUTION_MODE_GRID, "GRID", 0, "Grid", "Use resolution of the volume grid"}, + {VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_AMOUNT, + "VOXEL_AMOUNT", + 0, + "Amount", + "Desired number of voxels along one axis"}, + {VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_SIZE, + "VOXEL_SIZE", + 0, + "Size", + "Desired voxel side length"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + static void node_declare(NodeDeclarationBuilder &b) { + b.add_input("Resolution Mode") + .static_items(resolution_mode_items) + .description("How the voxel size is specified"); b.add_input("Volume") .supported_type(GeometryComponent::Type::Volume) - .translation_context(BLT_I18NCONTEXT_ID_ID); - auto &voxel_size = b.add_input("Voxel Size") - .default_value(0.3f) - .min(0.01f) - .subtype(PROP_DISTANCE) - .make_available([](bNode &node) { - node_storage(node).resolution_mode = - VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_SIZE; - }); - auto &voxel_amount = b.add_input("Voxel Amount") - .default_value(64.0f) - .min(0.0f) - .make_available([](bNode &node) { - node_storage(node).resolution_mode = - VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_AMOUNT; - }); + .translation_context(BLT_I18NCONTEXT_ID_ID) + .is_default_link_socket(); + b.add_input("Voxel Size") + .default_value(0.3f) + .min(0.01f) + .subtype(PROP_DISTANCE) + .usage_by_single_menu(VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_SIZE); + b.add_input("Voxel Amount") + .default_value(64.0f) + .min(0.0f) + .usage_by_single_menu(VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_AMOUNT); b.add_input("Threshold") .default_value(0.1f) .description("Values larger than the threshold are inside the generated mesh"); b.add_input("Adaptivity").min(0.0f).max(1.0f).subtype(PROP_FACTOR); b.add_output("Mesh"); - - const bNode *node = b.node_or_null(); - if (node != nullptr) { - const NodeGeometryVolumeToMesh &storage = node_storage(*node); - - voxel_size.available(storage.resolution_mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_SIZE); - voxel_amount.available(storage.resolution_mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_AMOUNT); - } -} - -static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) -{ - layout->use_property_split_set(true); - layout->use_property_decorate_set(false); - layout->prop(ptr, "resolution_mode", UI_ITEM_NONE, IFACE_("Resolution"), ICON_NONE); } static void node_init(bNodeTree * /*tree*/, bNode *node) { + /* Still used for forward compatibility. */ NodeGeometryVolumeToMesh *data = MEM_callocN(__func__); - data->resolution_mode = VOLUME_TO_MESH_RESOLUTION_MODE_GRID; node->storage = data; } @@ -79,10 +72,8 @@ static void node_init(bNodeTree * /*tree*/, bNode *node) static bke::VolumeToMeshResolution get_resolution_param(const GeoNodeExecParams ¶ms) { - const NodeGeometryVolumeToMesh &storage = node_storage(params.node()); - bke::VolumeToMeshResolution resolution; - resolution.mode = (VolumeToMeshResolutionMode)storage.resolution_mode; + resolution.mode = params.get_input("Resolution Mode"); if (resolution.mode == VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_AMOUNT) { resolution.settings.voxel_amount = std::max(params.get_input("Voxel Amount"), 0.0f); } @@ -211,35 +202,6 @@ static void node_geo_exec(GeoNodeExecParams params) #endif } -static void node_rna(StructRNA *srna) -{ - static EnumPropertyItem resolution_mode_items[] = { - {VOLUME_TO_MESH_RESOLUTION_MODE_GRID, - "GRID", - 0, - "Grid", - "Use resolution of the volume grid"}, - {VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_AMOUNT, - "VOXEL_AMOUNT", - 0, - "Amount", - "Desired number of voxels along one axis"}, - {VOLUME_TO_MESH_RESOLUTION_MODE_VOXEL_SIZE, - "VOXEL_SIZE", - 0, - "Size", - "Desired voxel side length"}, - {0, nullptr, 0, nullptr, nullptr}, - }; - - RNA_def_node_enum(srna, - "resolution_mode", - "Resolution Mode", - "How the voxel size is specified", - resolution_mode_items, - NOD_storage_enum_accessors(resolution_mode)); -} - static void node_register() { static blender::bke::bNodeType ntype; @@ -255,10 +217,7 @@ static void node_register() blender::bke::node_type_size(ntype, 170, 120, 700); ntype.initfunc = node_init; ntype.geometry_node_execute = node_geo_exec; - ntype.draw_buttons = node_layout; blender::bke::node_register_type(ntype); - - node_rna(ntype.rna_ext.srna); } NOD_REGISTER_NODE(node_register) diff --git a/source/blender/nodes/intern/node_declaration.cc b/source/blender/nodes/intern/node_declaration.cc index 5b69c5239ca..5ed4d5feb52 100644 --- a/source/blender/nodes/intern/node_declaration.cc +++ b/source/blender/nodes/intern/node_declaration.cc @@ -5,8 +5,10 @@ #include "NOD_node_declaration.hh" #include "NOD_socket_declarations.hh" #include "NOD_socket_declarations_geometry.hh" +#include "NOD_socket_usage_inference.hh" #include "BLI_assert.h" +#include "BLI_listbase.h" #include "BLI_utildefines.h" #include "BKE_geometry_fields.hh" @@ -773,6 +775,56 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::custom_draw(CustomSo return *this; } +BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::usage_inference( + InputSocketUsageInferenceFn fn) +{ + decl_base_->usage_inference_fn = std::make_unique(std::move(fn)); + return *this; +} + +static const bNodeSocket &find_single_menu_input(const bNode &node) +{ +#ifndef NDEBUG + int menu_input_count = 0; + /* Topology cache may not be available here and this function may be called while doing tree + * modifications. */ + LISTBASE_FOREACH (bNodeSocket *, socket, &node.inputs) { + if (socket->type == SOCK_MENU) { + menu_input_count++; + } + } + BLI_assert(menu_input_count == 1); +#endif + + LISTBASE_FOREACH (bNodeSocket *, socket, &node.inputs) { + if (!socket->is_available()) { + continue; + } + if (socket->type != SOCK_MENU) { + continue; + } + return *socket; + } + BLI_assert_unreachable(); + return node.input_socket(0); +} + +BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::usage_by_single_menu( + const int menu_value) +{ + this->make_available([menu_value](bNode &node) { + bNodeSocket &socket = const_cast(find_single_menu_input(node)); + bNodeSocketValueMenu *value = socket.default_value_typed(); + value->value = menu_value; + }); + this->usage_inference([menu_value](const socket_usage_inference::InputSocketUsageParams ¶ms) + -> std::optional { + const bNodeSocket &socket = find_single_menu_input(params.node); + return params.menu_input_may_be(socket.identifier, menu_value); + }); + return *this; +} + BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::align_with_previous(const bool value) { decl_base_->align_with_previous_socket = value; diff --git a/source/blender/nodes/intern/node_socket_declarations.cc b/source/blender/nodes/intern/node_socket_declarations.cc index 0d927dbaf1b..405b70fb0aa 100644 --- a/source/blender/nodes/intern/node_socket_declarations.cc +++ b/source/blender/nodes/intern/node_socket_declarations.cc @@ -2,8 +2,6 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#include "BLI_string.h" - #include "NOD_socket_declarations.hh" #include "NOD_socket_declarations_geometry.hh" @@ -11,6 +9,7 @@ #include "BKE_node_runtime.hh" #include "BLI_math_vector.h" +#include "BLI_string.h" namespace blender::nodes::decl { @@ -604,6 +603,30 @@ bNodeSocket &Menu::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &s return socket; } +MenuBuilder &MenuBuilder::static_items(const EnumPropertyItem *items) +{ + /* Using a global map ensures that the same runtime data is used for the same static items. + * This is necessary because otherwise each node would have a different (incompatible) menu + * definition. */ + static Mutex mutex; + static Map> + items_by_enum_ptr; + + std::lock_guard lock{mutex}; + decl_->items = items_by_enum_ptr.lookup_or_add_cb(items, [&]() { + bke::RuntimeNodeEnumItems *runtime_items = new bke::RuntimeNodeEnumItems(); + for (const EnumPropertyItem *item = items; item->identifier; item++) { + bke::RuntimeNodeEnumItem runtime_item; + runtime_item.name = item->name; + runtime_item.description = item->description; + runtime_item.identifier = item->value; + runtime_items->items.append(std::move(runtime_item)); + } + return ImplicitSharingPtr(runtime_items); + }); + return *this; +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/nodes/intern/socket_usage_inference.cc b/source/blender/nodes/intern/socket_usage_inference.cc index 50451d0e539..90696a26329 100644 --- a/source/blender/nodes/intern/socket_usage_inference.cc +++ b/source/blender/nodes/intern/socket_usage_inference.cc @@ -28,56 +28,11 @@ namespace blender::nodes::socket_usage_inference { -/** - * During socket usage inferencing, some socket values are computed. This class represents such a - * computed value. Not all possible values can be presented here, only "basic" once (like int, but - * not int-field). A value can also be unknown if it can't be determined statically. - */ -class InferenceValue { - private: - /** - * Non-owning pointer to a value of type #bNodeSocketType.base_cpp_type of the corresponding - * socket. If this is null, the value is assumed to be unknown (aka, it can't be determined - * statically). - */ - const void *value_ = nullptr; - - public: - explicit InferenceValue(const void *value) : value_(value) {} - - static InferenceValue Unknown() - { - return InferenceValue(nullptr); - } - - bool is_unknown() const - { - return value_ == nullptr; - } - - const void *data() const - { - return value_; - } - - template T get_known() const - { - BLI_assert(!this->is_unknown()); - return *static_cast(this->value_); - } - - template std::optional get() const - { - if (this->is_unknown()) { - return std::nullopt; - } - return this->get_known(); - } -}; - /** Utility class to simplify passing global state into all the functions during inferencing. */ struct SocketUsageInferencer { private: + friend InputSocketUsageParams; + /** Owns e.g. intermediate evaluated values. */ ResourceScope scope_; bke::ComputeContextCache compute_context_cache_; @@ -477,23 +432,23 @@ struct SocketUsageInferencer { void usage_task__input__fallback(const SocketInContext &socket) { - Vector dependent_boolean_inputs; - /* For built-in nodes we assume that sockets in a panel with a panel-toggle are disabled when - * the panel is disabled. */ - if (const SocketDeclaration *socket_decl = socket->runtime->declaration) { - for (const PanelDeclaration *panel_decl = socket_decl->parent; panel_decl; - panel_decl = panel_decl->parent) - { - if (const SocketDeclaration *panel_toggle_decl = panel_decl->panel_input_decl()) { - if (panel_toggle_decl != socket_decl) { - dependent_boolean_inputs.append( - &socket->owner_node().socket_by_decl(*panel_toggle_decl)); - } - } - } + const SocketDeclaration *socket_decl = socket->runtime->declaration; + if (!socket_decl) { + all_socket_usages_.add_new(socket, true); + return; } - this->usage_task__with_dependent_sockets( - socket, socket->owner_node().output_sockets(), dependent_boolean_inputs, socket.context); + if (!socket_decl->usage_inference_fn) { + all_socket_usages_.add_new(socket, true); + return; + } + InputSocketUsageParams params{ + *this, socket.context, socket->owner_tree(), socket->owner_node(), *socket}; + const std::optional is_used = (*socket_decl->usage_inference_fn)(params); + if (!is_used.has_value()) { + /* Some value was requested, come back later when that value is available. */ + return; + } + all_socket_usages_.add_new(socket, *is_used); } void usage_task__input__foreach_element_input_node(const SocketInContext &socket) @@ -1557,4 +1512,36 @@ void infer_group_interface_inputs_usage(const bNodeTree &group, group, input_values, r_input_usages); } +InputSocketUsageParams::InputSocketUsageParams(SocketUsageInferencer &inferencer, + const ComputeContext *compute_context, + const bNodeTree &tree, + const bNode &node, + const bNodeSocket &socket) + : inferencer_(inferencer), + compute_context_(compute_context), + tree(tree), + node(node), + socket(socket) +{ +} + +InferenceValue InputSocketUsageParams::get_input(const StringRef identifier) const +{ + const SocketInContext input_socket{compute_context_, + &this->node.input_by_identifier(identifier)}; + return inferencer_.get_socket_value(input_socket); +} + +bool InputSocketUsageParams::menu_input_may_be(const StringRef identifier, + const int enum_value) const +{ + BLI_assert(this->node.input_by_identifier(identifier).type == SOCK_MENU); + const InferenceValue value = this->get_input(identifier); + if (value.is_unknown()) { + /* The value is unknown, so it may be the requested enum value. */ + return true; + } + return value.get_known() == enum_value; +} + } // namespace blender::nodes::socket_usage_inference