diff options
26 files changed, 954 insertions, 235 deletions
diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py index 9b1cf11f6e7..a4a328fce1a 100644 --- a/release/scripts/startup/bl_ui/properties_paint_common.py +++ b/release/scripts/startup/bl_ui/properties_paint_common.py @@ -943,8 +943,22 @@ def brush_settings_advanced(layout, context, brush, popover=False): # boundary edges/face sets automasking col.prop(brush, "use_automasking_boundary_edges", text="Mesh Boundary") col.prop(brush, "use_automasking_boundary_face_sets", text="Face Sets Boundary") + col.prop(brush, "use_automasking_cavity", text="Cavity") + col.prop(brush, "use_automasking_cavity_inverted", text="Cavity (Inverted)") + + col.separator() col.prop(brush, "automasking_boundary_edges_propagation_steps") + if brush.use_automasking_cavity or brush.use_automasking_cavity_inverted: + col.separator() + + col.prop(brush, "automasking_cavity_factor", text="Cavity Factor") + col.prop(brush, "automasking_cavity_blur_steps", text="Cavity Blur") + col.prop(brush, "use_automasking_custom_cavity_curve", text="Use Curve") + + if brush.use_automasking_custom_cavity_curve: + col.template_curve_mapping(brush, "automasking_cavity_curve") + layout.separator() # sculpt plane settings diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 092fae671e0..fcf00ee80f6 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -3298,7 +3298,8 @@ class VIEW3D_MT_mask(Menu): layout.separator() - props = layout.operator("sculpt.dirty_mask", text='Dirty Mask') + props = layout.operator("sculpt.mask_from_cavity", text="Mask From Cavity") + props.use_automask_settings = False layout.separator() @@ -5494,6 +5495,8 @@ class VIEW3D_MT_sculpt_automasking_pie(Menu): pie.prop(sculpt, "use_automasking_face_sets", text="Face Sets") pie.prop(sculpt, "use_automasking_boundary_edges", text="Mesh Boundary") pie.prop(sculpt, "use_automasking_boundary_face_sets", text="Face Sets Boundary") + pie.prop(sculpt, "use_automasking_cavity", text="Cavity") + pie.prop(sculpt, "use_automasking_cavity_inverted", text="Cavity (Inverted)") class VIEW3D_MT_sculpt_face_sets_edit_pie(Menu): diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index 12e05840cfa..153bce78ec2 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-or-later -from bpy.types import Menu, Panel, UIList +from bpy.types import Menu, Panel, UIList, WindowManager from bl_ui.properties_grease_pencil_common import ( GreasePencilSculptAdvancedPanel, GreasePencilDisplayPanel, @@ -939,7 +939,6 @@ class VIEW3D_PT_sculpt_voxel_remesh(Panel, View3DPaintPanel): layout.operator("object.voxel_remesh", text="Remesh") - # TODO, move to space_view3d.py class VIEW3D_PT_sculpt_options(Panel, View3DPaintPanel): bl_context = ".sculpt_mode" # dot on purpose (access from topbar) @@ -967,10 +966,33 @@ class VIEW3D_PT_sculpt_options(Panel, View3DPaintPanel): col.separator() col = layout.column(heading="Auto-Masking", align=True) + col.prop(sculpt, "use_automasking_topology", text="Topology") col.prop(sculpt, "use_automasking_face_sets", text="Face Sets") col.prop(sculpt, "use_automasking_boundary_edges", text="Mesh Boundary") col.prop(sculpt, "use_automasking_boundary_face_sets", text="Face Sets Boundary") + col.prop(sculpt, "use_automasking_cavity", text="Cavity") + col.prop(sculpt, "use_automasking_cavity_inverted", text="Cavity (Inverted)") + + col.separator() + col.prop(sculpt.brush, "automasking_boundary_edges_propagation_steps") + + if sculpt.use_automasking_cavity or sculpt.use_automasking_cavity_inverted: + col.separator() + + col2 = col.column() + props = col2.operator("sculpt.mask_from_cavity", text="Mask From Cavity") + props.use_automask_settings = True + + col2 = col.column() + + col2.prop(sculpt, "automasking_cavity_factor", text="Cavity Factor") + col2.prop(sculpt, "automasking_cavity_blur_steps", text="Cavity Blur") + + col2.prop(sculpt, "use_automasking_custom_cavity_curve", text="Use Curve") + + if sculpt.use_automasking_custom_cavity_curve: + col2.template_curve_mapping(sculpt, "automasking_cavity_curve") class VIEW3D_PT_sculpt_options_gravity(Panel, View3DPaintPanel): diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 244c5d4609f..b6dd25d9f29 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -19,13 +19,13 @@ extern "C" { /* Blender major and minor version. */ #define BLENDER_VERSION 304 /* Blender patch version for bugfix releases. */ -#define BLENDER_VERSION_PATCH 0 +#define BLENDER_VERSION_PATCH 1 /** Blender release cycle stage: alpha/beta/rc/release. */ #define BLENDER_VERSION_CYCLE alpha /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 1 +#define BLENDER_FILE_SUBVERSION 2 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and show a warning if the file diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h index ed0969a6306..386fecfd278 100644 --- a/source/blender/blenkernel/BKE_paint.h +++ b/source/blender/blenkernel/BKE_paint.h @@ -52,6 +52,7 @@ struct Palette; struct PaletteColor; struct Scene; struct StrokeCache; +struct Sculpt; struct SubdivCCG; struct Tex; struct ToolSettings; @@ -473,6 +474,11 @@ typedef struct SculptBoundary { } twist; } SculptBoundary; +typedef struct CavityMaskData { + float factor; + int stroke_id; +} CavityMaskData; + typedef struct SculptFakeNeighbors { bool use_fake_neighbors; @@ -552,6 +558,9 @@ typedef struct SculptAttributePointers { /* BMesh */ SculptAttribute *dyntopo_node_id_vertex; SculptAttribute *dyntopo_node_id_face; + + SculptAttribute *stroke_id; + SculptAttribute *cavity; } SculptAttributePointers; typedef struct SculptSession { @@ -743,6 +752,9 @@ typedef struct SculptSession { */ char *last_paint_canvas_key; + uchar stroke_id; + int last_automasking_settings_hash; + uchar last_cavity_stroke_id; } SculptSession; void BKE_sculptsession_free(struct Object *ob); @@ -900,6 +912,8 @@ bool BKE_paint_canvas_image_get(struct PaintModeSettings *settings, struct ImageUser **r_image_user); int BKE_paint_canvas_uvmap_layer_index_get(const struct PaintModeSettings *settings, struct Object *ob); +void BKE_sculpt_check_cavity_curves(struct Sculpt *sd); +struct CurveMapping *BKE_sculpt_default_cavity_curve(void); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/intern/brush.cc b/source/blender/blenkernel/intern/brush.cc index 3708090f8ed..fb8a6785f64 100644 --- a/source/blender/blenkernel/intern/brush.cc +++ b/source/blender/blenkernel/intern/brush.cc @@ -72,6 +72,8 @@ static void brush_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, c } brush_dst->curve = BKE_curvemapping_copy(brush_src->curve); + brush_dst->automasking_cavity_curve = BKE_curvemapping_copy(brush_src->automasking_cavity_curve); + if (brush_src->gpencil_settings != nullptr) { brush_dst->gpencil_settings = MEM_cnew(__func__, *(brush_src->gpencil_settings)); brush_dst->gpencil_settings->curve_sensitivity = BKE_curvemapping_copy( @@ -109,6 +111,7 @@ static void brush_free_data(ID *id) IMB_freeImBuf(brush->icon_imbuf); } BKE_curvemapping_free(brush->curve); + BKE_curvemapping_free(brush->automasking_cavity_curve); if (brush->gpencil_settings != nullptr) { BKE_curvemapping_free(brush->gpencil_settings->curve_sensitivity); @@ -212,6 +215,10 @@ static void brush_blend_write(BlendWriter *writer, ID *id, const void *id_addres BKE_curvemapping_blend_write(writer, brush->curve); } + if (brush->automasking_cavity_curve) { + BKE_curvemapping_blend_write(writer, brush->automasking_cavity_curve); + } + if (brush->gpencil_settings) { BLO_write_struct(writer, BrushGpencilSettings, brush->gpencil_settings); @@ -267,6 +274,14 @@ static void brush_blend_read_data(BlendDataReader *reader, ID *id) BKE_brush_curve_preset(brush, CURVE_PRESET_SHARP); } + BLO_read_data_address(reader, &brush->automasking_cavity_curve); + if (brush->automasking_cavity_curve) { + BKE_curvemapping_blend_read(reader, brush->automasking_cavity_curve); + } + else { + brush->automasking_cavity_curve = BKE_sculpt_default_cavity_curve(); + } + /* grease pencil */ BLO_read_data_address(reader, &brush->gpencil_settings); if (brush->gpencil_settings != nullptr) { diff --git a/source/blender/blenkernel/intern/paint.cc b/source/blender/blenkernel/intern/paint.cc index a735250fd2a..08e49550426 100644 --- a/source/blender/blenkernel/intern/paint.cc +++ b/source/blender/blenkernel/intern/paint.cc @@ -2089,6 +2089,9 @@ void BKE_sculpt_toolsettings_data_ensure(Scene *scene) if (!sd->paint.tile_offset[2]) { sd->paint.tile_offset[2] = 1.0f; } + if (!sd->automasking_cavity_curve || !sd->automasking_cavity_curve_op) { + BKE_sculpt_check_cavity_curves(sd); + } } static bool check_sculpt_object_deformed(Object *object, const bool for_construction) diff --git a/source/blender/blenkernel/intern/scene.cc b/source/blender/blenkernel/intern/scene.cc index 6d10d31e976..4af6409347d 100644 --- a/source/blender/blenkernel/intern/scene.cc +++ b/source/blender/blenkernel/intern/scene.cc @@ -114,6 +114,32 @@ #include "bmesh.h" +CurveMapping *BKE_sculpt_default_cavity_curve() + +{ + CurveMapping *cumap = BKE_curvemapping_add(1, 0, 0, 1, 1); + + cumap->flag &= ~CUMA_EXTEND_EXTRAPOLATE; + cumap->preset = CURVE_PRESET_LINE; + + BKE_curvemap_reset(cumap->cm, &cumap->clipr, cumap->preset, CURVEMAP_SLOPE_POSITIVE); + BKE_curvemapping_changed(cumap, false); + BKE_curvemapping_init(cumap); + + return cumap; +} + +void BKE_sculpt_check_cavity_curves(Sculpt *sd) +{ + if (!sd->automasking_cavity_curve) { + sd->automasking_cavity_curve = BKE_sculpt_default_cavity_curve(); + } + + if (!sd->automasking_cavity_curve_op) { + sd->automasking_cavity_curve_op = BKE_sculpt_default_cavity_curve(); + } +} + static void scene_init_data(ID *id) { Scene *scene = (Scene *)id; @@ -940,6 +966,13 @@ static void scene_blend_write(BlendWriter *writer, ID *id, const void *id_addres } if (tos->sculpt) { BLO_write_struct(writer, Sculpt, tos->sculpt); + if (tos->sculpt->automasking_cavity_curve) { + BKE_curvemapping_blend_write(writer, tos->sculpt->automasking_cavity_curve); + } + if (tos->sculpt->automasking_cavity_curve_op) { + BKE_curvemapping_blend_write(writer, tos->sculpt->automasking_cavity_curve_op); + } + BKE_paint_blend_write(writer, &tos->sculpt->paint); } if (tos->uvsculpt) { @@ -1153,6 +1186,24 @@ static void scene_blend_read_data(BlendDataReader *reader, ID *id) sce->toolsettings->particle.object = nullptr; sce->toolsettings->gp_sculpt.paintcursor = nullptr; + if (sce->toolsettings->sculpt) { + BLO_read_data_address(reader, &sce->toolsettings->sculpt->automasking_cavity_curve); + BLO_read_data_address(reader, &sce->toolsettings->sculpt->automasking_cavity_curve_op); + + if (sce->toolsettings->sculpt->automasking_cavity_curve) { + BKE_curvemapping_blend_read(reader, sce->toolsettings->sculpt->automasking_cavity_curve); + BKE_curvemapping_init(sce->toolsettings->sculpt->automasking_cavity_curve); + } + + if (sce->toolsettings->sculpt->automasking_cavity_curve_op) { + BKE_curvemapping_blend_read(reader, + sce->toolsettings->sculpt->automasking_cavity_curve_op); + BKE_curvemapping_init(sce->toolsettings->sculpt->automasking_cavity_curve_op); + } + + BKE_sculpt_check_cavity_curves(sce->toolsettings->sculpt); + } + /* Relink grease pencil interpolation curves. */ BLO_read_data_address(reader, &sce->toolsettings->gp_interpolate.custom_ipo); if (sce->toolsettings->gp_interpolate.custom_ipo) { @@ -1740,6 +1791,18 @@ ToolSettings *BKE_toolsettings_copy(ToolSettings *toolsettings, const int flag) if (ts->sculpt) { ts->sculpt = static_cast<Sculpt *>(MEM_dupallocN(ts->sculpt)); BKE_paint_copy(&ts->sculpt->paint, &ts->sculpt->paint, flag); + + if (ts->sculpt->automasking_cavity_curve) { + ts->sculpt->automasking_cavity_curve = BKE_curvemapping_copy( + ts->sculpt->automasking_cavity_curve); + BKE_curvemapping_init(ts->sculpt->automasking_cavity_curve); + } + + if (ts->sculpt->automasking_cavity_curve_op) { + ts->sculpt->automasking_cavity_curve_op = BKE_curvemapping_copy( + ts->sculpt->automasking_cavity_curve_op); + BKE_curvemapping_init(ts->sculpt->automasking_cavity_curve_op); + } } if (ts->uvsculpt) { ts->uvsculpt = static_cast<UvSculpt *>(MEM_dupallocN(ts->uvsculpt)); @@ -1797,6 +1860,13 @@ void BKE_toolsettings_free(ToolSettings *toolsettings) MEM_freeN(toolsettings->wpaint); } if (toolsettings->sculpt) { + if (toolsettings->sculpt->automasking_cavity_curve) { + BKE_curvemapping_free(toolsettings->sculpt->automasking_cavity_curve); + } + if (toolsettings->sculpt->automasking_cavity_curve_op) { + BKE_curvemapping_free(toolsettings->sculpt->automasking_cavity_curve_op); + } + BKE_paint_free(&toolsettings->sculpt->paint); MEM_freeN(toolsettings->sculpt); } diff --git a/source/blender/blenloader/intern/versioning_300.cc b/source/blender/blenloader/intern/versioning_300.cc index 11c88a5d69b..878a4078295 100644 --- a/source/blender/blenloader/intern/versioning_300.cc +++ b/source/blender/blenloader/intern/versioning_300.cc @@ -3337,6 +3337,14 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } + if (!DNA_struct_elem_find(fd->filesdna, "Sculpt", "float", "automasking_cavity_factor")) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (scene->toolsettings && scene->toolsettings->sculpt) { + scene->toolsettings->sculpt->automasking_cavity_factor = 0.5f; + } + } + } + if (!MAIN_VERSION_ATLEAST(bmain, 302, 14)) { /* Compensate for previously wrong squared distance. */ LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { @@ -3519,6 +3527,7 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) BKE_main_namemap_validate_and_fix(bmain); } + if (!MAIN_VERSION_ATLEAST(bmain, 304, 1)) { /* Image generation information transferred to tiles. */ if (!DNA_struct_elem_find(fd->filesdna, "ImageTile", "int", "gen_x")) { @@ -3575,6 +3584,13 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) FOREACH_NODETREE_END; } + if (!MAIN_VERSION_ATLEAST(bmain, 304, 2)) { + /* Initialize brush curves sculpt settings. */ + LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) { + brush->automasking_cavity_factor = 0.5f; + } + } + /** * Versioning code until next subversion bump goes here. * diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 114c2c3839a..ac77e007081 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -3364,6 +3364,8 @@ static void do_brush_action(Sculpt *sd, /* Initialize auto-masking cache. */ if (SCULPT_is_automasking_enabled(sd, ss, brush)) { ss->cache->automasking = SCULPT_automasking_cache_init(sd, brush, ob); + ss->last_automasking_settings_hash = SCULPT_automasking_settings_hash( + ob, ss->cache->automasking); } /* Initialize surface smooth cache. */ if ((brush->sculpt_tool == SCULPT_TOOL_SMOOTH) && @@ -3546,6 +3548,11 @@ static void do_brush_action(Sculpt *sd, SCULPT_bmesh_topology_rake(sd, ob, nodes, totnode, brush->topology_rake_factor); } + if (!SCULPT_tool_can_reuse_cavity_mask(brush->sculpt_tool) || (ss->cache->supports_gravity && sd->gravity_factor > 0.0f)) { + /* Clear cavity mask cache. */ + ss->last_automasking_settings_hash = 0; + } + /* The cloth brush adds the gravity as a regular force and it is processed in the solver. */ if (ss->cache->supports_gravity && !ELEM(brush->sculpt_tool, SCULPT_TOOL_CLOTH, @@ -4234,6 +4241,8 @@ static void sculpt_update_cache_invariants( ss->cache = cache; + cache->stroke_id = ss->stroke_id; + /* Set scaling adjustment. */ max_scale = 0.0f; for (int i = 0; i < 3; i++) { @@ -4339,6 +4348,10 @@ static void sculpt_update_cache_invariants( cache->original = true; } + if (SCULPT_automasking_needs_original(sd, brush)) { + cache->original = true; + } + /* Draw sharp does not need the original coordinates to produce the accumulate effect, so it * should work the opposite way. */ if (brush->sculpt_tool == SCULPT_TOOL_DRAW_SHARP) { @@ -5398,6 +5411,8 @@ static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const f SCULPT_undo_push_begin_ex(ob, sculpt_tool_name(sd)); } + SCULPT_stroke_id_next(ob); + return true; } return false; @@ -5996,4 +6011,24 @@ void SCULPT_fake_neighbors_free(Object *ob) sculpt_pose_fake_neighbors_free(ss); } +void SCULPT_stroke_id_next(Object *ob) +{ + /* Manually wrap in int32 space to avoid tripping up undefined behavior + * sanitizers. + */ + ob->sculpt->stroke_id = (uchar)(((int)ob->sculpt->stroke_id + 1) & 255); +} + +void SCULPT_stroke_id_ensure(Object *ob) +{ + SculptSession *ss = ob->sculpt; + + if (!ss->attrs.stroke_id) { + SculptAttributeParams params = {0}; + + ss->attrs.stroke_id = BKE_sculpt_attribute_ensure( + ob, ATTR_DOMAIN_POINT, CD_PROP_INT8, SCULPT_ATTRIBUTE_NAME(stroke_id), ¶ms); + } +} + /** \} */ diff --git a/source/blender/editors/sculpt_paint/sculpt_automasking.cc b/source/blender/editors/sculpt_paint/sculpt_automasking.cc index e8d934f146c..47856c50b57 100644 --- a/source/blender/editors/sculpt_paint/sculpt_automasking.cc +++ b/source/blender/editors/sculpt_paint/sculpt_automasking.cc @@ -7,17 +7,22 @@ #include "MEM_guardedalloc.h" +#include "BLI_array.hh" #include "BLI_blenlib.h" #include "BLI_hash.h" #include "BLI_index_range.hh" #include "BLI_math.h" +#include "BLI_math_vec_types.hh" +#include "BLI_set.hh" #include "BLI_task.h" +#include "BLI_vector.hh" #include "DNA_brush_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "BKE_brush.h" +#include "BKE_colortools.h" #include "BKE_context.h" #include "BKE_mesh.h" #include "BKE_mesh_mapping.h" @@ -47,7 +52,10 @@ #include <cmath> #include <cstdlib> +using blender::float3; using blender::IndexRange; +using blender::Set; +using blender::Vector; AutomaskingCache *SCULPT_automasking_active_cache_get(SculptSession *ss) { @@ -64,10 +72,13 @@ bool SCULPT_is_automasking_mode_enabled(const Sculpt *sd, const Brush *br, const eAutomasking_flag mode) { + int automasking = sd->automasking_flags; + if (br) { - return br->automasking_flags & mode || sd->automasking_flags & mode; + automasking |= br->automasking_flags; } - return sd->automasking_flags & mode; + + return (eAutomasking_flag)automasking & mode; } bool SCULPT_is_automasking_enabled(const Sculpt *sd, const SculptSession *ss, const Brush *br) @@ -87,13 +98,31 @@ bool SCULPT_is_automasking_enabled(const Sculpt *sd, const SculptSession *ss, co if (SCULPT_is_automasking_mode_enabled(sd, br, BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS)) { return true; } + if (SCULPT_is_automasking_mode_enabled(sd, br, BRUSH_AUTOMASKING_CAVITY_ALL)) { + return true; + } + return false; } static int sculpt_automasking_mode_effective_bits(const Sculpt *sculpt, const Brush *brush) { if (brush) { - return sculpt->automasking_flags | brush->automasking_flags; + int flags = sculpt->automasking_flags | brush->automasking_flags; + + /* Check if we are using brush cavity settings. */ + if (brush->automasking_flags & BRUSH_AUTOMASKING_CAVITY_ALL) { + flags &= ~(BRUSH_AUTOMASKING_CAVITY_ALL | BRUSH_AUTOMASKING_CAVITY_USE_CURVE | + BRUSH_AUTOMASKING_CAVITY_NORMAL); + flags |= brush->automasking_flags; + } + else if (sculpt->automasking_flags & BRUSH_AUTOMASKING_CAVITY_ALL) { + flags &= ~(BRUSH_AUTOMASKING_CAVITY_ALL | BRUSH_AUTOMASKING_CAVITY_USE_CURVE | + BRUSH_AUTOMASKING_CAVITY_NORMAL); + flags |= sculpt->automasking_flags; + } + + return flags; } return sculpt->automasking_flags; } @@ -105,15 +134,239 @@ static bool SCULPT_automasking_needs_factors_cache(const Sculpt *sd, const Brush if (automasking_flags & BRUSH_AUTOMASKING_TOPOLOGY) { return true; } - if (automasking_flags & BRUSH_AUTOMASKING_BOUNDARY_EDGES) { - return brush && brush->automasking_boundary_edges_propagation_steps != 1; - } - if (automasking_flags & BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS) { + if (automasking_flags & + (BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS | BRUSH_AUTOMASKING_BOUNDARY_EDGES)) { return brush && brush->automasking_boundary_edges_propagation_steps != 1; } return false; } +static float sculpt_cavity_calc_factor(AutomaskingCache *automasking, + float factor) +{ + float sign = signf(factor); + + factor = fabsf(factor) * automasking->settings.cavity_factor * 50.0f; + + factor = factor * sign * 0.5f + 0.5f; + CLAMP(factor, 0.0f, 1.0f); + + return (automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_INVERTED) ? 1.0f - factor : + factor; +} + +struct CavityBlurVert { + PBVHVertRef vertex; + float dist; + int depth; + + CavityBlurVert(PBVHVertRef vertex_, float dist_, int depth_) + : vertex(vertex_), dist(dist_), depth(depth_) + { + } + + CavityBlurVert() + { + } + + CavityBlurVert(const CavityBlurVert &b) + { + vertex = b.vertex; + dist = b.dist; + depth = b.depth; + } +}; + +static void sculpt_calc_blurred_cavity(SculptSession *ss, + AutomaskingCache *automasking, + int steps, + PBVHVertRef vertex) +{ + float3 sno1(0.0f); + float3 sno2(0.0f); + float3 sco1(0.0f); + float3 sco2(0.0f); + float len1_sum = 0.0f, len2_sum = 0.0f; + int sco1_len = 0, sco2_len = 0; + + /* Steps starts at 1, but API and user interface + * are zero-based. + */ + steps++; + + Vector<CavityBlurVert, 64> queue; + Set<int64_t, 64> visit; + + int start = 0, end = 0; + + queue.resize(64); + + CavityBlurVert initial(vertex, 0.0f, 0); + + visit.add_new(vertex.i); + queue[0] = initial; + end = 1; + + const float *co1 = SCULPT_vertex_co_get(ss, vertex); + + while (start != end) { + CavityBlurVert &blurvert = queue[start]; + PBVHVertRef v = blurvert.vertex; + start = (start + 1) % queue.size(); + + float3 no; + + const float *co = SCULPT_vertex_co_get(ss, v); + SCULPT_vertex_normal_get(ss, v, no); + + float centdist = len_v3v3(co, co1); + + sco1 += co; + sno1 += no; + len1_sum += centdist; + sco1_len++; + + if (blurvert.depth < steps) { + sco2 += co; + sno2 += no; + len2_sum += centdist; + sco2_len++; + } + + if (blurvert.depth >= steps) { + continue; + } + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, v, ni) { + PBVHVertRef v2 = ni.vertex; + + if (visit.contains(v2.i)) { + continue; + } + + float dist = len_v3v3(SCULPT_vertex_co_get(ss, v2), SCULPT_vertex_co_get(ss, v)); + + visit.add_new(v2.i); + CavityBlurVert blurvert2(v2, dist, blurvert.depth + 1); + + int nextend = (end + 1) % queue.size(); + + if (nextend == start) { + int oldsize = queue.size(); + + queue.resize(queue.size() << 1); + + if (end < start) { + int n = oldsize - start; + + for (int i = 0; i < n; i++) { + queue[queue.size() - n + i] = queue[i + start]; + } + + start = queue.size() - n; + } + } + + queue[end] = blurvert2; + end = (end + 1) % queue.size(); + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + } + + BLI_assert(sco1_len != sco2_len); + + if (!sco1_len) { + sco1 = SCULPT_vertex_co_get(ss, vertex); + } + else { + sco1 /= (float)sco1_len; + len1_sum /= sco1_len; + } + + if (!sco2_len) { + sco2 = SCULPT_vertex_co_get(ss, vertex); + } + else { + sco2 /= (float)sco2_len; + len2_sum /= sco2_len; + } + + normalize_v3(sno1); + if (dot_v3v3(sno1, sno1) == 0.0f) { + SCULPT_vertex_normal_get(ss, vertex, sno1); + } + + normalize_v3(sno2); + if (dot_v3v3(sno2, sno2) == 0.0f) { + SCULPT_vertex_normal_get(ss, vertex, sno2); + } + + float3 vec = sco1 - sco2; + float factor_sum = dot_v3v3(vec, sno2) / len1_sum; + + factor_sum = sculpt_cavity_calc_factor(automasking, factor_sum); + + *(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.cavity) = factor_sum; + *(uchar *)SCULPT_vertex_attr_get(vertex, ss->attrs.stroke_id) = automasking->cavity_stroke_id; +} + +int SCULPT_automasking_settings_hash(Object *ob, AutomaskingCache *automasking) +{ + SculptSession *ss = ob->sculpt; + + int hash; + int totvert = SCULPT_vertex_count_get(ss); + + hash = BLI_hash_int(automasking->settings.flags); + hash = BLI_hash_int_2d(hash, totvert); + + if (automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_ALL) { + hash = BLI_hash_int_2d(hash, automasking->settings.cavity_blur_steps); + hash = BLI_hash_int_2d(hash, *reinterpret_cast<uint*>(&automasking->settings.cavity_factor)); + + if (automasking->settings.cavity_curve) { + CurveMap *cm = automasking->settings.cavity_curve->cm; + + for (int i = 0; i < cm->totpoint; i++) { + hash = BLI_hash_int_2d(hash, *reinterpret_cast<uint*>(&cm->curve[i].x)); + hash = BLI_hash_int_2d(hash, *reinterpret_cast<uint*>(&cm->curve[i].y)); + hash = BLI_hash_int_2d(hash, (uint)cm->curve[i].flag); + hash = BLI_hash_int_2d(hash, (uint)cm->curve[i].shorty); + } + } + } + + if (automasking->settings.flags & BRUSH_AUTOMASKING_FACE_SETS) { + hash = BLI_hash_int_2d(hash, automasking->settings.initial_face_set); + } + + return hash; +} + +static float sculpt_automasking_cavity_factor(AutomaskingCache *automasking, + SculptSession *ss, + PBVHVertRef vertex) +{ + uchar stroke_id = *(uchar *)SCULPT_vertex_attr_get(vertex, ss->attrs.stroke_id); + + if (stroke_id != automasking->cavity_stroke_id) { + sculpt_calc_blurred_cavity(ss, automasking, automasking->settings.cavity_blur_steps, vertex); + } + + float factor = *(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.cavity); + bool inverted = automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_INVERTED; + + if ((automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_ALL) && + (automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_USE_CURVE)) { + factor = inverted ? 1.0f - factor : factor; + factor = BKE_curvemapping_evaluateF(automasking->settings.cavity_curve, 0, factor); + factor = inverted ? 1.0f - factor : factor; + } + + return factor; +} + float SCULPT_automasking_factor_get(AutomaskingCache *automasking, SculptSession *ss, PBVHVertRef vert) @@ -126,7 +379,13 @@ float SCULPT_automasking_factor_get(AutomaskingCache *automasking, * automasking information can't be computed in real time per vertex and needs to be * initialized for the whole mesh when the stroke starts. */ if (ss->attrs.automasking_factor) { - return *(float *)SCULPT_vertex_attr_get(vert, ss->attrs.automasking_factor); + float factor = *(float *)SCULPT_vertex_attr_get(vert, ss->attrs.automasking_factor); + + if (automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_ALL) { + factor *= sculpt_automasking_cavity_factor(automasking, ss, vert); + } + + return factor; } if (automasking->settings.flags & BRUSH_AUTOMASKING_FACE_SETS) { @@ -147,6 +406,10 @@ float SCULPT_automasking_factor_get(AutomaskingCache *automasking, } } + if (automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_ALL) { + return sculpt_automasking_cavity_factor(automasking, ss, vert); + } + return 1.0f; } @@ -200,7 +463,7 @@ static void SCULPT_topology_automasking_init(Sculpt *sd, Object *ob) Brush *brush = BKE_paint_brush(&sd->paint); if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES && !ss->pmap) { - BLI_assert_msg(0, "Topology masking: pmap missing"); + BLI_assert_unreachable(); return; } @@ -328,6 +591,26 @@ static void SCULPT_automasking_cache_settings_update(AutomaskingCache *automaski { automasking->settings.flags = sculpt_automasking_mode_effective_bits(sd, brush); automasking->settings.initial_face_set = SCULPT_active_face_set_get(ss); + + if (brush && (brush->automasking_flags & BRUSH_AUTOMASKING_CAVITY_ALL)) { + automasking->settings.cavity_curve = brush->automasking_cavity_curve; + automasking->settings.cavity_factor = brush->automasking_cavity_factor; + automasking->settings.cavity_blur_steps = brush->automasking_cavity_blur_steps; + } + else { + automasking->settings.cavity_curve = sd->automasking_cavity_curve; + automasking->settings.cavity_factor = sd->automasking_cavity_factor; + automasking->settings.cavity_blur_steps = sd->automasking_cavity_blur_steps; + } +} + +bool SCULPT_tool_can_reuse_cavity_mask(int sculpt_tool) +{ + return ELEM(sculpt_tool, + SCULPT_TOOL_PAINT, + SCULPT_TOOL_SMEAR, + SCULPT_TOOL_MASK, + SCULPT_TOOL_DRAW_FACE_SETS); } AutomaskingCache *SCULPT_automasking_cache_init(Sculpt *sd, Brush *brush, Object *ob) @@ -344,6 +627,35 @@ AutomaskingCache *SCULPT_automasking_cache_init(Sculpt *sd, Brush *brush, Object SCULPT_automasking_cache_settings_update(automasking, ss, sd, brush); SCULPT_boundary_info_ensure(ob); + if (SCULPT_is_automasking_mode_enabled(sd, brush, BRUSH_AUTOMASKING_CAVITY_ALL)) { + if (SCULPT_is_automasking_mode_enabled(sd, brush, BRUSH_AUTOMASKING_CAVITY_USE_CURVE)) { + BKE_curvemapping_init(brush->automasking_cavity_curve); + BKE_curvemapping_init(sd->automasking_cavity_curve); + } + + SCULPT_stroke_id_ensure(ob); + automasking->cavity_stroke_id = ss->stroke_id; + + if (!ss->attrs.cavity) { + SculptAttributeParams params = {0}; + ss->attrs.cavity = BKE_sculpt_attribute_ensure( + ob, ATTR_DOMAIN_POINT, CD_PROP_FLOAT, SCULPT_ATTRIBUTE_NAME(cavity), ¶ms); + } + /* Can we reuse the previous stroke's cavity mask? */ + else if (brush && SCULPT_tool_can_reuse_cavity_mask(brush->sculpt_tool)) { + int hash = SCULPT_automasking_settings_hash(ob, automasking); + + if (hash == ss->last_automasking_settings_hash) { + automasking->cavity_stroke_id = ss->last_automasking_settings_hash; + automasking->can_reuse_cavity = true; + } + } + + if (!automasking->can_reuse_cavity) { + ss->last_cavity_stroke_id = ss->stroke_id; + } + } + if (!SCULPT_automasking_needs_factors_cache(sd, brush)) { return automasking; } @@ -385,3 +697,8 @@ AutomaskingCache *SCULPT_automasking_cache_init(Sculpt *sd, Brush *brush, Object return automasking; } + +bool SCULPT_automasking_needs_original(const Sculpt *sd, const Brush *brush) +{ + return sculpt_automasking_mode_effective_bits(sd, brush) & BRUSH_AUTOMASKING_CAVITY_ALL; +} diff --git a/source/blender/editors/sculpt_paint/sculpt_expand.c b/source/blender/editors/sculpt_paint/sculpt_expand.c index 72b0b3a97fe..ecf296fbd66 100644 --- a/source/blender/editors/sculpt_paint/sculpt_expand.c +++ b/source/blender/editors/sculpt_paint/sculpt_expand.c @@ -2102,6 +2102,8 @@ static int sculpt_expand_invoke(bContext *C, wmOperator *op, const wmEvent *even SculptSession *ss = ob->sculpt; Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + SCULPT_stroke_id_next(ob); + /* Create and configure the Expand Cache. */ ss->expand_cache = MEM_callocN(sizeof(ExpandCache), "expand cache"); sculpt_expand_cache_initial_config_set(C, op, ss->expand_cache); diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_color.c b/source/blender/editors/sculpt_paint/sculpt_filter_color.c index 161fc563950..6d1fad1fc65 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_color.c +++ b/source/blender/editors/sculpt_paint/sculpt_filter_color.c @@ -334,6 +334,9 @@ static int sculpt_color_filter_invoke(bContext *C, wmOperator *op, const wmEvent const bool use_automasking = SCULPT_is_automasking_enabled(sd, ss, NULL); if (use_automasking) { + /* Increment stroke id for automasking system. */ + SCULPT_stroke_id_next(ob); + /* Update the active face set manually as the paint cursor is not enabled when using the Mesh * Filter Tool. */ float mval_fl[2] = {UNPACK2(event->mval)}; diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_mask.c b/source/blender/editors/sculpt_paint/sculpt_filter_mask.c index bb27e4f1e9e..c4e719da006 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_mask.c +++ b/source/blender/editors/sculpt_paint/sculpt_filter_mask.c @@ -303,173 +303,3 @@ void SCULPT_OT_mask_filter(struct wmOperatorType *ot) "Auto Iteration Count", "Use a automatic number of iterations based on the number of vertices of the sculpt"); } - -static float neighbor_dirty_mask(SculptSession *ss, PBVHVertexIter *vd) -{ - int total = 0; - float avg[3]; - zero_v3(avg); - - SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->vertex, ni) { - float normalized[3]; - sub_v3_v3v3(normalized, SCULPT_vertex_co_get(ss, ni.vertex), vd->co); - normalize_v3(normalized); - add_v3_v3(avg, normalized); - total++; - } - SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - - if (total > 0) { - mul_v3_fl(avg, 1.0f / total); - float dot = dot_v3v3(avg, vd->no ? vd->no : vd->fno); - float angle = max_ff(saacosf(dot), 0.0f); - return angle; - } - return 0.0f; -} - -typedef struct DirtyMaskRangeData { - float min, max; -} DirtyMaskRangeData; - -static void dirty_mask_compute_range_task_cb(void *__restrict userdata, - const int i, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - PBVHNode *node = data->nodes[i]; - DirtyMaskRangeData *range = tls->userdata_chunk; - PBVHVertexIter vd; - - BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - float dirty_mask = neighbor_dirty_mask(ss, &vd); - range->min = min_ff(dirty_mask, range->min); - range->max = max_ff(dirty_mask, range->max); - } - BKE_pbvh_vertex_iter_end; -} - -static void dirty_mask_compute_range_reduce(const void *__restrict UNUSED(userdata), - void *__restrict chunk_join, - void *__restrict chunk) -{ - DirtyMaskRangeData *join = chunk_join; - DirtyMaskRangeData *range = chunk; - join->min = min_ff(range->min, join->min); - join->max = max_ff(range->max, join->max); -} - -static void dirty_mask_apply_task_cb(void *__restrict userdata, - const int i, - const TaskParallelTLS *__restrict UNUSED(tls)) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - PBVHNode *node = data->nodes[i]; - PBVHVertexIter vd; - - const bool dirty_only = data->dirty_mask_dirty_only; - const float min = data->dirty_mask_min; - const float max = data->dirty_mask_max; - - float range = max - min; - if (range < 0.0001f) { - range = 0.0f; - } - else { - range = 1.0f / range; - } - - BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - float dirty_mask = neighbor_dirty_mask(ss, &vd); - float mask = *vd.mask + (1.0f - ((dirty_mask - min) * range)); - if (dirty_only) { - mask = fminf(mask, 0.5f) * 2.0f; - } - *vd.mask = CLAMPIS(mask, 0.0f, 1.0f); - } - BKE_pbvh_vertex_iter_end; - BKE_pbvh_node_mark_update_mask(node); -} - -static int sculpt_dirty_mask_exec(bContext *C, wmOperator *op) -{ - ARegion *region = CTX_wm_region(C); - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - PBVH *pbvh = ob->sculpt->pbvh; - PBVHNode **nodes; - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - int totnode; - - BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); - - SCULPT_vertex_random_access_ensure(ss); - - if (!ob->sculpt->pmap) { - return OPERATOR_CANCELLED; - } - - BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode); - SCULPT_undo_push_begin(ob, op); - - for (int i = 0; i < totnode; i++) { - SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_MASK); - } - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .nodes = nodes, - .dirty_mask_dirty_only = RNA_boolean_get(op->ptr, "dirty_only"), - }; - DirtyMaskRangeData range = { - .min = FLT_MAX, - .max = -FLT_MAX, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - - settings.func_reduce = dirty_mask_compute_range_reduce; - settings.userdata_chunk = ⦥ - settings.userdata_chunk_size = sizeof(DirtyMaskRangeData); - - BLI_task_parallel_range(0, totnode, &data, dirty_mask_compute_range_task_cb, &settings); - data.dirty_mask_min = range.min; - data.dirty_mask_max = range.max; - BLI_task_parallel_range(0, totnode, &data, dirty_mask_apply_task_cb, &settings); - - MEM_SAFE_FREE(nodes); - - BKE_pbvh_update_vertex_data(pbvh, PBVH_UpdateMask); - - SCULPT_undo_push_end(ob); - - ED_region_tag_redraw(region); - - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - - return OPERATOR_FINISHED; -} - -void SCULPT_OT_dirty_mask(struct wmOperatorType *ot) -{ - /* Identifiers. */ - ot->name = "Dirty Mask"; - ot->idname = "SCULPT_OT_dirty_mask"; - ot->description = "Generates a mask based on the geometry cavity and pointiness"; - - /* API callbacks. */ - ot->exec = sculpt_dirty_mask_exec; - ot->poll = SCULPT_mode_poll; - - ot->flag = OPTYPE_REGISTER; - - /* RNA. */ - RNA_def_boolean( - ot->srna, "dirty_only", false, "Dirty Only", "Don't calculate cleans for convex areas"); -} diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c index e576cfda3af..0b68a527b59 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c +++ b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c @@ -681,6 +681,9 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent } if (use_automasking) { + /* Increment stroke id for automasking system. */ + SCULPT_stroke_id_next(ob); + /* Update the active face set manually as the paint cursor is not enabled when using the Mesh * Filter Tool. */ float mval_fl[2] = {UNPACK2(event->mval)}; diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index cdfa9c2586f..a28c18359e5 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -311,10 +311,6 @@ typedef struct SculptThreadedTaskData { float *cloth_sim_initial_location; float cloth_sim_radius; - float dirty_mask_min; - float dirty_mask_max; - bool dirty_mask_dirty_only; - /* Mask By Color Tool */ float mask_by_color_threshold; @@ -398,10 +394,16 @@ typedef struct AutomaskingSettings { /* Flags from eAutomasking_flag. */ int flags; int initial_face_set; + float cavity_factor; + int cavity_blur_steps; + struct CurveMapping *cavity_curve; } AutomaskingSettings; typedef struct AutomaskingCache { AutomaskingSettings settings; + + bool can_reuse_cavity; + uchar cavity_stroke_id; } AutomaskingCache; typedef struct FilterCache { @@ -633,6 +635,7 @@ typedef struct StrokeCache { rcti previous_r; /* previous redraw rectangle */ rcti current_r; /* current redraw rectangle */ + int stroke_id; } StrokeCache; /* -------------------------------------------------------------------- */ @@ -1297,6 +1300,9 @@ float *SCULPT_boundary_automasking_init(Object *ob, eBoundaryAutomaskMode mode, int propagation_steps, float *automask_factor); +bool SCULPT_automasking_needs_original(const struct Sculpt *sd, const struct Brush *brush); +int SCULPT_automasking_settings_hash(Object *ob, AutomaskingCache *automasking); + /** \} */ /* -------------------------------------------------------------------- */ @@ -1587,7 +1593,6 @@ void SCULPT_OT_color_filter(struct wmOperatorType *ot); /* Mask filter and Dirty Mask. */ void SCULPT_OT_mask_filter(struct wmOperatorType *ot); -void SCULPT_OT_dirty_mask(struct wmOperatorType *ot); /* Mask and Face Sets Expand. */ @@ -1842,6 +1847,10 @@ BLI_INLINE bool SCULPT_tool_is_face_sets(int tool) return ELEM(tool, SCULPT_TOOL_DRAW_FACE_SETS); } +void SCULPT_stroke_id_ensure(struct Object *ob); +void SCULPT_stroke_id_next(struct Object *ob); +bool SCULPT_tool_can_reuse_cavity_mask(int sculpt_tool); + #ifdef __cplusplus } #endif diff --git a/source/blender/editors/sculpt_paint/sculpt_ops.c b/source/blender/editors/sculpt_paint/sculpt_ops.c index 52bfa61cd95..d5edd5708ad 100644 --- a/source/blender/editors/sculpt_paint/sculpt_ops.c +++ b/source/blender/editors/sculpt_paint/sculpt_ops.c @@ -98,6 +98,7 @@ #include "RNA_access.h" #include "RNA_define.h" +#include "RNA_path.h" #include "UI_interface.h" #include "UI_resources.h" @@ -1002,6 +1003,252 @@ static void SCULPT_OT_mask_by_color(wmOperatorType *ot) 1.0f); } +typedef enum { + AUTOMASK_BAKE_MIX, + AUTOMASK_BAKE_MULTIPLY, + AUTOMASK_BAKE_DIVIDE, + AUTOMASK_BAKE_ADD, + AUTOMASK_BAKE_SUBTRACT, +} CavityBakeMixMode; + +typedef struct AutomaskBakeTaskData { + SculptSession *ss; + AutomaskingCache *automasking; + PBVHNode **nodes; + CavityBakeMixMode mode; + float factor; + Object *ob; +} AutomaskBakeTaskData; + +static void sculpt_bake_cavity_exec_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + AutomaskBakeTaskData *tdata = userdata; + SculptSession *ss = tdata->ss; + PBVHNode *node = tdata->nodes[n]; + PBVHVertexIter vd; + const CavityBakeMixMode mode = tdata->mode; + const float factor = tdata->factor; + + SCULPT_undo_push_node(tdata->ob, node, SCULPT_UNDO_MASK); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + float automask = SCULPT_automasking_factor_get(tdata->automasking, ss, vd.vertex); + float mask; + + switch (mode) { + case AUTOMASK_BAKE_MIX: + mask = automask; + break; + case AUTOMASK_BAKE_MULTIPLY: + mask = *vd.mask * automask; + break; + break; + case AUTOMASK_BAKE_DIVIDE: + mask = automask > 0.00001f ? *vd.mask / automask : 0.0f; + break; + break; + case AUTOMASK_BAKE_ADD: + mask = *vd.mask + automask; + break; + case AUTOMASK_BAKE_SUBTRACT: + mask = *vd.mask - automask; + break; + } + + mask = *vd.mask + (mask - *vd.mask) * factor; + CLAMP(mask, 0.0f, 1.0f); + + *vd.mask = mask; + } + BKE_pbvh_vertex_iter_end; + + BKE_pbvh_node_mark_update_mask(node); +} + +static int sculpt_bake_cavity_exec(bContext *C, wmOperator *op) +{ + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); + SCULPT_vertex_random_access_ensure(ss); + + MultiresModifierData *mmd = BKE_sculpt_multires_active(CTX_data_scene(C), ob); + BKE_sculpt_mask_layers_ensure(ob, mmd); + + SCULPT_undo_push_begin(ob, op); + + CavityBakeMixMode mode = RNA_enum_get(op->ptr, "mix_mode"); + float factor = RNA_float_get(op->ptr, "mix_factor"); + + PBVHNode **nodes; + int totnode; + + BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); + + AutomaskBakeTaskData tdata; + + /* Set up automasking settings. + */ + Sculpt sd2 = *sd; + + /* Override cavity mask settings if use_automask_settings is false. */ + if (!RNA_boolean_get(op->ptr, "use_automask_settings")) { + if (RNA_boolean_get(op->ptr, "invert")) { + sd2.automasking_flags = BRUSH_AUTOMASKING_CAVITY_INVERTED; + } + else { + sd2.automasking_flags = BRUSH_AUTOMASKING_CAVITY_NORMAL; + } + + if (RNA_boolean_get(op->ptr, "use_curve")) { + sd2.automasking_flags |= BRUSH_AUTOMASKING_CAVITY_USE_CURVE; + } + + sd2.automasking_cavity_blur_steps = RNA_int_get(op->ptr, "blur_steps"); + sd2.automasking_cavity_factor = RNA_float_get(op->ptr, "factor"); + + sd2.automasking_cavity_curve = sd->automasking_cavity_curve_op; + } + else { + sd2.automasking_flags &= BRUSH_AUTOMASKING_CAVITY_ALL | BRUSH_AUTOMASKING_CAVITY_USE_CURVE; + + /* Ensure cavity mask is actually enabled. */ + if (!(sd2.automasking_flags & BRUSH_AUTOMASKING_CAVITY_ALL)) { + sd2.automasking_flags |= BRUSH_AUTOMASKING_CAVITY_NORMAL; + } + } + + /* Create copy of brush with cleared automasking settings. */ + Brush brush2 = *brush; + brush2.automasking_flags = 0; + brush2.automasking_boundary_edges_propagation_steps = 1; + brush2.automasking_cavity_curve = sd2.automasking_cavity_curve; + + tdata.ob = ob; + tdata.mode = mode; + tdata.factor = factor; + tdata.ss = ss; + tdata.nodes = nodes; + tdata.automasking = SCULPT_automasking_cache_init(&sd2, &brush2, ob); + + SCULPT_stroke_id_next(ob); + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &tdata, sculpt_bake_cavity_exec_task_cb, &settings); + + MEM_SAFE_FREE(nodes); + SCULPT_automasking_cache_free(tdata.automasking); + + BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateMask); + SCULPT_undo_push_end(ob); + + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); + + /* Unlike other operators we do not tag the ID for update here; + * it triggers a PBVH rebuild which is too slow and ruins + * the interactivity of the tool. */ + + return OPERATOR_FINISHED; +} + +static void cavity_bake_ui(bContext *C, wmOperator *op) +{ + uiLayout *layout = op->layout; + Scene *scene = CTX_data_scene(C); + Sculpt *sd = scene->toolsettings ? scene->toolsettings->sculpt : NULL; + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + bool use_curve; + + if (!sd || !RNA_boolean_get(op->ptr, "use_automask_settings")) { + uiItemR(layout, op->ptr, "mix_mode", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "mix_factor", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "use_automask_settings", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "factor", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "blur_steps", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "invert", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "use_curve", 0, NULL, ICON_NONE); + + use_curve = RNA_boolean_get(op->ptr, "use_curve"); + } + else { + PointerRNA sculpt_ptr; + + RNA_pointer_create(&scene->id, &RNA_Sculpt, sd, &sculpt_ptr); + uiItemR(layout, op->ptr, "mix_mode", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "mix_factor", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "use_automask_settings", 0, NULL, ICON_NONE); + + use_curve = RNA_boolean_get(&sculpt_ptr, "use_automasking_custom_cavity_curve"); + } + + if (use_curve) { + Scene *scene = CTX_data_scene(C); + PointerRNA sculpt_ptr; + + const char *curve_prop; + + if (RNA_boolean_get(op->ptr, "use_automask_settings")) { + curve_prop = "automasking_cavity_curve"; + } + else { + curve_prop = "automasking_cavity_curve_op"; + } + + if (scene->toolsettings && scene->toolsettings->sculpt) { + RNA_pointer_create(&scene->id, &RNA_Sculpt, scene->toolsettings->sculpt, &sculpt_ptr); + uiTemplateCurveMapping(layout, &sculpt_ptr, curve_prop, 'v', false, false, false, false); + } + } +} + +static void SCULPT_OT_mask_from_cavity(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Mask From Cavity"; + ot->idname = "SCULPT_OT_mask_from_cavity"; + ot->description = "Creates a mask based on the curvature of the surface"; + ot->ui = cavity_bake_ui; + + static EnumPropertyItem mix_modes[] = { + {AUTOMASK_BAKE_MIX, "MIX", ICON_NONE, "Mix", ""}, + {AUTOMASK_BAKE_MULTIPLY, "MULTIPLY", ICON_NONE, "Multiply", ""}, + {AUTOMASK_BAKE_DIVIDE, "DIVIDE", ICON_NONE, "Divide", ""}, + {AUTOMASK_BAKE_ADD, "ADD", ICON_NONE, "Add", ""}, + {AUTOMASK_BAKE_SUBTRACT, "SUBTRACT", ICON_NONE, "Subtract", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + /* api callbacks */ + ot->exec = sculpt_bake_cavity_exec; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum(ot->srna, "mix_mode", mix_modes, AUTOMASK_BAKE_MIX, "Mode", "Mix mode"); + RNA_def_float(ot->srna, "mix_factor", 1.0f, 0.0f, 5.0f, "Mix Factor", "", 0.0f, 1.0f); + + RNA_def_boolean(ot->srna, + "use_automask_settings", + false, + "Use Automask Settings", + "Use default settings from Options panel in sculpt mode."); + + RNA_def_float(ot->srna, "factor", 0.5f, 0.0f, 5.0f, "Cavity Factor", "The contrast of the cavity mask", 0.0f, 1.0f); + RNA_def_int(ot->srna, "blur_steps", 2, 0, 25, "Cavity Blur", "The number of times the cavity mask is blurred", 0, 25); + RNA_def_boolean(ot->srna, "use_curve", false, "Use Curve", ""); + + RNA_def_boolean(ot->srna, "invert", false, "Cavity (Inverted)", ""); +} + void ED_operatortypes_sculpt(void) { WM_operatortype_append(SCULPT_OT_brush_stroke); @@ -1015,7 +1262,6 @@ void ED_operatortypes_sculpt(void) WM_operatortype_append(SCULPT_OT_set_detail_size); WM_operatortype_append(SCULPT_OT_mesh_filter); WM_operatortype_append(SCULPT_OT_mask_filter); - 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); @@ -1037,4 +1283,5 @@ void ED_operatortypes_sculpt(void) WM_operatortype_append(SCULPT_OT_mask_init); WM_operatortype_append(SCULPT_OT_expand); + WM_operatortype_append(SCULPT_OT_mask_from_cavity); } diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_color.c b/source/blender/editors/sculpt_paint/sculpt_paint_color.c index c494c71f1eb..f16a8769166 100644 --- a/source/blender/editors/sculpt_paint/sculpt_paint_color.c +++ b/source/blender/editors/sculpt_paint/sculpt_paint_color.c @@ -195,8 +195,11 @@ static void do_paint_brush_task_cb_ex(void *__restrict userdata, paint_color, paint_color, wet_mix_color, ss->cache->paint_brush.wet_mix); blend_color_mix_float(color_buffer->color[vd.i], color_buffer->color[vd.i], paint_color); - /* Final mix over the original color using brush alpha. */ - mul_v4_v4fl(buffer_color, color_buffer->color[vd.i], brush->alpha); + /* Final mix over the original color using brush alpha. We apply automaking again + * at this point to avoid washing out non-binary masking modes like cavity masking. + */ + float automasking = SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.vertex); + mul_v4_v4fl(buffer_color, color_buffer->color[vd.i], brush->alpha * automasking); float col[4]; SCULPT_vertex_color_get(ss, vd.vertex, col); diff --git a/source/blender/makesdna/DNA_brush_defaults.h b/source/blender/makesdna/DNA_brush_defaults.h index 530c056b584..348e8f4e098 100644 --- a/source/blender/makesdna/DNA_brush_defaults.h +++ b/source/blender/makesdna/DNA_brush_defaults.h @@ -91,6 +91,8 @@ .pose_ik_segments = 1, \ .hardness = 0.0f, \ .automasking_boundary_edges_propagation_steps = 1, \ + .automasking_cavity_blur_steps = 0,\ + .automasking_cavity_factor = 0.5f,\ \ /* A kernel radius of 1 has almost no effect (T63233). */ \ .blur_kernel_radius = 2, \ diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h index 7a339c3955a..e0567151aa4 100644 --- a/source/blender/makesdna/DNA_brush_enums.h +++ b/source/blender/makesdna/DNA_brush_enums.h @@ -330,6 +330,14 @@ typedef enum eAutomasking_flag { BRUSH_AUTOMASKING_FACE_SETS = (1 << 1), BRUSH_AUTOMASKING_BOUNDARY_EDGES = (1 << 2), BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS = (1 << 3), + BRUSH_AUTOMASKING_CAVITY_NORMAL = (1 << 4), + + /* Note: normal and inverted are mutually exclusive, + * inverted has priority if both bits are set. + */ + BRUSH_AUTOMASKING_CAVITY_INVERTED = (1 << 5), + BRUSH_AUTOMASKING_CAVITY_ALL = (1 << 4) | (1 << 5), + BRUSH_AUTOMASKING_CAVITY_USE_CURVE = (1 << 6), } eAutomasking_flag; typedef enum ePaintBrush_flag { diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index 761a36e8d1f..c0beb89392b 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -388,6 +388,11 @@ typedef struct Brush { struct BrushGpencilSettings *gpencil_settings; struct BrushCurvesSculptSettings *curves_sculpt_settings; + + int automasking_cavity_blur_steps; + float automasking_cavity_factor; + + struct CurveMapping *automasking_cavity_curve; } Brush; /* Struct to hold palette colors for sorting. */ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index c8ff0083b5e..cf163ab7019 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1021,8 +1021,12 @@ typedef struct Sculpt { float constant_detail; float detail_percent; + int automasking_cavity_blur_steps; + float automasking_cavity_factor; char _pad[4]; + struct CurveMapping *automasking_cavity_curve; + struct CurveMapping *automasking_cavity_curve_op; /* For use by operators */ struct Object *gravity_object; } Sculpt; diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index 66dcbab3a35..1bff91ca84e 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -92,6 +92,19 @@ static const EnumPropertyItem rna_enum_brush_texture_slot_map_texture_mode_items #endif /* clang-format off */ +/* Note: we don't actually turn these into a single enum bitmask property, + * instead we construct individual boolean properties. */ +const EnumPropertyItem RNA_automasking_flags[] = { + {BRUSH_AUTOMASKING_TOPOLOGY, "use_automasking_topology", 0,"Topology", "Affect only vertices connected to the active vertex under the brush"}, + {BRUSH_AUTOMASKING_FACE_SETS, "use_automasking_face_sets", 0,"Face Sets", "Affect only vertices that share Face Sets with the active vertex"}, + {BRUSH_AUTOMASKING_BOUNDARY_EDGES, "use_automasking_boundary_edges", 0,"Mesh Boundary Auto-Masking", "Do not affect non manifold boundary edges"}, + {BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS, "use_automasking_boundary_face_sets", 0,"Face Sets Boundary Automasking", "Do not affect vertices that belong to a Face Set boundary"}, + {BRUSH_AUTOMASKING_CAVITY_NORMAL, "use_automasking_cavity", 0,"Cavity Mask", "Do not affect vertices on peaks, based on the surface curvature"}, + {BRUSH_AUTOMASKING_CAVITY_INVERTED, "use_automasking_cavity_inverted", 0,"Inverted Cavity Mask", "Do not affect vertices within crevices, based on the surface curvature"}, + {BRUSH_AUTOMASKING_CAVITY_USE_CURVE, "use_automasking_custom_cavity_curve", 0,"Custom Cavity Curve", "Use custom curve"}, + {0, NULL, 0, NULL, NULL} +}; + const EnumPropertyItem rna_enum_brush_sculpt_tool_items[] = { {SCULPT_TOOL_DRAW, "DRAW", ICON_BRUSH_SCULPT_DRAW, "Draw", ""}, {SCULPT_TOOL_DRAW_SHARP, "DRAW_SHARP", ICON_BRUSH_SCULPT_DRAW, "Draw Sharp", ""}, @@ -1084,6 +1097,32 @@ static const EnumPropertyItem *rna_BrushTextureSlot_map_mode_itemf(bContext *C, # undef rna_enum_brush_texture_slot_map_sculpt_mode_items } +static void rna_Brush_automasking_invert_cavity_set(PointerRNA *ptr, bool val) +{ + Brush *brush = (Brush *)ptr->data; + + if (val) { + brush->automasking_flags &= ~BRUSH_AUTOMASKING_CAVITY_NORMAL; + brush->automasking_flags |= BRUSH_AUTOMASKING_CAVITY_INVERTED; + } + else { + brush->automasking_flags &= ~BRUSH_AUTOMASKING_CAVITY_INVERTED; + } +} + +static void rna_Brush_automasking_cavity_set(PointerRNA *ptr, bool val) +{ + Brush *brush = (Brush *)ptr->data; + + if (val) { + brush->automasking_flags &= ~BRUSH_AUTOMASKING_CAVITY_INVERTED; + brush->automasking_flags |= BRUSH_AUTOMASKING_CAVITY_NORMAL; + } + else { + brush->automasking_flags &= ~BRUSH_AUTOMASKING_CAVITY_NORMAL; + } +} + #else static void rna_def_brush_texture_slot(BlenderRNA *brna) @@ -3192,32 +3231,44 @@ static void rna_def_brush(BlenderRNA *brna) "When locked keep using the plane origin of surface where stroke was initiated"); RNA_def_property_update(prop, 0, "rna_Brush_update"); - prop = RNA_def_property(srna, "use_automasking_topology", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "automasking_flags", BRUSH_AUTOMASKING_TOPOLOGY); - RNA_def_property_ui_text(prop, - "Topology Auto-Masking", - "Affect only vertices connected to the active vertex under the brush"); - RNA_def_property_update(prop, 0, "rna_Brush_update"); + const EnumPropertyItem *entry = RNA_automasking_flags; + do { + prop = RNA_def_property(srna, entry->identifier, PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "automasking_flags", entry->value); + RNA_def_property_ui_text(prop, entry->name, entry->description); - 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"); + if (entry->value == BRUSH_AUTOMASKING_CAVITY_NORMAL) { + RNA_def_property_boolean_funcs(prop, NULL, "rna_Brush_automasking_cavity_set"); + } + else if (entry->value == BRUSH_AUTOMASKING_CAVITY_INVERTED) { + RNA_def_property_boolean_funcs(prop, NULL, "rna_Brush_automasking_invert_cavity_set"); + } + + RNA_def_property_update(prop, 0, "rna_Brush_update"); + } while ((++entry)->identifier); + + + prop = RNA_def_property(srna, "automasking_cavity_factor", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "automasking_cavity_factor"); + RNA_def_property_ui_text(prop, "Cavity Factor", "The contrast of the cavity mask"); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1, 3); + RNA_def_property_range(prop, 0.0f, 5.0f); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_update(prop, 0, "rna_Brush_update"); - prop = RNA_def_property(srna, "use_automasking_boundary_edges", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "automasking_flags", BRUSH_AUTOMASKING_BOUNDARY_EDGES); - RNA_def_property_ui_text( - prop, "Mesh Boundary Auto-Masking", "Do not affect non manifold boundary edges"); + prop = RNA_def_property(srna, "automasking_cavity_blur_steps", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "automasking_cavity_blur_steps"); + RNA_def_property_ui_text(prop, "Blur Steps", "The number of times the cavity mask is blurred."); + RNA_def_property_range(prop, 0.0f, 25.0f); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_update(prop, 0, "rna_Brush_update"); - prop = RNA_def_property(srna, "use_automasking_boundary_face_sets", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna( - prop, NULL, "automasking_flags", BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS); - RNA_def_property_ui_text(prop, - "Face Sets Boundary Automasking", - "Do not affect vertices that belong to a Face Set boundary"); + prop = RNA_def_property(srna, "automasking_cavity_curve", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "automasking_cavity_curve"); + RNA_def_property_struct_type(prop, "CurveMapping"); + RNA_def_property_ui_text(prop, "Cavity Curve", "Curve used for the sensitivity"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_update(prop, 0, "rna_Brush_update"); prop = RNA_def_property(srna, "use_scene_spacing", PROP_ENUM, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h index 3d5c1810558..8e652753ec0 100644 --- a/source/blender/makesrna/intern/rna_internal.h +++ b/source/blender/makesrna/intern/rna_internal.h @@ -24,7 +24,7 @@ struct AssetLibraryReference; struct FreestyleSettings; struct ID; struct IDOverrideLibrary; -struct IDOverrideLibraryPropertyOperation; +struct IDOverrideLibraryenOperation; struct IDProperty; struct Main; struct Object; diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c index a4298c8c3aa..60f2b289af0 100644 --- a/source/blender/makesrna/intern/rna_sculpt_paint.c +++ b/source/blender/makesrna/intern/rna_sculpt_paint.c @@ -9,6 +9,7 @@ #include "BLI_math.h" #include "BLI_utildefines.h" +#include "RNA_access.h" #include "RNA_define.h" #include "RNA_enum_types.h" @@ -33,6 +34,8 @@ #include "bmesh.h" +extern const EnumPropertyItem RNA_automasking_flags[]; + const EnumPropertyItem rna_enum_particle_edit_hair_brush_items[] = { {PE_BRUSH_COMB, "COMB", 0, "Comb", "Comb hairs"}, {PE_BRUSH_SMOOTH, "SMOOTH", 0, "Smooth", "Smooth hairs"}, @@ -598,6 +601,31 @@ static char *rna_GPencilSculptGuide_path(const PointerRNA *UNUSED(ptr)) return BLI_strdup("tool_settings.gpencil_sculpt.guide"); } +static void rna_Sculpt_automasking_invert_cavity_set(PointerRNA *ptr, bool val) +{ + Sculpt *sd = (Sculpt *)ptr->data; + + if (val) { + sd->automasking_flags &= ~BRUSH_AUTOMASKING_CAVITY_NORMAL; + sd->automasking_flags |= BRUSH_AUTOMASKING_CAVITY_INVERTED; + } + else { + sd->automasking_flags &= ~BRUSH_AUTOMASKING_CAVITY_INVERTED; + } +} + +static void rna_Sculpt_automasking_cavity_set(PointerRNA *ptr, bool val) +{ + Sculpt *sd = (Sculpt *)ptr->data; + + if (val) { + sd->automasking_flags &= ~BRUSH_AUTOMASKING_CAVITY_INVERTED; + sd->automasking_flags |= BRUSH_AUTOMASKING_CAVITY_NORMAL; + } + else { + sd->automasking_flags &= ~BRUSH_AUTOMASKING_CAVITY_NORMAL; + } +} #else static void rna_def_paint_curve(BlenderRNA *brna) @@ -883,32 +911,47 @@ 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_update"); - prop = RNA_def_property(srna, "use_automasking_topology", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "automasking_flags", BRUSH_AUTOMASKING_TOPOLOGY); - RNA_def_property_ui_text(prop, - "Topology Auto-Masking", - "Affect only vertices connected to the active vertex under the brush"); + const EnumPropertyItem *entry = RNA_automasking_flags; + do { + prop = RNA_def_property(srna, entry->identifier, PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "automasking_flags", entry->value); + RNA_def_property_ui_text(prop, entry->name, entry->description); + + if (entry->value == BRUSH_AUTOMASKING_CAVITY_NORMAL) { + RNA_def_property_boolean_funcs(prop, NULL, "rna_Sculpt_automasking_cavity_set"); + } + else if (entry->value == BRUSH_AUTOMASKING_CAVITY_INVERTED) { + RNA_def_property_boolean_funcs(prop, NULL, "rna_Sculpt_automasking_invert_cavity_set"); + } + + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); + } while ((++entry)->identifier); + + prop = RNA_def_property(srna, "automasking_cavity_factor", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "automasking_cavity_factor"); + RNA_def_property_ui_text(prop, "Cavity Factor", "The contrast of the cavity mask"); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1, 3); + RNA_def_property_range(prop, 0.0f, 5.0f); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); - 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"); + prop = RNA_def_property(srna, "automasking_cavity_blur_steps", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "automasking_cavity_blur_steps"); + RNA_def_property_ui_text(prop, "Blur Steps", "The number of times the cavity mask is blurred"); + RNA_def_property_range(prop, 0.0f, 25.0f); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); - prop = RNA_def_property(srna, "use_automasking_boundary_edges", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "automasking_flags", BRUSH_AUTOMASKING_BOUNDARY_EDGES); - RNA_def_property_ui_text( - prop, "Mesh Boundary Auto-Masking", "Do not affect non manifold boundary edges"); + prop = RNA_def_property(srna, "automasking_cavity_curve", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "automasking_cavity_curve"); + RNA_def_property_struct_type(prop, "CurveMapping"); + RNA_def_property_ui_text(prop, "Cavity Curve", "Curve used for the sensitivity"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); - prop = RNA_def_property(srna, "use_automasking_boundary_face_sets", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna( - prop, NULL, "automasking_flags", BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS); - RNA_def_property_ui_text(prop, - "Face Sets Boundary Auto-Masking", - "Do not affect vertices that belong to a Face Set boundary"); + prop = RNA_def_property(srna, "automasking_cavity_curve_op", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "automasking_cavity_curve_op"); + RNA_def_property_struct_type(prop, "CurveMapping"); + RNA_def_property_ui_text(prop, "Cavity Curve", "Curve used for the sensitivity"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); prop = RNA_def_property(srna, "symmetrize_direction", PROP_ENUM, PROP_NONE); diff --git a/tests/python/bl_run_operators.py b/tests/python/bl_run_operators.py index ccb0814e5eb..19e1d41d8cf 100644 --- a/tests/python/bl_run_operators.py +++ b/tests/python/bl_run_operators.py @@ -83,7 +83,7 @@ op_blacklist = ( "object.voxel_remesh", "mesh.paint_mask_slice", "paint.mask_flood_fill", - "sculpt.dirty_mask", + "sculpt.mask_from_cavity", # TODO: use empty temp dir to avoid behavior depending on local setup. "view3d.pastebuffer", # Needs active window. |