From 15d85c54c3f960814068074bcdff0a5546fa4d5a Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 15 Sep 2022 23:04:55 -0500 Subject: UI: Use full word for face set operator name, tweak description "Init" shouldn't be used in the UI, and avoid repeating the operator name in its description. --- .../editors/sculpt_paint/sculpt_face_set.cc | 1505 ++++++++++++++++++++ 1 file changed, 1505 insertions(+) create mode 100644 source/blender/editors/sculpt_paint/sculpt_face_set.cc diff --git a/source/blender/editors/sculpt_paint/sculpt_face_set.cc b/source/blender/editors/sculpt_paint/sculpt_face_set.cc new file mode 100644 index 00000000000..1d59ba34d3e --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_face_set.cc @@ -0,0 +1,1505 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2020 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_hash.h" +#include "BLI_math.h" +#include "BLI_task.h" + +#include "DNA_brush_types.h" +#include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BKE_brush.h" +#include "BKE_ccg.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_customdata.h" +#include "BKE_mesh.h" +#include "BKE_mesh_fair.h" +#include "BKE_mesh_mapping.h" +#include "BKE_multires.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_paint.h" +#include "BKE_pbvh.h" +#include "BKE_scene.h" + +#include "DEG_depsgraph.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_sculpt.h" +#include "ED_view3d.h" +#include "paint_intern.h" +#include "sculpt_intern.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "bmesh.h" + +#include +#include + +/* Utils. */ + +int ED_sculpt_face_sets_find_next_available_id(struct Mesh *mesh) +{ + const int *face_sets = CustomData_get_layer(&mesh->pdata, CD_SCULPT_FACE_SETS); + if (!face_sets) { + return SCULPT_FACE_SET_NONE; + } + + int next_face_set_id = 0; + for (int i = 0; i < mesh->totpoly; i++) { + next_face_set_id = max_ii(next_face_set_id, face_sets[i]); + } + next_face_set_id++; + + return next_face_set_id; +} + +void ED_sculpt_face_sets_initialize_none_to_id(struct Mesh *mesh, const int new_id) +{ + int *face_sets = CustomData_get_layer(&mesh->pdata, CD_SCULPT_FACE_SETS); + if (!face_sets) { + return; + } + + for (int i = 0; i < mesh->totpoly; i++) { + if (face_sets[i] == SCULPT_FACE_SET_NONE) { + face_sets[i] = new_id; + } + } +} + +int ED_sculpt_face_sets_active_update_and_get(bContext *C, Object *ob, const float mval[2]) +{ + SculptSession *ss = ob->sculpt; + if (!ss) { + return SCULPT_FACE_SET_NONE; + } + + SculptCursorGeometryInfo gi; + if (!SCULPT_cursor_geometry_info_update(C, &gi, mval, false)) { + return SCULPT_FACE_SET_NONE; + } + + return SCULPT_active_face_set_get(ss); +} + +/* Draw Face Sets Brush. */ + +static void do_draw_face_sets_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float bstrength = ss->cache->bstrength; + + PBVHVertexIter vd; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { + MeshElemMap *vert_map = &ss->pmap[vd.index]; + for (int j = 0; j < ss->pmap[vd.index].count; j++) { + const MPoly *p = &ss->mpoly[vert_map->indices[j]]; + + float poly_center[3]; + BKE_mesh_calc_poly_center(p, &ss->mloop[p->loopstart], mvert, poly_center); + + if (!sculpt_brush_test_sq_fn(&test, poly_center)) { + continue; + } + const bool face_hidden = ss->hide_poly && ss->hide_poly[vert_map->indices[j]]; + if (face_hidden) { + continue; + } + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.vertex, + thread_id); + + if (fade > 0.05f) { + ss->face_sets[vert_map->indices[j]] = ss->cache->paint_face_set; + } + } + } + else if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.vertex, + thread_id); + + if (fade > 0.05f) { + SCULPT_vertex_face_set_set(ss, vd.vertex, ss->cache->paint_face_set); + } + } + } + BKE_pbvh_vertex_iter_end; +} + +static void do_relax_face_sets_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + float bstrength = ss->cache->bstrength; + + PBVHVertexIter vd; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + + const bool relax_face_sets = !(ss->cache->iteration_count % 3 == 0); + /* This operations needs a strength tweak as the relax deformation is too weak by default. */ + if (relax_face_sets) { + bstrength *= 2.0f; + } + + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + if (relax_face_sets == SCULPT_vertex_has_unique_face_set(ss, vd.vertex)) { + continue; + } + + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.vertex, + thread_id); + + SCULPT_relax_vertex(ss, &vd, fade * bstrength, relax_face_sets, vd.co); + if (vd.mvert) { + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_draw_face_sets_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + BKE_curvemapping_init(brush->curve); + + /* Threaded loop over nodes. */ + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + if (ss->cache->alt_smooth) { + SCULPT_boundary_info_ensure(ob); + for (int i = 0; i < 4; i++) { + BLI_task_parallel_range(0, totnode, &data, do_relax_face_sets_brush_task_cb_ex, &settings); + } + } + else { + BLI_task_parallel_range(0, totnode, &data, do_draw_face_sets_brush_task_cb_ex, &settings); + } +} + +/* Face Sets Operators */ + +typedef enum eSculptFaceGroupsCreateModes { + SCULPT_FACE_SET_MASKED = 0, + SCULPT_FACE_SET_VISIBLE = 1, + SCULPT_FACE_SET_ALL = 2, + SCULPT_FACE_SET_SELECTION = 3, +} eSculptFaceGroupsCreateModes; + +static EnumPropertyItem prop_sculpt_face_set_create_types[] = { + { + SCULPT_FACE_SET_MASKED, + "MASKED", + 0, + "Face Set from Masked", + "Create a new Face Set from the masked faces", + }, + { + SCULPT_FACE_SET_VISIBLE, + "VISIBLE", + 0, + "Face Set from Visible", + "Create a new Face Set from the visible vertices", + }, + { + SCULPT_FACE_SET_ALL, + "ALL", + 0, + "Face Set Full Mesh", + "Create an unique Face Set with all faces in the sculpt", + }, + { + SCULPT_FACE_SET_SELECTION, + "SELECTION", + 0, + "Face Set from Edit Mode Selection", + "Create an Face Set corresponding to the Edit Mode face selection", + }, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static int sculpt_face_set_create_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + + const int mode = RNA_enum_get(op->ptr, "mode"); + + /* Dyntopo not supported. */ + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + return OPERATOR_CANCELLED; + } + + Mesh *mesh = ob->data; + ss->face_sets = BKE_sculpt_face_sets_ensure(mesh); + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, mode == SCULPT_FACE_SET_MASKED, false); + + const int tot_vert = SCULPT_vertex_count_get(ss); + float threshold = 0.5f; + + PBVH *pbvh = ob->sculpt->pbvh; + PBVHNode **nodes; + int totnode; + BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode); + + if (!nodes) { + return OPERATOR_CANCELLED; + } + + SCULPT_undo_push_begin(ob, op); + SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS); + + const int next_face_set = SCULPT_face_set_next_available_get(ss); + + if (mode == SCULPT_FACE_SET_MASKED) { + for (int i = 0; i < tot_vert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (SCULPT_vertex_mask_get(ss, vertex) >= threshold && + SCULPT_vertex_visible_get(ss, vertex)) { + SCULPT_vertex_face_set_set(ss, vertex, next_face_set); + } + } + } + + if (mode == SCULPT_FACE_SET_VISIBLE) { + + /* If all vertices in the sculpt are visible, create the new face set and update the default + * color. This way the new face set will be white, which is a quick way of disabling all face + * sets and the performance hit of rendering the overlay. */ + bool all_visible = true; + for (int i = 0; i < tot_vert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (!SCULPT_vertex_visible_get(ss, vertex)) { + all_visible = false; + break; + } + } + + if (all_visible) { + mesh->face_sets_color_default = next_face_set; + BKE_pbvh_face_sets_color_set( + ss->pbvh, mesh->face_sets_color_seed, mesh->face_sets_color_default); + } + + for (int i = 0; i < tot_vert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (SCULPT_vertex_visible_get(ss, vertex)) { + SCULPT_vertex_face_set_set(ss, vertex, next_face_set); + } + } + } + + if (mode == SCULPT_FACE_SET_ALL) { + for (int i = 0; i < tot_vert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + SCULPT_vertex_face_set_set(ss, vertex, next_face_set); + } + } + + if (mode == SCULPT_FACE_SET_SELECTION) { + BMesh *bm; + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh); + bm = BM_mesh_create(&allocsize, + &((struct BMeshCreateParams){ + .use_toolflags = true, + })); + + BM_mesh_bm_from_me(bm, + mesh, + (&(struct BMeshFromMeshParams){ + .calc_face_normal = true, + .calc_vert_normal = true, + })); + + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + ss->face_sets[BM_elem_index_get(f)] = next_face_set; + } + } + BM_mesh_free(bm); + } + + for (int i = 0; i < totnode; i++) { + BKE_pbvh_node_mark_redraw(nodes[i]); + } + + MEM_SAFE_FREE(nodes); + + SCULPT_undo_push_end(ob); + + SCULPT_tag_update_overlays(C); + + return OPERATOR_FINISHED; +} + +void SCULPT_OT_face_sets_create(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Create Face Set"; + ot->idname = "SCULPT_OT_face_sets_create"; + ot->description = "Create a new Face Set"; + + /* api callbacks */ + ot->exec = sculpt_face_set_create_exec; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum( + ot->srna, "mode", prop_sculpt_face_set_create_types, SCULPT_FACE_SET_MASKED, "Mode", ""); +} + +typedef enum eSculptFaceSetsInitMode { + SCULPT_FACE_SETS_FROM_LOOSE_PARTS = 0, + SCULPT_FACE_SETS_FROM_MATERIALS = 1, + SCULPT_FACE_SETS_FROM_NORMALS = 2, + SCULPT_FACE_SETS_FROM_UV_SEAMS = 3, + SCULPT_FACE_SETS_FROM_CREASES = 4, + SCULPT_FACE_SETS_FROM_SHARP_EDGES = 5, + SCULPT_FACE_SETS_FROM_BEVEL_WEIGHT = 6, + SCULPT_FACE_SETS_FROM_FACE_MAPS = 7, + SCULPT_FACE_SETS_FROM_FACE_SET_BOUNDARIES = 8, +} eSculptFaceSetsInitMode; + +static EnumPropertyItem prop_sculpt_face_sets_init_types[] = { + { + SCULPT_FACE_SETS_FROM_LOOSE_PARTS, + "LOOSE_PARTS", + 0, + "Face Sets from Loose Parts", + "Create a Face Set per loose part in the mesh", + }, + { + SCULPT_FACE_SETS_FROM_MATERIALS, + "MATERIALS", + 0, + "Face Sets from Material Slots", + "Create a Face Set per Material Slot", + }, + { + SCULPT_FACE_SETS_FROM_NORMALS, + "NORMALS", + 0, + "Face Sets from Mesh Normals", + "Create Face Sets for Faces that have similar normal", + }, + { + SCULPT_FACE_SETS_FROM_UV_SEAMS, + "UV_SEAMS", + 0, + "Face Sets from UV Seams", + "Create Face Sets using UV Seams as boundaries", + }, + { + SCULPT_FACE_SETS_FROM_CREASES, + "CREASES", + 0, + "Face Sets from Edge Creases", + "Create Face Sets using Edge Creases as boundaries", + }, + { + SCULPT_FACE_SETS_FROM_BEVEL_WEIGHT, + "BEVEL_WEIGHT", + 0, + "Face Sets from Bevel Weight", + "Create Face Sets using Bevel Weights as boundaries", + }, + { + SCULPT_FACE_SETS_FROM_SHARP_EDGES, + "SHARP_EDGES", + 0, + "Face Sets from Sharp Edges", + "Create Face Sets using Sharp Edges as boundaries", + }, + { + SCULPT_FACE_SETS_FROM_FACE_MAPS, + "FACE_MAPS", + 0, + "Face Sets from Face Maps", + "Create a Face Set per Face Map", + }, + { + SCULPT_FACE_SETS_FROM_FACE_SET_BOUNDARIES, + "FACE_SET_BOUNDARIES", + 0, + "Face Sets from Face Set Boundaries", + "Create a Face Set per isolated Face Set", + }, + + {0, nullptr, 0, nullptr, nullptr}, +}; + +typedef bool (*face_sets_flood_fill_test)( + BMesh *bm, BMFace *from_f, BMEdge *from_e, BMFace *to_f, const float threshold); + +static bool sculpt_face_sets_init_loose_parts_test(BMesh *UNUSED(bm), + BMFace *from_f, + BMEdge *UNUSED(from_e), + BMFace *to_f, + const float UNUSED(threshold)) +{ + return BM_elem_flag_test(from_f, BM_ELEM_HIDDEN) == BM_elem_flag_test(to_f, BM_ELEM_HIDDEN); +} + +static bool sculpt_face_sets_init_normals_test( + BMesh *UNUSED(bm), BMFace *from_f, BMEdge *UNUSED(from_e), BMFace *to_f, const float threshold) +{ + return fabsf(dot_v3v3(from_f->no, to_f->no)) > threshold; +} + +static bool sculpt_face_sets_init_uv_seams_test(BMesh *UNUSED(bm), + BMFace *UNUSED(from_f), + BMEdge *from_e, + BMFace *UNUSED(to_f), + const float UNUSED(threshold)) +{ + return !BM_elem_flag_test(from_e, BM_ELEM_SEAM); +} + +static bool sculpt_face_sets_init_crease_test( + BMesh *bm, BMFace *UNUSED(from_f), BMEdge *from_e, BMFace *UNUSED(to_f), const float threshold) +{ + return BM_elem_float_data_get(&bm->edata, from_e, CD_CREASE) < threshold; +} + +static bool sculpt_face_sets_init_bevel_weight_test( + BMesh *bm, BMFace *UNUSED(from_f), BMEdge *from_e, BMFace *UNUSED(to_f), const float threshold) +{ + return BM_elem_float_data_get(&bm->edata, from_e, CD_BWEIGHT) < threshold; +} + +static bool sculpt_face_sets_init_sharp_edges_test(BMesh *UNUSED(bm), + BMFace *UNUSED(from_f), + BMEdge *from_e, + BMFace *UNUSED(to_f), + const float UNUSED(threshold)) +{ + return BM_elem_flag_test(from_e, BM_ELEM_SMOOTH); +} + +static bool sculpt_face_sets_init_face_set_boundary_test( + BMesh *bm, BMFace *from_f, BMEdge *UNUSED(from_e), BMFace *to_f, const float UNUSED(threshold)) +{ + const int cd_face_sets_offset = CustomData_get_offset(&bm->pdata, CD_SCULPT_FACE_SETS); + return BM_ELEM_CD_GET_INT(from_f, cd_face_sets_offset) == + BM_ELEM_CD_GET_INT(to_f, cd_face_sets_offset); +} + +static void sculpt_face_sets_init_flood_fill(Object *ob, + face_sets_flood_fill_test test, + const float threshold) +{ + SculptSession *ss = ob->sculpt; + Mesh *mesh = ob->data; + BMesh *bm; + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh); + bm = BM_mesh_create(&allocsize, + &((struct BMeshCreateParams){ + .use_toolflags = true, + })); + + BM_mesh_bm_from_me(bm, + mesh, + (&(struct BMeshFromMeshParams){ + .calc_face_normal = true, + .calc_vert_normal = true, + })); + + BLI_bitmap *visited_faces = BLI_BITMAP_NEW(mesh->totpoly, "visited faces"); + const int totfaces = mesh->totpoly; + + int *face_sets = ss->face_sets; + + BM_mesh_elem_table_init(bm, BM_FACE); + BM_mesh_elem_table_ensure(bm, BM_FACE); + + int next_face_set = 1; + + for (int i = 0; i < totfaces; i++) { + if (BLI_BITMAP_TEST(visited_faces, i)) { + continue; + } + GSQueue *queue; + queue = BLI_gsqueue_new(sizeof(int)); + + face_sets[i] = next_face_set; + BLI_BITMAP_ENABLE(visited_faces, i); + BLI_gsqueue_push(queue, &i); + + while (!BLI_gsqueue_is_empty(queue)) { + int from_f; + BLI_gsqueue_pop(queue, &from_f); + + BMFace *f, *f_neighbor; + BMEdge *ed; + BMIter iter_a, iter_b; + + f = BM_face_at_index(bm, from_f); + + BM_ITER_ELEM (ed, &iter_a, f, BM_EDGES_OF_FACE) { + BM_ITER_ELEM (f_neighbor, &iter_b, ed, BM_FACES_OF_EDGE) { + if (f_neighbor == f) { + continue; + } + int neighbor_face_index = BM_elem_index_get(f_neighbor); + if (BLI_BITMAP_TEST(visited_faces, neighbor_face_index)) { + continue; + } + if (!test(bm, f, ed, f_neighbor, threshold)) { + continue; + } + + face_sets[neighbor_face_index] = next_face_set; + BLI_BITMAP_ENABLE(visited_faces, neighbor_face_index); + BLI_gsqueue_push(queue, &neighbor_face_index); + } + } + } + + next_face_set += 1; + + BLI_gsqueue_free(queue); + } + + MEM_SAFE_FREE(visited_faces); + + BM_mesh_free(bm); +} + +static void sculpt_face_sets_init_loop(Object *ob, const int mode) +{ + Mesh *mesh = ob->data; + SculptSession *ss = ob->sculpt; + BMesh *bm; + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh); + bm = BM_mesh_create(&allocsize, + &((struct BMeshCreateParams){ + .use_toolflags = true, + })); + + BM_mesh_bm_from_me(bm, + mesh, + (&(struct BMeshFromMeshParams){ + .calc_face_normal = true, + .calc_vert_normal = true, + })); + BMIter iter; + BMFace *f; + + const int cd_fmaps_offset = CustomData_get_offset(&bm->pdata, CD_FACEMAP); + + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (mode == SCULPT_FACE_SETS_FROM_MATERIALS) { + ss->face_sets[BM_elem_index_get(f)] = (int)(f->mat_nr + 1); + } + else if (mode == SCULPT_FACE_SETS_FROM_FACE_MAPS) { + if (cd_fmaps_offset != -1) { + ss->face_sets[BM_elem_index_get(f)] = BM_ELEM_CD_GET_INT(f, cd_fmaps_offset) + 2; + } + else { + ss->face_sets[BM_elem_index_get(f)] = 1; + } + } + } + BM_mesh_free(bm); +} + +static int sculpt_face_set_init_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + + const int mode = RNA_enum_get(op->ptr, "mode"); + + /* Dyntopo not supported. */ + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + return OPERATOR_CANCELLED; + } + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); + + PBVH *pbvh = ob->sculpt->pbvh; + PBVHNode **nodes; + int totnode; + BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode); + + if (!nodes) { + return OPERATOR_CANCELLED; + } + + SCULPT_undo_push_begin(ob, op); + SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS); + + const float threshold = RNA_float_get(op->ptr, "threshold"); + + Mesh *mesh = ob->data; + ss->face_sets = BKE_sculpt_face_sets_ensure(mesh); + + switch (mode) { + case SCULPT_FACE_SETS_FROM_LOOSE_PARTS: + sculpt_face_sets_init_flood_fill(ob, sculpt_face_sets_init_loose_parts_test, threshold); + break; + case SCULPT_FACE_SETS_FROM_MATERIALS: + sculpt_face_sets_init_loop(ob, SCULPT_FACE_SETS_FROM_MATERIALS); + break; + case SCULPT_FACE_SETS_FROM_NORMALS: + sculpt_face_sets_init_flood_fill(ob, sculpt_face_sets_init_normals_test, threshold); + break; + case SCULPT_FACE_SETS_FROM_UV_SEAMS: + sculpt_face_sets_init_flood_fill(ob, sculpt_face_sets_init_uv_seams_test, threshold); + break; + case SCULPT_FACE_SETS_FROM_CREASES: + sculpt_face_sets_init_flood_fill(ob, sculpt_face_sets_init_crease_test, threshold); + break; + case SCULPT_FACE_SETS_FROM_SHARP_EDGES: + sculpt_face_sets_init_flood_fill(ob, sculpt_face_sets_init_sharp_edges_test, threshold); + break; + case SCULPT_FACE_SETS_FROM_BEVEL_WEIGHT: + sculpt_face_sets_init_flood_fill(ob, sculpt_face_sets_init_bevel_weight_test, threshold); + break; + case SCULPT_FACE_SETS_FROM_FACE_SET_BOUNDARIES: + sculpt_face_sets_init_flood_fill( + ob, sculpt_face_sets_init_face_set_boundary_test, threshold); + break; + case SCULPT_FACE_SETS_FROM_FACE_MAPS: + sculpt_face_sets_init_loop(ob, SCULPT_FACE_SETS_FROM_FACE_MAPS); + break; + } + + SCULPT_undo_push_end(ob); + + /* Sync face sets visibility and vertex visibility as now all Face Sets are visible. */ + SCULPT_visibility_sync_all_from_faces(ob); + + for (int i = 0; i < totnode; i++) { + BKE_pbvh_node_mark_update_visibility(nodes[i]); + } + + BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility); + + MEM_SAFE_FREE(nodes); + + if (BKE_pbvh_type(pbvh) == PBVH_FACES) { + BKE_mesh_flush_hidden_from_verts(ob->data); + } + + SCULPT_tag_update_overlays(C); + + return OPERATOR_FINISHED; +} + +void SCULPT_OT_face_sets_init(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Initialize Face Sets"; + ot->idname = "SCULPT_OT_face_sets_init"; + ot->description = "Set the value for all faces sets on the mesh"; + + /* api callbacks */ + ot->exec = sculpt_face_set_init_exec; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum( + ot->srna, "mode", prop_sculpt_face_sets_init_types, SCULPT_FACE_SET_MASKED, "Mode", ""); + RNA_def_float( + ot->srna, + "threshold", + 0.5f, + 0.0f, + 1.0f, + "Threshold", + "Minimum value to consider a certain attribute a boundary when creating the Face Sets", + 0.0f, + 1.0f); +} + +typedef enum eSculptFaceGroupVisibilityModes { + SCULPT_FACE_SET_VISIBILITY_TOGGLE = 0, + SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE = 1, + SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE = 2, + SCULPT_FACE_SET_VISIBILITY_INVERT = 3, + SCULPT_FACE_SET_VISIBILITY_SHOW_ALL = 4, +} eSculptFaceGroupVisibilityModes; + +static EnumPropertyItem prop_sculpt_face_sets_change_visibility_types[] = { + { + SCULPT_FACE_SET_VISIBILITY_TOGGLE, + "TOGGLE", + 0, + "Toggle Visibility", + "Hide all Face Sets except for the active one", + }, + { + SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE, + "SHOW_ACTIVE", + 0, + "Show Active Face Set", + "Show Active Face Set", + }, + { + SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE, + "HIDE_ACTIVE", + 0, + "Hide Active Face Sets", + "Hide Active Face Sets", + }, + { + SCULPT_FACE_SET_VISIBILITY_INVERT, + "INVERT", + 0, + "Invert Face Set Visibility", + "Invert Face Set Visibility", + }, + { + SCULPT_FACE_SET_VISIBILITY_SHOW_ALL, + "SHOW_ALL", + 0, + "Show All Face Sets", + "Show All Face Sets", + }, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static int sculpt_face_sets_change_visibility_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + + /* Dyntopo not supported. */ + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + return OPERATOR_CANCELLED; + } + + if (!ss->face_sets) { + return OPERATOR_CANCELLED; + } + + Mesh *mesh = BKE_object_get_original_mesh(ob); + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); + + const int tot_vert = SCULPT_vertex_count_get(ss); + const int mode = RNA_enum_get(op->ptr, "mode"); + const int active_face_set = SCULPT_active_face_set_get(ss); + + SCULPT_undo_push_begin(ob, op); + + PBVH *pbvh = ob->sculpt->pbvh; + PBVHNode **nodes; + int totnode; + + BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode); + + if (totnode == 0) { + MEM_SAFE_FREE(nodes); + return OPERATOR_CANCELLED; + } + + SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS); + + if (mode == SCULPT_FACE_SET_VISIBILITY_TOGGLE) { + bool hidden_vertex = false; + + /* This can fail with regular meshes with non-manifold geometry as the visibility state can't + * be synced from face sets to non-manifold vertices. */ + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + for (int i = 0; i < tot_vert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (!SCULPT_vertex_visible_get(ss, vertex)) { + hidden_vertex = true; + break; + } + } + } + + if (ss->hide_poly) { + for (int i = 0; i < ss->totfaces; i++) { + if (ss->hide_poly[i]) { + hidden_vertex = true; + break; + } + } + } + + ss->hide_poly = BKE_sculpt_hide_poly_ensure(mesh); + + if (hidden_vertex) { + SCULPT_face_visibility_all_set(ss, true); + } + else { + SCULPT_face_visibility_all_set(ss, false); + SCULPT_face_set_visibility_set(ss, active_face_set, true); + } + } + + if (mode == SCULPT_FACE_SET_VISIBILITY_SHOW_ALL) { + /* As an optimization, free the hide attribute when making all geometry visible. This allows + * reduced memory usage without manually clearing it later, and allows sculpt operations to + * avoid checking element's hide status. */ + CustomData_free_layer_named(&mesh->pdata, ".hide_poly", mesh->totpoly); + ss->hide_poly = nullptr; + BKE_pbvh_update_hide_attributes_from_mesh(pbvh); + } + + if (mode == SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE) { + ss->hide_poly = BKE_sculpt_hide_poly_ensure(mesh); + SCULPT_face_visibility_all_set(ss, false); + SCULPT_face_set_visibility_set(ss, active_face_set, true); + } + + if (mode == SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE) { + ss->hide_poly = BKE_sculpt_hide_poly_ensure(mesh); + SCULPT_face_set_visibility_set(ss, active_face_set, false); + } + + if (mode == SCULPT_FACE_SET_VISIBILITY_INVERT) { + ss->hide_poly = BKE_sculpt_hide_poly_ensure(mesh); + SCULPT_face_visibility_all_invert(ss); + } + + /* For modes that use the cursor active vertex, update the rotation origin for viewport + * navigation. */ + if (ELEM(mode, SCULPT_FACE_SET_VISIBILITY_TOGGLE, SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE)) { + UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; + float location[3]; + copy_v3_v3(location, SCULPT_active_vertex_co_get(ss)); + mul_m4_v3(ob->obmat, location); + copy_v3_v3(ups->average_stroke_accum, location); + ups->average_stroke_counter = 1; + ups->last_stroke_valid = true; + } + + /* Sync face sets visibility and vertex visibility. */ + SCULPT_visibility_sync_all_from_faces(ob); + + SCULPT_undo_push_end(ob); + + for (int i = 0; i < totnode; i++) { + BKE_pbvh_node_mark_update_visibility(nodes[i]); + } + + BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility); + + MEM_SAFE_FREE(nodes); + + SCULPT_tag_update_overlays(C); + + return OPERATOR_FINISHED; +} + +static int sculpt_face_sets_change_visibility_invoke(bContext *C, + wmOperator *op, + const wmEvent *event) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + + /* Update the active vertex and Face Set using the cursor position to avoid relying on the paint + * cursor updates. */ + SculptCursorGeometryInfo sgi; + const float mval_fl[2] = {UNPACK2(event->mval)}; + SCULPT_vertex_random_access_ensure(ss); + SCULPT_cursor_geometry_info_update(C, &sgi, mval_fl, false); + + return sculpt_face_sets_change_visibility_exec(C, op); +} + +void SCULPT_OT_face_sets_change_visibility(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Face Sets Visibility"; + ot->idname = "SCULPT_OT_face_set_change_visibility"; + ot->description = "Change the visibility of the Face Sets of the sculpt"; + + /* Api callbacks. */ + ot->exec = sculpt_face_sets_change_visibility_exec; + ot->invoke = sculpt_face_sets_change_visibility_invoke; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum(ot->srna, + "mode", + prop_sculpt_face_sets_change_visibility_types, + SCULPT_FACE_SET_VISIBILITY_TOGGLE, + "Mode", + ""); +} + +static int sculpt_face_sets_randomize_colors_exec(bContext *C, wmOperator *UNUSED(op)) +{ + + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + + /* Dyntopo not supported. */ + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + return OPERATOR_CANCELLED; + } + + if (!ss->face_sets) { + return OPERATOR_CANCELLED; + } + + PBVH *pbvh = ob->sculpt->pbvh; + PBVHNode **nodes; + int totnode; + Mesh *mesh = ob->data; + + mesh->face_sets_color_seed += 1; + if (ss->face_sets) { + const int random_index = clamp_i(ss->totfaces * BLI_hash_int_01(mesh->face_sets_color_seed), + 0, + max_ii(0, ss->totfaces - 1)); + mesh->face_sets_color_default = ss->face_sets[random_index]; + } + BKE_pbvh_face_sets_color_set(pbvh, mesh->face_sets_color_seed, mesh->face_sets_color_default); + + BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode); + for (int i = 0; i < totnode; i++) { + BKE_pbvh_node_mark_redraw(nodes[i]); + } + + MEM_SAFE_FREE(nodes); + + SCULPT_tag_update_overlays(C); + + return OPERATOR_FINISHED; +} + +void SCULPT_OT_face_sets_randomize_colors(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Randomize Face Sets Colors"; + ot->idname = "SCULPT_OT_face_sets_randomize_colors"; + ot->description = "Generates a new set of random colors to render the Face Sets in the viewport"; + + /* Api callbacks. */ + ot->exec = sculpt_face_sets_randomize_colors_exec; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +typedef enum eSculptFaceSetEditMode { + SCULPT_FACE_SET_EDIT_GROW = 0, + SCULPT_FACE_SET_EDIT_SHRINK = 1, + SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY = 2, + SCULPT_FACE_SET_EDIT_FAIR_POSITIONS = 3, + SCULPT_FACE_SET_EDIT_FAIR_TANGENCY = 4, +} eSculptFaceSetEditMode; + +static EnumPropertyItem prop_sculpt_face_sets_edit_types[] = { + { + SCULPT_FACE_SET_EDIT_GROW, + "GROW", + 0, + "Grow Face Set", + "Grows the Face Sets boundary by one face based on mesh topology", + }, + { + SCULPT_FACE_SET_EDIT_SHRINK, + "SHRINK", + 0, + "Shrink Face Set", + "Shrinks the Face Sets boundary by one face based on mesh topology", + }, + { + SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY, + "DELETE_GEOMETRY", + 0, + "Delete Geometry", + "Deletes the faces that are assigned to the Face Set", + }, + { + SCULPT_FACE_SET_EDIT_FAIR_POSITIONS, + "FAIR_POSITIONS", + 0, + "Fair Positions", + "Creates a smooth as possible geometry patch from the Face Set minimizing changes in " + "vertex positions", + }, + { + SCULPT_FACE_SET_EDIT_FAIR_TANGENCY, + "FAIR_TANGENCY", + 0, + "Fair Tangency", + "Creates a smooth as possible geometry patch from the Face Set minimizing changes in " + "vertex tangents", + }, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static void sculpt_face_set_grow(Object *ob, + SculptSession *ss, + const int *prev_face_sets, + const int active_face_set_id, + const bool modify_hidden) +{ + Mesh *mesh = BKE_mesh_from_object(ob); + const MPoly *polys = BKE_mesh_polys(mesh); + const MLoop *loops = BKE_mesh_loops(mesh); + + for (int p = 0; p < mesh->totpoly; p++) { + if (!modify_hidden && prev_face_sets[p] <= 0) { + continue; + } + const MPoly *c_poly = &polys[p]; + for (int l = 0; l < c_poly->totloop; l++) { + const MLoop *c_loop = &loops[c_poly->loopstart + l]; + const MeshElemMap *vert_map = &ss->pmap[c_loop->v]; + for (int i = 0; i < vert_map->count; i++) { + const int neighbor_face_index = vert_map->indices[i]; + if (neighbor_face_index == p) { + continue; + } + if (abs(prev_face_sets[neighbor_face_index]) == active_face_set_id) { + ss->face_sets[p] = active_face_set_id; + } + } + } + } +} + +static void sculpt_face_set_shrink(Object *ob, + SculptSession *ss, + const int *prev_face_sets, + const int active_face_set_id, + const bool modify_hidden) +{ + Mesh *mesh = BKE_mesh_from_object(ob); + const MPoly *polys = BKE_mesh_polys(mesh); + const MLoop *loops = BKE_mesh_loops(mesh); + for (int p = 0; p < mesh->totpoly; p++) { + if (!modify_hidden && prev_face_sets[p] <= 0) { + continue; + } + if (abs(prev_face_sets[p]) == active_face_set_id) { + const MPoly *c_poly = &polys[p]; + for (int l = 0; l < c_poly->totloop; l++) { + const MLoop *c_loop = &loops[c_poly->loopstart + l]; + const MeshElemMap *vert_map = &ss->pmap[c_loop->v]; + for (int i = 0; i < vert_map->count; i++) { + const int neighbor_face_index = vert_map->indices[i]; + if (neighbor_face_index == p) { + continue; + } + if (abs(prev_face_sets[neighbor_face_index]) != active_face_set_id) { + ss->face_sets[p] = prev_face_sets[neighbor_face_index]; + } + } + } + } + } +} + +static bool check_single_face_set(SculptSession *ss, int *face_sets, const bool check_visible_only) +{ + if (face_sets == nullptr) { + return true; + } + int first_face_set = SCULPT_FACE_SET_NONE; + if (check_visible_only) { + for (int f = 0; f < ss->totfaces; f++) { + if (ss->hide_poly && ss->hide_poly[f]) { + continue; + } + first_face_set = face_sets[f]; + break; + } + } + else { + first_face_set = face_sets[0]; + } + + if (first_face_set == SCULPT_FACE_SET_NONE) { + return true; + } + + for (int f = 0; f < ss->totfaces; f++) { + if (check_visible_only && ss->hide_poly && ss->hide_poly[f]) { + continue; + } + if (face_sets[f] != first_face_set) { + return false; + } + } + return true; +} + +static void sculpt_face_set_delete_geometry(Object *ob, + SculptSession *ss, + const int active_face_set_id, + const bool modify_hidden) +{ + + Mesh *mesh = ob->data; + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh); + BMesh *bm = BM_mesh_create(&allocsize, + &((struct BMeshCreateParams){ + .use_toolflags = true, + })); + + BM_mesh_bm_from_me(bm, + mesh, + (&(struct BMeshFromMeshParams){ + .calc_face_normal = true, + .calc_vert_normal = true, + })); + + BM_mesh_elem_table_init(bm, BM_FACE); + BM_mesh_elem_table_ensure(bm, BM_FACE); + BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false); + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + const int face_index = BM_elem_index_get(f); + if (!modify_hidden && ss->hide_poly && ss->hide_poly[face_index]) { + continue; + } + BM_elem_flag_set(f, BM_ELEM_TAG, ss->face_sets[face_index] == active_face_set_id); + } + BM_mesh_delete_hflag_context(bm, BM_ELEM_TAG, DEL_FACES); + BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false); + + BM_mesh_bm_to_me(nullptr, + bm, + ob->data, + (&(struct BMeshToMeshParams){ + .calc_object_remap = false, + })); + + BM_mesh_free(bm); +} + +static void sculpt_face_set_edit_fair_face_set(Object *ob, + const int active_face_set_id, + const int fair_order) +{ + SculptSession *ss = ob->sculpt; + const int totvert = SCULPT_vertex_count_get(ss); + + Mesh *mesh = ob->data; + bool *fair_verts = MEM_malloc_arrayN(totvert, sizeof(bool), "fair vertices"); + + SCULPT_boundary_info_ensure(ob); + + for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + fair_verts[i] = !SCULPT_vertex_is_boundary(ss, vertex) && + SCULPT_vertex_has_face_set(ss, vertex, active_face_set_id) && + SCULPT_vertex_has_unique_face_set(ss, vertex); + } + + MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss); + BKE_mesh_prefair_and_fair_verts(mesh, mvert, fair_verts, fair_order); + MEM_freeN(fair_verts); +} + +static void sculpt_face_set_apply_edit(Object *ob, + const int active_face_set_id, + const int mode, + const bool modify_hidden) +{ + SculptSession *ss = ob->sculpt; + + switch (mode) { + case SCULPT_FACE_SET_EDIT_GROW: { + int *prev_face_sets = MEM_dupallocN(ss->face_sets); + sculpt_face_set_grow(ob, ss, prev_face_sets, active_face_set_id, modify_hidden); + MEM_SAFE_FREE(prev_face_sets); + break; + } + case SCULPT_FACE_SET_EDIT_SHRINK: { + int *prev_face_sets = MEM_dupallocN(ss->face_sets); + sculpt_face_set_shrink(ob, ss, prev_face_sets, active_face_set_id, modify_hidden); + MEM_SAFE_FREE(prev_face_sets); + break; + } + case SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY: + sculpt_face_set_delete_geometry(ob, ss, active_face_set_id, modify_hidden); + break; + case SCULPT_FACE_SET_EDIT_FAIR_POSITIONS: + sculpt_face_set_edit_fair_face_set(ob, active_face_set_id, MESH_FAIRING_DEPTH_POSITION); + break; + case SCULPT_FACE_SET_EDIT_FAIR_TANGENCY: + sculpt_face_set_edit_fair_face_set(ob, active_face_set_id, MESH_FAIRING_DEPTH_TANGENCY); + break; + } +} + +static bool sculpt_face_set_edit_is_operation_valid(SculptSession *ss, + const eSculptFaceSetEditMode mode, + const bool modify_hidden) +{ + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + /* Dyntopo is not supported. */ + return false; + } + + if (mode == SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY) { + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + /* Modification of base mesh geometry requires special remapping of multires displacement, + * which does not happen here. + * Disable delete operation. It can be supported in the future by doing similar displacement + * data remapping as what happens in the mesh edit mode. */ + return false; + } + if (check_single_face_set(ss, ss->face_sets, !modify_hidden)) { + /* Cancel the operator if the mesh only contains one Face Set to avoid deleting the + * entire object. */ + return false; + } + } + + if (ELEM(mode, SCULPT_FACE_SET_EDIT_FAIR_POSITIONS, SCULPT_FACE_SET_EDIT_FAIR_TANGENCY)) { + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + /* TODO: Multires topology representation using grids and duplicates can't be used directly + * by the fair algorithm. Multires topology needs to be exposed in a different way or + * converted to a mesh for this operation. */ + return false; + } + } + + return true; +} + +static void sculpt_face_set_edit_modify_geometry(bContext *C, + Object *ob, + const int active_face_set, + const eSculptFaceSetEditMode mode, + const bool modify_hidden, + wmOperator *op) +{ + ED_sculpt_undo_geometry_begin(ob, op); + sculpt_face_set_apply_edit(ob, abs(active_face_set), mode, modify_hidden); + ED_sculpt_undo_geometry_end(ob); + BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL); + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); +} + +static void face_set_edit_do_post_visibility_updates(Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + PBVH *pbvh = ss->pbvh; + + /* Sync face sets visibility and vertex visibility as now all Face Sets are visible. */ + SCULPT_visibility_sync_all_from_faces(ob); + + for (int i = 0; i < totnode; i++) { + BKE_pbvh_node_mark_update_visibility(nodes[i]); + } + + BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility); + + if (BKE_pbvh_type(pbvh) == PBVH_FACES) { + BKE_mesh_flush_hidden_from_verts(ob->data); + } +} + +static void sculpt_face_set_edit_modify_face_sets(Object *ob, + const int active_face_set, + const eSculptFaceSetEditMode mode, + const bool modify_hidden, + wmOperator *op) +{ + PBVH *pbvh = ob->sculpt->pbvh; + PBVHNode **nodes; + int totnode; + BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode); + + if (!nodes) { + return; + } + SCULPT_undo_push_begin(ob, op); + SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS); + sculpt_face_set_apply_edit(ob, abs(active_face_set), mode, modify_hidden); + SCULPT_undo_push_end(ob); + face_set_edit_do_post_visibility_updates(ob, nodes, totnode); + MEM_freeN(nodes); +} + +static void sculpt_face_set_edit_modify_coordinates(bContext *C, + Object *ob, + const int active_face_set, + const eSculptFaceSetEditMode mode, + wmOperator *op) +{ + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + SculptSession *ss = ob->sculpt; + PBVH *pbvh = ss->pbvh; + PBVHNode **nodes; + int totnode; + BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode); + SCULPT_undo_push_begin(ob, op); + for (int i = 0; i < totnode; i++) { + BKE_pbvh_node_mark_update(nodes[i]); + SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_COORDS); + } + sculpt_face_set_apply_edit(ob, abs(active_face_set), mode, false); + + if (ss->deform_modifiers_active || ss->shapekey_active) { + SCULPT_flush_stroke_deform(sd, ob, true); + } + SCULPT_flush_update_step(C, SCULPT_UPDATE_COORDS); + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS); + SCULPT_undo_push_end(ob); + MEM_freeN(nodes); +} + +static int sculpt_face_set_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + + const int mode = RNA_enum_get(op->ptr, "mode"); + const bool modify_hidden = RNA_boolean_get(op->ptr, "modify_hidden"); + + if (!sculpt_face_set_edit_is_operation_valid(ss, mode, modify_hidden)) { + return OPERATOR_CANCELLED; + } + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); + + /* Update the current active Face Set and Vertex as the operator can be used directly from the + * tool without brush cursor. */ + SculptCursorGeometryInfo sgi; + const float mval_fl[2] = {UNPACK2(event->mval)}; + if (!SCULPT_cursor_geometry_info_update(C, &sgi, mval_fl, false)) { + /* The cursor is not over the mesh. Cancel to avoid editing the last updated Face Set ID. */ + return OPERATOR_CANCELLED; + } + const int active_face_set = SCULPT_active_face_set_get(ss); + + switch (mode) { + case SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY: + sculpt_face_set_edit_modify_geometry(C, ob, active_face_set, mode, modify_hidden, op); + break; + case SCULPT_FACE_SET_EDIT_GROW: + case SCULPT_FACE_SET_EDIT_SHRINK: + sculpt_face_set_edit_modify_face_sets(ob, active_face_set, mode, modify_hidden, op); + break; + case SCULPT_FACE_SET_EDIT_FAIR_POSITIONS: + case SCULPT_FACE_SET_EDIT_FAIR_TANGENCY: + sculpt_face_set_edit_modify_coordinates(C, ob, active_face_set, mode, op); + break; + } + + SCULPT_tag_update_overlays(C); + + return OPERATOR_FINISHED; +} + +void SCULPT_OT_face_sets_edit(struct wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Edit Face Set"; + ot->idname = "SCULPT_OT_face_set_edit"; + ot->description = "Edits the current active Face Set"; + + /* Api callbacks. */ + ot->invoke = sculpt_face_set_edit_invoke; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum( + ot->srna, "mode", prop_sculpt_face_sets_edit_types, SCULPT_FACE_SET_EDIT_GROW, "Mode", ""); + ot->prop = RNA_def_boolean(ot->srna, + "modify_hidden", + true, + "Modify Hidden", + "Apply the edit operation to hidden Face Sets"); +} -- cgit v1.2.3