/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2020 Blender Foundation. All rights reserved. */ /** \file * \ingroup edsculpt */ #include #include #include #include "MEM_guardedalloc.h" #include "BLI_bit_vector.hh" #include "BLI_function_ref.hh" #include "BLI_hash.h" #include "BLI_math.h" #include "BLI_math_vector.hh" #include "BLI_span.hh" #include "BLI_task.h" #include "BLI_task.hh" #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_attribute.hh" #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_object.h" #include "BKE_paint.h" #include "BKE_pbvh.h" #include "DEG_depsgraph.h" #include "WM_api.h" #include "WM_types.h" #include "ED_sculpt.h" #include "sculpt_intern.h" #include "RNA_access.h" #include "RNA_define.h" #include "bmesh.h" /* Utils. */ int ED_sculpt_face_sets_find_next_available_id(struct Mesh *mesh) { const int *face_sets = static_cast( CustomData_get_layer_named(&mesh->pdata, CD_PROP_INT32, ".sculpt_face_set")); 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 = static_cast( CustomData_get_layer_named(&mesh->pdata, CD_PROP_INT32, ".sculpt_face_set")); 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 = static_cast(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); const float(*positions)[3] = SCULPT_mesh_deformed_positions_get(ss); AutomaskingNodeData automask_data; SCULPT_automasking_node_begin( data->ob, ss, ss->cache->automasking, &automask_data, data->nodes[n]); BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { SCULPT_automasking_node_update(ss, &automask_data, &vd); 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], positions, 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, &automask_data); 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, &automask_data); 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 = static_cast(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 && data->iteration < 2) { bstrength *= 1.5f; } const int thread_id = BLI_task_parallel_thread_id(tls); AutomaskingNodeData automask_data; SCULPT_automasking_node_begin( data->ob, ss, ss->cache->automasking, &automask_data, data->nodes[n]); BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { SCULPT_automasking_node_update(ss, &automask_data, &vd); 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, &automask_data); SCULPT_relax_vertex(ss, &vd, fade * bstrength, relax_face_sets, vd.co); if (vd.is_mesh) { 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); if (ss->pbvh) { Mesh *mesh = BKE_mesh_from_object(ob); BKE_pbvh_face_sets_color_set( ss->pbvh, mesh->face_sets_color_seed, mesh->face_sets_color_default); } BKE_curvemapping_init(brush->curve); /* Threaded loop over nodes. */ SculptThreadedTaskData data{}; data.sd = sd; data.ob = ob; data.brush = brush; data.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++) { data.iteration = 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 */ enum eSculptFaceGroupsCreateModes { SCULPT_FACE_SET_MASKED = 0, SCULPT_FACE_SET_VISIBLE = 1, SCULPT_FACE_SET_ALL = 2, SCULPT_FACE_SET_SELECTION = 3, }; 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) { using namespace blender; 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 = static_cast(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) { const bke::AttributeAccessor attributes = mesh->attributes(); const VArraySpan select_poly = attributes.lookup_or_default( ".select_poly", ATTR_DOMAIN_FACE, false); threading::parallel_for(IndexRange(mesh->totvert), 4096, [&](const IndexRange range) { for (const int i : range) { if (select_poly[i]) { ss->face_sets[i] = next_face_set; } } }); } 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", ""); } 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, }; 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}, }; using FaceSetsFloodFillFn = blender::FunctionRef; static void sculpt_face_sets_init_flood_fill(Object *ob, const FaceSetsFloodFillFn &test_fn) { using namespace blender; SculptSession *ss = ob->sculpt; Mesh *mesh = static_cast(ob->data); BitVector<> visited_faces(mesh->totpoly, false); int *face_sets = ss->face_sets; const Span edges = mesh->edges(); const Span polys = mesh->polys(); const Span loops = mesh->loops(); if (!ss->epmap) { BKE_mesh_edge_poly_map_create(&ss->epmap, &ss->epmap_mem, edges.data(), edges.size(), polys.data(), polys.size(), loops.data(), loops.size()); } int next_face_set = 1; for (const int i : polys.index_range()) { if (visited_faces[i]) { continue; } std::queue queue; face_sets[i] = next_face_set; visited_faces[i].set(true); queue.push(i); while (!queue.empty()) { const int poly_i = queue.front(); const MPoly &poly = polys[poly_i]; queue.pop(); for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) { const int edge_i = loop.e; const Span neighbor_polys(ss->epmap[edge_i].indices, ss->epmap[edge_i].count); for (const int neighbor_i : neighbor_polys) { if (neighbor_i == poly_i) { continue; } if (visited_faces[neighbor_i]) { continue; } if (!test_fn(poly_i, edge_i, neighbor_i)) { continue; } face_sets[neighbor_i] = next_face_set; visited_faces[neighbor_i].set(true); queue.push(neighbor_i); } } } next_face_set += 1; } } static void sculpt_face_sets_init_loop(Object *ob, const int mode) { using namespace blender; Mesh *mesh = static_cast(ob->data); SculptSession *ss = ob->sculpt; if (mode == SCULPT_FACE_SETS_FROM_MATERIALS) { const bke::AttributeAccessor attributes = mesh->attributes(); const VArraySpan material_indices = attributes.lookup_or_default( "material_index", ATTR_DOMAIN_FACE, 0); for (const int i : IndexRange(mesh->totpoly)) { ss->face_sets[i] = material_indices[i] + 1; } } else if (mode == SCULPT_FACE_SETS_FROM_FACE_MAPS) { const int *face_maps = static_cast(CustomData_get_layer(&mesh->pdata, CD_FACEMAP)); for (const int i : IndexRange(mesh->totpoly)) { ss->face_sets[i] = face_maps ? face_maps[i] : 1; } } } static int sculpt_face_set_init_exec(bContext *C, wmOperator *op) { using namespace blender; 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"); BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); /* Dyntopo not supported. */ if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { return OPERATOR_CANCELLED; } 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 = static_cast(ob->data); ss->face_sets = BKE_sculpt_face_sets_ensure(mesh); const bke::AttributeAccessor attributes = mesh->attributes(); switch (mode) { case SCULPT_FACE_SETS_FROM_LOOSE_PARTS: { const VArray hide_poly = attributes.lookup_or_default( ".hide_poly", ATTR_DOMAIN_FACE, false); sculpt_face_sets_init_flood_fill( ob, [&](const int from_face, const int /*edge*/, const int to_face) { return hide_poly[from_face] == hide_poly[to_face]; }); 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: { const Span poly_normals( reinterpret_cast(BKE_mesh_poly_normals_ensure(mesh)), mesh->totpoly); sculpt_face_sets_init_flood_fill( ob, [&](const int from_face, const int /*edge*/, const int to_face) -> bool { return std::abs(math::dot(poly_normals[from_face], poly_normals[to_face])) > threshold; }); break; } case SCULPT_FACE_SETS_FROM_UV_SEAMS: { const Span edges = mesh->edges(); sculpt_face_sets_init_flood_fill( ob, [&](const int /*from_face*/, const int edge, const int /*to_face*/) -> bool { return (edges[edge].flag & ME_SEAM) == 0; }); break; } case SCULPT_FACE_SETS_FROM_CREASES: { const float *creases = static_cast( CustomData_get_layer(&mesh->edata, CD_CREASE)); sculpt_face_sets_init_flood_fill( ob, [&](const int /*from_face*/, const int edge, const int /*to_face*/) -> bool { return creases[edge] < threshold; }); break; } case SCULPT_FACE_SETS_FROM_SHARP_EDGES: { const Span edges = mesh->edges(); sculpt_face_sets_init_flood_fill( ob, [&](const int /*from_face*/, const int edge, const int /*to_face*/) -> bool { return (edges[edge].flag & ME_SHARP) == 0; }); break; } case SCULPT_FACE_SETS_FROM_BEVEL_WEIGHT: { const float *bevel_weights = static_cast( CustomData_get_layer(&mesh->edata, CD_BWEIGHT)); sculpt_face_sets_init_flood_fill( ob, [&](const int /*from_face*/, const int edge, const int /*to_face*/) -> bool { return bevel_weights ? bevel_weights[edge] / 255.0f < threshold : true; }); break; } case SCULPT_FACE_SETS_FROM_FACE_SET_BOUNDARIES: { Array face_sets_copy(Span(ss->face_sets, mesh->totpoly)); sculpt_face_sets_init_flood_fill( ob, [&](const int from_face, const int /*edge*/, const int to_face) -> bool { return face_sets_copy[from_face] == face_sets_copy[to_face]; }); 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(mesh); } 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); } 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, }; 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", }, {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); Mesh *mesh = BKE_object_get_original_mesh(ob); BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); /* Not supported for dyntopo. */ if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { return OPERATOR_CANCELLED; } const int mode = RNA_enum_get(op->ptr, "mode"); const int tot_vert = SCULPT_vertex_count_get(ss); 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; } const int active_face_set = SCULPT_active_face_set_get(ss); SCULPT_undo_push_begin(ob, op); for (int i = 0; i < totnode; i++) { SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_HIDDEN); } switch (mode) { case 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 { if (ss->face_sets) { SCULPT_face_visibility_all_set(ss, false); SCULPT_face_set_visibility_set(ss, active_face_set, true); } else { SCULPT_face_visibility_all_set(ss, true); } } break; } case SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE: ss->hide_poly = BKE_sculpt_hide_poly_ensure(mesh); if (ss->face_sets) { SCULPT_face_visibility_all_set(ss, false); SCULPT_face_set_visibility_set(ss, active_face_set, true); } else { SCULPT_face_set_visibility_set(ss, active_face_set, true); } break; case SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE: ss->hide_poly = BKE_sculpt_hide_poly_ensure(mesh); if (ss->face_sets) { SCULPT_face_set_visibility_set(ss, active_face_set, false); } else { SCULPT_face_visibility_all_set(ss, false); } break; case SCULPT_FACE_SET_VISIBILITY_INVERT: ss->hide_poly = BKE_sculpt_hide_poly_ensure(mesh); SCULPT_face_visibility_all_invert(ss); break; } /* 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->object_to_world, 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] = {float(event->mval[0]), float(event->mval[1])}; 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 * /*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 = static_cast(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; } 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, }; 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, const 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 = static_cast(ob->data); const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh); BMeshCreateParams create_params{}; create_params.use_toolflags = true; BMesh *bm = BM_mesh_create(&allocsize, &create_params); BMeshFromMeshParams convert_params{}; convert_params.calc_vert_normal = true; convert_params.calc_face_normal = true; BM_mesh_bm_from_me(bm, mesh, &convert_params); 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); BMeshToMeshParams bmesh_to_mesh_params{}; bmesh_to_mesh_params.calc_object_remap = false; BM_mesh_bm_to_me(nullptr, bm, mesh, &bmesh_to_mesh_params); BM_mesh_free(bm); } static void sculpt_face_set_edit_fair_face_set(Object *ob, const int active_face_set_id, const eMeshFairingDepth fair_order) { SculptSession *ss = ob->sculpt; const int totvert = SCULPT_vertex_count_get(ss); Mesh *mesh = static_cast(ob->data); bool *fair_verts = static_cast( 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); } float(*positions)[3] = SCULPT_mesh_deformed_positions_get(ss); BKE_mesh_prefair_and_fair_verts(mesh, positions, 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 = static_cast(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 = static_cast(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) { Mesh *mesh = static_cast(ob->data); 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(mesh, BKE_MESH_BATCH_DIRTY_ALL); DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_DATA, mesh); } static void face_set_edit_do_post_visibility_updates(Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; PBVH *pbvh = ss->pbvh; Mesh *mesh = static_cast(ob->data); /* 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(mesh); } } 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 eSculptFaceSetEditMode mode = static_cast( 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; } ss->face_sets = BKE_sculpt_face_sets_ensure(BKE_mesh_from_object(ob)); 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] = {float(event->mval[0]), float(event->mval[1])}; 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"); }