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
This commit is contained in:
Jacques Lucke
2025-05-26 05:44:59 +02:00
parent 6bd750bc01
commit f66aa6529a
3 changed files with 106 additions and 149 deletions

View File

@@ -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<nodes::socket_usage_inference::SocketUsage> 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<bNodeTree &>(*node_tree), &tree_log);
}
static bool run_node_ui_poll(wmOperatorType * /*ot*/, PointerRNA *ptr)

View File

@@ -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

View File

@@ -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<ModifierSearchData, OperatorSearchData> 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<ModifierSearchData>(&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<OperatorSearchData>(&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<SocketSearchData *>(
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<SocketSearchData *>(
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<ModifierSearchData>();
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<OperatorSearchData>();
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