diff options
38 files changed, 1381 insertions, 101 deletions
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index bd381666eda..bd986e2bc95 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -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), ]) diff --git a/release/scripts/startup/bl_ui/properties_data_mesh.py b/release/scripts/startup/bl_ui/properties_data_mesh.py index d6aa986613d..347c41b23fb 100644 --- a/release/scripts/startup/bl_ui/properties_data_mesh.py +++ b/release/scripts/startup/bl_ui/properties_data_mesh.py @@ -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") diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py index 83455ae4af2..f1f6e9898b1 100644 --- a/release/scripts/startup/bl_ui/properties_paint_common.py +++ b/release/scripts/startup/bl_ui/properties_paint_common.py @@ -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") diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 2c79ceb5763..63fb72a71bd 100644 --- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -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", diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 8607c0c43b3..078ad967789 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -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, diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index 9c8378438c7..1990e9a7260 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -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") 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); |