From a5db451d311f4d3d0c23ae6705ade5763f11d40b Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 7 Feb 2023 13:42:30 -0500 Subject: [PATCH 1/4] Mesh: Set active attribute values edit mode operator This patch adds a simple operator to set values of the active attribute for the selected element. The aim is to give simple control over attribute values in edit mode rather than to provide the fastest workflow for most cases. Eventually this operator might be less important compared to more advanced attribute editing tools, but for now, exposing a little bit of functionality is low hanging fruit and will help to see the possibilities. The implementation mostly consists of boilerplate to register the necessary property types for the operator and draw their UI. Beyond that, we just loop over selected elements and set a value. --- release/scripts/startup/bl_ui/space_view3d.py | 1 + .../blenkernel/intern/editmesh_attribute.cc | 393 ++++++++++++++++++ source/blender/editors/mesh/CMakeLists.txt | 1 + source/blender/editors/mesh/mesh_intern.h | 4 + source/blender/editors/mesh/mesh_ops.c | 2 + 5 files changed, 401 insertions(+) create mode 100644 source/blender/blenkernel/intern/editmesh_attribute.cc diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 1ce457480e5..0f79ee5b22d 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -3866,6 +3866,7 @@ class VIEW3D_MT_edit_mesh(Menu): layout.menu("VIEW3D_MT_edit_mesh_normals") layout.menu("VIEW3D_MT_edit_mesh_shading") layout.menu("VIEW3D_MT_edit_mesh_weights") + layout.operator("mesh.attribute_set") layout.operator_menu_enum("mesh.sort_elements", "type", text="Sort Elements...") layout.separator() diff --git a/source/blender/blenkernel/intern/editmesh_attribute.cc b/source/blender/blenkernel/intern/editmesh_attribute.cc new file mode 100644 index 00000000000..def5873b8d6 --- /dev/null +++ b/source/blender/blenkernel/intern/editmesh_attribute.cc @@ -0,0 +1,393 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edmesh + */ + +#include "BLI_color.hh" +#include "BLI_generic_pointer.hh" + +#include "BKE_attribute.h" +#include "BKE_context.h" +#include "BKE_editmesh.h" +#include "BKE_layer.h" +#include "BKE_mesh.h" +#include "BKE_report.h" +#include "BKE_type_conversions.hh" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "ED_mesh.h" +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_transform.h" +#include "ED_view3d.h" + +#include "BLT_translation.h" + +#include "DNA_object_types.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "bmesh_tools.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "mesh_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Delete Operator + * \{ */ + +namespace blender::ed::mesh { + +static char domain_to_htype(const eAttrDomain domain) +{ + switch (domain) { + case ATTR_DOMAIN_POINT: + return BM_VERT; + case ATTR_DOMAIN_EDGE: + return BM_EDGE; + case ATTR_DOMAIN_FACE: + return BM_FACE; + case ATTR_DOMAIN_CORNER: + return BM_LOOP; + default: + BLI_assert_unreachable(); + return BM_VERT; + } +} + +static bool active_attribute_poll(bContext *C) +{ + if (!ED_operator_editmesh(C)) { + return false; + } + const Mesh *mesh = ED_mesh_context(C); + const CustomDataLayer *layer = BKE_id_attributes_active_get(&const_cast(mesh->id)); + if (!layer) { + CTX_wm_operator_poll_msg_set(C, "No active attribute"); + return false; + } + if (layer->type == CD_PROP_STRING) { + CTX_wm_operator_poll_msg_set(C, "Active string attribute not supported"); + return false; + } + return true; +} + +namespace set_attribute { + +static StringRefNull rna_property_name_for_type(const eCustomDataType type) +{ + switch (type) { + case CD_PROP_FLOAT: + return "value_float"; + case CD_PROP_FLOAT2: + return "value_float_vector_2d"; + case CD_PROP_FLOAT3: + return "value_float_vector_3d"; + case CD_PROP_COLOR: + case CD_PROP_BYTE_COLOR: + return "value_color"; + case CD_PROP_BOOL: + return "value_bool"; + case CD_PROP_INT8: + case CD_PROP_INT32: + return "value_int"; + default: + BLI_assert_unreachable(); + return ""; + } +} + +static void bmesh_vert_edge_face_layer_selected_values_set(BMesh &bm, + const BMIterType iter_type, + const GPointer value, + const int offset) +{ + const CPPType &type = *value.type(); + BMIter iter; + BMElem *elem; + BM_ITER_MESH (elem, &iter, &bm, iter_type) { + if (BM_elem_flag_test(elem, BM_ELEM_SELECT)) { + type.copy_assign(value.get(), POINTER_OFFSET(elem->head.data, offset)); + } + } +} + +/** + * For face select mode, set face corner values of any selected face. For edge and vertex + * select mode, set face corner values of loops connected to selected vertices. + */ +static void bmesh_loop_layer_selected_values_set(BMEditMesh &em, + const GPointer value, + const int offset) +{ + /* In the separate select modes we may set the same loop values more than once. + * This is okay because we're always setting the same value. */ + BMesh &bm = *em.bm; + const CPPType &type = *value.type(); + if (em.selectmode & SCE_SELECT_FACE) { + BMIter face_iter; + BMFace *face; + BM_ITER_MESH (face, &face_iter, &bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(face, BM_ELEM_SELECT)) { + BMIter loop_iter; + BMLoop *loop; + BM_ITER_ELEM (loop, &loop_iter, face, BM_LOOPS_OF_FACE) { + type.copy_assign(value.get(), POINTER_OFFSET(loop->head.data, offset)); + } + } + } + } + if (em.selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) { + BMIter vert_iter; + BMVert *vert; + BM_ITER_MESH (vert, &vert_iter, &bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(vert, BM_ELEM_SELECT)) { + BMIter loop_iter; + BMLoop *loop; + BM_ITER_ELEM (loop, &loop_iter, vert, BM_LOOPS_OF_VERT) { + type.copy_assign(value.get(), POINTER_OFFSET(loop->head.data, offset)); + } + } + } + } +} + +static int set_attribute_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + + Mesh *mesh = ED_mesh_context(C); + CustomDataLayer *active_attribute = BKE_id_attributes_active_get(&mesh->id); + const eCustomDataType active_type = eCustomDataType(active_attribute->type); + const CPPType &type = *bke::custom_data_type_to_cpp_type(active_type); + + BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); + BLI_SCOPED_DEFER([&]() { type.destruct(buffer); }); + + const StringRefNull prop_name = rna_property_name_for_type(active_type); + switch (active_type) { + case CD_PROP_FLOAT: + *static_cast(buffer) = RNA_float_get(op->ptr, prop_name.c_str()); + break; + case CD_PROP_FLOAT2: + RNA_float_get_array(op->ptr, prop_name.c_str(), static_cast(buffer)); + break; + case CD_PROP_FLOAT3: + RNA_float_get_array(op->ptr, prop_name.c_str(), static_cast(buffer)); + break; + case CD_PROP_COLOR: + RNA_float_get_array(op->ptr, prop_name.c_str(), static_cast(buffer)); + break; + case CD_PROP_BYTE_COLOR: + ColorGeometry4f value; + RNA_float_get_array(op->ptr, prop_name.c_str(), value); + *static_cast(buffer) = value.encode(); + break; + case CD_PROP_BOOL: + *static_cast(buffer) = RNA_boolean_get(op->ptr, prop_name.c_str()); + break; + case CD_PROP_INT8: + *static_cast(buffer) = RNA_int_get(op->ptr, prop_name.c_str()); + break; + case CD_PROP_INT32: + *static_cast(buffer) = RNA_int_get(op->ptr, prop_name.c_str()); + break; + default: + BLI_assert_unreachable(); + } + const GPointer value(type, buffer); + const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions(); + + bool changed = false; + for (const int i : IndexRange(objects_len)) { + Object *object = objects[i]; + Mesh *mesh = static_cast(object->data); + BMEditMesh *em = BKE_editmesh_from_object(object); + BMesh *bm = em->bm; + + CustomDataLayer *layer = BKE_id_attributes_active_get(&mesh->id); + if (!layer) { + continue; + } + /* Use implicit conversions to try to handle the case where the active attribute has a + * different type on multiple objects. */ + const eCustomDataType dst_data_type = eCustomDataType(active_attribute->type); + const CPPType &dst_type = *bke::custom_data_type_to_cpp_type(dst_data_type); + if (&type != &dst_type && !conversions.is_convertible(type, dst_type)) { + continue; + } + BUFFER_FOR_CPP_TYPE_VALUE(dst_type, dst_buffer); + BLI_SCOPED_DEFER([&]() { dst_type.destruct(dst_buffer); }); + conversions.convert_to_uninitialized(type, dst_type, value.get(), dst_buffer); + const GPointer dst_value(dst_type, dst_buffer); + switch (BKE_id_attribute_domain(&mesh->id, layer)) { + case ATTR_DOMAIN_POINT: + bmesh_vert_edge_face_layer_selected_values_set( + *bm, BM_VERTS_OF_MESH, dst_value, layer->offset); + break; + case ATTR_DOMAIN_EDGE: + bmesh_vert_edge_face_layer_selected_values_set( + *bm, BM_EDGES_OF_MESH, dst_value, layer->offset); + break; + case ATTR_DOMAIN_FACE: + bmesh_vert_edge_face_layer_selected_values_set( + *bm, BM_FACES_OF_MESH, dst_value, layer->offset); + break; + case ATTR_DOMAIN_CORNER: + bmesh_loop_layer_selected_values_set(*em, dst_value, layer->offset); + break; + default: + BLI_assert_unreachable(); + break; + } + + changed = true; + EDBMUpdate_Params update{}; + update.calc_looptri = false; + update.calc_normals = false; + update.is_destructive = false; + EDBM_update(mesh, &update); + } + + MEM_freeN(objects); + + return changed ? OPERATOR_FINISHED : OPERATOR_CANCELLED; +} + +static int set_attribute_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Mesh *mesh = ED_mesh_context(C); + BMesh *bm = mesh->edit_mesh->bm; + + const CustomDataLayer *layer = BKE_id_attributes_active_get(&mesh->id); + const eCustomDataType data_type = eCustomDataType(layer->type); + const eAttrDomain domain = BKE_id_attribute_domain(&mesh->id, layer); + const BMElem *active_elem = BM_mesh_active_elem_get(bm); + if (!active_elem) { + return WM_operator_props_popup(C, op, event); + } + + /* Only support filling the active data when the active selection mode matches the active + * attribute domain. NOTE: This doesn't work well for corner domain attributes. */ + if (active_elem->head.htype != domain_to_htype(domain)) { + return WM_operator_props_popup(C, op, event); + } + + const StringRefNull prop_name = rna_property_name_for_type(data_type); + const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type); + const GPointer active_value(type, POINTER_OFFSET(active_elem->head.data, layer->offset)); + + PropertyRNA *prop = RNA_struct_find_property(op->ptr, prop_name.c_str()); + if (!RNA_property_is_set(op->ptr, prop)) { + switch (data_type) { + case CD_PROP_FLOAT: + RNA_property_float_set(op->ptr, prop, *active_value.get()); + break; + case CD_PROP_FLOAT2: + RNA_property_float_set_array(op->ptr, prop, *active_value.get()); + break; + case CD_PROP_FLOAT3: + RNA_property_float_set_array(op->ptr, prop, *active_value.get()); + break; + case CD_PROP_BYTE_COLOR: + RNA_property_float_set_array(op->ptr, prop, active_value.get()->decode()); + break; + case CD_PROP_COLOR: + RNA_property_float_set_array(op->ptr, prop, *active_value.get()); + break; + case CD_PROP_BOOL: + RNA_property_boolean_set(op->ptr, prop, *active_value.get()); + break; + case CD_PROP_INT8: + RNA_property_int_set(op->ptr, prop, *active_value.get()); + break; + case CD_PROP_INT32: + RNA_property_int_set(op->ptr, prop, *active_value.get()); + break; + default: + BLI_assert_unreachable(); + } + } + + return WM_operator_props_popup(C, op, event); +} + +static void set_attribute_ui(bContext *C, wmOperator *op) +{ + uiLayout *layout = uiLayoutColumn(op->layout, true); + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + + Mesh *mesh = ED_mesh_context(C); + CustomDataLayer *active_attribute = BKE_id_attributes_active_get(&mesh->id); + const eCustomDataType active_type = eCustomDataType(active_attribute->type); + const StringRefNull prop_name = rna_property_name_for_type(active_type); + const char *name = active_attribute->name; + uiItemR(layout, op->ptr, prop_name.c_str(), 0, name, ICON_NONE); +} + +} // namespace set_attribute + +} // namespace blender::ed::mesh + +void MESH_OT_attribute_set(wmOperatorType *ot) +{ + using namespace blender::ed::mesh; + using namespace blender::ed::mesh::set_attribute; + ot->name = "Set Attribute"; + ot->description = "Set values of the active attribute for selected elements"; + ot->idname = "MESH_OT_attribute_set"; + + ot->exec = set_attribute_exec; + ot->invoke = set_attribute_invoke; + ot->poll = active_attribute_poll; + ot->ui = set_attribute_ui; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + static blender::float4 color_default(1); + + RNA_def_float(ot->srna, "value_float", 0.0f, -FLT_MAX, FLT_MAX, "Value", "", -FLT_MAX, FLT_MAX); + RNA_def_float_array(ot->srna, + "value_float_vector_2d", + 2, + nullptr, + -FLT_MAX, + FLT_MAX, + "Value", + "", + -FLT_MAX, + FLT_MAX); + RNA_def_float_array(ot->srna, + "value_float_vector_3d", + 3, + nullptr, + -FLT_MAX, + FLT_MAX, + "Value", + "", + -FLT_MAX, + FLT_MAX); + RNA_def_int(ot->srna, "value_int", 0, INT_MIN, INT_MAX, "Value", "", INT_MIN, INT_MAX); + RNA_def_float_color( + ot->srna, "value_color", 4, color_default, -FLT_MAX, FLT_MAX, "Value", "", 0.0f, 1.0f); + RNA_def_boolean(ot->srna, "value_bool", false, "Value", ""); +} + +/** \} */ diff --git a/source/blender/editors/mesh/CMakeLists.txt b/source/blender/editors/mesh/CMakeLists.txt index 283969cee47..b390b7cb17a 100644 --- a/source/blender/editors/mesh/CMakeLists.txt +++ b/source/blender/editors/mesh/CMakeLists.txt @@ -12,6 +12,7 @@ set(INC ../../draw ../../geometry ../../gpu + ../../functions ../../imbuf ../../makesdna ../../makesrna diff --git a/source/blender/editors/mesh/mesh_intern.h b/source/blender/editors/mesh/mesh_intern.h index 7386fe69b6c..e1fd3025c0c 100644 --- a/source/blender/editors/mesh/mesh_intern.h +++ b/source/blender/editors/mesh/mesh_intern.h @@ -120,6 +120,10 @@ void MESH_OT_primitive_ico_sphere_add(struct wmOperatorType *ot); void MESH_OT_primitive_cube_add_gizmo(struct wmOperatorType *ot); +/* *** editmesh_attribute.cc *** */ + +void MESH_OT_attribute_set(struct wmOperatorType *ot); + /* *** editmesh_bevel.c *** */ void MESH_OT_bevel(struct wmOperatorType *ot); diff --git a/source/blender/editors/mesh/mesh_ops.c b/source/blender/editors/mesh/mesh_ops.c index b9afeae275b..226c35a0637 100644 --- a/source/blender/editors/mesh/mesh_ops.c +++ b/source/blender/editors/mesh/mesh_ops.c @@ -54,6 +54,8 @@ void ED_operatortypes_mesh(void) WM_operatortype_append(MESH_OT_primitive_cube_add_gizmo); + WM_operatortype_append(MESH_OT_attribute_set); + WM_operatortype_append(MESH_OT_duplicate); WM_operatortype_append(MESH_OT_remove_doubles); WM_operatortype_append(MESH_OT_spin); -- 2.30.2 From 263eb412a6c52360224958d74eafb4909061ce13 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 7 Feb 2023 13:48:51 -0500 Subject: [PATCH 2/4] Geometry Nodes: Edges to Face Groups Node Add a new node that groups faces inside of boundary edge regions. This is the opposite action as the existing "Face Group Boundaries" node[1]. It's also the same as some of the "Initialize Face Sets" options in sculpt mode [2]. Discussion in T102962 has favored "Group" for a name for these sockets rather than "Set", so that is used here. [1]: https://docs.blender.org/manual/en/3.4/modeling/geometry_nodes/mesh/face_set_boundaries.html [2]: https://docs.blender.org/manual/en/3.5/sculpt_paint/sculpting/editing/face_sets.html#initialize-face-sets --- .../startup/bl_ui/node_add_menu_geometry.py | 1 + source/blender/blenkernel/BKE_mesh_mapping.h | 1 + source/blender/blenkernel/BKE_node.h | 1 + .../blender/blenkernel/intern/mesh_mapping.cc | 14 +++ source/blender/nodes/NOD_static_types.h | 1 + source/blender/nodes/geometry/CMakeLists.txt | 1 + .../nodes/geometry/node_geometry_register.cc | 1 + .../nodes/geometry/node_geometry_register.hh | 1 + .../nodes/node_geo_edges_to_face_groups.cc | 104 ++++++++++++++++++ .../geometry/nodes/node_geo_extrude_mesh.cc | 23 +--- 10 files changed, 130 insertions(+), 18 deletions(-) create mode 100644 source/blender/nodes/geometry/nodes/node_geo_edges_to_face_groups.cc diff --git a/release/scripts/startup/bl_ui/node_add_menu_geometry.py b/release/scripts/startup/bl_ui/node_add_menu_geometry.py index b829795a232..43fd87d0f83 100644 --- a/release/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/release/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -338,6 +338,7 @@ class NODE_MT_geometry_node_GEO_MESH_READ(Menu): node_add_menu.add_node_type(layout, "GeometryNodeInputMeshEdgeAngle") node_add_menu.add_node_type(layout, "GeometryNodeInputMeshEdgeNeighbors") node_add_menu.add_node_type(layout, "GeometryNodeInputMeshEdgeVertices") + node_add_menu.add_node_type(layout, "GeometryNodeEdgesToFaceGroups") node_add_menu.add_node_type(layout, "GeometryNodeInputMeshFaceArea") node_add_menu.add_node_type(layout, "GeometryNodeInputMeshFaceNeighbors") node_add_menu.add_node_type(layout, "GeometryNodeMeshFaceSetBoundaries") diff --git a/source/blender/blenkernel/BKE_mesh_mapping.h b/source/blender/blenkernel/BKE_mesh_mapping.h index 46f62220d91..b0f29a838bd 100644 --- a/source/blender/blenkernel/BKE_mesh_mapping.h +++ b/source/blender/blenkernel/BKE_mesh_mapping.h @@ -353,6 +353,7 @@ Array> build_vert_to_edge_map(Span edges, int verts_num); Array> build_vert_to_poly_map(Span polys, Span loops, int verts_num); Array> build_vert_to_loop_map(Span loops, int verts_num); Array> build_edge_to_loop_map(Span loops, int edges_num); +Array> build_edge_to_poly_map(Span polys, Span loops, int edges_num); Vector> build_edge_to_loop_map_resizable(Span loops, int edges_num); inline int poly_loop_prev(const MPoly &poly, int loop_i) diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index c358f56c0d9..c0690c1c3a8 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1534,6 +1534,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i #define GEO_NODE_BLUR_ATTRIBUTE 1190 #define GEO_NODE_IMAGE 1191 #define GEO_NODE_INTERPOLATE_CURVES 1192 +#define GEO_NODE_EDGES_TO_FACE_GROUPS 1193 /** \} */ diff --git a/source/blender/blenkernel/intern/mesh_mapping.cc b/source/blender/blenkernel/intern/mesh_mapping.cc index 780e32d540a..5e29565e02a 100644 --- a/source/blender/blenkernel/intern/mesh_mapping.cc +++ b/source/blender/blenkernel/intern/mesh_mapping.cc @@ -610,6 +610,20 @@ Array> build_edge_to_loop_map(const Span loops, const int edg return map; } +Array> build_edge_to_poly_map(const Span polys, + const Span loops, + const int edges_num) +{ + Array> map(edges_num); + for (const int64_t i : polys.index_range()) { + const MPoly &poly = polys[i]; + for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) { + map[loop.e].append(int(i)); + } + } + return map; +} + Vector> build_edge_to_loop_map_resizable(const Span loops, const int edges_num) { Vector> map(edges_num); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 6b8b917f3e0..cb97e88ac9f 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -314,6 +314,7 @@ DefNode(GeometryNode, GEO_NODE_DUAL_MESH, 0, "DUAL_MESH", DualMesh, "Dual Mesh", DefNode(GeometryNode, GEO_NODE_DUPLICATE_ELEMENTS, def_geo_duplicate_elements, "DUPLICATE_ELEMENTS", DuplicateElements, "Duplicate Elements", "Generate an arbitrary number copies of each selected input element") DefNode(GeometryNode, GEO_NODE_EDGE_PATHS_TO_CURVES, 0, "EDGE_PATHS_TO_CURVES", EdgePathsToCurves, "Edge Paths to Curves", "") DefNode(GeometryNode, GEO_NODE_EDGE_PATHS_TO_SELECTION, 0, "EDGE_PATHS_TO_SELECTION", EdgePathsToSelection, "Edge Paths to Selection", "") +DefNode(GeometryNode, GEO_NODE_EDGES_TO_FACE_GROUPS, 0, "EDGES_TO_FACE_GROUPS", EdgesToFaceGroups, "Edges to Face Groups", "Group faces into regions surrounded by the selected boundary edges") DefNode(GeometryNode, GEO_NODE_EVALUATE_AT_INDEX, def_geo_evaluate_at_index, "FIELD_AT_INDEX", FieldAtIndex, "Evaluate at Index", "Retrieve data of other elements in the context's geometry") DefNode(GeometryNode, GEO_NODE_EVALUATE_ON_DOMAIN, def_geo_evaluate_on_domain, "FIELD_ON_DOMAIN", FieldOnDomain, "Evaluate on Domain", "Retrieve values from a field on a different domain besides the domain from the context") DefNode(GeometryNode, GEO_NODE_EXTRUDE_MESH, def_geo_extrude_mesh, "EXTRUDE_MESH", ExtrudeMesh, "Extrude Mesh", "Generate new vertices, edges, or faces from selected elements and move them based on an offset while keeping them connected by their boundary") diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 9fd3feff27e..448a5e69de4 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -69,6 +69,7 @@ set(SRC nodes/node_geo_edge_paths_to_curves.cc nodes/node_geo_edge_paths_to_selection.cc nodes/node_geo_edge_split.cc + nodes/node_geo_edges_to_face_groups.cc nodes/node_geo_evaluate_at_index.cc nodes/node_geo_evaluate_on_domain.cc nodes/node_geo_extrude_mesh.cc diff --git a/source/blender/nodes/geometry/node_geometry_register.cc b/source/blender/nodes/geometry/node_geometry_register.cc index 149fb6752ab..b3ad4d86bfb 100644 --- a/source/blender/nodes/geometry/node_geometry_register.cc +++ b/source/blender/nodes/geometry/node_geometry_register.cc @@ -53,6 +53,7 @@ void register_geometry_nodes() register_node_type_geo_edge_paths_to_curves(); register_node_type_geo_edge_paths_to_selection(); register_node_type_geo_edge_split(); + register_node_type_geoedges_to_face_groups(); register_node_type_geo_evaluate_at_index(); register_node_type_geo_evaluate_on_domain(); register_node_type_geo_extrude_mesh(); diff --git a/source/blender/nodes/geometry/node_geometry_register.hh b/source/blender/nodes/geometry/node_geometry_register.hh index 5984bfa83ce..83200871d6d 100644 --- a/source/blender/nodes/geometry/node_geometry_register.hh +++ b/source/blender/nodes/geometry/node_geometry_register.hh @@ -50,6 +50,7 @@ void register_node_type_geo_duplicate_elements(); void register_node_type_geo_edge_paths_to_curves(); void register_node_type_geo_edge_paths_to_selection(); void register_node_type_geo_edge_split(); +void register_node_type_geoedges_to_face_groups(); void register_node_type_geo_evaluate_at_index(); void register_node_type_geo_evaluate_on_domain(); void register_node_type_geo_extrude_mesh(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_edges_to_face_groups.cc b/source/blender/nodes/geometry/nodes/node_geo_edges_to_face_groups.cc new file mode 100644 index 00000000000..321497ff5b4 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_edges_to_face_groups.cc @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" + +#include "BLI_disjoint_set.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_edges_to_face_groups_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input("Boundary Edges") + .default_value(true) + .hide_value() + .supports_field() + .description(N_("Edges used to split faces into separate groups")); + b.add_output("Face Group ID") + .dependent_field() + .description(N_("Index of the face group inside each boundary edge region")); +} + +class FaceSetFromBoundariesInput final : public bke::MeshFieldInput { + private: + Field non_boundary_edge_field_; + + public: + FaceSetFromBoundariesInput(Field selection) + : bke::MeshFieldInput(CPPType::get(), "Edges to Face Groups"), + non_boundary_edge_field_(std::move(selection)) + { + } + + GVArray get_varray_for_context(const Mesh &mesh, + const eAttrDomain domain, + const IndexMask mask) const final + { + const bke::MeshFieldContext context{mesh, ATTR_DOMAIN_EDGE}; + fn::FieldEvaluator evaluator{context, mesh.totedge}; + evaluator.add(non_boundary_edge_field_); + evaluator.evaluate(); + const IndexMask non_boundary_edges = evaluator.get_evaluated_as_mask(0); + + const Span polys = mesh.polys(); + const Span loops = mesh.loops(); + + /* Create Edge/Face Lookup */ + const Array> edge_to_face_map = bke::mesh_topology::build_edge_to_poly_map( + polys, loops, mesh.totedge); + + /* Join faces based on edge values */ + DisjointSet islands(polys.size()); + for (const int edge_i : non_boundary_edges) { + for (const int a : edge_to_face_map[edge_i]) { + for (const int b : edge_to_face_map[edge_i]) { + islands.join(a, b); + } + } + } + + /* Output faces final assigned groups */ + Array output(domain == ATTR_DOMAIN_FACE ? mask.min_array_size() : polys.size()); + VectorSet ordered_roots; + const IndexMask poly_mask = domain == ATTR_DOMAIN_FACE ? mask : polys.index_range(); + for (const int poly_i : poly_mask) { + const int64_t root = islands.find_root(poly_i); + output[poly_i] = ordered_roots.index_of_or_add(root); + } + + return mesh.attributes().adapt_domain( + VArray::ForContainer(std::move(output)), ATTR_DOMAIN_FACE, domain); + } + + std::optional preferred_domain(const Mesh & /*mesh*/) const final + { + return ATTR_DOMAIN_FACE; + } +}; + +static void geo_node_exec(GeoNodeExecParams params) +{ + Field boundary_edges = params.extract_input>("Boundary Edges"); + Field non_boundary_edges = fn::invert_boolean_field(std::move(boundary_edges)); + params.set_output( + "Face Group ID", + Field(std::make_shared(std::move(non_boundary_edges)))); +} + +} // namespace blender::nodes::node_geo_edges_to_face_groups_cc + +void register_node_type_geoedges_to_face_groups() +{ + namespace file_ns = blender::nodes::node_geo_edges_to_face_groups_cc; + + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_EDGES_TO_FACE_GROUPS, "Edges to Face Groups", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::geo_node_exec; + ntype.declare = file_ns::node_declare; + + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc index 28691252729..f3d36eaa6bd 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc @@ -10,6 +10,7 @@ #include "BKE_attribute_math.hh" #include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" #include "BKE_mesh_runtime.h" #include "UI_interface.h" @@ -288,22 +289,6 @@ static void extrude_mesh_vertices(Mesh &mesh, BKE_mesh_runtime_clear_cache(&mesh); } -static Array> mesh_calculate_polys_of_edge(const Mesh &mesh) -{ - const Span polys = mesh.polys(); - const Span loops = mesh.loops(); - Array> polys_of_edge(mesh.totedge); - - for (const int i_poly : polys.index_range()) { - const MPoly &poly = polys[i_poly]; - for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) { - polys_of_edge[loop.e].append(i_poly); - } - } - - return polys_of_edge; -} - static void fill_quad_consistent_direction(Span other_poly_loops, MutableSpan new_loops, const int vert_connected_to_poly_1, @@ -382,7 +367,8 @@ static void extrude_mesh_edges(Mesh &mesh, return; } - const Array> edge_to_poly_map = mesh_calculate_polys_of_edge(mesh); + const Array> edge_to_poly_map = bke::mesh_topology::build_edge_to_poly_map( + orig_polys, mesh.loops(), mesh.totedge); /* Find the offsets on the vertex domain for translation. This must be done before the mesh's * custom data layers are reallocated, in case the virtual array references one of them. */ @@ -684,7 +670,8 @@ static void extrude_mesh_face_regions(Mesh &mesh, } /* All of the faces (selected and deselected) connected to each edge. */ - const Array> edge_to_poly_map = mesh_calculate_polys_of_edge(mesh); + const Array> edge_to_poly_map = bke::mesh_topology::build_edge_to_poly_map( + orig_polys, orig_loops, orig_edges.size()); /* All vertices that are connected to the selected polygons. * Start the size at one vert per poly to reduce unnecessary reallocation. */ -- 2.30.2 From 493687491dacca9cceab8f9665a5dce0f41aeeef Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 13 Feb 2023 21:57:31 -0500 Subject: [PATCH 3/4] Fix file location (was moved to blenkernel mistakenly) --- source/blender/editors/mesh/CMakeLists.txt | 1 + .../{blenkernel/intern => editors/mesh}/editmesh_attribute.cc | 0 2 files changed, 1 insertion(+) rename source/blender/{blenkernel/intern => editors/mesh}/editmesh_attribute.cc (100%) diff --git a/source/blender/editors/mesh/CMakeLists.txt b/source/blender/editors/mesh/CMakeLists.txt index b8f15edabce..db25aeea289 100644 --- a/source/blender/editors/mesh/CMakeLists.txt +++ b/source/blender/editors/mesh/CMakeLists.txt @@ -28,6 +28,7 @@ set(SRC editface.cc editmesh_add.c editmesh_add_gizmo.c + editmesh_attribute.cc editmesh_automerge.c editmesh_bevel.c editmesh_bisect.c diff --git a/source/blender/blenkernel/intern/editmesh_attribute.cc b/source/blender/editors/mesh/editmesh_attribute.cc similarity index 100% rename from source/blender/blenkernel/intern/editmesh_attribute.cc rename to source/blender/editors/mesh/editmesh_attribute.cc -- 2.30.2 From 25e565f5068096ffe8b5d06b50f5cdd4af289be1 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 14 Feb 2023 22:28:17 -0500 Subject: [PATCH 4/4] Add `mesh_` prefix to operator callbacks --- .../blender/editors/mesh/editmesh_attribute.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/blender/editors/mesh/editmesh_attribute.cc b/source/blender/editors/mesh/editmesh_attribute.cc index def5873b8d6..8594f3ebc1c 100644 --- a/source/blender/editors/mesh/editmesh_attribute.cc +++ b/source/blender/editors/mesh/editmesh_attribute.cc @@ -65,7 +65,7 @@ static char domain_to_htype(const eAttrDomain domain) } } -static bool active_attribute_poll(bContext *C) +static bool mesh_active_attribute_poll(bContext *C) { if (!ED_operator_editmesh(C)) { return false; @@ -163,7 +163,7 @@ static void bmesh_loop_layer_selected_values_set(BMEditMesh &em, } } -static int set_attribute_exec(bContext *C, wmOperator *op) +static int mesh_set_attribute_exec(bContext *C, wmOperator *op) { const Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); @@ -270,7 +270,7 @@ static int set_attribute_exec(bContext *C, wmOperator *op) return changed ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } -static int set_attribute_invoke(bContext *C, wmOperator *op, const wmEvent *event) +static int mesh_set_attribute_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Mesh *mesh = ED_mesh_context(C); BMesh *bm = mesh->edit_mesh->bm; @@ -328,7 +328,7 @@ static int set_attribute_invoke(bContext *C, wmOperator *op, const wmEvent *even return WM_operator_props_popup(C, op, event); } -static void set_attribute_ui(bContext *C, wmOperator *op) +static void mesh_set_attribute_ui(bContext *C, wmOperator *op) { uiLayout *layout = uiLayoutColumn(op->layout, true); uiLayoutSetPropSep(layout, true); @@ -354,10 +354,10 @@ void MESH_OT_attribute_set(wmOperatorType *ot) ot->description = "Set values of the active attribute for selected elements"; ot->idname = "MESH_OT_attribute_set"; - ot->exec = set_attribute_exec; - ot->invoke = set_attribute_invoke; - ot->poll = active_attribute_poll; - ot->ui = set_attribute_ui; + ot->exec = mesh_set_attribute_exec; + ot->invoke = mesh_set_attribute_invoke; + ot->poll = mesh_active_attribute_poll; + ot->ui = mesh_set_attribute_ui; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -- 2.30.2