/* 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, abs(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 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.index, thread_id); if (fade > 0.05f && ss->face_sets[vert_map->indices[j]] > 0) { ss->face_sets[vert_map->indices[j]] = abs(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.index, thread_id); if (fade > 0.05f) { SCULPT_vertex_face_set_set(ss, vd.index, 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.index)) { 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.index, thread_id); SCULPT_relax_vertex(ss, &vd, fade * bstrength, relax_face_sets, vd.co); if (vd.mvert) { BKE_pbvh_vert_mark_update(ss->pbvh, vd.index); } } 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, NULL, 0, NULL, NULL}, }; 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; } 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, NULL, NULL, &nodes, &totnode); if (!nodes) { return OPERATOR_CANCELLED; } SCULPT_undo_push_begin(ob, "face set change"); 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++) { if (SCULPT_vertex_mask_get(ss, i) >= threshold && SCULPT_vertex_visible_get(ss, i)) { SCULPT_vertex_face_set_set(ss, i, 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++) { if (!SCULPT_vertex_visible_get(ss, i)) { all_visible = false; break; } } if (all_visible) { Mesh *mesh = ob->data; 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++) { if (SCULPT_vertex_visible_get(ss, i)) { SCULPT_vertex_face_set_set(ss, i, next_face_set); } } } if (mode == SCULPT_FACE_SET_ALL) { for (int i = 0; i < tot_vert; i++) { SCULPT_vertex_face_set_set(ss, i, next_face_set); } } if (mode == SCULPT_FACE_SET_SELECTION) { 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, })); 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, NULL, 0, NULL, NULL}, }; 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 *UNUSED(from_f), BMEdge *UNUSED(from_e), BMFace *UNUSED(to_f), const float UNUSED(threshold)) { return true; } 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, NULL, NULL, &nodes, &totnode); if (!nodes) { return OPERATOR_CANCELLED; } SCULPT_undo_push_begin(ob, "face set change"); SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS); const float threshold = RNA_float_get(op->ptr, "threshold"); 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_face_sets_to_vertices(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 = "Init Face Sets"; ot->idname = "SCULPT_OT_face_sets_init"; ot->description = "Initializes all Face Sets in 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, NULL, 0, NULL, NULL}, }; 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; } 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, "Hide area"); PBVH *pbvh = ob->sculpt->pbvh; PBVHNode **nodes; int totnode; BKE_pbvh_search_gather(pbvh, NULL, NULL, &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++) { if (!SCULPT_vertex_visible_get(ss, i)) { hidden_vertex = true; break; } } } for (int i = 0; i < ss->totfaces; i++) { if (ss->face_sets[i] <= 0) { hidden_vertex = true; break; } } if (hidden_vertex) { SCULPT_face_sets_visibility_all_set(ss, true); } else { SCULPT_face_sets_visibility_all_set(ss, false); SCULPT_face_set_visibility_set(ss, active_face_set, true); } } if (mode == SCULPT_FACE_SET_VISIBILITY_SHOW_ALL) { SCULPT_face_sets_visibility_all_set(ss, true); } if (mode == SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE) { SCULPT_face_sets_visibility_all_set(ss, false); SCULPT_face_set_visibility_set(ss, active_face_set, true); } if (mode == SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE) { SCULPT_face_set_visibility_set(ss, active_face_set, false); } if (mode == SCULPT_FACE_SET_VISIBILITY_INVERT) { SCULPT_face_sets_visibility_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_face_sets_to_vertices(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; } 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, NULL, NULL, &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, NULL, 0, NULL, NULL}, }; 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); for (int p = 0; p < mesh->totpoly; p++) { if (!modify_hidden && prev_face_sets[p] <= 0) { continue; } const MPoly *c_poly = &mesh->mpoly[p]; for (int l = 0; l < c_poly->totloop; l++) { const MLoop *c_loop = &mesh->mloop[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); 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 = &mesh->mpoly[p]; for (int l = 0; l < c_poly->totloop; l++) { const MLoop *c_loop = &mesh->mloop[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) { int first_face_set = SCULPT_FACE_SET_NONE; if (check_visible_only) { for (int f = 0; f < ss->totfaces; f++) { if (face_sets[f] > 0) { first_face_set = face_sets[f]; break; } } } else { first_face_set = abs(face_sets[0]); } if (first_face_set == SCULPT_FACE_SET_NONE) { return true; } for (int f = 0; f < ss->totfaces; f++) { const int face_set_id = check_visible_only ? face_sets[f] : abs(face_sets[f]); if (face_set_id != 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); const int face_set_id = modify_hidden ? abs(ss->face_sets[face_index]) : ss->face_sets[face_index]; BM_elem_flag_set(f, BM_ELEM_TAG, face_set_id == 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(NULL, 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_vertices = MEM_malloc_arrayN(totvert, sizeof(bool), "fair vertices"); SCULPT_boundary_info_ensure(ob); for (int i = 0; i < totvert; i++) { fair_vertices[i] = !SCULPT_vertex_is_boundary(ss, i) && SCULPT_vertex_has_face_set(ss, i, active_face_set_id) && SCULPT_vertex_has_unique_face_set(ss, i); } MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss); BKE_mesh_prefair_and_fair_vertices(mesh, mvert, fair_vertices, fair_order); MEM_freeN(fair_vertices); } 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) { ED_sculpt_undo_geometry_begin(ob, "edit face set delete geometry"); 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_face_sets_to_vertices(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) { PBVH *pbvh = ob->sculpt->pbvh; PBVHNode **nodes; int totnode; BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode); if (!nodes) { return; } SCULPT_undo_push_begin(ob, "face set edit"); 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) { 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, NULL, NULL, &nodes, &totnode); SCULPT_undo_push_begin(ob, "face set edit"); 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); 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); 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); 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"); }