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/presets/keyconfig/keymap_data/blender_default.py7
-rw-r--r--release/scripts/startup/bl_ui/properties_paint_common.py2
-rw-r--r--release/scripts/startup/bl_ui/space_toolsystem_toolbar.py76
-rw-r--r--release/scripts/startup/bl_ui/space_view3d.py36
-rw-r--r--source/blender/blenkernel/BKE_curves.hh10
-rw-r--r--source/blender/blenkernel/intern/curves_geometry.cc22
-rw-r--r--source/blender/editors/curves/intern/curves_ops.cc249
-rw-r--r--source/blender/editors/sculpt_paint/CMakeLists.txt2
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_add.cc29
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_comb.cc22
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_delete.cc14
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc16
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_intern.hh21
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_ops.cc2
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_selection.cc107
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc394
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc11
-rw-r--r--source/blender/makesdna/DNA_brush_enums.h1
-rw-r--r--source/blender/makesdna/DNA_curves_types.h8
-rw-r--r--source/blender/makesrna/RNA_enum_items.h1
-rw-r--r--source/blender/makesrna/intern/rna_attribute.c5
-rw-r--r--source/blender/makesrna/intern/rna_brush.c2
-rw-r--r--source/blender/makesrna/intern/rna_curves.c12
23 files changed, 1023 insertions, 26 deletions
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index 180dc9a894d..adaebf7ce7b 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -5594,7 +5594,14 @@ def km_sculpt_curves(params):
{"properties": [("mode", 'NORMAL')]}),
("sculpt_curves.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'INVERT')]}),
+ ("sculpt_curves.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
+ {"properties": [("mode", 'SMOOTH')]}),
+ ("curves.set_selection_domain", {"type": 'ONE', "value": 'PRESS'}, {"properties": [("domain", 'POINT')]}),
+ ("curves.set_selection_domain", {"type": 'TWO', "value": 'PRESS'}, {"properties": [("domain", 'CURVE')]}),
+ ("curves.disable_selection", {"type": 'ONE', "value": 'PRESS', "alt": True}, None),
+ ("curves.disable_selection", {"type": 'TWO', "value": 'PRESS', "alt": True}, None),
*_template_paint_radial_control("curves_sculpt"),
+ *_template_items_select_actions(params, "sculpt_curves.select_all"),
])
return keymap
diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py
index d5b106635ab..d7325257084 100644
--- a/release/scripts/startup/bl_ui/properties_paint_common.py
+++ b/release/scripts/startup/bl_ui/properties_paint_common.py
@@ -859,7 +859,7 @@ def brush_shared_settings(layout, context, brush, popover=False):
if mode == 'SCULPT_CURVES':
size = True
strength = True
- direction = brush.curves_sculpt_tool == 'GROW_SHRINK'
+ direction = brush.curves_sculpt_tool in {'GROW_SHRINK', 'SELECTION_PAINT'}
### Draw settings. ###
ups = context.scene.tool_settings.unified_paint_settings
diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py
index 5c6ca13776e..1c526cc89e4 100644
--- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py
+++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py
@@ -2316,14 +2316,58 @@ class _defs_gpencil_weight:
class _defs_curves_sculpt:
- @staticmethod
- def generate_from_brushes(context):
- return generate_from_enum_ex(
- context,
- idname_prefix="builtin_brush.",
- icon_prefix="ops.curves.sculpt_",
- type=bpy.types.Brush,
- attr="curves_sculpt_tool",
+ @ToolDef.from_fn
+ def selection_paint():
+ return dict(
+ idname="builtin_brush.selection_paint",
+ label="Selection Paint",
+ icon="ops.generic.select_paint",
+ data_block="SELECTION_PAINT"
+ )
+
+ @ToolDef.from_fn
+ def comb():
+ return dict(
+ idname="builtin_brush.comb",
+ label="Comb",
+ icon="ops.curves.sculpt_comb",
+ data_block='COMB'
+ )
+
+ @ToolDef.from_fn
+ def add():
+ return dict(
+ idname="builtin_brush.add",
+ label="Add",
+ icon="ops.curves.sculpt_add",
+ data_block='ADD'
+ )
+
+ @ToolDef.from_fn
+ def delete():
+ return dict(
+ idname="builtin_brush.delete",
+ label="Delete",
+ icon="ops.curves.sculpt_delete",
+ data_block='DELETE'
+ )
+
+ @ToolDef.from_fn
+ def snake_hook():
+ return dict(
+ idname="builtin_brush.snake_hook",
+ label="Snake Hook",
+ icon="ops.curves.sculpt_snake_hook",
+ data_block='SNAKE_HOOK'
+ )
+
+ @ToolDef.from_fn
+ def grow_shrink():
+ return dict(
+ idname="builtin_brush.grow_shrink",
+ label="Grow/Shrink",
+ icon="ops.curves.sculpt_grow_shrink",
+ data_block='GROW_SHRINK'
)
@@ -3076,7 +3120,21 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
),
],
'SCULPT_CURVES': [
- _defs_curves_sculpt.generate_from_brushes,
+ lambda context: (
+ (
+ _defs_curves_sculpt.selection_paint,
+ None,
+ )
+ if context is None or context.preferences.experimental.use_new_curves_tools
+ else ()
+ ),
+ _defs_curves_sculpt.comb,
+ _defs_curves_sculpt.add,
+ _defs_curves_sculpt.delete,
+ _defs_curves_sculpt.snake_hook,
+ _defs_curves_sculpt.grow_shrink,
+ None,
+ *_tools_annotate,
],
}
diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py
index 1af70895be9..116dd280b5c 100644
--- a/release/scripts/startup/bl_ui/space_view3d.py
+++ b/release/scripts/startup/bl_ui/space_view3d.py
@@ -535,6 +535,11 @@ class _draw_tool_settings_context_mode:
if brush.curves_sculpt_tool == 'DELETE':
layout.prop(brush, "falloff_shape", expand=True)
+ if brush.curves_sculpt_tool == 'SELECTION_PAINT':
+ layout.prop(brush, "direction", expand=True, text="")
+ layout.prop(brush, "falloff_shape", expand=True)
+ layout.popover("VIEW3D_PT_tools_brush_falloff")
+
class VIEW3D_HT_header(Header):
bl_space_type = 'VIEW_3D'
@@ -687,6 +692,24 @@ class VIEW3D_HT_header(Header):
if object_mode == 'PARTICLE_EDIT':
row = layout.row()
row.prop(tool_settings.particle_edit, "select_mode", text="", expand=True)
+ elif object_mode == 'SCULPT_CURVES' and obj.type == 'CURVES':
+ curves = obj.data
+
+ row = layout.row(align=True)
+
+ experimental = context.preferences.experimental
+ if experimental.use_new_curves_tools:
+ # Combine the "use selection" toggle with the "set domain" operators
+ # to allow turning selection off directly.
+ domain = curves.selection_domain
+ if domain == 'POINT':
+ row.prop(curves, "use_sculpt_selection", text="", icon='CURVE_BEZCIRCLE')
+ else:
+ row.operator("curves.set_selection_domain", text="", icon='CURVE_BEZCIRCLE').domain = 'POINT'
+ if domain == 'CURVE':
+ row.prop(curves, "use_sculpt_selection", text="", icon='CURVE_PATH')
+ else:
+ row.operator("curves.set_selection_domain", text="", icon='CURVE_PATH').domain = 'CURVE'
# Grease Pencil
if obj and obj.type == 'GPENCIL' and context.gpencil_data:
@@ -939,6 +962,7 @@ class VIEW3D_MT_editor_menus(Menu):
layout.menu("VIEW3D_MT_mask")
layout.menu("VIEW3D_MT_face_sets")
if mode_string == 'SCULPT_CURVES':
+ layout.menu("VIEW3D_MT_select_sculpt_curves")
layout.menu("VIEW3D_MT_sculpt_curves")
else:
@@ -1976,6 +2000,17 @@ class VIEW3D_MT_select_edit_curves(Menu):
pass
+class VIEW3D_MT_select_sculpt_curves(Menu):
+ bl_label = "Select"
+
+ def draw(self, _context):
+ layout = self.layout
+
+ layout.operator("sculpt_curves.select_all", text="All").action = 'SELECT'
+ layout.operator("sculpt_curves.select_all", text="None").action = 'DESELECT'
+ layout.operator("sculpt_curves.select_all", text="Invert").action = 'INVERT'
+
+
class VIEW3D_MT_angle_control(Menu):
bl_label = "Angle Control"
@@ -7703,6 +7738,7 @@ classes = (
VIEW3D_MT_select_paint_mask,
VIEW3D_MT_select_paint_mask_vertex,
VIEW3D_MT_select_edit_curves,
+ VIEW3D_MT_select_sculpt_curves,
VIEW3D_MT_angle_control,
VIEW3D_MT_mesh_add,
VIEW3D_MT_curve_add,
diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh
index cc056dab248..445f8d46f2d 100644
--- a/source/blender/blenkernel/BKE_curves.hh
+++ b/source/blender/blenkernel/BKE_curves.hh
@@ -281,6 +281,11 @@ class CurvesGeometry : public ::CurvesGeometry {
Span<float2> surface_triangle_coords() const;
MutableSpan<float2> surface_triangle_coords_for_write();
+ VArray<float> selection_point_float() const;
+ MutableSpan<float> selection_point_float_for_write();
+ VArray<float> selection_curve_float() const;
+ MutableSpan<float> selection_curve_float_for_write();
+
/**
* Calculate the largest and smallest position values, only including control points
* (rather than evaluated points). The existing values of `min` and `max` are taken into account.
@@ -406,6 +411,11 @@ class CurvesGeometry : public ::CurvesGeometry {
*/
GVArray adapt_domain(const GVArray &varray, AttributeDomain from, AttributeDomain to) const;
+ template<typename T>
+ VArray<T> adapt_domain(const VArray<T> &varray, AttributeDomain from, AttributeDomain to) const
+ {
+ return this->adapt_domain(GVArray(varray), from, to).typed<T>();
+ }
};
namespace curves {
diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc
index 0fd58a52f81..07e50eea88c 100644
--- a/source/blender/blenkernel/intern/curves_geometry.cc
+++ b/source/blender/blenkernel/intern/curves_geometry.cc
@@ -37,6 +37,8 @@ static const std::string ATTR_NURBS_WEIGHT = "nurbs_weight";
static const std::string ATTR_NURBS_KNOTS_MODE = "knots_mode";
static const std::string ATTR_SURFACE_TRIANGLE_INDEX = "surface_triangle_index";
static const std::string ATTR_SURFACE_TRIANGLE_COORDINATE = "surface_triangle_coordinate";
+static const std::string ATTR_SELECTION_POINT_FLOAT = ".selection_point_float";
+static const std::string ATTR_SELECTION_CURVE_FLOAT = ".selection_curve_float";
/* -------------------------------------------------------------------- */
/** \name Constructors/Destructor
@@ -438,6 +440,26 @@ MutableSpan<float2> CurvesGeometry::surface_triangle_coords_for_write()
return get_mutable_attribute<float2>(*this, ATTR_DOMAIN_CURVE, ATTR_SURFACE_TRIANGLE_COORDINATE);
}
+VArray<float> CurvesGeometry::selection_point_float() const
+{
+ return get_varray_attribute<float>(*this, ATTR_DOMAIN_POINT, ATTR_SELECTION_POINT_FLOAT, 1.0f);
+}
+
+MutableSpan<float> CurvesGeometry::selection_point_float_for_write()
+{
+ return get_mutable_attribute<float>(*this, ATTR_DOMAIN_POINT, ATTR_SELECTION_POINT_FLOAT, 1.0f);
+}
+
+VArray<float> CurvesGeometry::selection_curve_float() const
+{
+ return get_varray_attribute<float>(*this, ATTR_DOMAIN_CURVE, ATTR_SELECTION_CURVE_FLOAT, 1.0f);
+}
+
+MutableSpan<float> CurvesGeometry::selection_curve_float_for_write()
+{
+ return get_mutable_attribute<float>(*this, ATTR_DOMAIN_CURVE, ATTR_SELECTION_CURVE_FLOAT, 1.0f);
+}
+
/** \} */
/* -------------------------------------------------------------------- */
diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc
index 5588f7440a8..efa4f884e17 100644
--- a/source/blender/editors/curves/intern/curves_ops.cc
+++ b/source/blender/editors/curves/intern/curves_ops.cc
@@ -6,18 +6,23 @@
#include <atomic>
+#include "BLI_devirtualize_parameters.hh"
#include "BLI_utildefines.h"
+#include "BLI_vector_set.hh"
#include "ED_curves.h"
#include "ED_object.h"
#include "ED_screen.h"
+#include "ED_select_utils.h"
#include "WM_api.h"
#include "BKE_bvhutils.h"
#include "BKE_context.h"
#include "BKE_curves.hh"
+#include "BKE_geometry_set.hh"
#include "BKE_layer.h"
+#include "BKE_lib_id.h"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_object.h"
@@ -37,6 +42,7 @@
#include "RNA_access.h"
#include "RNA_define.h"
+#include "RNA_enum_types.h"
#include "RNA_prototypes.h"
/**
@@ -49,6 +55,41 @@
namespace blender::ed::curves {
+static bool object_has_editable_curves(const Main &bmain, const Object &object)
+{
+ if (object.type != OB_CURVES) {
+ return false;
+ }
+ if (!ELEM(object.mode, OB_MODE_SCULPT_CURVES, OB_MODE_EDIT)) {
+ return false;
+ }
+ if (!BKE_id_is_editable(&bmain, static_cast<const ID *>(object.data))) {
+ return false;
+ }
+ return true;
+}
+
+static VectorSet<Curves *> get_unique_editable_curves(const bContext &C)
+{
+ VectorSet<Curves *> unique_curves;
+
+ const Main &bmain = *CTX_data_main(&C);
+
+ Object *object = CTX_data_active_object(&C);
+ if (object && object_has_editable_curves(bmain, *object)) {
+ unique_curves.add_new(static_cast<Curves *>(object->data));
+ }
+
+ CTX_DATA_BEGIN (&C, Object *, object, selected_objects) {
+ if (object_has_editable_curves(bmain, *object)) {
+ unique_curves.add(static_cast<Curves *>(object->data));
+ }
+ }
+ CTX_DATA_END;
+
+ return unique_curves;
+}
+
using bke::CurvesGeometry;
namespace convert_to_particle_system {
@@ -645,6 +686,211 @@ static void CURVES_OT_snap_curves_to_surface(wmOperatorType *ot)
"How to find the point on the surface to attach to");
}
+static bool selection_poll(bContext *C)
+{
+ const Object *object = CTX_data_active_object(C);
+ if (object == nullptr) {
+ return false;
+ }
+ if (object->type != OB_CURVES) {
+ return false;
+ }
+ if (!BKE_id_is_editable(CTX_data_main(C), static_cast<const ID *>(object->data))) {
+ return false;
+ }
+ return true;
+}
+
+namespace set_selection_domain {
+
+static int curves_set_selection_domain_exec(bContext *C, wmOperator *op)
+{
+ const AttributeDomain domain = AttributeDomain(RNA_enum_get(op->ptr, "domain"));
+
+ for (Curves *curves_id : get_unique_editable_curves(*C)) {
+ if (curves_id->selection_domain == domain && (curves_id->flag & CV_SCULPT_SELECTION_ENABLED)) {
+ continue;
+ }
+
+ const AttributeDomain old_domain = AttributeDomain(curves_id->selection_domain);
+ curves_id->selection_domain = domain;
+ curves_id->flag |= CV_SCULPT_SELECTION_ENABLED;
+
+ CurveComponent component;
+ component.replace(curves_id, GeometryOwnershipType::Editable);
+ CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry);
+
+ if (old_domain == ATTR_DOMAIN_POINT && domain == ATTR_DOMAIN_CURVE) {
+ VArray<float> curve_selection = curves.adapt_domain(
+ curves.selection_point_float(), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE);
+ curve_selection.materialize(curves.selection_curve_float_for_write());
+ component.attribute_try_delete(".selection_point_float");
+ }
+ else if (old_domain == ATTR_DOMAIN_CURVE && domain == ATTR_DOMAIN_POINT) {
+ VArray<float> point_selection = curves.adapt_domain(
+ curves.selection_curve_float(), ATTR_DOMAIN_CURVE, ATTR_DOMAIN_POINT);
+ point_selection.materialize(curves.selection_point_float_for_write());
+ component.attribute_try_delete(".selection_curve_float");
+ }
+
+ /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
+ * attribute for now. */
+ DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id);
+ }
+
+ WM_main_add_notifier(NC_SPACE | ND_SPACE_VIEW3D, nullptr);
+
+ return OPERATOR_FINISHED;
+}
+
+} // namespace set_selection_domain
+
+static void CURVES_OT_set_selection_domain(wmOperatorType *ot)
+{
+ PropertyRNA *prop;
+
+ ot->name = "Set Select Mode";
+ ot->idname = __func__;
+ ot->description = "Change the mode used for selection masking in curves sculpt mode";
+
+ ot->exec = set_selection_domain::curves_set_selection_domain_exec;
+ ot->poll = selection_poll;
+
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ ot->prop = prop = RNA_def_enum(
+ ot->srna, "domain", rna_enum_attribute_curves_domain_items, 0, "Domain", "");
+ RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE));
+}
+
+namespace disable_selection {
+
+static int curves_disable_selection_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ for (Curves *curves_id : get_unique_editable_curves(*C)) {
+ curves_id->flag &= ~CV_SCULPT_SELECTION_ENABLED;
+
+ /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
+ * attribute for now. */
+ DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id);
+ }
+
+ WM_main_add_notifier(NC_SPACE | ND_SPACE_VIEW3D, nullptr);
+
+ return OPERATOR_FINISHED;
+}
+
+} // namespace disable_selection
+
+static void CURVES_OT_disable_selection(wmOperatorType *ot)
+{
+ ot->name = "Disable Selection";
+ ot->idname = __func__;
+ ot->description = "Disable the drawing of influence of selection in sculpt mode";
+
+ ot->exec = disable_selection::curves_disable_selection_exec;
+ ot->poll = selection_poll;
+
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+namespace select_all {
+
+static bool varray_contains_nonzero(const VArray<float> &data)
+{
+ bool contains_nonzero = false;
+ devirtualize_varray(data, [&](const auto array) {
+ for (const int i : data.index_range()) {
+ if (array[i] != 0.0f) {
+ contains_nonzero = true;
+ break;
+ }
+ }
+ });
+ return contains_nonzero;
+}
+
+static bool any_point_selected(const CurvesGeometry &curves)
+{
+ return varray_contains_nonzero(curves.selection_point_float());
+}
+
+static bool any_point_selected(const Span<Curves *> curves_ids)
+{
+ for (const Curves *curves_id : curves_ids) {
+ if (any_point_selected(CurvesGeometry::wrap(curves_id->geometry))) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void invert_selection(MutableSpan<float> selection)
+{
+ threading::parallel_for(selection.index_range(), 2048, [&](IndexRange range) {
+ for (const int i : range) {
+ selection[i] = 1.0f - selection[i];
+ }
+ });
+}
+
+static int select_all_exec(bContext *C, wmOperator *op)
+{
+ int action = RNA_enum_get(op->ptr, "action");
+
+ VectorSet<Curves *> unique_curves = get_unique_editable_curves(*C);
+
+ if (action == SEL_TOGGLE) {
+ action = any_point_selected(unique_curves) ? SEL_DESELECT : SEL_SELECT;
+ }
+
+ for (Curves *curves_id : unique_curves) {
+ if (action == SEL_SELECT) {
+ CurveComponent component;
+ component.replace(curves_id, GeometryOwnershipType::Editable);
+ component.attribute_try_delete(".selection_point_float");
+ component.attribute_try_delete(".selection_curve_float");
+ }
+ else {
+ CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry);
+ MutableSpan<float> selection = curves_id->selection_domain == ATTR_DOMAIN_POINT ?
+ curves.selection_point_float_for_write() :
+ curves.selection_curve_float_for_write();
+ if (action == SEL_DESELECT) {
+ selection.fill(0.0f);
+ }
+ else if (action == SEL_INVERT) {
+ invert_selection(selection);
+ }
+ }
+
+ /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic
+ * attribute for now. */
+ DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id);
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+} // namespace select_all
+
+static void SCULPT_CURVES_OT_select_all(wmOperatorType *ot)
+{
+ ot->name = "(De)select All";
+ ot->idname = __func__;
+ ot->description = "(De)select all control points";
+
+ ot->exec = select_all::select_all_exec;
+ ot->poll = selection_poll;
+
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ WM_operator_properties_select_all(ot);
+}
+
} // namespace blender::ed::curves
void ED_operatortypes_curves()
@@ -653,4 +899,7 @@ void ED_operatortypes_curves()
WM_operatortype_append(CURVES_OT_convert_to_particle_system);
WM_operatortype_append(CURVES_OT_convert_from_particle_system);
WM_operatortype_append(CURVES_OT_snap_curves_to_surface);
+ WM_operatortype_append(CURVES_OT_set_selection_domain);
+ WM_operatortype_append(SCULPT_CURVES_OT_select_all);
+ WM_operatortype_append(CURVES_OT_disable_selection);
}
diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt
index d3bf28798c4..dfd8f8b1bb9 100644
--- a/source/blender/editors/sculpt_paint/CMakeLists.txt
+++ b/source/blender/editors/sculpt_paint/CMakeLists.txt
@@ -33,6 +33,8 @@ set(SRC
curves_sculpt_delete.cc
curves_sculpt_grow_shrink.cc
curves_sculpt_ops.cc
+ curves_sculpt_selection.cc
+ curves_sculpt_selection_paint.cc
curves_sculpt_snake_hook.cc
paint_canvas.cc
paint_cursor.c
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc
index d7f4b71d2d0..fea0e862707 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc
@@ -652,6 +652,8 @@ struct AddOperationExecutor {
Array<float3> new_normals_su = this->compute_normals_for_added_curves_su(added_points);
this->initialize_surface_attachment(added_points);
+ this->fill_new_selection();
+
if (interpolate_shape_) {
this->initialize_position_with_interpolation(
added_points, neighbors_per_curve, new_normals_su, new_lengths_cu);
@@ -662,6 +664,33 @@ struct AddOperationExecutor {
}
}
+ /**
+ * Select newly created points or curves in new curves if necessary.
+ */
+ void fill_new_selection()
+ {
+ switch (curves_id_->selection_domain) {
+ case ATTR_DOMAIN_CURVE: {
+ const VArray<float> selection = curves_->selection_curve_float();
+ if (selection.is_single() && selection.get_internal_single() >= 1.0f) {
+ return;
+ }
+ curves_->selection_curve_float_for_write().drop_front(tot_old_curves_).fill(1.0f);
+ break;
+ }
+ case ATTR_DOMAIN_POINT: {
+ const VArray<float> selection = curves_->selection_point_float();
+ if (selection.is_single() && selection.get_internal_single() >= 1.0f) {
+ return;
+ }
+ curves_->selection_point_float_for_write().drop_front(tot_old_points_).fill(1.0f);
+ break;
+ }
+ default:
+ BLI_assert_unreachable();
+ }
+ }
+
Array<NeighborsVector> find_curve_neighbors(const AddedPoints &added_points)
{
const int tot_added_curves = added_points.bary_coords.size();
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc b/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc
index 1fcab2290e8..46332a66f60 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc
@@ -96,6 +96,10 @@ struct CombOperationExecutor {
Curves *curves_id_ = nullptr;
CurvesGeometry *curves_ = nullptr;
+ VArray<float> point_factors_;
+ Vector<int64_t> selected_curve_indices_;
+ IndexMask curve_selection_;
+
const Object *surface_ob_ = nullptr;
const Mesh *surface_ = nullptr;
Span<MLoopTri> surface_looptris_;
@@ -142,6 +146,9 @@ struct CombOperationExecutor {
return;
}
+ point_factors_ = get_point_selection(*curves_id_);
+ curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
+
brush_pos_prev_re_ = self_->brush_pos_last_re_;
brush_pos_re_ = stroke_extension.mouse_position;
brush_pos_diff_re_ = brush_pos_re_ - brush_pos_prev_re_;
@@ -217,9 +224,9 @@ struct CombOperationExecutor {
const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
const float brush_radius_sq_re = pow2f(brush_radius_re);
- threading::parallel_for(curves_->curves_range(), 256, [&](const IndexRange curves_range) {
+ threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
Vector<int> &local_changed_curves = r_changed_curves.local();
- for (const int curve_i : curves_range) {
+ for (const int curve_i : curve_selection_.slice(range)) {
bool curve_changed = false;
const IndexRange points = curves_->points_for_curve(curve_i);
for (const int point_i : points.drop_front(1)) {
@@ -241,9 +248,10 @@ struct CombOperationExecutor {
const float radius_falloff = BKE_brush_curve_strength(
brush_, distance_to_brush_re, brush_radius_re);
/* Combine the falloff and brush strength. */
- const float weight = brush_strength_ * radius_falloff;
+ const float weight = brush_strength_ * radius_falloff * point_factors_[point_i];
- /* Offset the old point position in screen space and transform it back into 3D space. */
+ /* Offset the old point position in screen space and transform it back into 3D space.
+ */
const float2 new_position_re = old_pos_re + brush_pos_diff_re_ * weight;
float3 new_position_wo;
ED_view3d_win_to_3d(
@@ -304,9 +312,9 @@ struct CombOperationExecutor {
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
const float3 brush_diff_cu = brush_end_cu - brush_start_cu;
- threading::parallel_for(curves_->curves_range(), 256, [&](const IndexRange curves_range) {
+ threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
Vector<int> &local_changed_curves = r_changed_curves.local();
- for (const int curve_i : curves_range) {
+ for (const int curve_i : curve_selection_.slice(range)) {
bool curve_changed = false;
const IndexRange points = curves_->points_for_curve(curve_i);
for (const int point_i : points.drop_front(1)) {
@@ -326,7 +334,7 @@ struct CombOperationExecutor {
const float radius_falloff = BKE_brush_curve_strength(
brush_, distance_to_brush_cu, brush_radius_cu);
/* Combine the falloff and brush strength. */
- const float weight = brush_strength_ * radius_falloff;
+ const float weight = brush_strength_ * radius_falloff * point_factors_[point_i];
/* Update the point position. */
positions_cu[point_i] = pos_old_cu + weight * brush_diff_cu;
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc b/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc
index 323e99df099..5a192346f39 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc
@@ -70,6 +70,9 @@ struct DeleteOperationExecutor {
Curves *curves_id_ = nullptr;
CurvesGeometry *curves_ = nullptr;
+ Vector<int64_t> selected_curve_indices_;
+ IndexMask curve_selection_;
+
const CurvesSculpt *curves_sculpt_ = nullptr;
const Brush *brush_ = nullptr;
float brush_radius_base_re_;
@@ -94,6 +97,9 @@ struct DeleteOperationExecutor {
curves_id_ = static_cast<Curves *>(object_->data);
curves_ = &CurvesGeometry::wrap(curves_id_->geometry);
+ selected_curve_indices_.clear();
+ curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
+
curves_sculpt_ = scene_->toolsettings->curves_sculpt;
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
brush_radius_base_re_ = BKE_brush_size_get(scene_, brush_);
@@ -158,8 +164,8 @@ struct DeleteOperationExecutor {
const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
const float brush_radius_sq_re = pow2f(brush_radius_re);
- threading::parallel_for(curves_->curves_range(), 512, [&](IndexRange curve_range) {
- for (const int curve_i : curve_range) {
+ threading::parallel_for(curve_selection_.index_range(), 512, [&](const IndexRange range) {
+ for (const int curve_i : curve_selection_.slice(range)) {
const IndexRange points = curves_->points_for_curve(curve_i);
if (points.size() == 1) {
const float3 pos_cu = brush_transform_inv * positions_cu[points.first()];
@@ -219,8 +225,8 @@ struct DeleteOperationExecutor {
const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_;
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
- threading::parallel_for(curves_->curves_range(), 512, [&](IndexRange curve_range) {
- for (const int curve_i : curve_range) {
+ threading::parallel_for(curve_selection_.index_range(), 512, [&](const IndexRange range) {
+ for (const int curve_i : curve_selection_.slice(range)) {
const IndexRange points = curves_->points_for_curve(curve_i);
if (points.size() == 1) {
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc
index 25eb8041f4c..d87828e7437 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc
@@ -280,6 +280,10 @@ struct CurvesEffectOperationExecutor {
Curves *curves_id_ = nullptr;
CurvesGeometry *curves_ = nullptr;
+ VArray<float> curve_selection_factors_;
+ Vector<int64_t> selected_curve_indices_;
+ IndexMask curve_selection_;
+
const Brush *brush_ = nullptr;
float brush_radius_base_re_;
float brush_radius_factor_;
@@ -318,6 +322,9 @@ struct CurvesEffectOperationExecutor {
return;
}
+ curve_selection_factors_ = get_curves_selection(*curves_id_);
+ curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
+
const CurvesSculpt &curves_sculpt = *scene_->toolsettings->curves_sculpt;
brush_ = BKE_paint_brush_for_read(&curves_sculpt.paint);
brush_strength_ = brush_strength_get(*scene_, *brush_, stroke_extension);
@@ -396,6 +403,8 @@ struct CurvesEffectOperationExecutor {
for (const int curve_i : curves_range) {
const IndexRange points = curves_->points_for_curve(curve_i);
+ const float curve_selection_factor = curve_selection_factors_[curve_i];
+
float max_move_distance_cu = 0.0f;
for (const float4x4 &brush_transform_inv : symmetry_brush_transforms_inv) {
for (const int segment_i : points.drop_back(1)) {
@@ -426,7 +435,7 @@ struct CurvesEffectOperationExecutor {
const float dist_to_brush_re = std::sqrt(dist_to_brush_sq_re);
const float radius_falloff = BKE_brush_curve_strength(
brush_, dist_to_brush_re, brush_radius_re);
- const float weight = brush_strength_ * radius_falloff;
+ const float weight = brush_strength_ * radius_falloff * curve_selection_factor;
const float3 closest_on_segment_cu = math::interpolate(
p1_cu, p2_cu, lambda_on_segment);
@@ -491,6 +500,9 @@ struct CurvesEffectOperationExecutor {
const IndexRange points = curves_->points_for_curve(curve_i);
float max_move_distance_cu = 0.0f;
+
+ const float curve_selection_factor = curve_selection_factors_[curve_i];
+
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
const float3 brush_pos_start_transformed_cu = brush_transform * brush_pos_start_cu;
const float3 brush_pos_end_transformed_cu = brush_transform * brush_pos_end_cu;
@@ -517,7 +529,7 @@ struct CurvesEffectOperationExecutor {
const float dist_to_brush_cu = std::sqrt(dist_to_brush_sq_cu);
const float radius_falloff = BKE_brush_curve_strength(
brush_, dist_to_brush_cu, brush_radius_cu);
- const float weight = brush_strength_ * radius_falloff;
+ const float weight = brush_strength_ * radius_falloff * curve_selection_factor;
const float move_distance_cu = weight * brush_pos_diff_length_cu;
max_move_distance_cu = std::max(max_move_distance_cu, move_distance_cu);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
index 842de234761..3635ad84c36 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
@@ -8,7 +8,10 @@
#include "paint_intern.h"
#include "BLI_math_vector.hh"
+#include "BLI_vector.hh"
+#include "BLI_virtual_array.hh"
+#include "BKE_attribute.h"
#include "BKE_curves.hh"
struct ARegion;
@@ -55,6 +58,8 @@ std::unique_ptr<CurvesSculptStrokeOperation> new_delete_operation();
std::unique_ptr<CurvesSculptStrokeOperation> new_snake_hook_operation();
std::unique_ptr<CurvesSculptStrokeOperation> new_grow_shrink_operation(
const BrushStrokeMode brush_mode, const bContext &C);
+std::unique_ptr<CurvesSculptStrokeOperation> new_selection_paint_operation(
+ const BrushStrokeMode brush_mode, const bContext &C);
struct CurvesBrush3D {
float3 position_cu;
@@ -74,4 +79,20 @@ std::optional<CurvesBrush3D> sample_curves_3d_brush(const Depsgraph &depsgraph,
Vector<float4x4> get_symmetry_brush_transforms(eCurvesSymmetryType symmetry);
+/**
+ * Get the floating point selection on the curve domain, averaged from points if necessary.
+ */
+VArray<float> get_curves_selection(const Curves &curves_id);
+
+/**
+ * Get the floating point selection on the curve domain, copied from curves if necessary.
+ */
+VArray<float> get_point_selection(const Curves &curves_id);
+
+/**
+ * Find curves that have any point selected (a selection factor greater than zero),
+ * or curves that have their own selection factor greater than zero.
+ */
+IndexMask retrieve_selected_curves(const Curves &curves_id, Vector<int64_t> &r_indices);
+
} // namespace blender::ed::sculpt_paint
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
index 15d0f73592d..aa2ec54c73f 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
@@ -123,6 +123,8 @@ static std::unique_ptr<CurvesSculptStrokeOperation> start_brush_operation(bConte
return new_add_operation(C, op.reports);
case CURVES_SCULPT_TOOL_GROW_SHRINK:
return new_grow_shrink_operation(mode, C);
+ case CURVES_SCULPT_TOOL_SELECTION_PAINT:
+ return new_selection_paint_operation(mode, C);
}
BLI_assert_unreachable();
return {};
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_selection.cc b/source/blender/editors/sculpt_paint/curves_sculpt_selection.cc
new file mode 100644
index 00000000000..9a14f0cfef0
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_selection.cc
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BLI_index_mask_ops.hh"
+
+#include "BKE_curves.hh"
+
+#include "curves_sculpt_intern.hh"
+
+namespace blender::ed::sculpt_paint {
+
+static VArray<float> get_curves_selection(const CurvesGeometry &curves,
+ const AttributeDomain domain)
+{
+ switch (domain) {
+ case ATTR_DOMAIN_CURVE:
+ return curves.selection_curve_float();
+ case ATTR_DOMAIN_POINT:
+ return curves.adapt_domain(
+ curves.selection_point_float(), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE);
+ default:
+ BLI_assert_unreachable();
+ return {};
+ }
+}
+
+VArray<float> get_curves_selection(const Curves &curves_id)
+{
+ if (!(curves_id.flag & CV_SCULPT_SELECTION_ENABLED)) {
+ return VArray<float>::ForSingle(1.0f, CurvesGeometry::wrap(curves_id.geometry).curves_num());
+ }
+ return get_curves_selection(CurvesGeometry::wrap(curves_id.geometry),
+ AttributeDomain(curves_id.selection_domain));
+}
+
+static VArray<float> get_point_selection(const CurvesGeometry &curves,
+ const AttributeDomain domain)
+{
+ switch (domain) {
+ case ATTR_DOMAIN_CURVE:
+ return curves.adapt_domain(
+ curves.selection_curve_float(), ATTR_DOMAIN_CURVE, ATTR_DOMAIN_POINT);
+ case ATTR_DOMAIN_POINT:
+ return curves.selection_point_float();
+ default:
+ BLI_assert_unreachable();
+ return {};
+ }
+}
+
+VArray<float> get_point_selection(const Curves &curves_id)
+{
+ if (!(curves_id.flag & CV_SCULPT_SELECTION_ENABLED)) {
+ return VArray<float>::ForSingle(1.0f, CurvesGeometry::wrap(curves_id.geometry).points_num());
+ }
+ return get_point_selection(CurvesGeometry::wrap(curves_id.geometry),
+ AttributeDomain(curves_id.selection_domain));
+}
+
+static IndexMask retrieve_selected_curves(const CurvesGeometry &curves,
+ const AttributeDomain domain,
+ Vector<int64_t> &r_indices)
+{
+ switch (domain) {
+ case ATTR_DOMAIN_POINT: {
+ const VArray<float> selection = curves.selection_point_float();
+ if (selection.is_single()) {
+ return selection.get_internal_single() == 0.0f ? IndexMask(0) :
+ IndexMask(curves.curves_num());
+ }
+ return index_mask_ops::find_indices_based_on_predicate(
+ curves.curves_range(), 512, r_indices, [&](const int curve_i) {
+ for (const int i : curves.points_for_curve(curve_i)) {
+ if (selection[i] > 0.0f) {
+ return true;
+ }
+ }
+ return false;
+ });
+ }
+ case ATTR_DOMAIN_CURVE: {
+ const VArray<float> selection = curves.selection_curve_float();
+ if (selection.is_single()) {
+ return selection.get_internal_single() == 0.0f ? IndexMask(0) :
+ IndexMask(curves.curves_num());
+ }
+ return index_mask_ops::find_indices_based_on_predicate(
+ curves.curves_range(), 2048, r_indices, [&](const int i) {
+ return selection[i] > 0.0f;
+ });
+ }
+ default:
+ BLI_assert_unreachable();
+ return {};
+ }
+}
+
+IndexMask retrieve_selected_curves(const Curves &curves_id, Vector<int64_t> &r_indices)
+{
+ if (!(curves_id.flag & CV_SCULPT_SELECTION_ENABLED)) {
+ return CurvesGeometry::wrap(curves_id.geometry).curves_range();
+ }
+ return retrieve_selected_curves(CurvesGeometry::wrap(curves_id.geometry),
+ AttributeDomain(curves_id.selection_domain),
+ r_indices);
+}
+
+} // namespace blender::ed::sculpt_paint
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc b/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc
new file mode 100644
index 00000000000..250987466d5
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc
@@ -0,0 +1,394 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <algorithm>
+#include <numeric>
+
+#include "BLI_memory_utils.hh"
+#include "BLI_task.hh"
+
+#include "DNA_brush_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_context.h"
+#include "BKE_curves.hh"
+
+#include "DEG_depsgraph.h"
+
+#include "ED_screen.h"
+#include "ED_view3d.h"
+
+#include "WM_api.h"
+
+#include "curves_sculpt_intern.hh"
+
+/**
+ * The code below uses a suffix naming convention to indicate the coordinate space:
+ * cu: Local space of the curves object that is being edited.
+ * wo: World space.
+ * re: 2D coordinates within the region.
+ */
+
+namespace blender::ed::sculpt_paint {
+
+using bke::CurvesGeometry;
+
+class SelectionPaintOperation : public CurvesSculptStrokeOperation {
+ private:
+ bool use_select_;
+ bool clear_selection_;
+
+ CurvesBrush3D brush_3d_;
+
+ friend struct SelectionPaintOperationExecutor;
+
+ public:
+ SelectionPaintOperation(const bool use_select, const bool clear_selection)
+ : use_select_(use_select), clear_selection_(clear_selection)
+ {
+ }
+ void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
+};
+
+struct SelectionPaintOperationExecutor {
+ SelectionPaintOperation *self_ = nullptr;
+ const bContext *C_ = nullptr;
+ const Depsgraph *depsgraph_ = nullptr;
+ const Scene *scene_ = nullptr;
+ ARegion *region_ = nullptr;
+ const View3D *v3d_ = nullptr;
+ const RegionView3D *rv3d_ = nullptr;
+
+ Object *object_ = nullptr;
+ Curves *curves_id_ = nullptr;
+ CurvesGeometry *curves_ = nullptr;
+
+ const Brush *brush_ = nullptr;
+ float brush_radius_base_re_;
+ float brush_radius_factor_;
+ float brush_strength_;
+
+ float selection_goal_;
+
+ float2 brush_pos_re_;
+
+ float4x4 curves_to_world_mat_;
+ float4x4 world_to_curves_mat_;
+
+ void execute(SelectionPaintOperation &self,
+ const bContext &C,
+ const StrokeExtension &stroke_extension)
+ {
+ self_ = &self;
+ depsgraph_ = CTX_data_depsgraph_pointer(&C);
+ scene_ = CTX_data_scene(&C);
+ object_ = CTX_data_active_object(&C);
+ region_ = CTX_wm_region(&C);
+ v3d_ = CTX_wm_view3d(&C);
+ rv3d_ = CTX_wm_region_view3d(&C);
+
+ curves_id_ = static_cast<Curves *>(object_->data);
+ curves_ = &CurvesGeometry::wrap(curves_id_->geometry);
+ curves_id_->flag |= CV_SCULPT_SELECTION_ENABLED;
+
+ brush_ = BKE_paint_brush_for_read(&scene_->toolsettings->curves_sculpt->paint);
+ brush_radius_base_re_ = BKE_brush_size_get(scene_, brush_);
+ brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
+ brush_strength_ = BKE_brush_alpha_get(scene_, brush_);
+
+ brush_pos_re_ = stroke_extension.mouse_position;
+
+ if (self.clear_selection_) {
+ if (stroke_extension.is_first) {
+ if (curves_id_->selection_domain == ATTR_DOMAIN_POINT) {
+ curves_->selection_point_float_for_write().fill(0.0f);
+ }
+ else if (curves_id_->selection_domain == ATTR_DOMAIN_CURVE) {
+ curves_->selection_curve_float_for_write().fill(0.0f);
+ }
+ }
+ }
+
+ curves_to_world_mat_ = object_->obmat;
+ world_to_curves_mat_ = curves_to_world_mat_.inverted();
+
+ const eBrushFalloffShape falloff_shape = static_cast<eBrushFalloffShape>(
+ brush_->falloff_shape);
+
+ selection_goal_ = self_->use_select_ ? 1.0f : 0.0f;
+
+ if (stroke_extension.is_first) {
+ if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
+ this->initialize_spherical_brush_reference_point();
+ }
+ }
+
+ if (curves_id_->selection_domain == ATTR_DOMAIN_POINT) {
+ MutableSpan<float> selection = curves_->selection_point_float_for_write();
+ if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
+ this->paint_point_selection_projected_with_symmetry(selection);
+ }
+ else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
+ this->paint_point_selection_spherical_with_symmetry(selection);
+ }
+ }
+ else {
+ MutableSpan<float> selection = curves_->selection_curve_float_for_write();
+ if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
+ this->paint_curve_selection_projected_with_symmetry(selection);
+ }
+ else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
+ this->paint_curve_selection_spherical_with_symmetry(selection);
+ }
+ }
+
+ /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because
+ * selection is handled as a generic attribute for now. */
+ DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
+ WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
+ ED_region_tag_redraw(region_);
+ }
+
+ void paint_point_selection_projected_with_symmetry(MutableSpan<float> selection)
+ {
+ const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
+ eCurvesSymmetryType(curves_id_->symmetry));
+ for (const float4x4 &brush_transform : symmetry_brush_transforms) {
+ this->paint_point_selection_projected(brush_transform, selection);
+ }
+ }
+
+ void paint_point_selection_projected(const float4x4 &brush_transform,
+ MutableSpan<float> selection)
+ {
+ const float4x4 brush_transform_inv = brush_transform.inverted();
+
+ float4x4 projection;
+ ED_view3d_ob_project_mat_get(rv3d_, object_, projection.values);
+
+ Span<float3> positions_cu = curves_->positions();
+
+ const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
+ const float brush_radius_sq_re = pow2f(brush_radius_re);
+
+ threading::parallel_for(curves_->points_range(), 1024, [&](const IndexRange point_range) {
+ for (const int point_i : point_range) {
+ const float3 pos_cu = brush_transform_inv * positions_cu[point_i];
+
+ /* Find the position of the point in screen space. */
+ float2 pos_re;
+ ED_view3d_project_float_v2_m4(region_, pos_cu, pos_re, projection.values);
+
+ const float distance_to_brush_sq_re = math::distance_squared(pos_re, brush_pos_re_);
+ if (distance_to_brush_sq_re > brush_radius_sq_re) {
+ /* Ignore the point because it's too far away. */
+ continue;
+ }
+
+ const float distance_to_brush_re = std::sqrt(distance_to_brush_sq_re);
+ /* A falloff that is based on how far away the point is from the stroke. */
+ const float radius_falloff = BKE_brush_curve_strength(
+ brush_, distance_to_brush_re, brush_radius_re);
+ /* Combine the falloff and brush strength. */
+ const float weight = brush_strength_ * radius_falloff;
+
+ selection[point_i] = math::interpolate(selection[point_i], selection_goal_, weight);
+ }
+ });
+ }
+
+ void paint_point_selection_spherical_with_symmetry(MutableSpan<float> selection)
+ {
+ float4x4 projection;
+ ED_view3d_ob_project_mat_get(rv3d_, object_, projection.values);
+
+ float3 brush_wo;
+ ED_view3d_win_to_3d(v3d_,
+ region_,
+ curves_to_world_mat_ * self_->brush_3d_.position_cu,
+ brush_pos_re_,
+ brush_wo);
+ const float3 brush_cu = world_to_curves_mat_ * brush_wo;
+
+ const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
+ eCurvesSymmetryType(curves_id_->symmetry));
+
+ for (const float4x4 &brush_transform : symmetry_brush_transforms) {
+ this->paint_point_selection_spherical(selection, brush_transform * brush_cu);
+ }
+ }
+
+ void paint_point_selection_spherical(MutableSpan<float> selection, const float3 &brush_cu)
+ {
+ Span<float3> positions_cu = curves_->positions();
+
+ const float brush_radius_cu = self_->brush_3d_.radius_cu;
+ const float brush_radius_sq_cu = pow2f(brush_radius_cu);
+
+ threading::parallel_for(curves_->points_range(), 1024, [&](const IndexRange point_range) {
+ for (const int i : point_range) {
+ const float3 pos_old_cu = positions_cu[i];
+
+ /* Compute distance to the brush. */
+ const float distance_to_brush_sq_cu = math::distance_squared(pos_old_cu, brush_cu);
+ if (distance_to_brush_sq_cu > brush_radius_sq_cu) {
+ /* Ignore the point because it's too far away. */
+ continue;
+ }
+
+ const float distance_to_brush_cu = std::sqrt(distance_to_brush_sq_cu);
+
+ /* A falloff that is based on how far away the point is from the stroke. */
+ const float radius_falloff = BKE_brush_curve_strength(
+ brush_, distance_to_brush_cu, brush_radius_cu);
+ /* Combine the falloff and brush strength. */
+ const float weight = brush_strength_ * radius_falloff;
+
+ selection[i] = math::interpolate(selection[i], selection_goal_, weight);
+ }
+ });
+ }
+
+ void paint_curve_selection_projected_with_symmetry(MutableSpan<float> selection)
+ {
+ const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
+ eCurvesSymmetryType(curves_id_->symmetry));
+ for (const float4x4 &brush_transform : symmetry_brush_transforms) {
+ this->paint_curve_selection_projected(brush_transform, selection);
+ }
+ }
+
+ void paint_curve_selection_projected(const float4x4 &brush_transform,
+ MutableSpan<float> selection)
+ {
+ const Span<float3> positions_cu = curves_->positions();
+ const float4x4 brush_transform_inv = brush_transform.inverted();
+
+ float4x4 projection;
+ ED_view3d_ob_project_mat_get(rv3d_, object_, projection.values);
+
+ const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
+ const float brush_radius_sq_re = pow2f(brush_radius_re);
+
+ threading::parallel_for(curves_->curves_range(), 1024, [&](const IndexRange curves_range) {
+ for (const int curve_i : curves_range) {
+ const float max_weight = threading::parallel_reduce(
+ curves_->points_for_curve(curve_i).drop_back(1),
+ 1024,
+ 0.0f,
+ [&](const IndexRange segment_range, const float init) {
+ float max_weight = init;
+ for (const int segment_i : segment_range) {
+ const float3 pos1_cu = brush_transform_inv * positions_cu[segment_i];
+ const float3 pos2_cu = brush_transform_inv * positions_cu[segment_i + 1];
+
+ float2 pos1_re;
+ float2 pos2_re;
+ ED_view3d_project_float_v2_m4(region_, pos1_cu, pos1_re, projection.values);
+ ED_view3d_project_float_v2_m4(region_, pos2_cu, pos2_re, projection.values);
+
+ const float distance_sq_re = dist_squared_to_line_segment_v2(
+ brush_pos_re_, pos1_re, pos2_re);
+ if (distance_sq_re > brush_radius_sq_re) {
+ continue;
+ }
+ const float radius_falloff = BKE_brush_curve_strength(
+ brush_, std::sqrt(distance_sq_re), brush_radius_re);
+ const float weight = brush_strength_ * radius_falloff;
+ max_weight = std::max(max_weight, weight);
+ }
+ return max_weight;
+ },
+ [](float a, float b) { return std::max(a, b); });
+ selection[curve_i] = math::interpolate(selection[curve_i], selection_goal_, max_weight);
+ }
+ });
+ }
+
+ void paint_curve_selection_spherical_with_symmetry(MutableSpan<float> selection)
+ {
+ float4x4 projection;
+ ED_view3d_ob_project_mat_get(rv3d_, object_, projection.values);
+
+ float3 brush_wo;
+ ED_view3d_win_to_3d(v3d_,
+ region_,
+ curves_to_world_mat_ * self_->brush_3d_.position_cu,
+ brush_pos_re_,
+ brush_wo);
+ const float3 brush_cu = world_to_curves_mat_ * brush_wo;
+
+ const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
+ eCurvesSymmetryType(curves_id_->symmetry));
+
+ for (const float4x4 &brush_transform : symmetry_brush_transforms) {
+ this->paint_curve_selection_spherical(selection, brush_transform * brush_cu);
+ }
+ }
+
+ void paint_curve_selection_spherical(MutableSpan<float> selection, const float3 &brush_cu)
+ {
+ const Span<float3> positions_cu = curves_->positions();
+
+ const float brush_radius_cu = self_->brush_3d_.radius_cu;
+ const float brush_radius_sq_cu = pow2f(brush_radius_cu);
+
+ threading::parallel_for(curves_->curves_range(), 1024, [&](const IndexRange curves_range) {
+ for (const int curve_i : curves_range) {
+ const float max_weight = threading::parallel_reduce(
+ curves_->points_for_curve(curve_i).drop_back(1),
+ 1024,
+ 0.0f,
+ [&](const IndexRange segment_range, const float init) {
+ float max_weight = init;
+ for (const int segment_i : segment_range) {
+ const float3 &pos1_cu = positions_cu[segment_i];
+ const float3 &pos2_cu = positions_cu[segment_i + 1];
+
+ const float distance_sq_cu = dist_squared_to_line_segment_v3(
+ brush_cu, pos1_cu, pos2_cu);
+ if (distance_sq_cu > brush_radius_sq_cu) {
+ continue;
+ }
+ const float radius_falloff = BKE_brush_curve_strength(
+ brush_, std::sqrt(distance_sq_cu), brush_radius_cu);
+ const float weight = brush_strength_ * radius_falloff;
+ max_weight = std::max(max_weight, weight);
+ }
+ return max_weight;
+ },
+ [](float a, float b) { return std::max(a, b); });
+ selection[curve_i] = math::interpolate(selection[curve_i], selection_goal_, max_weight);
+ }
+ });
+ }
+
+ void initialize_spherical_brush_reference_point()
+ {
+ std::optional<CurvesBrush3D> brush_3d = sample_curves_3d_brush(
+ *depsgraph_, *region_, *v3d_, *rv3d_, *object_, brush_pos_re_, brush_radius_base_re_);
+ if (brush_3d.has_value()) {
+ self_->brush_3d_ = *brush_3d;
+ }
+ }
+};
+
+void SelectionPaintOperation::on_stroke_extended(const bContext &C,
+ const StrokeExtension &stroke_extension)
+{
+ SelectionPaintOperationExecutor executor;
+ executor.execute(*this, C, stroke_extension);
+}
+
+std::unique_ptr<CurvesSculptStrokeOperation> new_selection_paint_operation(
+ const BrushStrokeMode brush_mode, const bContext &C)
+{
+ Scene &scene = *CTX_data_scene(&C);
+ Brush &brush = *BKE_paint_brush(&scene.toolsettings->curves_sculpt->paint);
+ const bool use_select = ELEM(brush_mode, BRUSH_STROKE_INVERT) ==
+ ((brush.flag & BRUSH_DIR_IN) != 0);
+ const bool clear_selection = use_select && brush_mode != BRUSH_STROKE_SMOOTH;
+
+ return std::make_unique<SelectionPaintOperation>(use_select, clear_selection);
+}
+
+} // namespace blender::ed::sculpt_paint
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc b/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc
index bcdeaaeabf2..f834d1e350f 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc
@@ -90,6 +90,10 @@ struct SnakeHookOperatorExecutor {
Curves *curves_id_ = nullptr;
CurvesGeometry *curves_ = nullptr;
+ VArray<float> curve_factors_;
+ Vector<int64_t> selected_curve_indices_;
+ IndexMask curve_selection_;
+
float4x4 curves_to_world_mat_;
float4x4 world_to_curves_mat_;
@@ -130,6 +134,9 @@ struct SnakeHookOperatorExecutor {
return;
}
+ curve_factors_ = get_curves_selection(*curves_id_);
+ curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
+
brush_pos_prev_re_ = self.last_mouse_position_re_;
brush_pos_re_ = stroke_extension.mouse_position;
brush_pos_diff_re_ = brush_pos_re_ - brush_pos_prev_re_;
@@ -199,7 +206,7 @@ struct SnakeHookOperatorExecutor {
const float radius_falloff = BKE_brush_curve_strength(
brush_, std::sqrt(distance_to_brush_sq_re), brush_radius_re);
- const float weight = brush_strength_ * radius_falloff;
+ const float weight = brush_strength_ * radius_falloff * curve_factors_[curve_i];
const float2 new_position_re = old_pos_re + brush_pos_diff_re_ * weight;
float3 new_position_wo;
@@ -265,7 +272,7 @@ struct SnakeHookOperatorExecutor {
const float radius_falloff = BKE_brush_curve_strength(
brush_, distance_to_brush_cu, brush_radius_cu);
- const float weight = brush_strength_ * radius_falloff;
+ const float weight = brush_strength_ * radius_falloff * curve_factors_[curve_i];
const float3 new_pos_cu = old_pos_cu + weight * brush_diff_cu;
diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h
index f409d1c0442..f2cd49b6dea 100644
--- a/source/blender/makesdna/DNA_brush_enums.h
+++ b/source/blender/makesdna/DNA_brush_enums.h
@@ -468,6 +468,7 @@ typedef enum eBrushCurvesSculptTool {
CURVES_SCULPT_TOOL_SNAKE_HOOK = 2,
CURVES_SCULPT_TOOL_ADD = 3,
CURVES_SCULPT_TOOL_GROW_SHRINK = 4,
+ CURVES_SCULPT_TOOL_SELECTION_PAINT = 5,
} eBrushCurvesSculptTool;
/** When #BRUSH_ACCUMULATE is used */
diff --git a/source/blender/makesdna/DNA_curves_types.h b/source/blender/makesdna/DNA_curves_types.h
index 2388f04cc39..45b2309c5bf 100644
--- a/source/blender/makesdna/DNA_curves_types.h
+++ b/source/blender/makesdna/DNA_curves_types.h
@@ -141,7 +141,12 @@ typedef struct Curves {
* symmetrical geometry.
*/
char symmetry;
- char _pad2[5];
+ /**
+ * #AttributeDomain. The active selection mode domain. At most one selection mode can be active
+ * at a time.
+ */
+ char selection_domain;
+ char _pad[4];
/**
* Used as base mesh when curves represent e.g. hair or fur. This surface is used in edit modes.
@@ -159,6 +164,7 @@ typedef struct Curves {
/** #Curves.flag */
enum {
HA_DS_EXPAND = (1 << 0),
+ CV_SCULPT_SELECTION_ENABLED = (1 << 1),
};
/** #Curves.symmetry */
diff --git a/source/blender/makesrna/RNA_enum_items.h b/source/blender/makesrna/RNA_enum_items.h
index 8c42bbe2ee6..37af598729b 100644
--- a/source/blender/makesrna/RNA_enum_items.h
+++ b/source/blender/makesrna/RNA_enum_items.h
@@ -209,6 +209,7 @@ DEF_ENUM(rna_enum_attribute_type_items)
DEF_ENUM(rna_enum_color_attribute_type_items)
DEF_ENUM(rna_enum_attribute_type_with_auto_items)
DEF_ENUM(rna_enum_attribute_domain_items)
+DEF_ENUM(rna_enum_attribute_curves_domain_items)
DEF_ENUM(rna_enum_color_attribute_domain_items)
DEF_ENUM(rna_enum_attribute_domain_without_corner_items)
DEF_ENUM(rna_enum_attribute_domain_with_auto_items)
diff --git a/source/blender/makesrna/intern/rna_attribute.c b/source/blender/makesrna/intern/rna_attribute.c
index 0cea693cd2a..76e1e712cad 100644
--- a/source/blender/makesrna/intern/rna_attribute.c
+++ b/source/blender/makesrna/intern/rna_attribute.c
@@ -106,6 +106,11 @@ const EnumPropertyItem rna_enum_color_attribute_domain_items[] = {
{ATTR_DOMAIN_CORNER, "CORNER", 0, "Face Corner", ""},
{0, NULL, 0, NULL, NULL}};
+const EnumPropertyItem rna_enum_attribute_curves_domain_items[] = {
+ {ATTR_DOMAIN_POINT, "POINT", 0, "Control Point", ""},
+ {ATTR_DOMAIN_CURVE, "CURVE", 0, "Curve", ""},
+ {0, NULL, 0, NULL, NULL}};
+
#ifdef RNA_RUNTIME
# include "BLI_math.h"
diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c
index 2735a00ee75..46b009191ca 100644
--- a/source/blender/makesrna/intern/rna_brush.c
+++ b/source/blender/makesrna/intern/rna_brush.c
@@ -249,6 +249,7 @@ const EnumPropertyItem rna_enum_brush_curves_sculpt_tool_items[] = {
{CURVES_SCULPT_TOOL_SNAKE_HOOK, "SNAKE_HOOK", ICON_NONE, "Curves Snake Hook", ""},
{CURVES_SCULPT_TOOL_ADD, "ADD", ICON_NONE, "Add Curves", ""},
{CURVES_SCULPT_TOOL_GROW_SHRINK, "GROW_SHRINK", ICON_NONE, "Grow / Shrink Curves", ""},
+ {CURVES_SCULPT_TOOL_SELECTION_PAINT, "SELECTION_PAINT", ICON_NONE, "Paint Selection", ""},
{0, NULL, 0, NULL, NULL},
};
@@ -885,6 +886,7 @@ static const EnumPropertyItem *rna_Brush_direction_itemf(bContext *C,
case PAINT_MODE_SCULPT_CURVES:
switch (me->curves_sculpt_tool) {
case CURVES_SCULPT_TOOL_GROW_SHRINK:
+ case CURVES_SCULPT_TOOL_SELECTION_PAINT:
return prop_direction_items;
default:
return DummyRNA_DEFAULT_items;
diff --git a/source/blender/makesrna/intern/rna_curves.c b/source/blender/makesrna/intern/rna_curves.c
index 7cf34db4cf4..caefd2f45ff 100644
--- a/source/blender/makesrna/intern/rna_curves.c
+++ b/source/blender/makesrna/intern/rna_curves.c
@@ -308,6 +308,18 @@ static void rna_def_curves(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Z", "Enable symmetry in the Z axis");
RNA_def_property_update(prop, 0, "rna_Curves_update_draw");
+ prop = RNA_def_property(srna, "selection_domain", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, rna_enum_attribute_curves_domain_items);
+ RNA_def_property_ui_text(prop, "Selection Domain", "");
+ RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
+ RNA_def_property_update(prop, 0, "rna_Curves_update_data");
+
+ prop = RNA_def_property(srna, "use_sculpt_selection", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", CV_SCULPT_SELECTION_ENABLED);
+ RNA_def_property_ui_text(prop, "Use Sculpt Selection", "");
+ RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
+ RNA_def_property_update(prop, 0, "rna_Curves_update_draw");
+
/* attributes */
rna_def_attributes_common(srna);