From f66aa6529a9959ba5ed474cf2bfcdeadcdd517d5 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 26 May 2025 05:44:59 +0200 Subject: [PATCH] Geometry Nodes: support panels and attribute/layer search in node group operator Previously, the node group operator only had fairly basic flat drawing for the inputs. Due to previous refactors, it's now possible to reuse the drawing code of the Geometry Nodes modifier. That way the redo panel now has all the features that also exist in the modifier. Also, future improvements will benefit both systems and potentially more in the future. Pull Request: https://projects.blender.org/blender/blender/pulls/139389 --- .../editors/geometry/node_group_operator.cc | 141 +----------------- .../nodes/NOD_geometry_nodes_caller_ui.hh | 13 +- .../nodes/intern/geometry_nodes_caller_ui.cc | 101 +++++++++++-- 3 files changed, 106 insertions(+), 149 deletions(-) diff --git a/source/blender/editors/geometry/node_group_operator.cc b/source/blender/editors/geometry/node_group_operator.cc index c6c993f0620..2410ce2f854 100644 --- a/source/blender/editors/geometry/node_group_operator.cc +++ b/source/blender/editors/geometry/node_group_operator.cc @@ -68,6 +68,7 @@ #include "BLT_translation.hh" +#include "NOD_geometry_nodes_caller_ui.hh" #include "NOD_geometry_nodes_dependencies.hh" #include "NOD_geometry_nodes_execute.hh" #include "NOD_geometry_nodes_lazy_function.hh" @@ -811,130 +812,6 @@ static std::string run_node_group_get_description(bContext *C, return asset->get_metadata().description; } -struct DrawOperatorInputsContext { - const bNodeTree &ntree; - PointerRNA *bmain_ptr; - PointerRNA *op_ptr; - nodes::PropertiesVectorSet properties; - Array input_usages; -}; - -static void add_attribute_search_or_value_buttons(DrawOperatorInputsContext &ctx, - uiLayout *layout, - const StringRef socket_id_esc, - const StringRefNull rna_path, - const bNodeTreeInterfaceSocket &socket) -{ - bke::bNodeSocketType *typeinfo = bke::node_socket_type_find(socket.socket_type); - const eNodeSocketDatatype socket_type = eNodeSocketDatatype(typeinfo->type); - - const std::string rna_path_use_attribute = fmt::format( - "[\"{}{}\"]", socket_id_esc, nodes::input_use_attribute_suffix); - const std::string rna_path_attribute_name = fmt::format( - "[\"{}{}\"]", socket_id_esc, nodes::input_attribute_name_suffix); - - /* We're handling this manually in this case. */ - uiLayoutSetPropDecorate(layout, false); - - uiLayout *split = &layout->split(0.4f, false); - uiLayout *name_row = &split->row(false); - uiLayoutSetAlignment(name_row, UI_LAYOUT_ALIGN_RIGHT); - - const bool use_attribute = RNA_boolean_get(ctx.op_ptr, rna_path_use_attribute.c_str()); - if (socket_type == SOCK_BOOLEAN && !use_attribute) { - name_row->label("", ICON_NONE); - } - else { - name_row->label(socket.name ? socket.name : "", ICON_NONE); - } - - uiLayout *prop_row = &split->row(true); - if (socket_type == SOCK_BOOLEAN) { - uiLayoutSetPropSep(prop_row, false); - uiLayoutSetAlignment(prop_row, UI_LAYOUT_ALIGN_EXPAND); - } - - if (use_attribute) { - /* TODO: Add attribute search. */ - prop_row->prop(ctx.op_ptr, rna_path_attribute_name, UI_ITEM_NONE, "", ICON_NONE); - } - else { - const char *name = socket_type == SOCK_BOOLEAN ? (socket.name ? socket.name : "") : ""; - prop_row->prop(ctx.op_ptr, rna_path, UI_ITEM_NONE, name, ICON_NONE); - } - - prop_row->prop(ctx.op_ptr, rna_path_use_attribute, UI_ITEM_R_ICON_ONLY, "", ICON_SPREADSHEET); -} - -static void draw_property_for_socket(DrawOperatorInputsContext &ctx, - uiLayout *layout, - const bNodeTreeInterfaceSocket &socket) -{ - bke::bNodeSocketType *typeinfo = bke::node_socket_type_find(socket.socket_type); - const eNodeSocketDatatype socket_type = eNodeSocketDatatype(typeinfo->type); - - /* The property should be created in #MOD_nodes_update_interface with the correct type. */ - const IDProperty *property = ctx.properties.lookup_key_default_as(socket.identifier, nullptr); - - /* IDProperties can be removed with python, so there could be a situation where - * there isn't a property for a socket or it doesn't have the correct type. */ - if (!property || !nodes::id_property_type_matches_socket(socket, *property, true)) { - return; - } - - const int socket_index = ctx.ntree.interface_input_index(socket); - const bool affects_output = ctx.input_usages[socket_index].is_used; - - const std::string socket_id_esc = BLI_str_escape(socket.identifier); - const std::string rna_path = fmt::format("[\"{}\"]", socket_id_esc); - - uiLayout *row = &layout->row(true); - uiLayoutSetActive(row, affects_output); - uiLayoutSetPropDecorate(row, false); - - /* Use #uiItemPointerR to draw pointer properties because #uiLayout::prop would not have enough - * information about what type of ID to select for editing the values. This is because - * pointer IDProperties contain no information about their type. */ - const char *name = socket.name ? socket.name : ""; - switch (socket_type) { - case SOCK_OBJECT: - uiItemPointerR(row, ctx.op_ptr, rna_path, ctx.bmain_ptr, "objects", name, ICON_OBJECT_DATA); - break; - case SOCK_COLLECTION: - uiItemPointerR( - row, ctx.op_ptr, rna_path, ctx.bmain_ptr, "collections", name, ICON_OUTLINER_COLLECTION); - break; - case SOCK_MATERIAL: - uiItemPointerR(row, ctx.op_ptr, rna_path, ctx.bmain_ptr, "materials", name, ICON_MATERIAL); - break; - case SOCK_TEXTURE: - uiItemPointerR(row, ctx.op_ptr, rna_path, ctx.bmain_ptr, "textures", name, ICON_TEXTURE); - break; - case SOCK_IMAGE: - uiItemPointerR(row, ctx.op_ptr, rna_path, ctx.bmain_ptr, "images", name, ICON_IMAGE); - break; - case SOCK_MENU: { - if (socket.flag & NODE_INTERFACE_SOCKET_MENU_EXPANDED) { - row->prop(ctx.op_ptr, rna_path, UI_ITEM_R_EXPAND, name, ICON_NONE); - } - else { - row->prop(ctx.op_ptr, rna_path, UI_ITEM_NONE, name, ICON_NONE); - } - break; - } - default: - if (nodes::input_has_attribute_toggle(ctx.ntree, socket_index)) { - add_attribute_search_or_value_buttons(ctx, row, socket_id_esc, rna_path, socket); - } - else { - row->prop(ctx.op_ptr, rna_path, UI_ITEM_NONE, name, ICON_NONE); - } - } - if (!nodes::input_has_attribute_toggle(ctx.ntree, socket_index)) { - row->label("", ICON_BLANK1); - } -} - static void run_node_group_ui(bContext *C, wmOperator *op) { uiLayout *layout = op->layout; @@ -948,17 +825,11 @@ static void run_node_group_ui(bContext *C, wmOperator *op) return; } - node_tree->ensure_interface_cache(); - - DrawOperatorInputsContext ctx{*node_tree, &bmain_ptr, op->ptr}; - ctx.properties = nodes::build_properties_vector_set(op->properties); - ctx.input_usages.reinitialize(node_tree->interface_inputs().size()); - nodes::socket_usage_inference::infer_group_interface_inputs_usage( - *node_tree, ctx.properties, ctx.input_usages); - - for (const bNodeTreeInterfaceSocket *io_socket : node_tree->interface_inputs()) { - draw_property_for_socket(ctx, layout, *io_socket); - } + bke::OperatorComputeContext compute_context; + GeoOperatorLog &eval_log = get_static_eval_log(); + geo_log::GeoTreeLog &tree_log = eval_log.log->get_tree_log(compute_context.hash()); + nodes::draw_geometry_nodes_operator_redo_ui( + *C, *op, const_cast(*node_tree), &tree_log); } static bool run_node_ui_poll(wmOperatorType * /*ot*/, PointerRNA *ptr) diff --git a/source/blender/nodes/NOD_geometry_nodes_caller_ui.hh b/source/blender/nodes/NOD_geometry_nodes_caller_ui.hh index d098c607f63..c7265dd7b1d 100644 --- a/source/blender/nodes/NOD_geometry_nodes_caller_ui.hh +++ b/source/blender/nodes/NOD_geometry_nodes_caller_ui.hh @@ -7,11 +7,22 @@ struct bContext; struct PointerRNA; struct uiLayout; +struct wmOperator; +struct bNodeTree; namespace blender::nodes { +namespace geo_eval_log { +class GeoTreeLog; +} + void draw_geometry_nodes_modifier_ui(const bContext &C, PointerRNA *modifier_ptr, uiLayout &layout); -} +void draw_geometry_nodes_operator_redo_ui(const bContext &C, + wmOperator &op, + bNodeTree &tree, + geo_eval_log::GeoTreeLog *tree_log); + +} // namespace blender::nodes diff --git a/source/blender/nodes/intern/geometry_nodes_caller_ui.cc b/source/blender/nodes/intern/geometry_nodes_caller_ui.cc index 7cfca9ccaa1..c1df794dca9 100644 --- a/source/blender/nodes/intern/geometry_nodes_caller_ui.cc +++ b/source/blender/nodes/intern/geometry_nodes_caller_ui.cc @@ -13,6 +13,7 @@ #include "BKE_modifier.hh" #include "BKE_node.hh" #include "BKE_node_runtime.hh" +#include "BKE_screen.hh" #include "BLI_string.h" @@ -55,9 +56,18 @@ struct SearchInfo { IDProperty *properties = nullptr; }; -struct SocketSearchData { +struct ModifierSearchData { uint32_t object_session_uid; char modifier_name[MAX_NAME]; +}; + +struct OperatorSearchData { + /** Can store this data directly, because it's more persistent than for the modifier. */ + SearchInfo info; +}; + +struct SocketSearchData { + std::variant search_data; char socket_identifier[MAX_NAME]; bool is_output; @@ -92,7 +102,7 @@ static geo_log::GeoTreeLog *get_root_tree_log(const NodesModifierData &nmd) static NodesModifierData *get_modifier_data(Main &bmain, const wmWindowManager &wm, - const SocketSearchData &data) + const ModifierSearchData &data) { if (ED_screen_animation_playing(&wm)) { /* Work around an issue where the attribute search exec function has stale pointers when data @@ -116,15 +126,22 @@ static NodesModifierData *get_modifier_data(Main &bmain, SearchInfo SocketSearchData::info(const bContext &C) const { - const NodesModifierData *nmd = get_modifier_data(*CTX_data_main(&C), *CTX_wm_manager(&C), *this); - if (nmd == nullptr) { - return {}; + if (const auto *modifier_search_data = std::get_if(&this->search_data)) { + const NodesModifierData *nmd = get_modifier_data( + *CTX_data_main(&C), *CTX_wm_manager(&C), *modifier_search_data); + if (nmd == nullptr) { + return {}; + } + if (nmd->node_group == nullptr) { + return {}; + } + geo_log::GeoTreeLog *tree_log = get_root_tree_log(*nmd); + return {tree_log, nmd->node_group, nmd->settings.properties}; } - if (nmd->node_group == nullptr) { - return {}; + if (const auto *operator_search_data = std::get_if(&this->search_data)) { + return operator_search_data->info; } - geo_log::GeoTreeLog *tree_log = get_root_tree_log(*nmd); - return {tree_log, nmd->node_group, nmd->settings.properties}; + return {}; } static void layer_name_search_update_fn( @@ -232,7 +249,11 @@ static void add_layer_name_search_button(DrawGroupInputsContext &ctx, return; } - SocketSearchData *data = MEM_dupallocN(__func__, ctx.socket_search_data_fn(socket)); + /* Using a custom free function make the search not work currently. So make sure this data can be + * freed with MEM_freeN. */ + SocketSearchData *data = static_cast( + MEM_mallocN(sizeof(SocketSearchData), __func__)); + *data = ctx.socket_search_data_fn(socket); UI_but_func_search_set_results_are_suggestions(but, true); UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP); UI_but_func_search_set(but, @@ -346,7 +367,11 @@ static void add_attribute_search_button(DrawGroupInputsContext &ctx, return; } - SocketSearchData *data = MEM_dupallocN(__func__, ctx.socket_search_data_fn(socket)); + /* Using a custom free function make the search not work currently. So make sure this data can be + * freed with MEM_freeN. */ + SocketSearchData *data = static_cast( + MEM_mallocN(sizeof(SocketSearchData), __func__)); + *data = ctx.socket_search_data_fn(socket); UI_but_func_search_set_results_are_suggestions(but, true); UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP); UI_but_func_search_set(but, @@ -881,8 +906,9 @@ void draw_geometry_nodes_modifier_ui(const bContext &C, PointerRNA *modifier_ptr }; ctx.socket_search_data_fn = [&](const bNodeTreeInterfaceSocket &io_socket) -> SocketSearchData { SocketSearchData data{}; - data.object_session_uid = object.id.session_uid; - STRNCPY(data.modifier_name, nmd.modifier.name); + ModifierSearchData &modifier_search_data = data.search_data.emplace(); + modifier_search_data.object_session_uid = object.id.session_uid; + STRNCPY(modifier_search_data.modifier_name, nmd.modifier.name); STRNCPY(data.socket_identifier, io_socket.identifier); data.is_output = io_socket.flag & NODE_INTERFACE_SOCKET_OUTPUT; return data; @@ -936,4 +962,53 @@ void draw_geometry_nodes_modifier_ui(const bContext &C, PointerRNA *modifier_ptr } } +void draw_geometry_nodes_operator_redo_ui(const bContext &C, + wmOperator &op, + bNodeTree &tree, + geo_eval_log::GeoTreeLog *tree_log) +{ + uiLayout &layout = *op.layout; + Main &bmain = *CTX_data_main(&C); + PointerRNA bmain_ptr = RNA_main_pointer_create(&bmain); + + DrawGroupInputsContext ctx{ + C, &tree, tree_log, nodes::build_properties_vector_set(op.properties), op.ptr, &bmain_ptr}; + ctx.panel_open_property_fn = [&](const bNodeTreeInterfacePanel &io_panel) -> PanelOpenProperty { + Panel *root_panel = uiLayoutGetRootPanel(&layout); + LayoutPanelState *state = BKE_panel_layout_panel_state_ensure( + root_panel, + "node_operator_panel_" + std::to_string(io_panel.identifier), + io_panel.flag & NODE_INTERFACE_PANEL_DEFAULT_CLOSED); + PointerRNA state_ptr = RNA_pointer_create_discrete(nullptr, &RNA_LayoutPanelState, state); + return {state_ptr, "is_open"}; + }; + ctx.socket_search_data_fn = [&](const bNodeTreeInterfaceSocket &io_socket) -> SocketSearchData { + SocketSearchData data{}; + OperatorSearchData &operator_search_data = data.search_data.emplace(); + operator_search_data.info.tree = &tree; + operator_search_data.info.tree_log = tree_log; + operator_search_data.info.properties = op.properties; + STRNCPY(data.socket_identifier, io_socket.identifier); + data.is_output = io_socket.flag & NODE_INTERFACE_SOCKET_OUTPUT; + return data; + }; + ctx.draw_attribute_toggle_fn = + [&](uiLayout &layout, const int icon, const bNodeTreeInterfaceSocket &io_socket) { + const std::string prop_name = fmt::format( + "[\"{}{}\"]", BLI_str_escape(io_socket.identifier), nodes::input_use_attribute_suffix); + layout.prop(op.ptr, prop_name, UI_ITEM_R_ICON_ONLY, "", icon); + }; + + uiLayoutSetPropSep(&layout, true); + /* Decorators are added manually for supported properties because the + * attribute/value toggle requires a manually built layout anyway. */ + uiLayoutSetPropDecorate(&layout, false); + + tree.ensure_interface_cache(); + ctx.input_usages.reinitialize(tree.interface_inputs().size()); + nodes::socket_usage_inference::infer_group_interface_inputs_usage( + tree, ctx.properties, ctx.input_usages); + draw_interface_panel_content(ctx, &layout, tree.tree_interface.root_panel); +} + } // namespace blender::nodes