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
This commit is contained in:
Pablo Dobarro 2020-03-05 14:53:23 +01:00
parent e56471bcd3
commit 38d6533f21
Notes: blender-bot 2024-03-25 12:30:38 +01:00
Referenced by issue #74694, Vertex and weight paint mode broken after using sculpt mode mask tools
Referenced by pull request #110709, Cleanup: Sculpt: Remove duplicate visibility update function
Referenced by commit c9621a002d, Cleanup: Sculpt: Remove duplicate visibility update function
38 changed files with 1384 additions and 104 deletions

View File

@ -3901,12 +3901,14 @@ def km_sculpt(params):
("sculpt.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
# Partial Visibility Show/hide
("paint.hide_show", {"type": 'H', "value": 'PRESS', "shift": True},
{"properties": [("action", 'SHOW'), ("area", 'INSIDE')]}),
("paint.hide_show", {"type": 'H', "value": 'PRESS'},
{"properties": [("action", 'HIDE'), ("area", 'INSIDE')]}),
("paint.hide_show", {"type": 'H', "value": 'PRESS', "alt": True},
{"properties": [("action", 'SHOW'), ("area", 'ALL')]}),
("sculpt.face_set_change_visibility", {"type": 'H', "value": 'PRESS'},
{"properties": [("mode", 'TOGGLE')]}),
("sculpt.face_set_change_visibility", {"type": 'H', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'HIDE_ACTIVE')]}),
("sculpt.face_set_change_visibility", {"type": 'H', "value": 'PRESS', "alt": True},
{"properties": [("mode", 'SHOW_ALL')]}),
("sculpt.mask_expand", {"type": 'W', "value": 'PRESS', "shift": True},
{"properties": [("use_normals", False), ("keep_previous_mask", False), ("invert", False), ("smooth_iterations", 0), ("create_face_set", True)]}),
# Subdivision levels
*_template_items_object_subdivision_set(),
("object.subdivision_set", {"type": 'PAGE_UP', "value": 'PRESS'},
@ -3922,9 +3924,9 @@ def km_sculpt(params):
("wm.context_toggle", {"type": 'M', "value": 'PRESS', "ctrl": True},
{"properties": [("data_path", 'scene.tool_settings.sculpt.show_mask')]}),
("sculpt.mask_expand", {"type": 'A', "value": 'PRESS', "shift": True},
{"properties": [("use_normals", False), ("keep_previous_mask", False), ("invert", True), ("smooth_iterations", 2)]}),
{"properties": [("use_normals", False), ("keep_previous_mask", False), ("invert", True), ("smooth_iterations", 2), ("create_face_set", False)]}),
("sculpt.mask_expand", {"type": 'A', "value": 'PRESS', "shift": True, 'alt': True},
{"properties": [("use_normals", True), ("keep_previous_mask", True), ("invert", False), ("smooth_iterations", 0)]}),
{"properties": [("use_normals", True), ("keep_previous_mask", True), ("invert", False), ("smooth_iterations", 0), ("create_face_set", False)]}),
# Dynamic topology
("sculpt.dynamic_topology_toggle", {"type": 'D', "value": 'PRESS', "ctrl": True}, None),
("sculpt.set_detail_size", {"type": 'D', "value": 'PRESS', "shift": True}, None),
@ -3980,6 +3982,7 @@ def km_sculpt(params):
{"properties": [("data_path", 'tool_settings.sculpt.brush.use_smooth_stroke')]}),
op_menu("VIEW3D_MT_angle_control", {"type": 'R', "value": 'PRESS'}),
op_menu_pie("VIEW3D_MT_sculpt_mask_edit_pie", {"type" : 'A', "value": 'PRESS'}),
op_menu_pie("VIEW3D_MT_sculpt_face_sets_edit_pie", {"type" : 'W', "value": 'PRESS'}),
*_template_items_context_panel("VIEW3D_PT_sculpt_context_menu", params.context_menu_event),
])

View File

@ -487,6 +487,7 @@ class DATA_PT_remesh(MeshButtonsPanel, Panel):
col.prop(mesh, "use_remesh_smooth_normals")
col.prop(mesh, "use_remesh_preserve_volume")
col.prop(mesh, "use_remesh_preserve_paint_mask")
col.prop(mesh, "remesh_preserve_sculpt_face_sets")
col.operator("object.voxel_remesh", text="Voxel Remesh")
else:
col.operator("object.quadriflow_remesh", text="QuadriFlow Remesh")

View File

@ -807,6 +807,9 @@ def brush_settings_advanced(layout, context, brush, popover=False):
# topology automasking
layout.prop(brush, "use_automasking_topology")
# face masks automasking
layout.prop(brush, "use_automasking_face_sets")
# sculpt plane settings
if capabilities.has_sculpt_plane:
layout.prop(brush, "sculpt_plane")

View File

@ -1030,6 +1030,7 @@ class _defs_sculpt:
layout.prop(props, "type", expand=False)
layout.prop(props, "strength")
layout.prop(props, "deform_axis")
layout.prop(props, "use_face_sets")
return dict(
idname="builtin.mesh_filter",

View File

@ -878,6 +878,7 @@ class VIEW3D_MT_editor_menus(Menu):
layout.menu("VIEW3D_MT_%s" % mode_string.lower())
if mode_string == 'SCULPT':
layout.menu("VIEW3D_MT_mask")
layout.menu("VIEW3D_MT_face_sets")
else:
layout.menu("VIEW3D_MT_object")
@ -2996,12 +2997,14 @@ class VIEW3D_MT_mask(Menu):
props.keep_previous_mask = False
props.invert = True
props.smooth_iterations = 2
props.create_face_set = False
props = layout.operator("sculpt.mask_expand", text="Expand Mask By Curvature")
props.use_normals = True
props.keep_previous_mask = True
props.invert = False
props.smooth_iterations = 0
props.create_face_set = False
layout.separator()
@ -3020,6 +3023,31 @@ class VIEW3D_MT_mask(Menu):
props = layout.operator("sculpt.dirty_mask", text='Dirty Mask')
class VIEW3D_MT_face_sets(Menu):
bl_label = "Face Sets"
def draw(self, _context):
layout = self.layout
op = layout.operator("sculpt.face_sets_create", text='Face Set From Masked')
op.mode = 'MASKED'
op = layout.operator("sculpt.face_sets_create", text='Face Set From Visible')
op.mode = 'VISIBLE'
layout.separator()
op = layout.operator("sculpt.face_set_change_visibility", text='Invert Visible Face Sets')
op.mode = 'INVERT'
op = layout.operator("sculpt.face_set_change_visibility", text='Show All Face Sets')
op.mode = 'SHOW_ALL'
layout.separator()
op = layout.operator("sculpt.face_sets_randomize_colors", text='Randomize Colors')
class VIEW3D_MT_sculpt_set_pivot(Menu):
bl_label = "Sculpt Set Pivot"
@ -5037,6 +5065,24 @@ class VIEW3D_MT_sculpt_mask_edit_pie(Menu):
op.filter_type = 'CONTRAST_DECREASE'
op.auto_iteration_count = False
class VIEW3D_MT_sculpt_face_sets_edit_pie(Menu):
bl_label = "Face Sets Edit"
def draw(self, _context):
layout = self.layout
pie = layout.menu_pie()
op = pie.operator("sculpt.face_sets_create", text='Face Set From Masked')
op.mode = 'MASKED'
op = pie.operator("sculpt.face_sets_create", text='Face Set From Visible')
op.mode = 'VISIBLE'
op = pie.operator("sculpt.face_set_change_visibility", text='Invert Visible')
op.mode = 'INVERT'
op = pie.operator("sculpt.face_set_change_visibility", text='Show All')
op.mode = 'SHOW_ALL'
class VIEW3D_MT_wpaint_vgroup_lock_pie(Menu):
bl_label = "Vertex Group Locks"
@ -6121,6 +6167,12 @@ class VIEW3D_PT_overlay_sculpt(Panel):
sub.active = sculpt.show_mask
sub.prop(overlay, "sculpt_mode_mask_opacity", text="Mask")
row = layout.row(align=True)
row.prop(sculpt, "show_face_sets", text="")
sub = row.row()
sub.active = sculpt.show_face_sets
row.prop(overlay, "sculpt_mode_face_sets_opacity", text="Face Sets")
class VIEW3D_PT_overlay_pose(Panel):
bl_space_type = 'VIEW_3D'
@ -7064,6 +7116,7 @@ classes = (
VIEW3D_MT_sculpt,
VIEW3D_MT_sculpt_set_pivot,
VIEW3D_MT_mask,
VIEW3D_MT_face_sets,
VIEW3D_MT_particle,
VIEW3D_MT_particle_context_menu,
VIEW3D_MT_particle_showhide,
@ -7148,6 +7201,7 @@ classes = (
VIEW3D_MT_proportional_editing_falloff_pie,
VIEW3D_MT_sculpt_mask_edit_pie,
VIEW3D_MT_wpaint_vgroup_lock_pie,
VIEW3D_MT_sculpt_face_sets_edit_pie,
VIEW3D_PT_active_tool,
VIEW3D_PT_active_tool_duplicate,
VIEW3D_PT_view3d_properties,

View File

@ -846,6 +846,7 @@ class VIEW3D_PT_sculpt_voxel_remesh(Panel, View3DPaintPanel):
col.prop(mesh, "use_remesh_smooth_normals")
col.prop(mesh, "use_remesh_preserve_volume")
col.prop(mesh, "use_remesh_preserve_paint_mask")
col.prop(mesh, "use_remesh_preserve_sculpt_face_sets")
col.operator("object.voxel_remesh", text="Remesh")

View File

@ -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
}

View File

@ -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 */

View File

@ -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,

View File

@ -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;

View File

@ -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");
}

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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)

View File

@ -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;

View File

@ -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;
}
}
}
}
}
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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) {

File diff suppressed because it is too large Load Diff

View File

@ -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,

View File

@ -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;
}

View File

@ -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);

View File

@ -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 */

View File

@ -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 */ \

View File

@ -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)

View File

@ -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, \
}

View File

@ -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 */

View File

@ -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 */

View File

@ -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 | \

View File

@ -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. */

View File

@ -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);

View File

@ -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);

View File

@ -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(

View File

@ -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);