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:
-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.