Geometry Nodes: Add volume grid name search
Very similar to the layer name search added somewhat recently. This just displays the names of existing grids in the "Name" inputs to the Store and Get Named Grid nodes. Pull Request: https://projects.blender.org/blender/blender/pulls/147163
This commit is contained in:
@@ -33,6 +33,7 @@ set(SRC
|
||||
node_edit.cc
|
||||
node_geometry_attribute_search.cc
|
||||
node_geometry_layer_search.cc
|
||||
node_geometry_volume_grid_search.cc
|
||||
node_gizmo.cc
|
||||
node_group.cc
|
||||
node_ops.cc
|
||||
|
||||
@@ -1040,6 +1040,22 @@ static bool socket_needs_layer_search(const bNode &node, const bNodeSocket &sock
|
||||
return node_decl->inputs[socket_index]->is_layer_name;
|
||||
}
|
||||
|
||||
static bool socket_needs_volume_grid_search(const bNode &node, const bNodeSocket &socket)
|
||||
{
|
||||
const nodes::NodeDeclaration *node_decl = node.declaration();
|
||||
if (node_decl == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (node_decl->skip_updating_sockets) {
|
||||
return false;
|
||||
}
|
||||
if (socket.in_out == SOCK_OUT) {
|
||||
return false;
|
||||
}
|
||||
const int socket_index = BLI_findindex(&node.inputs, &socket);
|
||||
return node_decl->inputs[socket_index]->is_volume_grid_name;
|
||||
}
|
||||
|
||||
static void draw_gizmo_pin_icon(uiLayout *layout, PointerRNA *socket_ptr)
|
||||
{
|
||||
layout->prop(socket_ptr, "pin_gizmo", UI_ITEM_NONE, "", ICON_GIZMO);
|
||||
@@ -1204,6 +1220,16 @@ static void std_node_socket_draw(
|
||||
node_geometry_add_layer_search_button(*C, *node, *ptr, *row);
|
||||
}
|
||||
}
|
||||
else if (socket_needs_volume_grid_search(*node, *sock)) {
|
||||
if (optional_label) {
|
||||
node_geometry_add_volume_grid_search_button(*C, *node, *ptr, *layout, label);
|
||||
}
|
||||
else {
|
||||
uiLayout *row = &layout->split(0.4f, false);
|
||||
row->label(label, ICON_NONE);
|
||||
node_geometry_add_volume_grid_search_button(*C, *node, *ptr, *row);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (optional_label) {
|
||||
layout->prop(ptr,
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
#include "DNA_node_types.h"
|
||||
#include "DNA_space_types.h"
|
||||
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
#include "BKE_node_tree_zones.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
#include "ED_node.hh"
|
||||
#include "ED_screen.hh"
|
||||
#include "ED_undo.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_log.hh"
|
||||
|
||||
#include "UI_string_search.hh"
|
||||
|
||||
#include "node_intern.hh"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
using blender::nodes::geo_eval_log::GeometryInfoLog;
|
||||
using blender::nodes::geo_eval_log::VolumeGridInfo;
|
||||
|
||||
namespace blender::ed::space_node {
|
||||
|
||||
struct GridSearchData {
|
||||
int32_t node_id;
|
||||
char socket_identifier[MAX_NAME];
|
||||
bool can_create_grid;
|
||||
};
|
||||
|
||||
/* This class must not have a destructor, since it is used by buttons and freed with #MEM_freeN. */
|
||||
BLI_STATIC_ASSERT(std::is_trivially_destructible_v<GridSearchData>, "");
|
||||
|
||||
static Vector<const VolumeGridInfo *> get_grid_names_from_context(const bContext &C,
|
||||
GridSearchData &data)
|
||||
{
|
||||
using namespace nodes::geo_eval_log;
|
||||
|
||||
SpaceNode *snode = CTX_wm_space_node(&C);
|
||||
if (!snode) {
|
||||
BLI_assert_unreachable();
|
||||
return {};
|
||||
}
|
||||
bNodeTree *node_tree = snode->edittree;
|
||||
if (node_tree == nullptr) {
|
||||
BLI_assert_unreachable();
|
||||
return {};
|
||||
}
|
||||
const bNode *node = node_tree->node_by_id(data.node_id);
|
||||
if (node == nullptr) {
|
||||
BLI_assert_unreachable();
|
||||
return {};
|
||||
}
|
||||
const bke::bNodeTreeZones *tree_zones = node_tree->zones();
|
||||
if (!tree_zones) {
|
||||
return {};
|
||||
}
|
||||
const ContextualGeoTreeLogs tree_logs = GeoNodesLog::get_contextual_tree_logs(*snode);
|
||||
|
||||
Set<StringRef> names;
|
||||
|
||||
GeoTreeLog *tree_log = tree_logs.get_main_tree_log(*node);
|
||||
if (!tree_log) {
|
||||
return {};
|
||||
}
|
||||
tree_log->ensure_socket_values();
|
||||
GeoNodeLog *node_log = tree_log->nodes.lookup_ptr(node->identifier);
|
||||
if (node_log == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Vector<const VolumeGridInfo *> grids;
|
||||
for (const bNodeSocket *input_socket : node->input_sockets()) {
|
||||
if (input_socket->type != SOCK_GEOMETRY) {
|
||||
continue;
|
||||
}
|
||||
const ValueLog *value_log = tree_log->find_socket_value_log(*input_socket);
|
||||
if (value_log == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (const GeometryInfoLog *geo_log = dynamic_cast<const GeometryInfoLog *>(value_log)) {
|
||||
if (const std::optional<GeometryInfoLog::VolumeInfo> &volume_info = geo_log->volume_info) {
|
||||
for (const VolumeGridInfo &info : volume_info->grids) {
|
||||
if (names.add(info.name)) {
|
||||
grids.append(&info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return grids;
|
||||
}
|
||||
|
||||
static StringRef grid_data_type_string(const VolumeGridType type)
|
||||
{
|
||||
const char *name = nullptr;
|
||||
RNA_enum_name_from_value(rna_enum_volume_grid_data_type_items, type, &name);
|
||||
return IFACE_(StringRef(name));
|
||||
}
|
||||
|
||||
static bool grid_search_item_add(uiSearchItems &items, const VolumeGridInfo &item)
|
||||
{
|
||||
std::string text = fmt::format(
|
||||
"{}" UI_SEP_CHAR_S "{}", item.name, grid_data_type_string(item.grid_type));
|
||||
return UI_search_item_add(&items, text, (void *)&item, ICON_NONE, UI_BUT_HAS_SEP_CHAR, 0);
|
||||
}
|
||||
|
||||
static void volume_grid_search_add_items(const StringRef str,
|
||||
const bool can_create_grid,
|
||||
const Span<const VolumeGridInfo *> grids,
|
||||
uiSearchItems &seach_items,
|
||||
const bool is_first)
|
||||
{
|
||||
static std::string dummy_str;
|
||||
|
||||
/* Any string may be valid, so add the current search string along with the hints. */
|
||||
if (!str.is_empty()) {
|
||||
bool contained = false;
|
||||
for (const VolumeGridInfo *info : grids) {
|
||||
if (info->name == str) {
|
||||
contained = true;
|
||||
}
|
||||
}
|
||||
if (!contained) {
|
||||
dummy_str = str;
|
||||
UI_search_item_add(
|
||||
&seach_items, str, &dummy_str, can_create_grid ? ICON_ADD : ICON_NONE, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (str.is_empty() && !is_first) {
|
||||
/* Allow clearing the text field when the string is empty, but not on the first pass,
|
||||
* or opening a name field for the first time would show this search item. */
|
||||
dummy_str = str;
|
||||
UI_search_item_add(&seach_items, str, &dummy_str, ICON_X, 0, 0);
|
||||
}
|
||||
|
||||
/* Don't filter when the menu is first opened, but still run the search
|
||||
* so the items are in the same order they will appear in while searching. */
|
||||
const StringRef string = is_first ? "" : str;
|
||||
|
||||
ui::string_search::StringSearch<const VolumeGridInfo> search;
|
||||
for (const VolumeGridInfo *info : grids) {
|
||||
search.add(info->name, info);
|
||||
}
|
||||
|
||||
const Vector<const VolumeGridInfo *> filtered_names = search.query(string);
|
||||
for (const VolumeGridInfo *info : filtered_names) {
|
||||
if (!grid_search_item_add(seach_items, *info)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void grid_search_update_fn(
|
||||
const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
|
||||
{
|
||||
if (ED_screen_animation_playing(CTX_wm_manager(C))) {
|
||||
return;
|
||||
}
|
||||
|
||||
GridSearchData *data = static_cast<GridSearchData *>(arg);
|
||||
|
||||
Vector<const VolumeGridInfo *> grids = get_grid_names_from_context(*C, *data);
|
||||
|
||||
BLI_assert(items);
|
||||
volume_grid_search_add_items(str, data->can_create_grid, grids, *items, is_first);
|
||||
}
|
||||
|
||||
static void grid_search_exec_fn(bContext *C, void *data_v, void *item_v)
|
||||
{
|
||||
if (ED_screen_animation_playing(CTX_wm_manager(C))) {
|
||||
return;
|
||||
}
|
||||
std::string *item = static_cast<std::string *>(item_v);
|
||||
if (item == nullptr) {
|
||||
return;
|
||||
}
|
||||
SpaceNode *snode = CTX_wm_space_node(C);
|
||||
if (!snode) {
|
||||
BLI_assert_unreachable();
|
||||
return;
|
||||
}
|
||||
bNodeTree *node_tree = snode->edittree;
|
||||
if (node_tree == nullptr) {
|
||||
BLI_assert_unreachable();
|
||||
return;
|
||||
}
|
||||
GridSearchData *data = static_cast<GridSearchData *>(data_v);
|
||||
bNode *node = node_tree->node_by_id(data->node_id);
|
||||
if (node == nullptr) {
|
||||
BLI_assert_unreachable();
|
||||
return;
|
||||
}
|
||||
|
||||
bNodeSocket *socket = bke::node_find_enabled_input_socket(*node, data->socket_identifier);
|
||||
if (socket == nullptr) {
|
||||
BLI_assert_unreachable();
|
||||
return;
|
||||
}
|
||||
BLI_assert(socket->type == SOCK_STRING);
|
||||
|
||||
bNodeSocketValueString *value = static_cast<bNodeSocketValueString *>(socket->default_value);
|
||||
BLI_strncpy_utf8(value->value, item->c_str(), MAX_NAME);
|
||||
|
||||
ED_undo_push(C, "Assign Grid Name");
|
||||
}
|
||||
|
||||
void node_geometry_add_volume_grid_search_button(const bContext & /*C*/,
|
||||
const bNode &node,
|
||||
PointerRNA &socket_ptr,
|
||||
uiLayout &layout,
|
||||
const StringRef placeholder)
|
||||
{
|
||||
uiBlock *block = layout.block();
|
||||
uiBut *but = uiDefIconTextButR(block,
|
||||
ButType::SearchMenu,
|
||||
0,
|
||||
ICON_NONE,
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
10 * UI_UNIT_X, /* Dummy value, replaced by layout system. */
|
||||
UI_UNIT_Y,
|
||||
&socket_ptr,
|
||||
"default_value",
|
||||
0,
|
||||
"");
|
||||
UI_but_placeholder_set(but, placeholder);
|
||||
|
||||
const bNodeSocket &socket = *static_cast<const bNodeSocket *>(socket_ptr.data);
|
||||
GridSearchData *data = MEM_callocN<GridSearchData>(__func__);
|
||||
data->node_id = node.identifier;
|
||||
data->can_create_grid = node.is_type("GeometryNodeStoreNamedGrid");
|
||||
STRNCPY_UTF8(data->socket_identifier, socket.identifier);
|
||||
|
||||
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,
|
||||
nullptr,
|
||||
grid_search_update_fn,
|
||||
static_cast<void *>(data),
|
||||
true,
|
||||
nullptr,
|
||||
grid_search_exec_fn,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
} // namespace blender::ed::space_node
|
||||
@@ -436,6 +436,13 @@ void node_geometry_add_layer_search_button(const bContext &C,
|
||||
PointerRNA &socket_ptr,
|
||||
uiLayout &layout,
|
||||
StringRef placeholder = "");
|
||||
/* `node_geometry_volume_grid_search.cc` */
|
||||
|
||||
void node_geometry_add_volume_grid_search_button(const bContext &C,
|
||||
const bNode &node,
|
||||
PointerRNA &socket_ptr,
|
||||
uiLayout &layout,
|
||||
StringRef placeholder = "");
|
||||
|
||||
/* `node_context_path.cc` */
|
||||
|
||||
|
||||
@@ -638,7 +638,7 @@ class SocketTooltipBuilder {
|
||||
case bke::GeometryComponent::Type::Volume: {
|
||||
const geo_log::GeometryInfoLog::VolumeInfo &info = *geometry_log.volume_info;
|
||||
component_str = fmt::format(fmt::runtime(TIP_("Volume: {} grids")),
|
||||
this->count_to_string(info.grids_num));
|
||||
this->count_to_string(info.grids.size()));
|
||||
break;
|
||||
}
|
||||
case bke::GeometryComponent::Type::Curve: {
|
||||
|
||||
@@ -135,6 +135,11 @@ struct GeometryAttributeInfo {
|
||||
std::optional<bke::AttrType> data_type;
|
||||
};
|
||||
|
||||
struct VolumeGridInfo {
|
||||
std::string name;
|
||||
VolumeGridType grid_type;
|
||||
};
|
||||
|
||||
/**
|
||||
* Geometries are not logged entirely, because that would result in a lot of time and memory
|
||||
* overhead. Instead, only the data needed for UI features is logged.
|
||||
@@ -168,7 +173,7 @@ class GeometryInfoLog : public ValueLog {
|
||||
int gizmo_transforms_num = 0;
|
||||
};
|
||||
struct VolumeInfo {
|
||||
int grids_num;
|
||||
Vector<VolumeGridInfo> grids;
|
||||
};
|
||||
|
||||
std::optional<MeshInfo> mesh_info;
|
||||
|
||||
@@ -232,6 +232,7 @@ class SocketDeclaration : public ItemDeclaration {
|
||||
/** This socket is used as a toggle for the parent panel. */
|
||||
bool is_panel_toggle = false;
|
||||
bool is_layer_name = false;
|
||||
bool is_volume_grid_name = false;
|
||||
|
||||
/** Index in the list of inputs or outputs of the node. */
|
||||
int index = -1;
|
||||
@@ -474,6 +475,7 @@ class BaseSocketDeclarationBuilder {
|
||||
BaseSocketDeclarationBuilder &structure_type(StructureType structure_type);
|
||||
|
||||
BaseSocketDeclarationBuilder &is_layer_name(bool value = true);
|
||||
BaseSocketDeclarationBuilder &is_volume_grid_name(bool value = true);
|
||||
|
||||
/** Index in the list of inputs or outputs. */
|
||||
int index() const;
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace blender::nodes::node_geo_get_named_grid_cc {
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Geometry>("Volume").description("Volume to take a named grid out of");
|
||||
b.add_input<decl::String>("Name").optional_label();
|
||||
b.add_input<decl::String>("Name").optional_label().is_volume_grid_name();
|
||||
b.add_input<decl::Bool>("Remove").default_value(true).translation_context(
|
||||
BLT_I18NCONTEXT_OPERATOR_DEFAULT);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ static void node_declare(NodeDeclarationBuilder &b)
|
||||
b.add_default_layout();
|
||||
b.add_input<decl::Geometry>("Volume").description("Volume geometry to add a grid to");
|
||||
b.add_output<decl::Geometry>("Volume").align_with_previous();
|
||||
b.add_input<decl::String>("Name").optional_label();
|
||||
b.add_input<decl::String>("Name").optional_label().is_volume_grid_name();
|
||||
|
||||
const bNode *node = b.node_or_null();
|
||||
if (!node) {
|
||||
|
||||
@@ -172,7 +172,11 @@ GeometryInfoLog::GeometryInfoLog(const bke::GeometrySet &geometry_set)
|
||||
const auto &volume_component = *static_cast<const bke::VolumeComponent *>(component);
|
||||
if (const Volume *volume = volume_component.get()) {
|
||||
VolumeInfo &info = this->volume_info.emplace();
|
||||
info.grids_num = BKE_volume_num_grids(volume);
|
||||
info.grids.resize(BKE_volume_num_grids(volume));
|
||||
for (const int i : IndexRange(BKE_volume_num_grids(volume))) {
|
||||
const bke::VolumeGridData *grid = BKE_volume_grid_get(volume, i);
|
||||
info.grids[i] = {grid->name(), bke::volume_grid::get_type(*grid)};
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -938,6 +938,12 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::is_layer_name(const
|
||||
return *this;
|
||||
}
|
||||
|
||||
BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::is_volume_grid_name(const bool value)
|
||||
{
|
||||
decl_base_->is_volume_grid_name = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
OutputFieldDependency OutputFieldDependency::ForFieldSource()
|
||||
{
|
||||
OutputFieldDependency field_dependency;
|
||||
|
||||
Reference in New Issue
Block a user