Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/sculpt_paint/sculpt_face_set.cc')
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_face_set.cc1470
1 files changed, 1470 insertions, 0 deletions
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..8fb4dea668e
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/sculpt_face_set.cc
@@ -0,0 +1,1470 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2020 Blender Foundation. All rights reserved. */
+
+/** \file
+ * \ingroup edsculpt
+ */
+
+#include <cmath>
+#include <cstdlib>
+#include <queue>
+
+#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<const int *>(
+ 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<int *>(
+ 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<SculptThreadedTaskData *>(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);
+ 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], 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,
+ &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<SculptThreadedTaskData *>(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.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);
+
+ 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<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) {
+ const bke::AttributeAccessor attributes = mesh->attributes();
+ const VArraySpan<bool> select_poly = attributes.lookup_or_default<bool>(
+ ".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<bool(int from_face, int edge, int to_face)>;
+
+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<Mesh *>(ob->data);
+
+ BitVector<> visited_faces(mesh->totpoly, false);
+
+ int *face_sets = ss->face_sets;
+
+ const Span<MEdge> edges = mesh->edges();
+ const Span<MPoly> polys = mesh->polys();
+ const Span<MLoop> 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<int> 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<int> 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<Mesh *>(ob->data);
+ SculptSession *ss = ob->sculpt;
+
+ if (mode == SCULPT_FACE_SETS_FROM_MATERIALS) {
+ const bke::AttributeAccessor attributes = mesh->attributes();
+ const VArraySpan<int> material_indices = attributes.lookup_or_default<int>(
+ "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<int *>(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<Mesh *>(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<bool> hide_poly = attributes.lookup_or_default<bool>(
+ ".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<float3> poly_normals(
+ reinterpret_cast<const float3 *>(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<MEdge> 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<const float *>(
+ 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<MEdge> 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<const float *>(
+ 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<int> face_sets_copy(Span<int>(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->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] = {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<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;
+}
+
+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<Mesh *>(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<Mesh *>(ob->data);
+ bool *fair_verts = static_cast<bool *>(
+ 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 = static_cast<int *>(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<int *>(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<Mesh *>(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<Mesh *>(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<eSculptFaceSetEditMode>(
+ 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");
+}