Geometry Nodes: Add Active Element tool node

This pull request adds an "Active Element" node that exposes the active
vertex, edge, or face index to the geometry node tool context. The
presence of an active element is available as a boolean.

This node enables the creation of "active-to-selected" style operators.

Co-authored-by: Hans Goudey <hans@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/121333
This commit is contained in:
Colin Basnett 2024-05-20 21:01:30 +02:00 committed by Hans Goudey
parent 1904e2fc8d
commit 2fe92c63d3
12 changed files with 161 additions and 2 deletions

@ -187,6 +187,7 @@ class NODE_MT_geometry_node_GEO_GEOMETRY_READ(Menu):
node_add_menu.add_node_type(layout, "GeometryNodeInputRadius")
if context.space_data.geometry_nodes_type == 'TOOL':
node_add_menu.add_node_type(layout, "GeometryNodeToolSelection")
node_add_menu.add_node_type(layout, "GeometryNodeToolActiveElement")
node_add_menu.draw_assets_for_catalog(layout, "Geometry/Read")

@ -1330,6 +1330,7 @@ void BKE_nodetree_remove_layer_n(bNodeTree *ntree, Scene *scene, int layer_index
#define GEO_NODE_TOOL_VIEWPORT_TRANSFORM 2132
#define GEO_NODE_TOOL_MOUSE_POSITION 2133
#define GEO_NODE_SAMPLE_GRID_INDEX 2134
#define GEO_NODE_TOOL_ACTIVE_ELEMENT 2135
/** \} */

@ -838,6 +838,30 @@ void BM_mesh_active_face_set(BMesh *bm, BMFace *f)
bm->act_face = f;
}
int BM_mesh_active_face_index_get(BMesh *bm, bool is_sloppy, bool is_selected)
{
const BMFace *f = BM_mesh_active_face_get(bm, is_sloppy, is_selected);
return f ? BM_elem_index_get(f) : -1;
}
int BM_mesh_active_edge_index_get(BMesh *bm)
{
const BMEdge *e = BM_mesh_active_edge_get(bm);
return e ? BM_elem_index_get(e) : -1;
}
int BM_mesh_active_vert_index_get(BMesh *bm)
{
const BMVert *v = BM_mesh_active_vert_get(bm);
return v ? BM_elem_index_get(v) : -1;
}
int BM_mesh_active_elem_index_get(BMesh *bm)
{
const BMElem *e = BM_mesh_active_elem_get(bm);
return e ? BM_elem_index_get(e) : -1;
}
BMFace *BM_mesh_active_face_get(BMesh *bm, const bool is_sloppy, const bool is_selected)
{
if (bm->act_face && (!is_selected || BM_elem_flag_test(bm->act_face, BM_ELEM_SELECT))) {

@ -120,6 +120,10 @@ int BM_mesh_elem_hflag_count_disabled(BMesh *bm, char htype, char hflag, bool re
/* Edit selection stuff. */
void BM_mesh_active_face_set(BMesh *bm, BMFace *f);
int BM_mesh_active_face_index_get(BMesh *bm, bool is_sloppy, bool is_selected);
int BM_mesh_active_edge_index_get(BMesh *bm);
int BM_mesh_active_vert_index_get(BMesh *bm);
int BM_mesh_active_elem_index_get(BMesh *bm);
BMFace *BM_mesh_active_face_get(BMesh *bm, bool is_sloppy, bool is_selected);
BMEdge *BM_mesh_active_edge_get(BMesh *bm);
BMVert *BM_mesh_active_vert_get(BMesh *bm);

@ -170,7 +170,8 @@ static void find_socket_log_contexts(const Main &bmain,
* we need to create evaluated copies of geometry before passing it to geometry nodes. Implicit
* sharing lets us avoid copying attribute data though.
*/
static bke::GeometrySet get_original_geometry_eval_copy(Object &object)
static bke::GeometrySet get_original_geometry_eval_copy(Object &object,
nodes::GeoNodesOperatorData &operator_data)
{
switch (object.type) {
case OB_CURVES: {
@ -185,6 +186,9 @@ static bke::GeometrySet get_original_geometry_eval_copy(Object &object)
case OB_MESH: {
const Mesh *mesh = static_cast<const Mesh *>(object.data);
if (std::shared_ptr<BMEditMesh> &em = mesh->runtime->edit_mesh) {
operator_data.active_point_index = BM_mesh_active_vert_index_get(em->bm);
operator_data.active_edge_index = BM_mesh_active_edge_index_get(em->bm);
operator_data.active_face_index = BM_mesh_active_face_index_get(em->bm, false, true);
Mesh *mesh_copy = BKE_mesh_wrapper_from_editmesh(em, nullptr, mesh);
BKE_mesh_wrapper_ensure_mdata(mesh_copy);
Mesh *final_copy = BKE_mesh_copy_for_eval(*mesh_copy);
@ -556,7 +560,7 @@ static int run_node_group_exec(bContext *C, wmOperator *op)
call_data.socket_log_contexts = &socket_log_contexts;
}
bke::GeometrySet geometry_orig = get_original_geometry_eval_copy(*object);
bke::GeometrySet geometry_orig = get_original_geometry_eval_copy(*object, operator_eval_data);
bke::GeometrySet new_geometry = nodes::execute_geometry_nodes_on_geometry(
*node_tree, properties, compute_context, call_data, std::move(geometry_orig));

@ -1988,6 +1988,11 @@ typedef struct NodeGeometryBake {
char _pad[4];
} NodeGeometryBake;
typedef struct NodeGeometryToolActiveElement {
/** #AttrDomain. */
int8_t domain;
} NodeGeometryToolActiveElement;
/* script node mode */
enum {
NODE_SCRIPT_INTERNAL = 0,

@ -226,6 +226,7 @@ DEF_ENUM(rna_enum_attribute_domain_items)
DEF_ENUM(rna_enum_attribute_domain_edge_face_items)
DEF_ENUM(rna_enum_attribute_domain_only_mesh_items)
DEF_ENUM(rna_enum_attribute_domain_only_mesh_no_edge_items)
DEF_ENUM(rna_enum_attribute_domain_only_mesh_no_corner_items)
DEF_ENUM(rna_enum_attribute_domain_point_face_curve_items)
DEF_ENUM(rna_enum_attribute_domain_point_edge_face_curve_items)
DEF_ENUM(rna_enum_attribute_curves_domain_items)

@ -111,6 +111,13 @@ const EnumPropertyItem rna_enum_attribute_domain_only_mesh_no_edge_items[] = {
{0, nullptr, 0, nullptr, nullptr},
};
const EnumPropertyItem rna_enum_attribute_domain_only_mesh_no_corner_items[] = {
{int(AttrDomain::Point), "POINT", 0, "Point", "Attribute on point"},
{int(AttrDomain::Edge), "EDGE", 0, "Edge", "Attribute on mesh edge"},
{int(AttrDomain::Face), "FACE", 0, "Face", "Attribute on mesh faces"},
{0, nullptr, 0, nullptr, nullptr},
};
const EnumPropertyItem rna_enum_attribute_domain_point_face_curve_items[] = {
{int(AttrDomain::Point), "POINT", 0, "Point", "Attribute on point"},
{int(AttrDomain::Face), "FACE", 0, "Face", "Attribute on mesh faces"},

@ -193,6 +193,9 @@ struct GeoNodesOperatorData {
int2 mouse_position;
int2 region_size;
const RegionView3D *rv3d = nullptr;
int active_point_index = -1;
int active_edge_index = -1;
int active_face_index = -1;
};
struct GeoNodesCallData {

@ -475,6 +475,7 @@ DefNode(GeometryNode, GEO_NODE_TOOL_3D_CURSOR, 0, "TOOL_3D_CURSOR", Tool3DCursor
DefNode(GeometryNode, GEO_NODE_TOOL_FACE_SET, 0, "TOOL_FACE_SET", ToolFaceSet, "Face Set", "Each face's sculpt face set value")
DefNode(GeometryNode, GEO_NODE_TOOL_MOUSE_POSITION, 0, "TOOL_MOUSE_POSITION", ToolMousePosition, "Mouse Position", "Retrieve the position of the mouse cursor")
DefNode(GeometryNode, GEO_NODE_TOOL_SELECTION, 0, "TOOL_SELECTION", ToolSelection, "Selection", "User selection of the edited geometry, for tool execution")
DefNode(GeometryNode, GEO_NODE_TOOL_ACTIVE_ELEMENT, 0, "TOOL_ACTIVE_ELEMENT", ToolActiveElement, "Active Element", "Active element indices of the edited geometry, for tool execution")
DefNode(GeometryNode, GEO_NODE_TOOL_SET_FACE_SET, 0, "TOOL_SET_FACE_SET", ToolSetFaceSet, "Set Face Set", "Set sculpt face set values for faces")
DefNode(GeometryNode, GEO_NODE_TOOL_SET_SELECTION, 0, "TOOL_SELECTION_SET", ToolSetSelection, "Set Selection", "Set selection of the edited geometry, for tool execution")
DefNode(GeometryNode, GEO_NODE_TOOL_VIEWPORT_TRANSFORM, 0, "VIEWPORT_TRANFORM", ViewportTransform, "Viewport Transform", "Retrieve the view direction and location of the 3D viewport")

@ -197,6 +197,7 @@ set(SRC
nodes/node_geo_subdivision_surface.cc
nodes/node_geo_switch.cc
nodes/node_geo_tool_3d_cursor.cc
nodes/node_geo_tool_active_element.cc
nodes/node_geo_tool_face_set.cc
nodes/node_geo_tool_selection.cc
nodes/node_geo_tool_set_face_set.cc

@ -0,0 +1,107 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "UI_interface.hh"
#include "RNA_enum_types.hh"
#include "BKE_mesh.hh"
#include "BKE_node.hh"
#include "DNA_meshdata_types.h"
#include "NOD_rna_define.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_tool_active_element_cc {
NODE_STORAGE_FUNCS(NodeGeometryToolActiveElement)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Int>("Index").description(
"Index of the active element in the specified domain");
b.add_output<decl::Bool>("Exists").description(
"True if an active element exists in the mesh, false otherwise");
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometryToolActiveElement *data = MEM_cnew<NodeGeometryToolActiveElement>(__func__);
data->domain = int16_t(AttrDomain::Point);
node->storage = data;
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(srna,
"domain",
"Domain",
"",
rna_enum_attribute_domain_only_mesh_no_corner_items,
NOD_storage_enum_accessors(domain),
int(AttrDomain::Point));
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
uiItemR(layout, ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_exec(GeoNodeExecParams params)
{
if (!check_tool_context_and_error(params)) {
return;
}
if (params.user_data()->call_data->operator_data->mode != OB_MODE_EDIT) {
params.set_default_remaining_outputs();
return;
}
const GeoNodesOperatorData *operator_data = params.user_data()->call_data->operator_data;
switch (static_cast<AttrDomain>(node_storage(params.node()).domain)) {
case AttrDomain::Point:
params.set_output("Exists", operator_data->active_point_index >= 0);
params.set_output("Index", std::max(0, operator_data->active_point_index));
break;
case AttrDomain::Edge:
params.set_output("Exists", operator_data->active_edge_index >= 0);
params.set_output("Index", std::max(0, operator_data->active_edge_index));
break;
case AttrDomain::Face:
params.set_output("Exists", operator_data->active_face_index >= 0);
params.set_output("Index", std::max(0, operator_data->active_face_index));
break;
default:
params.set_default_remaining_outputs();
BLI_assert_unreachable();
break;
}
}
static void node_register()
{
static blender::bke::bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_TOOL_ACTIVE_ELEMENT, "Active Element", NODE_CLASS_INPUT);
node_type_storage(&ntype,
"NodeGeometryToolActiveElement",
node_free_standard_storage,
node_copy_standard_storage);
ntype.initfunc = node_init;
ntype.geometry_node_execute = node_exec;
ntype.declare = node_declare;
ntype.gather_link_search_ops = search_link_ops_for_tool_node;
ntype.draw_buttons = node_layout;
blender::bke::nodeRegisterType(&ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_tool_active_element_cc