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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Eagar <joeedh@gmail.com>2022-09-29 02:22:34 +0300
committerJoseph Eagar <joeedh@gmail.com>2022-09-29 02:22:34 +0300
commit0156a677c7d13106a1d049d74fca43b5311eb53e (patch)
tree9e2c73fc0349cde80555dbe0cbf67e2dfb531011
parent53ac3192ba07c4271c86479186ed0d0b8cd7846f (diff)
Sculpt: New Cavity Automasking Mode
Add new cavity automasking mode based on local mesh curvature. Cavity masking is a great way to quickly add detail in crevices and the like. It's meant to be used with the Paint brush in color attribute mode. It does work with other brushes but the results can be unpredictable. {F13131497} The old "dirty mask" operator has been replace with a new "mask from cavity" operator that shares the same code with cavity automasking. Differences from the sculpt-dev implementation: * It uses the word "cavity." When I first implemented this I wasn't aware this feature existed in other software (and other paint modes in Blender), and for reasons that escape me today I initially decided to call it a concave or concavity mask. * The cavity factor works a bit differently. It's no longer non-linear and functions as a simple scale around 0.5f. * Supports custom curves. * Supports blurring. Reviewed By: Julian Kaspar, Jeroen Bakker and Campbell Barton Differential Revision: https://developer.blender.org/D15122 Ref D15122
-rw-r--r--release/scripts/startup/bl_ui/properties_paint_common.py14
-rw-r--r--release/scripts/startup/bl_ui/space_view3d.py5
-rw-r--r--release/scripts/startup/bl_ui/space_view3d_toolbar.py26
-rw-r--r--source/blender/blenkernel/BKE_blender_version.h4
-rw-r--r--source/blender/blenkernel/BKE_paint.h14
-rw-r--r--source/blender/blenkernel/intern/brush.cc15
-rw-r--r--source/blender/blenkernel/intern/paint.cc3
-rw-r--r--source/blender/blenkernel/intern/scene.cc70
-rw-r--r--source/blender/blenloader/intern/versioning_300.cc16
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c35
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_automasking.cc335
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_expand.c2
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_filter_color.c3
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_filter_mask.c170
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_filter_mesh.c3
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h19
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_ops.c249
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_paint_color.c7
-rw-r--r--source/blender/makesdna/DNA_brush_defaults.h2
-rw-r--r--source/blender/makesdna/DNA_brush_enums.h8
-rw-r--r--source/blender/makesdna/DNA_brush_types.h5
-rw-r--r--source/blender/makesdna/DNA_scene_types.h4
-rw-r--r--source/blender/makesrna/intern/rna_brush.c93
-rw-r--r--source/blender/makesrna/intern/rna_internal.h2
-rw-r--r--source/blender/makesrna/intern/rna_sculpt_paint.c83
-rw-r--r--tests/python/bl_run_operators.py2
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), &params);
+ }
+}
+
/** \} */
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), &params);
+ }
+ /* 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 = &range;
- 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.