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:
authorPablo Dobarro <pablodp606@gmail.com>2020-03-05 16:53:23 +0300
committerPablo Dobarro <pablodp606@gmail.com>2020-03-05 23:07:20 +0300
commit38d6533f212bf9001cd5f70ed8757eccb9b39dad (patch)
tree239533648a8f075e2d4697ff3b44ccad5deb766c /source/blender
parente56471bcd3a5f0a9bdc369cc4ca9ceb061e321dd (diff)
Sculpt Face Sets
Face Sets are the new system to control the visibility state of the mesh in sculpt and paint modes. They are designed to work in modes where brushes are the primary way of interaction and they provide much more control when working with meshes with complex shapes and overlapping surfaces. This initial commit includes: - Sculpt Face Sets data structures and PBVH rendering. - Face Set overlay and opacity controls. - Sculpt Undo support. - Remesher reprojection support. The visibility state of the mesh is also preserved when remeshing. - Automasking and Mesh filter support. - Mask expand operator mode to expand Face Sets (Shift + W) and flood fill areas by connectivity (press Ctrl while expanding). - Sculpt Mode Face Sets and Visibility API. - Sculpt Face Sets creation and visibility management operators. - Operator to randomize the Face Sets colors. - Draw Face Sets brush tool to create and edit the Face Sets. Drawing on the mesh creates a new Face Set. Pressing Ctrl before drawing modifies the Face Set under the brush at the beginning of the stroke. - Updated keymap and menu to work with Face Sets from Sculpt Mode (H to toggle visibility, Alt + H to show all, Shit + H to hide). - Pie menu on the W key with Face common Sets operations. Know limitations: - Multires support. The Face Sets and Visibility API needs to be implemented for Multires. Reviewed By: jbakker, #user_interface, Severin Differential Revision: https://developer.blender.org/D6070
Diffstat (limited to 'source/blender')
-rw-r--r--source/blender/blenkernel/BKE_mesh_remesh_voxel.h1
-rw-r--r--source/blender/blenkernel/BKE_paint.h6
-rw-r--r--source/blender/blenkernel/BKE_pbvh.h16
-rw-r--r--source/blender/blenkernel/intern/brush.c7
-rw-r--r--source/blender/blenkernel/intern/customdata.c17
-rw-r--r--source/blender/blenkernel/intern/mesh.c8
-rw-r--r--source/blender/blenkernel/intern/mesh_remesh_voxel.c49
-rw-r--r--source/blender/blenkernel/intern/paint.c16
-rw-r--r--source/blender/blenkernel/intern/pbvh.c106
-rw-r--r--source/blender/blenkernel/intern/pbvh_intern.h4
-rw-r--r--source/blender/blenloader/intern/versioning_280.c13
-rw-r--r--source/blender/blenloader/intern/writefile.c4
-rw-r--r--source/blender/draw/engines/overlay/overlay_sculpt.c10
-rw-r--r--source/blender/draw/engines/overlay/shaders/sculpt_mask_vert.glsl5
-rw-r--r--source/blender/draw/intern/draw_manager_data.c6
-rw-r--r--source/blender/editors/object/object_remesh.c11
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c857
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h26
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_undo.c81
-rw-r--r--source/blender/gpu/GPU_buffers.h6
-rw-r--r--source/blender/gpu/intern/gpu_buffers.c103
-rw-r--r--source/blender/makesdna/DNA_brush_types.h3
-rw-r--r--source/blender/makesdna/DNA_customdata_types.h7
-rw-r--r--source/blender/makesdna/DNA_mesh_defaults.h1
-rw-r--r--source/blender/makesdna/DNA_mesh_types.h7
-rw-r--r--source/blender/makesdna/DNA_scene_types.h3
-rw-r--r--source/blender/makesdna/DNA_view3d_defaults.h1
-rw-r--r--source/blender/makesdna/DNA_view3d_types.h2
-rw-r--r--source/blender/makesrna/intern/rna_brush.c8
-rw-r--r--source/blender/makesrna/intern/rna_mesh.c7
-rw-r--r--source/blender/makesrna/intern/rna_sculpt_paint.c6
-rw-r--r--source/blender/makesrna/intern/rna_space.c6
32 files changed, 1310 insertions, 93 deletions
diff --git a/source/blender/blenkernel/BKE_mesh_remesh_voxel.h b/source/blender/blenkernel/BKE_mesh_remesh_voxel.h
index 794b8dca4bc..b63f9a9814b 100644
--- a/source/blender/blenkernel/BKE_mesh_remesh_voxel.h
+++ b/source/blender/blenkernel/BKE_mesh_remesh_voxel.h
@@ -60,6 +60,7 @@ struct Mesh *BKE_mesh_remesh_quadriflow_to_mesh_nomain(struct Mesh *mesh,
/* Data reprojection functions */
void BKE_mesh_remesh_reproject_paint_mask(struct Mesh *target, struct Mesh *source);
+void BKE_remesh_reproject_sculpt_face_sets(struct Mesh *target, struct Mesh *source);
#ifdef __cplusplus
}
diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h
index 016012d7288..ceb48783e20 100644
--- a/source/blender/blenkernel/BKE_paint.h
+++ b/source/blender/blenkernel/BKE_paint.h
@@ -218,6 +218,8 @@ void BKE_paint_toolslots_brush_update(struct Paint *paint);
void BKE_paint_toolslots_brush_validate(struct Main *bmain, struct Paint *paint);
struct Brush *BKE_paint_toolslots_brush_get(struct Paint *paint, int slot_index);
+#define SCULPT_FACE_SET_NONE 0
+
/* Used for both vertex color and weight paint */
struct SculptVertexPaintGeomMap {
int *vert_map_mem;
@@ -290,6 +292,9 @@ typedef struct SculptSession {
struct MeshElemMap *pmap;
int *pmap_mem;
+ /* Mesh Face Sets */
+ int *face_sets;
+
/* BMesh for dynamic topology sculpting */
struct BMesh *bm;
int cd_vert_node_offset;
@@ -304,6 +309,7 @@ typedef struct SculptSession {
/* PBVH acceleration structure */
struct PBVH *pbvh;
bool show_mask;
+ bool show_face_sets;
/* Painting on deformed mesh */
bool deform_modifiers_active; /* object is deformed with some modifiers */
diff --git a/source/blender/blenkernel/BKE_pbvh.h b/source/blender/blenkernel/BKE_pbvh.h
index 3971b248a2e..6097fab814f 100644
--- a/source/blender/blenkernel/BKE_pbvh.h
+++ b/source/blender/blenkernel/BKE_pbvh.h
@@ -102,6 +102,7 @@ void BKE_pbvh_build_mesh(PBVH *bvh,
int totvert,
struct CustomData *vdata,
struct CustomData *ldata,
+ struct CustomData *pdata,
const struct MLoopTri *looptri,
int looptri_num);
void BKE_pbvh_build_grids(PBVH *bvh,
@@ -244,10 +245,10 @@ bool BKE_pbvh_bmesh_update_topology(PBVH *bvh,
void BKE_pbvh_node_mark_update(PBVHNode *node);
void BKE_pbvh_node_mark_update_mask(PBVHNode *node);
+void BKE_pbvh_node_mark_update_visibility(PBVHNode *node);
void BKE_pbvh_node_mark_rebuild_draw(PBVHNode *node);
void BKE_pbvh_node_mark_redraw(PBVHNode *node);
void BKE_pbvh_node_mark_normals_update(PBVHNode *node);
-void BKE_pbvh_node_mark_visibility_update(PBVHNode *node);
void BKE_pbvh_node_mark_topology_update(PBVHNode *node);
void BKE_pbvh_node_fully_hidden_set(PBVHNode *node, int fully_hidden);
void BKE_pbvh_node_fully_masked_set(PBVHNode *node, int fully_masked);
@@ -298,6 +299,8 @@ void BKE_pbvh_grids_update(PBVH *bvh,
struct DMFlagMat *flagmats,
unsigned int **grid_hidden);
+void BKE_pbvh_face_sets_color_seed_set(PBVH *bvh, int seed);
+
/* Layer displacement */
/* Get the node's displacement layer, creating it if necessary */
@@ -361,6 +364,7 @@ typedef struct PBVHVertexIter {
short *no;
float *fno;
float *mask;
+ bool visible;
} PBVHVertexIter;
void pbvh_vertex_iter_init(PBVH *bvh, PBVHNode *node, PBVHVertexIter *vi, int mode);
@@ -390,6 +394,7 @@ void pbvh_vertex_iter_init(PBVH *bvh, PBVHNode *node, PBVHVertexIter *vi, int mo
vi.mask = vi.key.has_mask ? CCG_elem_mask(&vi.key, vi.grid) : NULL; \
vi.grid = CCG_elem_next(&vi.key, vi.grid); \
vi.index++; \
+ vi.visible = true; \
if (vi.gh) { \
if (BLI_BITMAP_TEST(vi.gh, vi.gy * vi.gridsize + vi.gx)) \
continue; \
@@ -397,7 +402,8 @@ void pbvh_vertex_iter_init(PBVH *bvh, PBVHNode *node, PBVHVertexIter *vi, int mo
} \
else if (vi.mverts) { \
vi.mvert = &vi.mverts[vi.vert_indices[vi.gx]]; \
- if (mode == PBVH_ITER_UNIQUE && vi.mvert->flag & ME_HIDE) \
+ vi.visible = !(vi.mvert->flag & ME_HIDE); \
+ if (mode == PBVH_ITER_UNIQUE && !vi.visible) \
continue; \
vi.co = vi.mvert->co; \
vi.no = vi.mvert->no; \
@@ -414,7 +420,8 @@ void pbvh_vertex_iter_init(PBVH *bvh, PBVHNode *node, PBVHVertexIter *vi, int mo
vi.bm_vert = BLI_gsetIterator_getKey(&vi.bm_other_verts); \
BLI_gsetIterator_step(&vi.bm_other_verts); \
} \
- if (mode == PBVH_ITER_UNIQUE && BM_elem_flag_test(vi.bm_vert, BM_ELEM_HIDDEN)) \
+ vi.visible = !BM_elem_flag_test_bool(vi.bm_vert, BM_ELEM_HIDDEN); \
+ if (mode == PBVH_ITER_UNIQUE && !vi.visible) \
continue; \
vi.co = vi.bm_vert->co; \
vi.fno = vi.bm_vert->no; \
@@ -445,6 +452,9 @@ bool BKE_pbvh_node_vert_update_check_any(PBVH *bvh, PBVHNode *node);
bool pbvh_has_mask(PBVH *bvh);
void pbvh_show_mask_set(PBVH *bvh, bool show_mask);
+bool pbvh_has_face_sets(PBVH *bvh);
+void pbvh_show_face_sets_set(PBVH *bvh, bool show_face_sets);
+
/* Parallelization */
typedef void (*PBVHParallelRangeFunc)(void *__restrict userdata,
const int iter,
diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c
index 8abad2d541d..a1725197a36 100644
--- a/source/blender/blenkernel/intern/brush.c
+++ b/source/blender/blenkernel/intern/brush.c
@@ -1010,6 +1010,12 @@ void BKE_brush_sculpt_reset(Brush *br)
br->flag &= ~BRUSH_SPACE;
br->flag &= ~BRUSH_SPACE_ATTEN;
break;
+ case SCULPT_TOOL_DRAW_FACE_SETS:
+ br->alpha = 0.5f;
+ br->flag &= ~BRUSH_ALPHA_PRESSURE;
+ br->flag &= ~BRUSH_SPACE;
+ br->flag &= ~BRUSH_SPACE_ATTEN;
+ break;
case SCULPT_TOOL_GRAB:
br->alpha = 0.4f;
br->size = 75;
@@ -1085,6 +1091,7 @@ void BKE_brush_sculpt_reset(Brush *br)
case SCULPT_TOOL_SIMPLIFY:
case SCULPT_TOOL_MASK:
+ case SCULPT_TOOL_DRAW_FACE_SETS:
br->add_col[0] = 0.750000;
br->add_col[1] = 0.750000;
br->add_col[2] = 0.750000;
diff --git a/source/blender/blenkernel/intern/customdata.c b/source/blender/blenkernel/intern/customdata.c
index 93db44ce60a..33707d3f18d 100644
--- a/source/blender/blenkernel/intern/customdata.c
+++ b/source/blender/blenkernel/intern/customdata.c
@@ -1621,6 +1621,8 @@ static const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = {
{sizeof(short[4][3]), "", 0, NULL, NULL, NULL, NULL, layerSwap_flnor, NULL},
/* 41: CD_CUSTOMLOOPNORMAL */
{sizeof(short[2]), "vec2s", 1, NULL, NULL, NULL, NULL, NULL, NULL},
+ /* 42: CD_SCULPT_FACE_SETS */
+ {sizeof(int), "", 0, NULL, NULL, NULL, NULL, NULL, NULL},
};
static const char *LAYERTYPENAMES[CD_NUMTYPES] = {
@@ -1668,6 +1670,7 @@ static const char *LAYERTYPENAMES[CD_NUMTYPES] = {
/* 39-41 */ "CDMLoopTangent",
"CDTessLoopNormal",
"CDCustomLoopNormal",
+ "CDSculptFaceGroups",
};
const CustomData_MeshMasks CD_MASK_BAREMESH = {
@@ -1692,7 +1695,7 @@ const CustomData_MeshMasks CD_MASK_MESH = {
.lmask = (CD_MASK_MLOOP | CD_MASK_MDISPS | CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL |
CD_MASK_CUSTOMLOOPNORMAL | CD_MASK_GRID_PAINT_MASK | CD_MASK_GENERIC_DATA),
.pmask = (CD_MASK_MPOLY | CD_MASK_RECAST | CD_MASK_FACEMAP | CD_MASK_FREESTYLE_FACE |
- CD_MASK_GENERIC_DATA),
+ CD_MASK_GENERIC_DATA | CD_MASK_SCULPT_FACE_SETS),
};
const CustomData_MeshMasks CD_MASK_EDITMESH = {
.vmask = (CD_MASK_MDEFORMVERT | CD_MASK_PAINT_MASK | CD_MASK_MVERT_SKIN | CD_MASK_SHAPEKEY |
@@ -1701,7 +1704,7 @@ const CustomData_MeshMasks CD_MASK_EDITMESH = {
.fmask = 0,
.lmask = (CD_MASK_MDISPS | CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | CD_MASK_CUSTOMLOOPNORMAL |
CD_MASK_GRID_PAINT_MASK | CD_MASK_GENERIC_DATA),
- .pmask = (CD_MASK_RECAST | CD_MASK_FACEMAP | CD_MASK_GENERIC_DATA),
+ .pmask = (CD_MASK_RECAST | CD_MASK_FACEMAP | CD_MASK_GENERIC_DATA | CD_MASK_SCULPT_FACE_SETS),
};
const CustomData_MeshMasks CD_MASK_DERIVEDMESH = {
.vmask = (CD_MASK_ORIGINDEX | CD_MASK_MDEFORMVERT | CD_MASK_SHAPEKEY | CD_MASK_MVERT_SKIN |
@@ -1712,7 +1715,7 @@ const CustomData_MeshMasks CD_MASK_DERIVEDMESH = {
CD_MASK_PREVIEW_MLOOPCOL | CD_MASK_ORIGSPACE_MLOOP |
CD_MASK_GENERIC_DATA), /* XXX MISSING CD_MASK_MLOOPTANGENT ? */
.pmask = (CD_MASK_ORIGINDEX | CD_MASK_RECAST | CD_MASK_FREESTYLE_FACE | CD_MASK_FACEMAP |
- CD_MASK_GENERIC_DATA),
+ CD_MASK_GENERIC_DATA | CD_MASK_SCULPT_FACE_SETS),
};
const CustomData_MeshMasks CD_MASK_BMESH = {
.vmask = (CD_MASK_MDEFORMVERT | CD_MASK_BWEIGHT | CD_MASK_MVERT_SKIN | CD_MASK_SHAPEKEY |
@@ -1721,7 +1724,8 @@ const CustomData_MeshMasks CD_MASK_BMESH = {
.fmask = 0,
.lmask = (CD_MASK_MDISPS | CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | CD_MASK_CUSTOMLOOPNORMAL |
CD_MASK_GRID_PAINT_MASK | CD_MASK_GENERIC_DATA),
- .pmask = (CD_MASK_RECAST | CD_MASK_FREESTYLE_FACE | CD_MASK_FACEMAP | CD_MASK_GENERIC_DATA),
+ .pmask = (CD_MASK_RECAST | CD_MASK_FREESTYLE_FACE | CD_MASK_FACEMAP | CD_MASK_GENERIC_DATA |
+ CD_MASK_SCULPT_FACE_SETS),
};
/**
* cover values copied by #BKE_mesh_loops_to_tessdata
@@ -1750,7 +1754,8 @@ const CustomData_MeshMasks CD_MASK_EVERYTHING = {
CD_MASK_MLOOPTANGENT | CD_MASK_PREVIEW_MLOOPCOL | CD_MASK_ORIGSPACE_MLOOP |
CD_MASK_GRID_PAINT_MASK | CD_MASK_GENERIC_DATA),
.pmask = (CD_MASK_MPOLY | CD_MASK_BM_ELEM_PYPTR | CD_MASK_ORIGINDEX | CD_MASK_NORMAL |
- CD_MASK_RECAST | CD_MASK_FACEMAP | CD_MASK_FREESTYLE_FACE | CD_MASK_GENERIC_DATA),
+ CD_MASK_RECAST | CD_MASK_FACEMAP | CD_MASK_FREESTYLE_FACE | CD_MASK_GENERIC_DATA |
+ CD_MASK_SCULPT_FACE_SETS),
};
static const LayerTypeInfo *layerType_getInfo(int type)
@@ -4221,7 +4226,7 @@ bool CustomData_verify_versions(struct CustomData *data, int index)
/* 0 structnum is used in writing code to tag layer types that should not be written. */
else if (typeInfo->structnum == 0 &&
/* XXX Not sure why those three are exception, maybe that should be fixed? */
- !ELEM(layer->type, CD_PAINT_MASK, CD_FACEMAP, CD_MTEXPOLY)) {
+ !ELEM(layer->type, CD_PAINT_MASK, CD_FACEMAP, CD_MTEXPOLY, CD_SCULPT_FACE_SETS)) {
keeplayer = false;
CLOG_WARN(&LOG, ".blend file read: removing a data layer that should not have been written");
}
diff --git a/source/blender/blenkernel/intern/mesh.c b/source/blender/blenkernel/intern/mesh.c
index 51f37254d8f..81ba48bd106 100644
--- a/source/blender/blenkernel/intern/mesh.c
+++ b/source/blender/blenkernel/intern/mesh.c
@@ -31,6 +31,8 @@
#include "BLI_utildefines.h"
#include "BLI_bitmap.h"
+#include "BLI_ghash.h"
+#include "BLI_hash.h"
#include "BLI_math.h"
#include "BLI_linklist.h"
#include "BLI_memarena.h"
@@ -51,6 +53,8 @@
#include "BKE_object.h"
#include "BKE_editmesh.h"
+#include "PIL_time.h"
+
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
@@ -542,6 +546,8 @@ void BKE_mesh_init(Mesh *me)
CustomData_reset(&me->ldata);
BKE_mesh_runtime_reset(me);
+
+ me->face_sets_color_seed = BLI_hash_int(PIL_check_seconds_timer_i() & UINT_MAX);
}
Mesh *BKE_mesh_add(Main *bmain, const char *name)
@@ -671,6 +677,8 @@ void BKE_mesh_copy_settings(Mesh *me_dst, const Mesh *me_src)
me_dst->remesh_voxel_adaptivity = me_src->remesh_voxel_adaptivity;
me_dst->remesh_mode = me_src->remesh_mode;
+ me_dst->face_sets_color_seed = me_src->face_sets_color_seed;
+
/* Copy texture space. */
me_dst->texflag = me_src->texflag;
copy_v3_v3(me_dst->loc, me_src->loc);
diff --git a/source/blender/blenkernel/intern/mesh_remesh_voxel.c b/source/blender/blenkernel/intern/mesh_remesh_voxel.c
index afc380fd369..983c19857dd 100644
--- a/source/blender/blenkernel/intern/mesh_remesh_voxel.c
+++ b/source/blender/blenkernel/intern/mesh_remesh_voxel.c
@@ -356,6 +356,55 @@ void BKE_mesh_remesh_reproject_paint_mask(Mesh *target, Mesh *source)
free_bvhtree_from_mesh(&bvhtree);
}
+void BKE_remesh_reproject_sculpt_face_sets(Mesh *target, Mesh *source)
+{
+ BVHTreeFromMesh bvhtree = {
+ .nearest_callback = NULL,
+ };
+
+ const MPoly *target_polys = CustomData_get_layer(&target->pdata, CD_MPOLY);
+ const MVert *target_verts = CustomData_get_layer(&target->vdata, CD_MVERT);
+ const MLoop *target_loops = CustomData_get_layer(&target->ldata, CD_MLOOP);
+
+ int *target_face_sets;
+ if (CustomData_has_layer(&target->pdata, CD_SCULPT_FACE_SETS)) {
+ target_face_sets = CustomData_get_layer(&target->pdata, CD_SCULPT_FACE_SETS);
+ }
+ else {
+ target_face_sets = CustomData_add_layer(
+ &target->pdata, CD_SCULPT_FACE_SETS, CD_CALLOC, NULL, target->totpoly);
+ }
+
+ int *source_face_sets;
+ if (CustomData_has_layer(&source->pdata, CD_SCULPT_FACE_SETS)) {
+ source_face_sets = CustomData_get_layer(&source->pdata, CD_SCULPT_FACE_SETS);
+ }
+ else {
+ source_face_sets = CustomData_add_layer(
+ &source->pdata, CD_SCULPT_FACE_SETS, CD_CALLOC, NULL, source->totpoly);
+ }
+
+ const MLoopTri *looptri = BKE_mesh_runtime_looptri_ensure(source);
+ BKE_bvhtree_from_mesh_get(&bvhtree, source, BVHTREE_FROM_LOOPTRI, 2);
+
+ for (int i = 0; i < target->totpoly; i++) {
+ float from_co[3];
+ BVHTreeNearest nearest;
+ nearest.index = -1;
+ nearest.dist_sq = FLT_MAX;
+ const MPoly *mpoly = &target_polys[i];
+ BKE_mesh_calc_poly_center(mpoly, &target_loops[mpoly->loopstart], target_verts, from_co);
+ BLI_bvhtree_find_nearest(bvhtree.tree, from_co, &nearest, bvhtree.nearest_callback, &bvhtree);
+ if (nearest.index != -1) {
+ target_face_sets[i] = source_face_sets[looptri[nearest.index].poly];
+ }
+ else {
+ target_face_sets[i] = 1;
+ }
+ }
+ free_bvhtree_from_mesh(&bvhtree);
+}
+
struct Mesh *BKE_mesh_remesh_voxel_fix_poles(struct Mesh *mesh)
{
const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh);
diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c
index 9f05b1656cd..bd585d56a51 100644
--- a/source/blender/blenkernel/intern/paint.c
+++ b/source/blender/blenkernel/intern/paint.c
@@ -1212,6 +1212,7 @@ static void sculpt_update_object(
ss->deform_modifiers_active = sculpt_modifiers_active(scene, sd, ob);
ss->show_mask = (sd->flags & SCULPT_HIDE_MASK) == 0;
+ ss->show_face_sets = (sd->flags & SCULPT_HIDE_FACE_SETS) == 0;
ss->building_vp_handle = false;
@@ -1251,6 +1252,16 @@ static void sculpt_update_object(
ss->mloop = me->mloop;
ss->multires = NULL;
ss->vmask = CustomData_get_layer(&me->vdata, CD_PAINT_MASK);
+
+ /* Sculpt Face Sets. */
+ if (!CustomData_has_layer(&me->pdata, CD_SCULPT_FACE_SETS)) {
+ ss->face_sets = CustomData_add_layer(
+ &me->pdata, CD_SCULPT_FACE_SETS, CD_CALLOC, NULL, me->totpoly);
+ for (int i = 0; i < me->totpoly; i++) {
+ ss->face_sets[i] = 1;
+ }
+ }
+ ss->face_sets = CustomData_get_layer(&me->pdata, CD_SCULPT_FACE_SETS);
}
ss->subdiv_ccg = me_eval->runtime.subdiv_ccg;
@@ -1265,6 +1276,7 @@ static void sculpt_update_object(
}
pbvh_show_mask_set(ss->pbvh, ss->show_mask);
+ pbvh_show_face_sets_set(ss->pbvh, ss->show_face_sets);
if (ss->deform_modifiers_active) {
if (!ss->orig_cos) {
@@ -1501,6 +1513,7 @@ static PBVH *build_pbvh_for_dynamic_topology(Object *ob)
ob->sculpt->cd_vert_node_offset,
ob->sculpt->cd_face_node_offset);
pbvh_show_mask_set(pbvh, ob->sculpt->show_mask);
+ pbvh_show_face_sets_set(pbvh, false);
return pbvh;
}
@@ -1522,10 +1535,12 @@ static PBVH *build_pbvh_from_regular_mesh(Object *ob, Mesh *me_eval_deform)
me->totvert,
&me->vdata,
&me->ldata,
+ &me->pdata,
looptri,
looptris_num);
pbvh_show_mask_set(pbvh, ob->sculpt->show_mask);
+ pbvh_show_face_sets_set(pbvh, ob->sculpt->show_face_sets);
const bool is_deformed = check_sculpt_object_deformed(ob, true);
if (is_deformed && me_eval_deform != NULL) {
@@ -1551,6 +1566,7 @@ static PBVH *build_pbvh_from_ccg(Object *ob, SubdivCCG *subdiv_ccg)
subdiv_ccg->grid_flag_mats,
subdiv_ccg->grid_hidden);
pbvh_show_mask_set(pbvh, ob->sculpt->show_mask);
+ pbvh_show_face_sets_set(pbvh, false);
return pbvh;
}
diff --git a/source/blender/blenkernel/intern/pbvh.c b/source/blender/blenkernel/intern/pbvh.c
index 95e7218a920..61caccccf90 100644
--- a/source/blender/blenkernel/intern/pbvh.c
+++ b/source/blender/blenkernel/intern/pbvh.c
@@ -26,6 +26,7 @@
#include "BLI_math.h"
#include "BLI_ghash.h"
#include "BLI_task.h"
+#include "BLI_rand.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
@@ -36,6 +37,8 @@
#include "BKE_mesh.h" /* for BKE_mesh_calc_normals */
#include "BKE_paint.h"
+#include "PIL_time.h"
+
#include "GPU_buffers.h"
#include "bmesh.h"
@@ -541,6 +544,7 @@ void BKE_pbvh_build_mesh(PBVH *bvh,
int totvert,
struct CustomData *vdata,
struct CustomData *ldata,
+ struct CustomData *pdata,
const MLoopTri *looptri,
int looptri_num)
{
@@ -558,6 +562,9 @@ void BKE_pbvh_build_mesh(PBVH *bvh,
bvh->leaf_limit = LEAF_LIMIT;
bvh->vdata = vdata;
bvh->ldata = ldata;
+ bvh->pdata = pdata;
+
+ bvh->face_sets_color_seed = mesh->face_sets_color_seed;
BB_reset(&cb);
@@ -992,6 +999,7 @@ typedef struct PBVHUpdateData {
float (*vnors)[3];
int flag;
bool show_vcol;
+ bool show_sculpt_face_sets;
} PBVHUpdateData;
static void pbvh_update_normals_accum_task_cb(void *__restrict userdata,
@@ -1155,6 +1163,44 @@ static void pbvh_update_mask_redraw(PBVH *bvh, PBVHNode **nodes, int totnode, in
BKE_pbvh_parallel_range(0, totnode, &data, pbvh_update_mask_redraw_task_cb, &settings);
}
+static void pbvh_update_visibility_redraw_task_cb(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+
+ PBVHUpdateData *data = userdata;
+ PBVH *bvh = data->bvh;
+ PBVHNode *node = data->nodes[n];
+ if (node->flag & PBVH_UpdateVisibility) {
+ node->flag &= ~PBVH_UpdateVisibility;
+ BKE_pbvh_node_fully_hidden_set(node, true);
+ if (node->flag & PBVH_Leaf) {
+ PBVHVertexIter vd;
+ BKE_pbvh_vertex_iter_begin(bvh, node, vd, PBVH_ITER_ALL)
+ {
+ if (vd.visible) {
+ BKE_pbvh_node_fully_hidden_set(node, false);
+ return;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+ }
+ }
+}
+
+static void pbvh_update_visibility_redraw(PBVH *bvh, PBVHNode **nodes, int totnode, int flag)
+{
+ PBVHUpdateData data = {
+ .bvh = bvh,
+ .nodes = nodes,
+ .flag = flag,
+ };
+
+ PBVHParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, totnode);
+ BKE_pbvh_parallel_range(0, totnode, &data, pbvh_update_visibility_redraw_task_cb, &settings);
+}
+
static void pbvh_update_BB_redraw_task_cb(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict UNUSED(tls))
@@ -1198,6 +1244,7 @@ static int pbvh_get_buffers_update_flags(PBVH *bvh, bool show_vcol)
int update_flags = 0;
update_flags |= bvh->show_mask ? GPU_PBVH_BUFFERS_SHOW_MASK : 0;
update_flags |= show_vcol ? GPU_PBVH_BUFFERS_SHOW_VCOL : 0;
+ update_flags |= bvh->show_face_sets ? GPU_PBVH_BUFFERS_SHOW_SCULPT_FACE_SETS : 0;
return update_flags;
}
@@ -1218,14 +1265,16 @@ static void pbvh_update_draw_buffer_cb(void *__restrict userdata,
node->draw_buffers = GPU_pbvh_grid_buffers_build(node->totprim, bvh->grid_hidden);
break;
case PBVH_FACES:
- node->draw_buffers = GPU_pbvh_mesh_buffers_build(node->face_vert_indices,
- bvh->mpoly,
- bvh->mloop,
- bvh->looptri,
- bvh->verts,
- node->prim_indices,
- node->totprim,
- bvh->mesh);
+ node->draw_buffers = GPU_pbvh_mesh_buffers_build(
+ node->face_vert_indices,
+ bvh->mpoly,
+ bvh->mloop,
+ bvh->looptri,
+ bvh->verts,
+ node->prim_indices,
+ CustomData_get_layer(bvh->pdata, CD_SCULPT_FACE_SETS),
+ node->totprim,
+ bvh->mesh);
break;
case PBVH_BMESH:
node->draw_buffers = GPU_pbvh_bmesh_buffers_build(bvh->flags &
@@ -1253,6 +1302,8 @@ static void pbvh_update_draw_buffer_cb(void *__restrict userdata,
node->uniq_verts + node->face_verts,
CustomData_get_layer(bvh->vdata, CD_PAINT_MASK),
CustomData_get_layer(bvh->ldata, CD_MLOOPCOL),
+ CustomData_get_layer(bvh->pdata, CD_SCULPT_FACE_SETS),
+ bvh->face_sets_color_seed,
node->face_vert_indices,
update_flags);
break;
@@ -1373,6 +1424,10 @@ void BKE_pbvh_update_vertex_data(PBVH *bvh, int flag)
pbvh_update_mask_redraw(bvh, nodes, totnode, flag);
}
+ if (flag & (PBVH_UpdateVisibility)) {
+ pbvh_update_visibility_redraw(bvh, nodes, totnode, flag);
+ }
+
if (nodes) {
MEM_freeN(nodes);
}
@@ -1650,6 +1705,12 @@ void BKE_pbvh_node_mark_update_mask(PBVHNode *node)
node->flag |= PBVH_UpdateMask | PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw;
}
+void BKE_pbvh_node_mark_update_visibility(PBVHNode *node)
+{
+ node->flag |= PBVH_UpdateVisibility | PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers |
+ PBVH_UpdateRedraw;
+}
+
void BKE_pbvh_node_mark_rebuild_draw(PBVHNode *node)
{
node->flag |= PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw;
@@ -1665,11 +1726,6 @@ void BKE_pbvh_node_mark_normals_update(PBVHNode *node)
node->flag |= PBVH_UpdateNormals;
}
-void BKE_pbvh_node_mark_visibility_update(PBVHNode *node)
-{
- node->flag |= PBVH_UpdateVisibility;
-}
-
void BKE_pbvh_node_fully_hidden_set(PBVHNode *node, int fully_hidden)
{
BLI_assert(node->flag & PBVH_Leaf);
@@ -2559,6 +2615,11 @@ void BKE_pbvh_update_normals(PBVH *bvh, struct SubdivCCG *subdiv_ccg)
MEM_SAFE_FREE(nodes);
}
+void BKE_pbvh_face_sets_color_seed_set(PBVH *bvh, int seed)
+{
+ bvh->face_sets_color_seed = seed;
+}
+
/**
* PBVH drawing, updating draw buffers as needed and culling any nodes outside
* the specified frustum.
@@ -2873,11 +2934,30 @@ bool pbvh_has_mask(PBVH *bvh)
return false;
}
+bool pbvh_has_face_sets(PBVH *bvh)
+{
+ switch (bvh->type) {
+ case PBVH_GRIDS:
+ return false;
+ case PBVH_FACES:
+ return (bvh->pdata && CustomData_get_layer(bvh->pdata, CD_SCULPT_FACE_SETS));
+ case PBVH_BMESH:
+ return false;
+ }
+
+ return false;
+}
+
void pbvh_show_mask_set(PBVH *bvh, bool show_mask)
{
bvh->show_mask = show_mask;
}
+void pbvh_show_face_sets_set(PBVH *bvh, bool show_face_sets)
+{
+ bvh->show_face_sets = show_face_sets;
+}
+
void BKE_pbvh_parallel_range_settings(PBVHParallelSettings *settings,
bool use_threading,
int totnode)
diff --git a/source/blender/blenkernel/intern/pbvh_intern.h b/source/blender/blenkernel/intern/pbvh_intern.h
index bdee05f1aab..51342eb1faa 100644
--- a/source/blender/blenkernel/intern/pbvh_intern.h
+++ b/source/blender/blenkernel/intern/pbvh_intern.h
@@ -134,6 +134,9 @@ struct PBVH {
const MLoopTri *looptri;
CustomData *vdata;
CustomData *ldata;
+ CustomData *pdata;
+
+ int face_sets_color_seed;
/* Grid Data */
CCGKey gridkey;
@@ -154,6 +157,7 @@ struct PBVH {
/* flag are verts/faces deformed */
bool deformed;
bool show_mask;
+ bool show_face_sets;
/* Dynamic topology */
BMesh *bm;
diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c
index 627b38a58e8..300880a5839 100644
--- a/source/blender/blenloader/intern/versioning_280.c
+++ b/source/blender/blenloader/intern/versioning_280.c
@@ -4514,5 +4514,18 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
*/
{
/* Keep this block, even when empty. */
+ if (!DNA_struct_elem_find(
+ fd->filesdna, "View3DOverlay", "float", "sculpt_mode_face_sets_opacity")) {
+ for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) {
+ for (ScrArea *sa = screen->areabase.first; sa; sa = sa->next) {
+ for (SpaceLink *sl = sa->spacedata.first; sl; sl = sl->next) {
+ if (sl->spacetype == SPACE_VIEW3D) {
+ View3D *v3d = (View3D *)sl;
+ v3d->overlay.sculpt_mode_face_sets_opacity = 0.0f;
+ }
+ }
+ }
+ }
+ }
}
}
diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c
index 7076ed906c1..e1cbdc89022 100644
--- a/source/blender/blenloader/intern/writefile.c
+++ b/source/blender/blenloader/intern/writefile.c
@@ -2093,6 +2093,10 @@ static void write_customdata(WriteData *wd,
const float *layer_data = layer->data;
writedata(wd, DATA, sizeof(*layer_data) * count, layer_data);
}
+ else if (layer->type == CD_SCULPT_FACE_SETS) {
+ const float *layer_data = layer->data;
+ writedata(wd, DATA, sizeof(*layer_data) * count, layer_data);
+ }
else if (layer->type == CD_GRID_PAINT_MASK) {
write_grid_paint_mask(wd, count, layer->data);
}
diff --git a/source/blender/draw/engines/overlay/overlay_sculpt.c b/source/blender/draw/engines/overlay/overlay_sculpt.c
index dc286d36e9e..ba37f56800b 100644
--- a/source/blender/draw/engines/overlay/overlay_sculpt.c
+++ b/source/blender/draw/engines/overlay/overlay_sculpt.c
@@ -34,12 +34,14 @@ void OVERLAY_sculpt_cache_init(OVERLAY_Data *vedata)
OVERLAY_PrivateData *pd = vedata->stl->pd;
DRWShadingGroup *grp;
- DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL | DRW_STATE_BLEND_ALPHA;
+ DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL | DRW_STATE_BLEND_MUL;
DRW_PASS_CREATE(psl->sculpt_mask_ps, state | pd->clipping_state);
GPUShader *sh = OVERLAY_shader_sculpt_mask();
pd->sculpt_mask_grp = grp = DRW_shgroup_create(sh, psl->sculpt_mask_ps);
DRW_shgroup_uniform_float_copy(grp, "maskOpacity", pd->overlay.sculpt_mode_mask_opacity);
+ DRW_shgroup_uniform_float_copy(
+ grp, "faceSetsOpacity", pd->overlay.sculpt_mode_face_sets_opacity);
}
void OVERLAY_sculpt_cache_populate(OVERLAY_Data *vedata, Object *ob)
@@ -51,7 +53,7 @@ void OVERLAY_sculpt_cache_populate(OVERLAY_Data *vedata, Object *ob)
const bool use_pbvh = BKE_sculptsession_use_pbvh_draw(ob, draw_ctx->v3d);
if (use_pbvh || !ob->sculpt->deform_modifiers_active || ob->sculpt->shapekey_active) {
- if (!use_pbvh || pbvh_has_mask(pbvh)) {
+ if (!use_pbvh || pbvh_has_mask(pbvh) || pbvh_has_face_sets(pbvh)) {
DRW_shgroup_call_sculpt(pd->sculpt_mask_grp, ob, false, true, false);
}
}
@@ -60,10 +62,10 @@ void OVERLAY_sculpt_cache_populate(OVERLAY_Data *vedata, Object *ob)
void OVERLAY_sculpt_draw(OVERLAY_Data *vedata)
{
OVERLAY_PassList *psl = vedata->psl;
- OVERLAY_FramebufferList *fbl = vedata->fbl;
+ DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
if (DRW_state_is_fbo()) {
- GPU_framebuffer_bind(fbl->overlay_default_fb);
+ GPU_framebuffer_bind(dfbl->default_fb);
}
DRW_draw_pass(psl->sculpt_mask_ps);
diff --git a/source/blender/draw/engines/overlay/shaders/sculpt_mask_vert.glsl b/source/blender/draw/engines/overlay/shaders/sculpt_mask_vert.glsl
index 38559677706..5d79195a40b 100644
--- a/source/blender/draw/engines/overlay/shaders/sculpt_mask_vert.glsl
+++ b/source/blender/draw/engines/overlay/shaders/sculpt_mask_vert.glsl
@@ -1,7 +1,9 @@
uniform float maskOpacity;
+uniform float faceSetsOpacity;
in vec3 pos;
+in vec3 fset;
in float msk;
out vec4 finalColor;
@@ -11,7 +13,8 @@ void main()
vec3 world_pos = point_object_to_world(pos);
gl_Position = point_world_to_ndc(world_pos);
- finalColor = vec4(0.0, 0.0, 0.0, msk * maskOpacity);
+ finalColor = vec4(mix(vec3(1.0), fset, faceSetsOpacity), 1.0);
+ finalColor.rgb *= (1.0 - (msk * maskOpacity));
#ifdef USE_WORLD_CLIP_PLANES
world_clip_planes_calc_clip_distance(world_pos);
diff --git a/source/blender/draw/intern/draw_manager_data.c b/source/blender/draw/intern/draw_manager_data.c
index 6f2a9cc056f..28d5daf011c 100644
--- a/source/blender/draw/intern/draw_manager_data.c
+++ b/source/blender/draw/intern/draw_manager_data.c
@@ -823,6 +823,7 @@ typedef struct DRWSculptCallbackData {
bool use_wire;
bool use_mats;
bool use_mask;
+ bool use_fsets;
bool fast_mode; /* Set by draw manager. Do not init. */
int debug_node_nr;
@@ -843,11 +844,6 @@ static float sculpt_debug_colors[9][4] = {
static void sculpt_draw_cb(DRWSculptCallbackData *scd, GPU_PBVH_Buffers *buffers)
{
- /* Meh... use_mask is a bit misleading here. */
- if (scd->use_mask && !GPU_pbvh_buffers_has_mask(buffers)) {
- return;
- }
-
GPUBatch *geom = GPU_pbvh_buffers_batch_get(buffers, scd->fast_mode, scd->use_wire);
short index = 0;
diff --git a/source/blender/editors/object/object_remesh.c b/source/blender/editors/object/object_remesh.c
index 4454af5a096..8d268be5a78 100644
--- a/source/blender/editors/object/object_remesh.c
+++ b/source/blender/editors/object/object_remesh.c
@@ -138,16 +138,23 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op)
BKE_mesh_calc_normals(new_mesh);
}
- if (mesh->flag & ME_REMESH_REPROJECT_VOLUME) {
+ if (mesh->flag & ME_REMESH_REPROJECT_VOLUME || mesh->flag & ME_REMESH_REPROJECT_PAINT_MASK ||
+ mesh->flag & ME_REMESH_REPROJECT_SCULPT_FACE_SETS) {
BKE_mesh_runtime_clear_geometry(mesh);
+ }
+
+ if (mesh->flag & ME_REMESH_REPROJECT_VOLUME) {
BKE_shrinkwrap_remesh_target_project(new_mesh, mesh, ob);
}
if (mesh->flag & ME_REMESH_REPROJECT_PAINT_MASK) {
- BKE_mesh_runtime_clear_geometry(mesh);
BKE_mesh_remesh_reproject_paint_mask(new_mesh, mesh);
}
+ if (mesh->flag & ME_REMESH_REPROJECT_SCULPT_FACE_SETS) {
+ BKE_remesh_reproject_sculpt_face_sets(new_mesh, mesh);
+ }
+
BKE_mesh_nomain_to_mesh(new_mesh, mesh, ob, &CD_MASK_MESH, true);
if (mesh->flag & ME_REMESH_SMOOTH_NORMALS) {
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
index 390b69df7ff..b87a050a7c2 100644
--- a/source/blender/editors/sculpt_paint/sculpt.c
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -35,6 +35,8 @@
#include "BLT_translation.h"
+#include "PIL_time.h"
+
#include "DNA_customdata_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
@@ -218,6 +220,249 @@ static void sculpt_active_vertex_normal_get(SculptSession *ss, float normal[3])
sculpt_vertex_normal_get(ss, sculpt_active_vertex_get(ss), normal);
}
+/* Sculpt Face Sets and Visibility*/
+
+static void sculpt_vertex_visible_set(SculptSession *ss, int index, bool visible)
+{
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_FACES:
+ SET_FLAG_FROM_TEST(ss->mvert[index].flag, !visible, ME_HIDE);
+ ss->mvert[index].flag |= ME_VERT_PBVH_UPDATE;
+ break;
+ case PBVH_BMESH:
+ BM_elem_flag_set(BM_vert_at_index(ss->bm, index), BM_ELEM_HIDDEN, !visible);
+ break;
+ case PBVH_GRIDS:
+ break;
+ }
+}
+
+static bool sculpt_vertex_visible_get(SculptSession *ss, int index)
+{
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_FACES:
+ return !(ss->mvert[index].flag & ME_HIDE);
+ case PBVH_BMESH:
+ return !BM_elem_flag_test(BM_vert_at_index(ss->bm, index), BM_ELEM_HIDDEN);
+ case PBVH_GRIDS:
+ return true;
+ }
+ return true;
+}
+
+static void sculpt_face_set_visibility_set(SculptSession *ss, int face_set, bool visible)
+{
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_FACES:
+ for (int i = 0; i < ss->totpoly; i++) {
+ if (abs(ss->face_sets[i]) == face_set) {
+ if (visible) {
+ ss->face_sets[i] = abs(ss->face_sets[i]);
+ }
+ else {
+ ss->face_sets[i] = -abs(ss->face_sets[i]);
+ }
+ }
+ }
+ break;
+ case PBVH_BMESH:
+ break;
+ case PBVH_GRIDS:
+ break;
+ }
+}
+
+static void sculpt_face_sets_visibility_invert(SculptSession *ss)
+{
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_FACES:
+ for (int i = 0; i < ss->totpoly; i++) {
+ ss->face_sets[i] *= -1;
+ }
+ break;
+ case PBVH_BMESH:
+ break;
+ case PBVH_GRIDS:
+ break;
+ }
+}
+
+static void sculpt_face_sets_visibility_all_set(SculptSession *ss, bool visible)
+{
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_FACES:
+ for (int i = 0; i < ss->totpoly; i++) {
+ if (visible) {
+ ss->face_sets[i] = abs(ss->face_sets[i]);
+ }
+ else {
+ ss->face_sets[i] = -abs(ss->face_sets[i]);
+ }
+ }
+ break;
+ case PBVH_BMESH:
+ break;
+ case PBVH_GRIDS:
+ break;
+ }
+}
+
+static bool sculpt_vertex_visibility_from_face_sets_get(SculptSession *ss, int index)
+{
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_FACES: {
+ MeshElemMap *vert_map = &ss->pmap[index];
+ for (int j = 0; j < ss->pmap[index].count; j++) {
+ if (ss->face_sets[vert_map->indices[j]] > 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+ case PBVH_BMESH:
+ return true;
+ case PBVH_GRIDS:
+ return true;
+ }
+ return true;
+}
+
+static void sculpt_vertex_face_set_set(SculptSession *ss, int index, int face_set)
+{
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_FACES: {
+ MeshElemMap *vert_map = &ss->pmap[index];
+ for (int j = 0; j < ss->pmap[index].count; j++) {
+ if (ss->face_sets[vert_map->indices[j]] > 0) {
+ ss->face_sets[vert_map->indices[j]] = abs(face_set);
+ }
+ else {
+ ss->face_sets[vert_map->indices[j]] = -abs(face_set);
+ }
+ }
+ } break;
+ case PBVH_BMESH:
+ break;
+ case PBVH_GRIDS:
+ break;
+ }
+}
+
+static int sculpt_vertex_face_set_get(SculptSession *ss, int index)
+{
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_FACES: {
+ MeshElemMap *vert_map = &ss->pmap[index];
+ int face_set = 0;
+ for (int i = 0; i < ss->pmap[index].count; i++) {
+ if (ss->face_sets[vert_map->indices[i]] > face_set) {
+ face_set = abs(ss->face_sets[vert_map->indices[i]]);
+ }
+ }
+ return face_set;
+ }
+ case PBVH_BMESH:
+ return 0;
+ case PBVH_GRIDS:
+ return 0;
+ }
+ return 0;
+}
+
+static bool sculpt_vertex_has_face_set(SculptSession *ss, int index, int face_set)
+{
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_FACES: {
+ MeshElemMap *vert_map = &ss->pmap[index];
+ for (int i = 0; i < ss->pmap[index].count; i++) {
+ if (ss->face_sets[vert_map->indices[i]] == face_set) {
+ return true;
+ }
+ }
+ return false;
+ }
+ case PBVH_BMESH:
+ return true;
+ case PBVH_GRIDS:
+ return true;
+ }
+ return true;
+}
+
+static void sculpt_visibility_sync_face_sets_to_vertex(SculptSession *ss, int index)
+{
+ sculpt_vertex_visible_set(ss, index, sculpt_vertex_visibility_from_face_sets_get(ss, index));
+}
+
+void sculpt_visibility_sync_all_face_sets_to_vertices(SculptSession *ss)
+{
+ for (int i = 0; i < ss->totvert; i++) {
+ sculpt_visibility_sync_face_sets_to_vertex(ss, i);
+ }
+}
+
+static void sculpt_visibility_sync_vertex_to_face_sets(SculptSession *ss, int index)
+{
+ MeshElemMap *vert_map = &ss->pmap[index];
+ const bool visible = sculpt_vertex_visible_get(ss, index);
+ for (int i = 0; i < ss->pmap[index].count; i++) {
+ if (visible) {
+ ss->face_sets[vert_map->indices[i]] = abs(ss->face_sets[vert_map->indices[i]]);
+ }
+ else {
+ ss->face_sets[vert_map->indices[i]] = -abs(ss->face_sets[vert_map->indices[i]]);
+ }
+ }
+ ss->mvert[index].flag |= ME_VERT_PBVH_UPDATE;
+}
+
+void sculpt_visibility_sync_all_vertex_to_face_sets(SculptSession *ss)
+{
+ for (int i = 0; i < ss->totvert; i++) {
+ sculpt_visibility_sync_vertex_to_face_sets(ss, i);
+ }
+}
+
+static bool UNUSED_FUNCTION(sculpt_vertex_has_unique_face_set)(SculptSession *ss, int index)
+{
+ MeshElemMap *vert_map = &ss->pmap[index];
+ int face_set = -1;
+ for (int i = 0; i < ss->pmap[index].count; i++) {
+ if (face_set == -1) {
+ face_set = ss->face_sets[vert_map->indices[i]];
+ }
+ else {
+ if (ss->face_sets[vert_map->indices[i]] != face_set) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+static int sculpt_face_set_next_available_get(SculptSession *ss)
+{
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_FACES: {
+ int next_face_set = 0;
+ for (int i = 0; i < ss->totpoly; i++) {
+ if (abs(ss->face_sets[i]) > next_face_set) {
+ next_face_set = abs(ss->face_sets[i]);
+ }
+ }
+ next_face_set++;
+ return next_face_set;
+ }
+ case PBVH_BMESH:
+ return 0;
+ case PBVH_GRIDS:
+ return 0;
+ }
+ return 0;
+}
+
+/* Sculpt Neighbor Iterators */
+
#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256
static void sculpt_vertex_neighbor_add(SculptVertexNeighborIter *iter, int neighbor_index)
@@ -609,8 +854,12 @@ static bool sculpt_tool_needs_original(const char sculpt_tool)
static bool sculpt_tool_is_proxy_used(const char sculpt_tool)
{
- return ELEM(
- sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_LAYER, SCULPT_TOOL_POSE, SCULPT_TOOL_CLOTH);
+ return ELEM(sculpt_tool,
+ SCULPT_TOOL_SMOOTH,
+ SCULPT_TOOL_LAYER,
+ SCULPT_TOOL_POSE,
+ SCULPT_TOOL_CLOTH,
+ SCULPT_TOOL_DRAW_FACE_SETS);
}
static bool sculpt_brush_use_topology_rake(const SculptSession *ss, const Brush *brush)
@@ -1219,6 +1468,9 @@ static bool sculpt_automasking_enabled(SculptSession *ss, const Brush *br)
if (br->automasking_flags & BRUSH_AUTOMASKING_TOPOLOGY) {
return true;
}
+ if (br->automasking_flags & BRUSH_AUTOMASKING_FACE_SETS) {
+ return true;
+ }
return false;
}
@@ -1305,6 +1557,34 @@ static float *sculpt_topology_automasking_init(Sculpt *sd, Object *ob, float *au
return automask_factor;
}
+static float *sculpt_face_sets_automasking_init(Sculpt *sd, Object *ob, float *automask_factor)
+{
+ SculptSession *ss = ob->sculpt;
+ Brush *brush = BKE_paint_brush(&sd->paint);
+
+ if (!sculpt_automasking_enabled(ss, brush)) {
+ return NULL;
+ }
+
+ if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES && !ss->pmap) {
+ BLI_assert(!"Face Sets automasking: pmap missing");
+ return NULL;
+ }
+
+ int tot_vert = sculpt_vertex_count_get(ss);
+ int active_face_set = sculpt_vertex_face_set_get(ss, sculpt_active_vertex_get(ss));
+ for (int i = 0; i < tot_vert; i++) {
+ if (sculpt_vertex_has_face_set(ss, i, active_face_set)) {
+ automask_factor[i] = 1;
+ }
+ else {
+ automask_factor[i] = 0;
+ }
+ }
+
+ return automask_factor;
+}
+
static void sculpt_automasking_init(Sculpt *sd, Object *ob)
{
SculptSession *ss = ob->sculpt;
@@ -1317,6 +1597,10 @@ static void sculpt_automasking_init(Sculpt *sd, Object *ob)
sculpt_vertex_random_access_init(ss);
sculpt_topology_automasking_init(sd, ob, ss->cache->automask);
}
+ if (brush->automasking_flags & BRUSH_AUTOMASKING_FACE_SETS) {
+ sculpt_vertex_random_access_init(ss);
+ sculpt_face_sets_automasking_init(sd, ob, ss->cache->automask);
+ }
}
/* ===== Sculpting =====
@@ -1796,6 +2080,8 @@ static float brush_strength(const Sculpt *sd,
* brush and object. */
return 10.0f * alpha * flip * pressure * overlap * feather;
}
+ case SCULPT_TOOL_DRAW_FACE_SETS:
+ return alpha * pressure * overlap * feather;
case SCULPT_TOOL_SLIDE_RELAX:
return alpha * pressure * overlap * feather * 2.0f;
case SCULPT_TOOL_CLAY_STRIPS:
@@ -2969,6 +3255,74 @@ static void do_draw_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
BKE_pbvh_parallel_range(0, totnode, &data, do_draw_brush_task_cb_ex, &settings);
}
+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);
+
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+ {
+ if (sculpt_brush_test_sq_fn(&test, vd.co)) {
+ const float fade = bstrength * tex_strength(ss,
+ brush,
+ vd.co,
+ sqrtf(test.dist),
+ vd.no,
+ vd.fno,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ tls->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_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->cache->first_time && ss->cache->mirror_symmetry_pass == 0 &&
+ ss->cache->radial_symmetry_pass == 0) {
+ if (ss->cache->invert) {
+ /* When inverting the brush, pick the paint face mask ID from the mesh. */
+ ss->cache->paint_face_set = sculpt_vertex_face_set_get(ss, sculpt_active_vertex_get(ss));
+ }
+ else {
+ /* By default create a new Face Sets. */
+ ss->cache->paint_face_set = sculpt_face_set_next_available_get(ss);
+ }
+ }
+
+ BKE_curvemapping_initialize(brush->curve);
+
+ /* Threaded loop over nodes. */
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ };
+
+ PBVHParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
+ BKE_pbvh_parallel_range(0, totnode, &data, do_draw_face_sets_brush_task_cb_ex, &settings);
+}
+
static void do_draw_sharp_brush_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict tls)
@@ -5684,10 +6038,13 @@ static void do_brush_action_task_cb(void *__restrict userdata,
{
SculptThreadedTaskData *data = userdata;
- sculpt_undo_push_node(data->ob,
- data->nodes[n],
- data->brush->sculpt_tool == SCULPT_TOOL_MASK ? SCULPT_UNDO_MASK :
- SCULPT_UNDO_COORDS);
+ /* Face Sets modifications do a single undo push */
+ if (data->brush->sculpt_tool != SCULPT_TOOL_DRAW_FACE_SETS) {
+ sculpt_undo_push_node(data->ob,
+ data->nodes[n],
+ data->brush->sculpt_tool == SCULPT_TOOL_MASK ? SCULPT_UNDO_MASK :
+ SCULPT_UNDO_COORDS);
+ }
if (data->brush->sculpt_tool == SCULPT_TOOL_MASK) {
BKE_pbvh_node_mark_update_mask(data->nodes[n]);
}
@@ -5751,6 +6108,11 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
BKE_pbvh_parallel_range(0, totnode, &task_data, do_brush_action_task_cb, &settings);
+ if (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS && ss->cache->first_time &&
+ ss->cache->mirror_symmetry_pass == 0) {
+ sculpt_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS);
+ }
+
if (sculpt_brush_needs_normal(ss, brush)) {
update_sculpt_normal(sd, ob, nodes, totnode);
}
@@ -5859,6 +6221,9 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
case SCULPT_TOOL_CLOTH:
SCULPT_do_cloth_brush(sd, ob, nodes, totnode);
break;
+ case SCULPT_TOOL_DRAW_FACE_SETS:
+ do_draw_face_sets_brush(sd, ob, nodes, totnode);
+ break;
}
if (!ELEM(brush->sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_MASK) &&
@@ -6383,6 +6748,8 @@ static const char *sculpt_tool_name(Sculpt *sd)
return "Slide/Relax Brush";
case SCULPT_TOOL_CLOTH:
return "Cloth Brush";
+ case SCULPT_TOOL_DRAW_FACE_SETS:
+ return "Draw Face Sets";
}
return "Sculpting";
@@ -6923,7 +7290,8 @@ static bool sculpt_needs_connectivity_info(const Brush *brush, SculptSession *ss
((brush->sculpt_tool == SCULPT_TOOL_MASK) && (brush->mask_tool == BRUSH_MASK_SMOOTH)) ||
(brush->sculpt_tool == SCULPT_TOOL_POSE) ||
(brush->sculpt_tool == SCULPT_TOOL_SLIDE_RELAX) ||
- (brush->sculpt_tool == SCULPT_TOOL_CLOTH));
+ (brush->sculpt_tool == SCULPT_TOOL_CLOTH) ||
+ (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS));
}
static void sculpt_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush)
@@ -8931,6 +9299,9 @@ static void sculpt_filter_cache_free(SculptSession *ss)
if (ss->filter_cache->normal_factor) {
MEM_freeN(ss->filter_cache->normal_factor);
}
+ if (ss->filter_cache->prev_face_set) {
+ MEM_freeN(ss->filter_cache->prev_face_set);
+ }
MEM_freeN(ss->filter_cache);
ss->filter_cache = NULL;
}
@@ -8998,12 +9369,19 @@ static void mesh_filter_task_cb(void *__restrict userdata,
continue;
}
+ if (ss->filter_cache->active_face_set != SCULPT_FACE_SET_NONE) {
+ if (!sculpt_vertex_has_face_set(ss, vd.index, ss->filter_cache->active_face_set)) {
+ continue;
+ }
+ }
+
if (filter_type == MESH_FILTER_RELAX) {
copy_v3_v3(orig_co, vd.co);
}
else {
copy_v3_v3(orig_co, orig_data.co);
}
+
switch (filter_type) {
case MESH_FILTER_SMOOTH:
CLAMP(fade, -1.0f, 1.0f);
@@ -9144,7 +9522,7 @@ static int sculpt_mesh_filter_modal(bContext *C, wmOperator *op, const wmEvent *
return OPERATOR_RUNNING_MODAL;
}
-static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
Object *ob = CTX_data_active_object(C);
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
@@ -9158,6 +9536,15 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent
return OPERATOR_CANCELLED;
}
+ if (RNA_boolean_get(op->ptr, "use_face_sets")) {
+ /* Update the active vertex */
+ float mouse[2];
+ SculptCursorGeometryInfo sgi;
+ mouse[0] = event->mval[0];
+ mouse[1] = event->mval[1];
+ sculpt_cursor_geometry_info_update(C, &sgi, mouse, false);
+ }
+
sculpt_vertex_random_access_init(ss);
bool needs_pmap = sculpt_mesh_filter_needs_pmap(filter_type);
@@ -9171,6 +9558,14 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent
sculpt_filter_cache_init(ob, sd);
+ if (RNA_boolean_get(op->ptr, "use_face_sets")) {
+ ss->filter_cache->active_face_set = sculpt_vertex_face_set_get(ss,
+ sculpt_active_vertex_get(ss));
+ }
+ else {
+ ss->filter_cache->active_face_set = SCULPT_FACE_SET_NONE;
+ }
+
ss->filter_cache->enabled_axis[0] = deform_axis & MESH_FILTER_DEFORM_X;
ss->filter_cache->enabled_axis[1] = deform_axis & MESH_FILTER_DEFORM_Y;
ss->filter_cache->enabled_axis[2] = deform_axis & MESH_FILTER_DEFORM_Z;
@@ -9208,6 +9603,11 @@ static void SCULPT_OT_mesh_filter(struct wmOperatorType *ot)
MESH_FILTER_DEFORM_X | MESH_FILTER_DEFORM_Y | MESH_FILTER_DEFORM_Z,
"Deform axis",
"Apply the deformation in the selected axis");
+ ot->prop = RNA_def_boolean(ot->srna,
+ "use_face_sets",
+ false,
+ "Use Face Sets",
+ "Apply the filter only to the Face Mask under the cursor");
}
typedef enum eSculptMaskFilterTypes {
@@ -9617,7 +10017,7 @@ static int sculpt_dirty_mask_exec(bContext *C, wmOperator *op)
MEM_SAFE_FREE(nodes);
- BKE_pbvh_update_vertex_data(pbvh, SCULPT_UPDATE_MASK);
+ BKE_pbvh_update_vertex_data(pbvh, PBVH_UpdateMask);
sculpt_undo_push_end();
@@ -9705,20 +10105,29 @@ static void sculpt_expand_task_cb(void *__restrict userdata,
}
}
- if (data->mask_expand_keep_prev_mask) {
- final_mask = MAX2(ss->filter_cache->prev_mask[vd.index], final_mask);
+ if (data->mask_expand_create_face_set) {
+ if (final_mask == 1.0f) {
+ sculpt_vertex_face_set_set(ss, vd.index, ss->filter_cache->new_face_set);
+ }
+ BKE_pbvh_node_mark_redraw(node);
}
+ else {
- if (data->mask_expand_invert_mask) {
- final_mask = 1.0f - final_mask;
- }
+ if (data->mask_expand_keep_prev_mask) {
+ final_mask = MAX2(ss->filter_cache->prev_mask[vd.index], final_mask);
+ }
- if (*vd.mask != final_mask) {
- if (vd.mvert) {
- vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ if (data->mask_expand_invert_mask) {
+ final_mask = 1.0f - final_mask;
+ }
+
+ if (*vd.mask != final_mask) {
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ *vd.mask = final_mask;
+ BKE_pbvh_node_mark_update_mask(node);
}
- *vd.mask = final_mask;
- BKE_pbvh_node_mark_update_mask(node);
}
}
BKE_pbvh_vertex_iter_end;
@@ -9730,6 +10139,7 @@ static int sculpt_mask_expand_modal(bContext *C, wmOperator *op, const wmEvent *
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+ ARegion *ar = CTX_wm_region(C);
float prevclick_f[2];
copy_v2_v2(prevclick_f, op->customdata);
int prevclick[2] = {(int)prevclick_f[0], (int)prevclick_f[1]};
@@ -9739,6 +10149,8 @@ static int sculpt_mask_expand_modal(bContext *C, wmOperator *op, const wmEvent *
int mask_expand_update_it = len / mask_speed;
mask_expand_update_it = mask_expand_update_it + 1;
+ const bool create_face_set = RNA_boolean_get(op->ptr, "create_face_set");
+
if (RNA_boolean_get(op->ptr, "use_cursor")) {
SculptCursorGeometryInfo sgi;
float mouse[2];
@@ -9823,15 +10235,28 @@ static int sculpt_mask_expand_modal(bContext *C, wmOperator *op, const wmEvent *
return OPERATOR_FINISHED;
}
- if (event->type != MOUSEMOVE) {
+ /* When pressing Ctrl, expand directly to the max number of iterations. This allows to flood fill
+ * mask and face sets by connectivity directly. */
+ if (event->ctrl) {
+ mask_expand_update_it = ss->filter_cache->mask_update_last_it - 1;
+ }
+
+ if (!ELEM(event->type, MOUSEMOVE, LEFTCTRLKEY, RIGHTCTRLKEY)) {
return OPERATOR_RUNNING_MODAL;
}
if (mask_expand_update_it == ss->filter_cache->mask_update_current_it) {
+ ED_region_tag_redraw(ar);
return OPERATOR_RUNNING_MODAL;
}
if (mask_expand_update_it < ss->filter_cache->mask_update_last_it) {
+
+ if (create_face_set) {
+ for (int i = 0; i < ss->totpoly; i++) {
+ ss->face_sets[i] = ss->filter_cache->prev_face_set[i];
+ }
+ }
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
@@ -9840,6 +10265,7 @@ static int sculpt_mask_expand_modal(bContext *C, wmOperator *op, const wmEvent *
.mask_expand_use_normals = RNA_boolean_get(op->ptr, "use_normals"),
.mask_expand_invert_mask = RNA_boolean_get(op->ptr, "invert"),
.mask_expand_keep_prev_mask = RNA_boolean_get(op->ptr, "keep_previous_mask"),
+ .mask_expand_create_face_set = RNA_boolean_get(op->ptr, "create_face_set"),
};
PBVHParallelSettings settings;
BKE_pbvh_parallel_range_settings(
@@ -9903,7 +10329,8 @@ static int sculpt_mask_expand_invoke(bContext *C, wmOperator *op, const wmEvent
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
PBVH *pbvh = ob->sculpt->pbvh;
- bool use_normals = RNA_boolean_get(op->ptr, "use_normals");
+ const bool use_normals = RNA_boolean_get(op->ptr, "use_normals");
+ const bool create_face_set = RNA_boolean_get(op->ptr, "create_face_set");
SculptCursorGeometryInfo sgi;
float mouse[2];
@@ -9927,9 +10354,17 @@ static int sculpt_mask_expand_invoke(bContext *C, wmOperator *op, const wmEvent
sculpt_undo_push_begin("Mask Expand");
- for (int i = 0; i < ss->filter_cache->totnode; i++) {
- sculpt_undo_push_node(ob, ss->filter_cache->nodes[i], SCULPT_UNDO_MASK);
- BKE_pbvh_node_mark_redraw(ss->filter_cache->nodes[i]);
+ if (create_face_set) {
+ sculpt_undo_push_node(ob, ss->filter_cache->nodes[0], SCULPT_UNDO_FACE_SETS);
+ for (int i = 0; i < ss->filter_cache->totnode; i++) {
+ BKE_pbvh_node_mark_redraw(ss->filter_cache->nodes[i]);
+ }
+ }
+ else {
+ for (int i = 0; i < ss->filter_cache->totnode; i++) {
+ sculpt_undo_push_node(ob, ss->filter_cache->nodes[i], SCULPT_UNDO_MASK);
+ BKE_pbvh_node_mark_redraw(ss->filter_cache->nodes[i]);
+ }
}
ss->filter_cache->mask_update_it = MEM_callocN(sizeof(int) * vertex_count,
@@ -9944,9 +10379,18 @@ static int sculpt_mask_expand_invoke(bContext *C, wmOperator *op, const wmEvent
}
}
- ss->filter_cache->prev_mask = MEM_callocN(sizeof(float) * vertex_count, "prev mask");
- for (int i = 0; i < vertex_count; i++) {
- ss->filter_cache->prev_mask[i] = sculpt_vertex_mask_get(ss, i);
+ if (create_face_set) {
+ ss->filter_cache->prev_face_set = MEM_callocN(sizeof(float) * ss->totpoly, "prev face mask");
+ for (int i = 0; i < ss->totpoly; i++) {
+ ss->filter_cache->prev_face_set[i] = ss->face_sets[i];
+ }
+ ss->filter_cache->new_face_set = sculpt_face_set_next_available_get(ss);
+ }
+ else {
+ ss->filter_cache->prev_mask = MEM_callocN(sizeof(float) * vertex_count, "prev mask");
+ for (int i = 0; i < vertex_count; i++) {
+ ss->filter_cache->prev_mask[i] = sculpt_vertex_mask_get(ss, i);
+ }
}
ss->filter_cache->mask_update_last_it = 1;
@@ -9992,6 +10436,7 @@ static int sculpt_mask_expand_invoke(bContext *C, wmOperator *op, const wmEvent
.mask_expand_use_normals = RNA_boolean_get(op->ptr, "use_normals"),
.mask_expand_invert_mask = RNA_boolean_get(op->ptr, "invert"),
.mask_expand_keep_prev_mask = RNA_boolean_get(op->ptr, "keep_previous_mask"),
+ .mask_expand_create_face_set = RNA_boolean_get(op->ptr, "create_face_set"),
};
PBVHParallelSettings settings;
BKE_pbvh_parallel_range_settings(
@@ -10053,6 +10498,11 @@ static void SCULPT_OT_mask_expand(wmOperatorType *ot)
"using normals to generate the mask",
0,
2000);
+ ot->prop = RNA_def_boolean(ot->srna,
+ "create_face_set",
+ false,
+ "Expand Face Mask",
+ "Expand a new Face Mask instead of the sculpt mask");
}
void sculpt_geometry_preview_lines_update(bContext *C, SculptSession *ss, float radius)
@@ -10437,6 +10887,356 @@ static void SCULPT_OT_set_pivot_position(wmOperatorType *ot)
10000.0f);
}
+typedef enum eSculptFaceGroupsCreateModes {
+ SCULPT_FACE_SET_MASKED = 0,
+ SCULPT_FACE_SET_VISIBLE = 1,
+ SCULPT_FACE_SET_ALL = 2,
+} eSculptFaceGroupsCreateModes;
+
+static EnumPropertyItem prop_sculpt_face_set_create_types[] = {
+ {
+ SCULPT_FACE_SET_MASKED,
+ "MASKED",
+ 0,
+ "Face Mask From Masked",
+ "Create a new Face Mask from the masked faces",
+ },
+ {
+ SCULPT_FACE_SET_VISIBLE,
+ "VISIBLE",
+ 0,
+ "Face Mask From Visible",
+ "Create a new Face Mask from the visible vertices",
+ },
+ {
+ SCULPT_FACE_SET_ALL,
+ "ALL",
+ 0,
+ "Face Mask Full Mesh",
+ "Create an unique Face Mask with all faces in the sculpt",
+ },
+ {0, NULL, 0, NULL, NULL},
+};
+
+static int sculpt_face_set_create_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+ ARegion *ar = CTX_wm_region(C);
+ Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
+
+ const int mode = RNA_enum_get(op->ptr, "mode");
+
+ /* Dyntopo and Multires not supported for now. */
+ if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
+ return OPERATOR_CANCELLED;
+ }
+
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, mode == SCULPT_FACE_SET_MASKED);
+
+ 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("face mask 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_face_set_set(ss, i, next_face_set);
+ }
+ }
+ }
+
+ if (mode == SCULPT_FACE_SET_VISIBLE) {
+ 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);
+ }
+ }
+
+ for (int i = 0; i < totnode; i++) {
+ BKE_pbvh_node_mark_redraw(nodes[i]);
+ }
+
+ MEM_SAFE_FREE(nodes);
+
+ sculpt_undo_push_end();
+
+ ED_region_tag_redraw(ar);
+ WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+static void SCULPT_OT_face_sets_create(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Create Face Group";
+ ot->idname = "SCULPT_OT_face_sets_create";
+ ot->description = "Create a new Face Group";
+
+ /* api callbacks */
+ ot->invoke = sculpt_face_set_create_invoke;
+ 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 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 Mask",
+ "Show Active Face Mask",
+ },
+ {
+ 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 Mask Visibility",
+ "Invert Face Mask 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_invoke(bContext *C,
+ wmOperator *op,
+ const wmEvent *UNUSED(event))
+{
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+ ARegion *ar = CTX_wm_region(C);
+ Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
+
+ /* Dyntopo and Multires not supported for now. */
+ if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
+ return OPERATOR_CANCELLED;
+ }
+
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true);
+
+ const int tot_vert = sculpt_vertex_count_get(ss);
+ const int mode = RNA_enum_get(op->ptr, "mode");
+ int active_vertex_index = sculpt_active_vertex_get(ss);
+ int active_face_set = sculpt_vertex_face_set_get(ss, active_vertex_index);
+
+ sculpt_undo_push_begin("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;
+ 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->totpoly; 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);
+ for (int i = 0; i < tot_vert; i++) {
+ sculpt_vertex_visible_set(ss,
+ i,
+ sculpt_vertex_visible_get(ss, i) &&
+ sculpt_vertex_has_face_set(ss, i, active_face_set));
+ }
+ }
+
+ 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);
+ }
+
+ /* Sync face mask visibility and vertex visibility. */
+ sculpt_visibility_sync_all_face_sets_to_vertices(ss);
+
+ sculpt_undo_push_end();
+
+ 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);
+ }
+
+ ED_region_tag_redraw(ar);
+
+ View3D *v3d = CTX_wm_view3d(C);
+ if (!BKE_sculptsession_use_pbvh_draw(ob, v3d)) {
+ DEG_id_tag_update(&ob->id, ID_RECALC_SHADING);
+ DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
+ }
+ return OPERATOR_FINISHED;
+}
+
+static void SCULPT_OT_face_sets_change_visibility(wmOperatorType *ot)
+{
+ /* Identifiers. */
+ ot->name = "Face Mask Visibility";
+ ot->idname = "SCULPT_OT_face_set_change_visibility";
+ ot->description = "Change the visibility of the Face Sets of the sculpt";
+
+ /* Api callbacks. */
+ 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_invoke(bContext *C,
+ wmOperator *UNUSED(op),
+ const wmEvent *UNUSED(event))
+{
+
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+ ARegion *ar = CTX_wm_region(C);
+
+ /* Dyntopo and Multires not supported for now. */
+ if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
+ return OPERATOR_CANCELLED;
+ }
+
+ PBVH *pbvh = ob->sculpt->pbvh;
+ PBVHNode **nodes;
+ int totnode;
+ Mesh *mesh = ob->data;
+
+ int new_seed = BLI_hash_int(PIL_check_seconds_timer_i() & UINT_MAX);
+ mesh->face_sets_color_seed = new_seed;
+ BKE_pbvh_face_sets_color_seed_set(pbvh, new_seed);
+
+ 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);
+
+ View3D *v3d = CTX_wm_view3d(C);
+ if (!BKE_sculptsession_use_pbvh_draw(ob, v3d)) {
+ DEG_id_tag_update(&ob->id, ID_RECALC_SHADING);
+ }
+
+ ED_region_tag_redraw(ar);
+ WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data);
+
+ return OPERATOR_FINISHED;
+}
+
+static 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->invoke = sculpt_face_sets_randomize_colors_invoke;
+ ot->poll = sculpt_mode_poll;
+
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
void ED_operatortypes_sculpt(void)
{
WM_operatortype_append(SCULPT_OT_brush_stroke);
@@ -10453,4 +11253,7 @@ void ED_operatortypes_sculpt(void)
WM_operatortype_append(SCULPT_OT_dirty_mask);
WM_operatortype_append(SCULPT_OT_mask_expand);
WM_operatortype_append(SCULPT_OT_set_pivot_position);
+ WM_operatortype_append(SCULPT_OT_face_sets_create);
+ WM_operatortype_append(SCULPT_OT_face_sets_change_visibility);
+ WM_operatortype_append(SCULPT_OT_face_sets_randomize_colors);
}
diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h
index 91656cb2ad5..a935e32cc08 100644
--- a/source/blender/editors/sculpt_paint/sculpt_intern.h
+++ b/source/blender/editors/sculpt_paint/sculpt_intern.h
@@ -54,6 +54,7 @@ bool sculpt_poll_view3d(struct bContext *C);
typedef enum SculptUpdateType {
SCULPT_UPDATE_COORDS = 1 << 0,
SCULPT_UPDATE_MASK = 1 << 1,
+ SCULPT_UPDATE_VISIBILITY = 1 << 2,
} SculptUpdateType;
/* Stroke */
@@ -236,9 +237,17 @@ struct SculptPoseIKChain *SCULPT_pose_ik_chain_init(struct Sculpt *sd,
struct Brush *br,
const float initial_location[3],
const float radius);
-
void SCULPT_pose_ik_chain_free(struct SculptPoseIKChain *ik_chain);
+/* Sculpt Visibility API */
+void sculpt_visibility_sync_all_face_sets_to_vertices(struct SculptSession *ss);
+void sculpt_visibility_sync_all_vertex_to_face_sets(struct SculptSession *ss);
+
+/* Dynamic topology */
+void sculpt_pbvh_clear(Object *ob);
+void sculpt_dyntopo_node_layers_add(struct SculptSession *ss);
+void sculpt_dynamic_topology_disable(bContext *C, struct SculptUndoNode *unode);
+
/* Undo */
typedef enum {
@@ -249,6 +258,7 @@ typedef enum {
SCULPT_UNDO_DYNTOPO_END,
SCULPT_UNDO_DYNTOPO_SYMMETRIZE,
SCULPT_UNDO_GEOMETRY,
+ SCULPT_UNDO_FACE_SETS,
} SculptUndoType;
typedef struct SculptUndoNode {
@@ -298,6 +308,9 @@ typedef struct SculptUndoNode {
float pivot_pos[3];
float pivot_rot[4];
+ /* Sculpt Face Sets */
+ int *face_sets;
+
size_t undo_size;
} SculptUndoNode;
@@ -386,6 +399,7 @@ typedef struct SculptThreadedTaskData {
bool mask_expand_invert_mask;
bool mask_expand_use_normals;
bool mask_expand_keep_prev_mask;
+ bool mask_expand_create_face_set;
float transform_mats[8][4][4];
@@ -395,6 +409,8 @@ typedef struct SculptThreadedTaskData {
float dirty_mask_max;
bool dirty_mask_dirty_only;
+ int face_set;
+
ThreadMutex mutex;
} SculptThreadedTaskData;
@@ -528,6 +544,9 @@ typedef struct StrokeCache {
bool is_rake_rotation_valid;
struct SculptRakeData rake_data;
+ /* Face Sets */
+ int paint_face_set;
+
/* Symmetry index between 0 and 7 bit combo 0 is Brush only;
* 1 is X mirror; 2 is Y mirror; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */
int symmetry;
@@ -612,6 +631,11 @@ typedef struct FilterCache {
float *edge_factor;
float *prev_mask;
float mask_expand_initial_co[3];
+
+ int new_face_set;
+ int *prev_face_set;
+
+ int active_face_set;
} FilterCache;
void sculpt_cache_calc_brushdata_symm(StrokeCache *cache,
diff --git a/source/blender/editors/sculpt_paint/sculpt_undo.c b/source/blender/editors/sculpt_paint/sculpt_undo.c
index 044d5b3c0b0..d1896f47c52 100644
--- a/source/blender/editors/sculpt_paint/sculpt_undo.c
+++ b/source/blender/editors/sculpt_paint/sculpt_undo.c
@@ -79,8 +79,7 @@ static void update_cb(PBVHNode *node, void *rebuild)
BKE_pbvh_node_mark_update(node);
BKE_pbvh_node_mark_update_mask(node);
if (*((bool *)rebuild)) {
- BKE_pbvh_node_mark_rebuild_draw(node);
- BKE_pbvh_node_mark_visibility_update(node);
+ BKE_pbvh_node_mark_update_visibility(node);
}
BKE_pbvh_node_fully_hidden_set(node, 0);
}
@@ -332,6 +331,15 @@ static bool sculpt_undo_restore_mask(bContext *C, SculptUndoNode *unode)
return true;
}
+static bool sculpt_undo_restore_face_sets(bContext *C, SculptUndoNode *unode)
+{
+ ViewLayer *view_layer = CTX_data_view_layer(C);
+ Object *ob = OBACT(view_layer);
+ SculptSession *ss = ob->sculpt;
+ memcpy(ss->face_sets, unode->face_sets, ss->totpoly * sizeof(int));
+ return false;
+}
+
static void sculpt_undo_bmesh_restore_generic_task_cb(
void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls))
{
@@ -533,6 +541,30 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase
BKE_sculpt_update_object_for_edit(depsgraph, ob, false, need_mask);
return;
}
+ else if (unode->type == SCULPT_UNDO_FACE_SETS) {
+
+ sculpt_undo_restore_face_sets(C, unode);
+
+ rebuild = true;
+ BKE_pbvh_search_callback(ss->pbvh, NULL, NULL, update_cb, &rebuild);
+
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, need_mask);
+
+ sculpt_visibility_sync_all_face_sets_to_vertices(ss);
+ BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility);
+
+ if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) {
+ BKE_mesh_flush_hidden_from_verts(ob->data);
+ }
+
+ if (!BKE_sculptsession_use_pbvh_draw(ob, v3d)) {
+ DEG_id_tag_update(&ob->id, ID_RECALC_SHADING);
+ DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
+ }
+
+ unode->applied = true;
+ return;
+ }
}
BKE_sculpt_update_object_for_edit(depsgraph, ob, false, need_mask);
@@ -584,6 +616,8 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase
update_mask = true;
}
break;
+ case SCULPT_UNDO_FACE_SETS:
+ break;
case SCULPT_UNDO_DYNTOPO_BEGIN:
case SCULPT_UNDO_DYNTOPO_END:
@@ -619,10 +653,16 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase
};
BKE_pbvh_search_callback(ss->pbvh, NULL, NULL, update_cb_partial, &data);
BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateBB | PBVH_UpdateOriginalBB | PBVH_UpdateRedraw);
+
if (update_mask) {
BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateMask);
}
+ if (update_visibility) {
+ sculpt_visibility_sync_all_vertex_to_face_sets(ss);
+ BKE_pbvh_update_visibility(ss->pbvh);
+ }
+
if (BKE_sculpt_multires_active(scene, ob)) {
if (rebuild) {
multires_mark_as_modified(depsgraph, ob, MULTIRES_HIDDEN_MODIFIED);
@@ -642,10 +682,6 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase
BKE_sculptsession_free_deformMats(ss);
}
- if (update_visibility) {
- BKE_pbvh_update_visibility(ss->pbvh);
- }
-
if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES && update_visibility) {
Mesh *mesh = ob->data;
BKE_mesh_flush_hidden_from_verts(mesh);
@@ -714,6 +750,10 @@ static void sculpt_undo_free_list(ListBase *lb)
CustomData_free(&unode->geom_pdata, unode->geom_totpoly);
}
+ if (unode->face_sets) {
+ MEM_freeN(unode->face_sets);
+ }
+
MEM_freeN(unode);
unode = unode_next;
@@ -828,6 +868,7 @@ static SculptUndoNode *sculpt_undo_alloc_node(Object *ob, PBVHNode *node, Sculpt
case SCULPT_UNDO_DYNTOPO_SYMMETRIZE:
BLI_assert(!"Dynamic topology should've already been handled");
case SCULPT_UNDO_GEOMETRY:
+ case SCULPT_UNDO_FACE_SETS:
break;
}
@@ -939,6 +980,27 @@ static SculptUndoNode *sculpt_undo_geometry_push(Object *ob, SculptUndoType type
return unode;
}
+static SculptUndoNode *sculpt_undo_face_sets_push(Object *ob, SculptUndoType type)
+{
+ UndoSculpt *usculpt = sculpt_undo_get_nodes();
+ SculptSession *ss = ob->sculpt;
+
+ SculptUndoNode *unode = usculpt->nodes.first;
+
+ unode = MEM_callocN(sizeof(*unode), __func__);
+
+ BLI_strncpy(unode->idname, ob->id.name, sizeof(unode->idname));
+ unode->type = type;
+ unode->applied = true;
+
+ unode->face_sets = MEM_callocN(ss->totpoly * sizeof(int), "sculpt face sets");
+ memcpy(unode->face_sets, ss->face_sets, ss->totpoly * sizeof(int));
+
+ BLI_addtail(&usculpt->nodes, unode);
+
+ return unode;
+}
+
static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, SculptUndoType type)
{
UndoSculpt *usculpt = sculpt_undo_get_nodes();
@@ -1022,6 +1084,7 @@ static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, Sculpt
case SCULPT_UNDO_DYNTOPO_END:
case SCULPT_UNDO_DYNTOPO_SYMMETRIZE:
case SCULPT_UNDO_GEOMETRY:
+ case SCULPT_UNDO_FACE_SETS:
break;
}
}
@@ -1051,6 +1114,11 @@ SculptUndoNode *sculpt_undo_push_node(Object *ob, PBVHNode *node, SculptUndoType
BLI_thread_unlock(LOCK_CUSTOM1);
return unode;
}
+ else if (type == SCULPT_UNDO_FACE_SETS) {
+ unode = sculpt_undo_face_sets_push(ob, type);
+ BLI_thread_unlock(LOCK_CUSTOM1);
+ return unode;
+ }
else if ((unode = sculpt_undo_get_node(node))) {
BLI_thread_unlock(LOCK_CUSTOM1);
return unode;
@@ -1091,6 +1159,7 @@ SculptUndoNode *sculpt_undo_push_node(Object *ob, PBVHNode *node, SculptUndoType
case SCULPT_UNDO_DYNTOPO_SYMMETRIZE:
BLI_assert(!"Dynamic topology should've already been handled");
case SCULPT_UNDO_GEOMETRY:
+ case SCULPT_UNDO_FACE_SETS:
break;
}
diff --git a/source/blender/gpu/GPU_buffers.h b/source/blender/gpu/GPU_buffers.h
index c4540cc1b1a..f5f7f9ee07c 100644
--- a/source/blender/gpu/GPU_buffers.h
+++ b/source/blender/gpu/GPU_buffers.h
@@ -54,6 +54,7 @@ GPU_PBVH_Buffers *GPU_pbvh_mesh_buffers_build(const int (*face_vert_indices)[3],
const struct MLoopTri *looptri,
const struct MVert *verts,
const int *face_indices,
+ const int *sculpt_facemap,
const int face_indices_len,
const struct Mesh *mesh);
@@ -70,7 +71,8 @@ void GPU_pbvh_grid_buffers_update_free(GPU_PBVH_Buffers *buffers,
/* Update mesh buffers without topology changes. Threaded. */
enum {
GPU_PBVH_BUFFERS_SHOW_MASK = (1 << 1),
- GPU_PBVH_BUFFERS_SHOW_VCOL = (1 << 1),
+ GPU_PBVH_BUFFERS_SHOW_VCOL = (1 << 2),
+ GPU_PBVH_BUFFERS_SHOW_SCULPT_FACE_SETS = (1 << 3),
};
void GPU_pbvh_mesh_buffers_update(GPU_PBVH_Buffers *buffers,
@@ -79,6 +81,8 @@ void GPU_pbvh_mesh_buffers_update(GPU_PBVH_Buffers *buffers,
int totvert,
const float *vmask,
const struct MLoopCol *vcol,
+ const int *sculpt_face_sets,
+ const int face_sets_color_seed,
const int (*face_vert_indices)[3],
const int update_flags);
diff --git a/source/blender/gpu/intern/gpu_buffers.c b/source/blender/gpu/intern/gpu_buffers.c
index bf7b1908321..b4d18ba0928 100644
--- a/source/blender/gpu/intern/gpu_buffers.c
+++ b/source/blender/gpu/intern/gpu_buffers.c
@@ -30,9 +30,11 @@
#include "MEM_guardedalloc.h"
#include "BLI_bitmap.h"
+#include "BLI_math_color.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
#include "BLI_ghash.h"
+#include "BLI_hash.h"
#include "DNA_meshdata_types.h"
@@ -95,7 +97,7 @@ struct GPU_PBVH_Buffers {
static struct {
GPUVertFormat format;
- uint pos, nor, msk, col;
+ uint pos, nor, msk, col, fset;
} g_vbo_id = {{0}};
/** \} */
@@ -117,6 +119,8 @@ void gpu_pbvh_init()
&g_vbo_id.format, "msk", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
g_vbo_id.col = GPU_vertformat_attr_add(
&g_vbo_id.format, "ac", GPU_COMP_U16, 4, GPU_FETCH_INT_TO_FLOAT_UNIT);
+ g_vbo_id.fset = GPU_vertformat_attr_add(
+ &g_vbo_id.format, "fset", GPU_COMP_U8, 3, GPU_FETCH_INT_TO_FLOAT_UNIT);
}
}
@@ -191,11 +195,15 @@ void GPU_pbvh_mesh_buffers_update(GPU_PBVH_Buffers *buffers,
int totvert,
const float *vmask,
const MLoopCol *vcol,
+ const int *sculpt_face_sets,
+ const int face_sets_color_seed,
const int (*face_vert_indices)[3],
const int update_flags)
{
const bool show_mask = vmask && (update_flags & GPU_PBVH_BUFFERS_SHOW_MASK) != 0;
const bool show_vcol = vcol && (update_flags & GPU_PBVH_BUFFERS_SHOW_VCOL) != 0;
+ const bool show_face_sets = sculpt_face_sets &&
+ (update_flags & GPU_PBVH_BUFFERS_SHOW_SCULPT_FACE_SETS) != 0;
bool empty_mask = true;
{
@@ -207,12 +215,12 @@ void GPU_pbvh_mesh_buffers_update(GPU_PBVH_Buffers *buffers,
GPUVertBufRaw nor_step = {0};
GPUVertBufRaw msk_step = {0};
GPUVertBufRaw col_step = {0};
+ GPUVertBufRaw fset_step = {0};
GPU_vertbuf_attr_get_raw_data(buffers->vert_buf, g_vbo_id.pos, &pos_step);
GPU_vertbuf_attr_get_raw_data(buffers->vert_buf, g_vbo_id.nor, &nor_step);
- if (show_mask) {
- GPU_vertbuf_attr_get_raw_data(buffers->vert_buf, g_vbo_id.msk, &msk_step);
- }
+ GPU_vertbuf_attr_get_raw_data(buffers->vert_buf, g_vbo_id.msk, &msk_step);
+ GPU_vertbuf_attr_get_raw_data(buffers->vert_buf, g_vbo_id.fset, &fset_step);
if (show_vcol) {
GPU_vertbuf_attr_get_raw_data(buffers->vert_buf, g_vbo_id.col, &col_step);
}
@@ -227,10 +235,34 @@ void GPU_pbvh_mesh_buffers_update(GPU_PBVH_Buffers *buffers,
copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), v->co);
copy_v3_v3_short(GPU_vertbuf_raw_step(&nor_step), v->no);
+ float mask;
if (show_mask) {
- float mask = vmask[vidx];
- *(float *)GPU_vertbuf_raw_step(&msk_step) = mask;
- empty_mask = empty_mask && (mask == 0.0f);
+ mask = vmask[vidx];
+ }
+ else {
+ mask = 0.0f;
+ }
+ *(float *)GPU_vertbuf_raw_step(&msk_step) = mask;
+ empty_mask = empty_mask && (mask == 0.0f);
+ }
+
+ /* Face Sets. */
+ uchar face_set_color[4] = {UCHAR_MAX, UCHAR_MAX, UCHAR_MAX, UCHAR_MAX};
+ for (uint i = 0; i < buffers->face_indices_len; i++) {
+ if (show_face_sets) {
+ const MLoopTri *lt = &buffers->looptri[buffers->face_indices[i]];
+ float rgba[4];
+ hsv_to_rgb(BLI_hash_int_01(abs(sculpt_face_sets[lt->poly]) + face_sets_color_seed),
+ 0.65f,
+ 1.0f,
+ &rgba[0],
+ &rgba[1],
+ &rgba[2]);
+ rgba_float_to_uchar(face_set_color, rgba);
+ }
+ for (int j = 0; j < 3; j++) {
+ const int vidx = face_vert_indices[i][j];
+ GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.fset, vidx, &face_set_color);
}
}
@@ -268,6 +300,10 @@ void GPU_pbvh_mesh_buffers_update(GPU_PBVH_Buffers *buffers,
continue;
}
+ if (sculpt_face_sets[lt->poly] <= 0) {
+ continue;
+ }
+
/* Face normal and mask */
if (lt->poly != mpoly_prev) {
const MPoly *mp = &buffers->mpoly[lt->poly];
@@ -277,6 +313,18 @@ void GPU_pbvh_mesh_buffers_update(GPU_PBVH_Buffers *buffers,
mpoly_prev = lt->poly;
}
+ uchar face_set_color[4] = {UCHAR_MAX, UCHAR_MAX, UCHAR_MAX, UCHAR_MAX};
+ if (show_face_sets) {
+ float rgba[4];
+ hsv_to_rgb(BLI_hash_int_01(abs(sculpt_face_sets[lt->poly]) + face_sets_color_seed),
+ 0.65f,
+ 1.0f,
+ &rgba[0],
+ &rgba[1],
+ &rgba[2]);
+ rgba_float_to_uchar(face_set_color, rgba);
+ }
+
float fmask = 0.0f;
if (show_mask) {
fmask = (vmask[vtri[0]] + vmask[vtri[1]] + vmask[vtri[2]]) / 3.0f;
@@ -287,10 +335,10 @@ void GPU_pbvh_mesh_buffers_update(GPU_PBVH_Buffers *buffers,
copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), v->co);
copy_v3_v3_short(GPU_vertbuf_raw_step(&nor_step), no);
- if (show_mask) {
- *(float *)GPU_vertbuf_raw_step(&msk_step) = fmask;
- empty_mask = empty_mask && (fmask == 0.0f);
- }
+ *(float *)GPU_vertbuf_raw_step(&msk_step) = fmask;
+ empty_mask = empty_mask && (fmask == 0.0f);
+ /* Face Sets. */
+ memcpy(GPU_vertbuf_raw_step(&fset_step), face_set_color, sizeof(uchar) * 3);
if (show_vcol) {
const uint loop_index = lt->tri[j];
@@ -326,6 +374,7 @@ GPU_PBVH_Buffers *GPU_pbvh_mesh_buffers_build(const int (*face_vert_indices)[3],
const MLoopTri *looptri,
const MVert *mvert,
const int *face_indices,
+ const int *sculpt_face_sets,
const int face_indices_len,
const struct Mesh *mesh)
{
@@ -343,7 +392,8 @@ GPU_PBVH_Buffers *GPU_pbvh_mesh_buffers_build(const int (*face_vert_indices)[3],
/* Count the number of visible triangles */
for (i = 0, tottri = 0; i < face_indices_len; i++) {
const MLoopTri *lt = &looptri[face_indices[i]];
- if (!paint_is_face_hidden(lt, mvert, mloop)) {
+ if (!paint_is_face_hidden(lt, mvert, mloop) && sculpt_face_sets &&
+ sculpt_face_sets[lt->poly] > 0) {
int r_edges[3];
BKE_mesh_looptri_get_real_edges(mesh, lt, r_edges);
for (int j = 0; j < 3; j++) {
@@ -411,7 +461,8 @@ GPU_PBVH_Buffers *GPU_pbvh_mesh_buffers_build(const int (*face_vert_indices)[3],
const MLoopTri *lt = &looptri[face_indices[i]];
/* Skip hidden faces */
- if (paint_is_face_hidden(lt, mvert, mloop)) {
+ if (paint_is_face_hidden(lt, mvert, mloop) ||
+ (sculpt_face_sets && sculpt_face_sets[lt->poly] < 0)) {
continue;
}
@@ -668,6 +719,9 @@ void GPU_pbvh_grid_buffers_update(GPU_PBVH_Buffers *buffers,
GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.col, vbo_index, &vcol);
}
+ uchar fsets[3] = {UCHAR_MAX, UCHAR_MAX, UCHAR_MAX};
+ GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.fset, vbo_index, &fsets);
+
vbo_index += 1;
}
}
@@ -705,14 +759,14 @@ void GPU_pbvh_grid_buffers_update(GPU_PBVH_Buffers *buffers,
GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.nor, vbo_index + 3, no_short);
if (has_mask && show_mask) {
- float fmask = (*CCG_elem_mask(key, elems[0]) + *CCG_elem_mask(key, elems[1]) +
+ float fsets = (*CCG_elem_mask(key, elems[0]) + *CCG_elem_mask(key, elems[1]) +
*CCG_elem_mask(key, elems[2]) + *CCG_elem_mask(key, elems[3])) *
0.25f;
- GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.msk, vbo_index + 0, &fmask);
- GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.msk, vbo_index + 1, &fmask);
- GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.msk, vbo_index + 2, &fmask);
- GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.msk, vbo_index + 3, &fmask);
- empty_mask = empty_mask && (fmask == 0.0f);
+ GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.msk, vbo_index + 0, &fsets);
+ GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.msk, vbo_index + 1, &fsets);
+ GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.msk, vbo_index + 2, &fsets);
+ GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.msk, vbo_index + 3, &fsets);
+ empty_mask = empty_mask && (fsets == 0.0f);
}
ushort vcol[4] = {USHRT_MAX, USHRT_MAX, USHRT_MAX, USHRT_MAX};
@@ -720,6 +774,13 @@ void GPU_pbvh_grid_buffers_update(GPU_PBVH_Buffers *buffers,
GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.col, vbo_index + 1, &vcol);
GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.col, vbo_index + 2, &vcol);
GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.col, vbo_index + 3, &vcol);
+
+ uchar fsets[3] = {UCHAR_MAX, UCHAR_MAX, UCHAR_MAX};
+ GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.fset, vbo_index + 0, &fsets);
+ GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.fset, vbo_index + 1, &fsets);
+ GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.fset, vbo_index + 2, &fsets);
+ GPU_vertbuf_attr_set(buffers->vert_buf, g_vbo_id.fset, vbo_index + 3, &fsets);
+
vbo_index += 4;
}
}
@@ -794,6 +855,10 @@ static void gpu_bmesh_vert_to_buffer_copy(BMVert *v,
ushort vcol[4] = {USHRT_MAX, USHRT_MAX, USHRT_MAX, USHRT_MAX};
GPU_vertbuf_attr_set(vert_buf, g_vbo_id.col, v_index, &vcol);
}
+
+ /* Add default face sets color to avoid artifacts. */
+ uchar face_set[3] = {UCHAR_MAX, UCHAR_MAX, UCHAR_MAX};
+ GPU_vertbuf_attr_set(vert_buf, g_vbo_id.fset, v_index, &face_set);
}
/* Return the total number of vertices that don't have BM_ELEM_HIDDEN set */
diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h
index 2b7e9246d36..0109fba909b 100644
--- a/source/blender/makesdna/DNA_brush_types.h
+++ b/source/blender/makesdna/DNA_brush_types.h
@@ -227,6 +227,7 @@ typedef enum eBrushClothForceFalloffType {
typedef enum eAutomasking_flag {
BRUSH_AUTOMASKING_TOPOLOGY = (1 << 0),
+ BRUSH_AUTOMASKING_FACE_SETS = (1 << 1),
} eAutomasking_flag;
typedef struct Brush {
@@ -540,6 +541,7 @@ typedef enum eBrushSculptTool {
SCULPT_TOOL_SLIDE_RELAX = 24,
SCULPT_TOOL_CLAY_THUMB = 25,
SCULPT_TOOL_CLOTH = 26,
+ SCULPT_TOOL_DRAW_FACE_SETS = 27,
} eBrushSculptTool;
/* Brush.uv_sculpt_tool */
@@ -582,6 +584,7 @@ typedef enum eBrushUVSculptTool {
SCULPT_TOOL_SLIDE_RELAX, \
SCULPT_TOOL_ELASTIC_DEFORM, \
SCULPT_TOOL_POSE, \
+ SCULPT_TOOL_DRAW_FACE_SETS, \
\
/* These brushes could handle dynamic topology, \ \
* but user feedback indicates it's better not to */ \
diff --git a/source/blender/makesdna/DNA_customdata_types.h b/source/blender/makesdna/DNA_customdata_types.h
index 552de61df8a..4d0d66f29ff 100644
--- a/source/blender/makesdna/DNA_customdata_types.h
+++ b/source/blender/makesdna/DNA_customdata_types.h
@@ -76,8 +76,7 @@ typedef struct CustomData {
* MUST be >= CD_NUMTYPES, but we cant use a define here.
* Correct size is ensured in CustomData_update_typemap assert().
*/
- int typemap[42];
- char _pad0[4];
+ int typemap[43];
/** Number of layers, size of layers array. */
int totlayer, maxlayer;
/** In editmode, total size of all data layers. */
@@ -146,8 +145,9 @@ typedef enum CustomDataType {
CD_MLOOPTANGENT = 39,
CD_TESSLOOPNORMAL = 40,
CD_CUSTOMLOOPNORMAL = 41,
+ CD_SCULPT_FACE_SETS = 42,
- CD_NUMTYPES = 42,
+ CD_NUMTYPES = 43,
} CustomDataType;
/* Bits for CustomDataMask */
@@ -195,6 +195,7 @@ typedef enum CustomDataType {
#define CD_MASK_MLOOPTANGENT (1LL << CD_MLOOPTANGENT)
#define CD_MASK_TESSLOOPNORMAL (1LL << CD_TESSLOOPNORMAL)
#define CD_MASK_CUSTOMLOOPNORMAL (1LL << CD_CUSTOMLOOPNORMAL)
+#define CD_MASK_SCULPT_FACE_SETS (1LL << CD_SCULPT_FACE_SETS)
/** Data types that may be defined for all mesh elements types. */
#define CD_MASK_GENERIC_DATA (CD_MASK_PROP_FLT | CD_MASK_PROP_INT | CD_MASK_PROP_STR)
diff --git a/source/blender/makesdna/DNA_mesh_defaults.h b/source/blender/makesdna/DNA_mesh_defaults.h
index f605827d120..275bcc64562 100644
--- a/source/blender/makesdna/DNA_mesh_defaults.h
+++ b/source/blender/makesdna/DNA_mesh_defaults.h
@@ -35,6 +35,7 @@
.texflag = ME_AUTOSPACE, \
.remesh_voxel_size = 0.1f, \
.remesh_voxel_adaptivity = 0.0f, \
+ .face_sets_color_seed = 0, \
.flag = ME_REMESH_FIX_POLES | ME_REMESH_REPROJECT_VOLUME, \
}
diff --git a/source/blender/makesdna/DNA_mesh_types.h b/source/blender/makesdna/DNA_mesh_types.h
index 86ea22690ee..c0c7c0465bb 100644
--- a/source/blender/makesdna/DNA_mesh_types.h
+++ b/source/blender/makesdna/DNA_mesh_types.h
@@ -196,7 +196,11 @@ typedef struct Mesh {
float remesh_voxel_size;
float remesh_voxel_adaptivity;
char remesh_mode;
- char _pad1[3];
+
+ char _pad1[7];
+
+ int face_sets_color_seed;
+
/** Deprecated multiresolution modeling data, only keep for loading old files. */
struct Multires *mr DNA_DEPRECATED;
@@ -258,6 +262,7 @@ enum {
ME_REMESH_REPROJECT_PAINT_MASK = 1 << 12,
ME_REMESH_FIX_POLES = 1 << 13,
ME_REMESH_REPROJECT_VOLUME = 1 << 14,
+ ME_REMESH_REPROJECT_SCULPT_FACE_SETS = 1 << 15,
};
/* me->cd_flag */
diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h
index 71f701b87ad..8617f122d55 100644
--- a/source/blender/makesdna/DNA_scene_types.h
+++ b/source/blender/makesdna/DNA_scene_types.h
@@ -2211,6 +2211,9 @@ typedef enum eSculptFlags {
/* Don't display mask in viewport, but still use it for strokes. */
SCULPT_HIDE_MASK = (1 << 15),
+
+ /* Don't display face sets in viewport. */
+ SCULPT_HIDE_FACE_SETS = (1 << 16),
} eSculptFlags;
/* ImagePaintSettings.mode */
diff --git a/source/blender/makesdna/DNA_view3d_defaults.h b/source/blender/makesdna/DNA_view3d_defaults.h
index f6c8c0b1f6d..c139d4dc0d1 100644
--- a/source/blender/makesdna/DNA_view3d_defaults.h
+++ b/source/blender/makesdna/DNA_view3d_defaults.h
@@ -59,6 +59,7 @@
/* Intentionally different to vertex/paint mode, \
* we typically want to see shading too. */ \
.sculpt_mode_mask_opacity = 0.75f, \
+ .sculpt_mode_face_sets_opacity = 0.0f, \
\
.edit_flag = V3D_OVERLAY_EDIT_FACES | V3D_OVERLAY_EDIT_SEAMS | \
V3D_OVERLAY_EDIT_SHARP | V3D_OVERLAY_EDIT_FREESTYLE_EDGE | \
diff --git a/source/blender/makesdna/DNA_view3d_types.h b/source/blender/makesdna/DNA_view3d_types.h
index c45a1480087..ce0c68055d9 100644
--- a/source/blender/makesdna/DNA_view3d_types.h
+++ b/source/blender/makesdna/DNA_view3d_types.h
@@ -210,9 +210,9 @@ typedef struct View3DOverlay {
float vertex_paint_mode_opacity;
float weight_paint_mode_opacity;
float sculpt_mode_mask_opacity;
+ float sculpt_mode_face_sets_opacity;
/** Armature edit/pose mode settings. */
- int _pad3;
float xray_alpha_bone;
/** Other settings. */
diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c
index cfaaa0cd1b3..c2dbb2973a3 100644
--- a/source/blender/makesrna/intern/rna_brush.c
+++ b/source/blender/makesrna/intern/rna_brush.c
@@ -99,6 +99,7 @@ const EnumPropertyItem rna_enum_brush_sculpt_tool_items[] = {
{SCULPT_TOOL_CLOTH, "CLOTH", ICON_BRUSH_SCULPT_DRAW, "Cloth", ""},
{SCULPT_TOOL_SIMPLIFY, "SIMPLIFY", ICON_BRUSH_DATA, "Simplify", ""},
{SCULPT_TOOL_MASK, "MASK", ICON_BRUSH_MASK, "Mask", ""},
+ {SCULPT_TOOL_DRAW_FACE_SETS, "DRAW_FACE_SETS", ICON_BRUSH_MASK, "Draw Face Sets", ""},
{0, NULL, 0, NULL, NULL},
};
/* clang-format on */
@@ -2152,6 +2153,13 @@ static void rna_def_brush(BlenderRNA *brna)
"Affect only vertices connected to the active vertex under the brush");
RNA_def_property_update(prop, 0, "rna_Brush_update");
+ prop = RNA_def_property(srna, "use_automasking_face_sets", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "automasking_flags", BRUSH_AUTOMASKING_FACE_SETS);
+ RNA_def_property_ui_text(prop,
+ "Face Sets Auto-masking",
+ "Affect only vertices that share Face Sets with the active vertex");
+ RNA_def_property_update(prop, 0, "rna_Brush_update");
+
prop = RNA_def_property(srna, "use_scene_spacing", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag");
RNA_def_property_enum_items(prop, brush_spacing_unit_items);
diff --git a/source/blender/makesrna/intern/rna_mesh.c b/source/blender/makesrna/intern/rna_mesh.c
index 689b36ffea0..1721095167a 100644
--- a/source/blender/makesrna/intern/rna_mesh.c
+++ b/source/blender/makesrna/intern/rna_mesh.c
@@ -3030,6 +3030,13 @@ static void rna_def_mesh(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Preserve Paint Mask", "Keep the current mask on the new mesh");
RNA_def_property_update(prop, 0, "rna_Mesh_update_draw");
+ prop = RNA_def_property(srna, "use_remesh_preserve_sculpt_face_sets", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", ME_REMESH_REPROJECT_SCULPT_FACE_SETS);
+ RNA_def_property_boolean_default(prop, false);
+ RNA_def_property_ui_text(
+ prop, "Preserve Face Sets", "Keep the current Face Sets on the new mesh");
+ RNA_def_property_update(prop, 0, "rna_Mesh_update_draw");
+
prop = RNA_def_property(srna, "remesh_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "remesh_mode");
RNA_def_property_enum_items(prop, rna_enum_mesh_remesh_mode_items);
diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c
index 1457bbfd3c3..14cfe9d29ab 100644
--- a/source/blender/makesrna/intern/rna_sculpt_paint.c
+++ b/source/blender/makesrna/intern/rna_sculpt_paint.c
@@ -805,6 +805,12 @@ static void rna_def_sculpt(BlenderRNA *brna)
RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE);
RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Sculpt_ShowMask_update");
+ prop = RNA_def_property(srna, "show_face_sets", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_negative_sdna(prop, NULL, "flags", SCULPT_HIDE_FACE_SETS);
+ RNA_def_property_ui_text(prop, "Show Face Sets", "Show Face Sets as overlay on object");
+ RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE);
+ RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Sculpt_ShowMask_update");
+
prop = RNA_def_property(srna, "detail_size", PROP_FLOAT, PROP_PIXEL);
RNA_def_property_ui_range(prop, 0.5, 40.0, 10, 2);
RNA_def_property_ui_text(
diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c
index 63817137a02..cc9f4b301d5 100644
--- a/source/blender/makesrna/intern/rna_space.c
+++ b/source/blender/makesrna/intern/rna_space.c
@@ -3763,6 +3763,12 @@ static void rna_def_space_view3d_overlay(BlenderRNA *brna)
RNA_def_property_range(prop, 0.0f, 1.0f);
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+ prop = RNA_def_property(srna, "sculpt_mode_face_sets_opacity", PROP_FLOAT, PROP_FACTOR);
+ RNA_def_property_float_sdna(prop, NULL, "overlay.sculpt_mode_face_sets_opacity");
+ RNA_def_property_ui_text(prop, "Sculpt Face Sets Opacity", "");
+ RNA_def_property_range(prop, 0.0f, 1.0f);
+ RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+
/* grease pencil paper settings */
prop = RNA_def_property(srna, "show_annotation", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag2", V3D_SHOW_ANNOTATION);