GPv3: Implementation of sculpt mode tools

This implements all the sculpt tools in Grease Pencil 3.

UI changes in the 3D view header and keymap entries for sculpt mode are
still minimal, more entries should be added once the relevant operators
are supported.

A set of utility functions and a shared base class
`GreasePencilStrokeOperationCommon` for sculpt operations has been added
to make individual operations less verbose.
The `GreasePencilStrokeParams` struct bundles common arguments to reduce
the amount of boilerplate code. The `foreach_editable_drawing` utility
function takes care of setting up the parameters and finding the right
drawings, so the tool only has to modify the data. Common features like
tracking mouse movement and inverting brush influence are handled by the
common base class.

Most operations are then relatively simple, with the exception of the
Grab and Clone operations.
- __Grab__ stores a stroke mask and weights on initialization of the
  stroke, rather than working with the usual selection mask.
- __Clone__ needs access to the clipboard, which requires exposing the
  clipboard in the editor API.

Pull Request: https://projects.blender.org/blender/blender/pulls/120508
This commit is contained in:
Lukas Tönne
2024-04-25 20:20:27 +02:00
parent fea1d1d71f
commit 91f1f3fc06
24 changed files with 1707 additions and 112 deletions

View File

@@ -4693,6 +4693,12 @@ def km_grease_pencil_sculpt_mode(params):
{"properties": [("scalar", 0.9)]}),
("brush.scale_size", {"type": 'RIGHT_BRACKET', "value": 'PRESS', "repeat": True},
{"properties": [("scalar", 1.0 / 0.9)]}),
# Invoke sculpt operator
("grease_pencil.sculpt_paint", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
("grease_pencil.sculpt_paint", {"type": 'LEFTMOUSE', "value": 'PRESS',
"ctrl": True}, {"properties": [("mode", 'INVERT')]}),
("grease_pencil.sculpt_paint", {"type": 'LEFTMOUSE', "value": 'PRESS',
"shift": True}, {"properties": [("mode", 'SMOOTH')]}),
*_template_paint_radial_control("gpencil_sculpt_paint"),
])

View File

@@ -474,9 +474,14 @@ class _draw_tool_settings_context_mode:
)
# direction
if not capabilities.has_direction:
if brush.gpencil_sculpt_tool in {'THICKNESS', 'STRENGTH', 'PINCH', 'TWIST'}:
layout.row().prop(brush, "direction", expand=True, text="")
# Brush falloff
layout.popover("VIEW3D_PT_tools_brush_falloff")
# Active layer only switch
layout.prop(brush.gpencil_settings, "use_active_layer_only")
return True
@staticmethod
@@ -894,11 +899,11 @@ class VIEW3D_HT_header(Header):
row = layout.row(align=True)
row.prop(tool_settings, "use_gpencil_draw_additive", text="", icon='FREEZE')
if object_mode in {'PAINT_GPENCIL', 'EDIT', 'WEIGHT_GPENCIL'}:
if object_mode in {'PAINT_GPENCIL', 'EDIT', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL'}:
row = layout.row(align=True)
row.prop(tool_settings, "use_grease_pencil_multi_frame_editing", text="")
if object_mode in {'EDIT', 'WEIGHT_GPENCIL'}:
if object_mode in {'EDIT', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL'}:
sub = row.row(align=True)
sub.enabled = tool_settings.use_grease_pencil_multi_frame_editing
sub.popover(
@@ -975,6 +980,35 @@ class VIEW3D_HT_header(Header):
text="Multiframe",
)
# Grease Pencil
if obj and obj.type == 'GREASEPENCIL':
if object_mode == 'PAINT_GREASE_PENCIL':
row = layout.row()
sub = row.row(align=True)
sub.prop(tool_settings, "use_gpencil_draw_onback", text="", icon='MOD_OPACITY')
sub.separator(factor=0.4)
sub.prop(tool_settings, "use_gpencil_automerge_strokes", text="")
sub.separator(factor=0.4)
sub.prop(tool_settings, "use_gpencil_weight_data_add", text="", icon='WPAINT_HLT')
sub.separator(factor=0.4)
sub.prop(tool_settings, "use_gpencil_draw_additive", text="", icon='FREEZE')
# Select mode for Editing
if object_mode == 'EDIT':
row = layout.row(align=True)
row.prop_enum(tool_settings, "gpencil_selectmode_edit", text="", value='POINT')
row.prop_enum(tool_settings, "gpencil_selectmode_edit", text="", value='STROKE')
subrow = row.row(align=True)
subrow.prop_enum(tool_settings, "gpencil_selectmode_edit", text="", value='SEGMENT')
# Select mode for Sculpt
if object_mode == 'SCULPT_GPENCIL':
row = layout.row(align=True)
row.prop(tool_settings, "use_gpencil_select_mask_point", text="")
row.prop(tool_settings, "use_gpencil_select_mask_stroke", text="")
row.prop(tool_settings, "use_gpencil_select_mask_segment", text="")
overlay = view.overlay
VIEW3D_MT_editor_menus.draw_collapsible(context, layout)
@@ -1188,7 +1222,12 @@ class VIEW3D_MT_editor_menus(Menu):
mode_string = context.mode
edit_object = context.edit_object
gp_edit = obj and obj.type == 'GPENCIL' and obj.mode in {
'EDIT_GPENCIL', 'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL',
'EDIT_GPENCIL',
'PAINT_GPENCIL',
'SCULPT_GPENCIL',
'SCULPT_GREASE_PENCIL',
'WEIGHT_GPENCIL',
'VERTEX_GPENCIL',
}
tool_settings = context.tool_settings
@@ -1196,18 +1235,16 @@ class VIEW3D_MT_editor_menus(Menu):
# Select Menu
if gp_edit:
if mode_string not in {'PAINT_GPENCIL', 'WEIGHT_GPENCIL'}:
if (
mode_string == 'SCULPT_GPENCIL' and
(tool_settings.use_gpencil_select_mask_point or
tool_settings.use_gpencil_select_mask_stroke or
tool_settings.use_gpencil_select_mask_segment)
):
layout.menu("VIEW3D_MT_select_edit_gpencil")
elif mode_string == 'EDIT_GPENCIL':
layout.menu("VIEW3D_MT_select_edit_gpencil")
elif mode_string == 'VERTEX_GPENCIL':
layout.menu("VIEW3D_MT_select_edit_gpencil")
use_gpencil_masking = (tool_settings.use_gpencil_select_mask_point or
tool_settings.use_gpencil_select_mask_stroke or
tool_settings.use_gpencil_select_mask_segment)
if mode_string in {
'EDIT_GPENCIL',
'VERTEX_GPENCIL'} or (
mode_string in {
'SCULPT_GPENCIL',
'SCULPT_GREASE_PENCIL'} and use_gpencil_masking):
layout.menu("VIEW3D_MT_select_edit_gpencil")
elif mode_string in {'PAINT_WEIGHT', 'PAINT_VERTEX', 'PAINT_TEXTURE'}:
mesh = obj.data
if mesh.use_paint_mask:

View File

@@ -2193,18 +2193,20 @@ static void GREASE_PENCIL_OT_separate(wmOperatorType *ot)
* \{ */
/* Global clipboard for Grease Pencil curves. */
struct Clipboard {
static struct Clipboard {
bke::CurvesGeometry curves;
/* We store the material uid's of the copied curves, so we can match those when pasting the
* clipboard into another object. */
Vector<std::pair<uint, int>> materials;
int materials_in_source_num;
};
} *grease_pencil_clipboard = nullptr;
static Clipboard &get_grease_pencil_clipboard()
static Clipboard &ensure_grease_pencil_clipboard()
{
static Clipboard clipboard;
return clipboard;
if (grease_pencil_clipboard == nullptr) {
grease_pencil_clipboard = MEM_new<Clipboard>(__func__);
}
return *grease_pencil_clipboard;
}
static int grease_pencil_paste_strokes_exec(bContext *C, wmOperator *op)
@@ -2217,8 +2219,6 @@ static int grease_pencil_paste_strokes_exec(bContext *C, wmOperator *op)
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
const bool paste_on_back = RNA_boolean_get(op->ptr, "paste_back");
Clipboard &clipboard = get_grease_pencil_clipboard();
/* Get active layer in the target object. */
if (!grease_pencil.has_active_layer()) {
BKE_report(op->reports, RPT_ERROR, "No active Grease Pencil layer");
@@ -2251,60 +2251,8 @@ static int grease_pencil_paste_strokes_exec(bContext *C, wmOperator *op)
selection_in_target.finish();
});
/* Get a list of all materials in the scene. */
Map<uint, Material *> scene_materials;
LISTBASE_FOREACH (Material *, material, &bmain->materials) {
scene_materials.add(material->id.session_uid, material);
}
clipboard_paste_strokes(*bmain, *object, *target_drawing, paste_on_back);
/* Map the materials used in the clipboard curves to the materials in the target object. */
Array<int> clipboard_material_remap(clipboard.materials_in_source_num, 0);
for (const int i : clipboard.materials.index_range()) {
/* Check if the material name exists in the scene. */
int target_index;
uint material_id = clipboard.materials[i].first;
Material *material = scene_materials.lookup_default(material_id, nullptr);
if (!material) {
/* Material is removed, so create a new material. */
BKE_grease_pencil_object_material_new(bmain, object, nullptr, &target_index);
clipboard_material_remap[clipboard.materials[i].second] = target_index;
continue;
}
/* Find or add the material to the target object. */
target_index = BKE_object_material_ensure(bmain, object, material);
clipboard_material_remap[clipboard.materials[i].second] = target_index;
}
/* Get the index range of the pasted curves in the target layer. */
IndexRange pasted_curves_range = paste_on_back ?
IndexRange(0, clipboard.curves.curves_num()) :
IndexRange(target_drawing->strokes().curves_num(),
clipboard.curves.curves_num());
/* Append the geometry from the clipboard to the target layer. */
Curves *clipboard_curves = curves_new_nomain(clipboard.curves);
Curves *target_curves = curves_new_nomain(std::move(target_drawing->strokes_for_write()));
Array<bke::GeometrySet> geometry_sets = {
bke::GeometrySet::from_curves(paste_on_back ? clipboard_curves : target_curves),
bke::GeometrySet::from_curves(paste_on_back ? target_curves : clipboard_curves)};
bke::GeometrySet joined_curves = geometry::join_geometries(geometry_sets, {});
target_drawing->strokes_for_write() = std::move(
joined_curves.get_curves_for_write()->geometry.wrap());
/* Remap the material indices of the pasted curves to the target object material indices. */
bke::MutableAttributeAccessor attributes =
target_drawing->strokes_for_write().attributes_for_write();
bke::SpanAttributeWriter<int> material_indices = attributes.lookup_or_add_for_write_span<int>(
"material_index", bke::AttrDomain::Curve);
if (material_indices) {
for (const int i : pasted_curves_range) {
material_indices.span[i] = clipboard_material_remap[material_indices.span[i]];
}
material_indices.finish();
}
target_drawing->tag_topology_changed();
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
@@ -2319,7 +2267,7 @@ static int grease_pencil_copy_strokes_exec(bContext *C, wmOperator *op)
const bke::AttrDomain selection_domain = ED_grease_pencil_selection_domain_get(
scene->toolsettings);
Clipboard &clipboard = get_grease_pencil_clipboard();
Clipboard &clipboard = ensure_grease_pencil_clipboard();
bool anything_copied = false;
int num_copied = 0;
@@ -2400,8 +2348,7 @@ static bool grease_pencil_paste_strokes_poll(bContext *C)
}
/* Check for curves in the Grease Pencil clipboard. */
Clipboard &clipboard = get_grease_pencil_clipboard();
return (clipboard.curves.curves_num() > 0);
return (grease_pencil_clipboard && grease_pencil_clipboard->curves.curves_num() > 0);
}
static void GREASE_PENCIL_OT_paste(wmOperatorType *ot)
@@ -2439,6 +2386,94 @@ static void GREASE_PENCIL_OT_copy(wmOperatorType *ot)
/** \} */
void clipboard_free()
{
if (grease_pencil_clipboard) {
MEM_delete(grease_pencil_clipboard);
grease_pencil_clipboard = nullptr;
}
}
const bke::CurvesGeometry &clipboard_curves()
{
using namespace blender::ed::greasepencil;
return ensure_grease_pencil_clipboard().curves;
}
static Array<int> clipboard_materials_remap(Main &bmain, Object &object)
{
using namespace blender::ed::greasepencil;
/* Get a list of all materials in the scene. */
Map<uint, Material *> scene_materials;
LISTBASE_FOREACH (Material *, material, &bmain.materials) {
scene_materials.add(material->id.session_uid, material);
}
Clipboard &clipboard = ensure_grease_pencil_clipboard();
Array<int> clipboard_material_remap(clipboard.materials_in_source_num, 0);
for (const int i : clipboard.materials.index_range()) {
/* Check if the material name exists in the scene. */
int target_index;
uint material_id = clipboard.materials[i].first;
Material *material = scene_materials.lookup_default(material_id, nullptr);
if (!material) {
/* Material is removed, so create a new material. */
BKE_grease_pencil_object_material_new(&bmain, &object, nullptr, &target_index);
clipboard_material_remap[clipboard.materials[i].second] = target_index;
continue;
}
/* Find or add the material to the target object. */
target_index = BKE_object_material_ensure(&bmain, &object, material);
clipboard_material_remap[clipboard.materials[i].second] = target_index;
}
return clipboard_material_remap;
}
IndexRange clipboard_paste_strokes(Main &bmain,
Object &object,
bke::greasepencil::Drawing &drawing,
const bool paste_back)
{
const bke::CurvesGeometry &clipboard_curves = ed::greasepencil::clipboard_curves();
/* Get a list of all materials in the scene. */
const Array<int> clipboard_material_remap = ed::greasepencil::clipboard_materials_remap(bmain,
object);
/* Get the index range of the pasted curves in the target layer. */
const IndexRange pasted_curves_range = paste_back ?
IndexRange(0, clipboard_curves.curves_num()) :
IndexRange(drawing.strokes().curves_num(),
clipboard_curves.curves_num());
/* Append the geometry from the clipboard to the target layer. */
Curves *clipboard_id = bke::curves_new_nomain(clipboard_curves);
Curves *target_id = curves_new_nomain(std::move(drawing.strokes_for_write()));
const Array<bke::GeometrySet> geometry_sets = {
bke::GeometrySet::from_curves(paste_back ? clipboard_id : target_id),
bke::GeometrySet::from_curves(paste_back ? target_id : clipboard_id)};
bke::GeometrySet joined_curves = geometry::join_geometries(geometry_sets, {});
drawing.strokes_for_write() = std::move(joined_curves.get_curves_for_write()->geometry.wrap());
/* Remap the material indices of the pasted curves to the target object material indices. */
bke::MutableAttributeAccessor attributes = drawing.strokes_for_write().attributes_for_write();
bke::SpanAttributeWriter<int> material_indices = attributes.lookup_or_add_for_write_span<int>(
"material_index", bke::AttrDomain::Curve);
if (material_indices) {
for (const int i : pasted_curves_range) {
material_indices.span[i] = clipboard_material_remap[material_indices.span[i]];
}
material_indices.finish();
}
drawing.tag_topology_changed();
return pasted_curves_range;
}
} // namespace blender::ed::greasepencil
void ED_operatortypes_grease_pencil_edit()

View File

@@ -563,6 +563,47 @@ Vector<MutableDrawingInfo> retrieve_editable_drawings_from_layer(
return editable_drawings;
}
Vector<MutableDrawingInfo> retrieve_editable_drawings_from_layer_with_falloff(
const Scene &scene,
GreasePencil &grease_pencil,
const blender::bke::greasepencil::Layer &layer)
{
using namespace blender::bke::greasepencil;
const int current_frame = scene.r.cfra;
const ToolSettings *toolsettings = scene.toolsettings;
const bool use_multi_frame_editing = (toolsettings->gpencil_flags &
GP_USE_MULTI_FRAME_EDITING) != 0;
const bool use_multi_frame_falloff = use_multi_frame_editing &&
(toolsettings->gp_sculpt.flag &
GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) != 0;
const int layer_index = *grease_pencil.get_layer_index(layer);
int center_frame;
std::pair<int, int> minmax_frame;
if (use_multi_frame_falloff) {
BKE_curvemapping_init(toolsettings->gp_sculpt.cur_falloff);
minmax_frame = get_minmax_selected_frame_numbers(grease_pencil, current_frame);
center_frame = math::clamp(current_frame, minmax_frame.first, minmax_frame.second);
}
Vector<MutableDrawingInfo> editable_drawings;
const Array<int> frame_numbers = get_editable_frames_for_layer(
layer, current_frame, use_multi_frame_editing);
for (const int frame_number : frame_numbers) {
if (Drawing *drawing = grease_pencil.get_editable_drawing_at(layer, frame_number)) {
const float falloff = use_multi_frame_falloff ?
get_multi_frame_falloff(frame_number,
center_frame,
minmax_frame.first,
minmax_frame.second,
toolsettings->gp_sculpt.cur_falloff) :
1.0f;
editable_drawings.append({*drawing, layer_index, frame_number, falloff});
}
}
return editable_drawings;
}
Vector<DrawingInfo> retrieve_visible_drawings(const Scene &scene,
const GreasePencil &grease_pencil,
const bool do_onion_skinning)

View File

@@ -34,7 +34,8 @@ struct ViewContext;
namespace blender {
namespace bke {
enum class AttrDomain : int8_t;
}
class CurvesGeometry;
} // namespace bke
} // namespace blender
enum {
@@ -62,6 +63,7 @@ void ED_primitivetool_modal_keymap(wmKeyConfig *keyconf);
void GREASE_PENCIL_OT_stroke_cutter(wmOperatorType *ot);
void ED_undosys_type_grease_pencil(UndoType *undo_type);
/**
* Get the selection mode for Grease Pencil selection operators: point, stroke, segment.
*/
@@ -256,6 +258,8 @@ Array<Vector<MutableDrawingInfo>> retrieve_editable_drawings_grouped_per_frame(
const Scene &scene, GreasePencil &grease_pencil);
Vector<MutableDrawingInfo> retrieve_editable_drawings_from_layer(
const Scene &scene, GreasePencil &grease_pencil, const bke::greasepencil::Layer &layer);
Vector<MutableDrawingInfo> retrieve_editable_drawings_from_layer_with_falloff(
const Scene &scene, GreasePencil &grease_pencil, const bke::greasepencil::Layer &layer);
Vector<DrawingInfo> retrieve_visible_drawings(const Scene &scene,
const GreasePencil &grease_pencil,
bool do_onion_skinning);
@@ -381,4 +385,16 @@ void normalize_vertex_weights(MDeformVert &dvert,
Span<bool> vertex_group_is_locked,
Span<bool> vertex_group_is_bone_deformed);
void clipboard_free();
const bke::CurvesGeometry &clipboard_curves();
/**
* Paste curves from the clipboard into the drawing.
* \param paste_back Render behind existing curves by inserting curves at the front.
* \return Index range of the new curves in the drawing after pasting.
*/
IndexRange clipboard_paste_strokes(Main &bmain,
Object &object,
bke::greasepencil::Drawing &drawing,
bool paste_back);
} // namespace blender::ed::greasepencil

View File

@@ -43,6 +43,16 @@ set(SRC
grease_pencil_draw_ops.cc
grease_pencil_erase.cc
grease_pencil_paint.cc
grease_pencil_sculpt_clone.cc
grease_pencil_sculpt_common.cc
grease_pencil_sculpt_grab.cc
grease_pencil_sculpt_pinch.cc
grease_pencil_sculpt_push.cc
grease_pencil_sculpt_randomize.cc
grease_pencil_sculpt_smooth.cc
grease_pencil_sculpt_strength.cc
grease_pencil_sculpt_thickness.cc
grease_pencil_sculpt_twist.cc
grease_pencil_tint.cc
grease_pencil_weight_average.cc
grease_pencil_weight_blur.cc

View File

@@ -27,6 +27,7 @@
#include "WM_message.hh"
#include "WM_toolsystem.hh"
#include "curves_sculpt_intern.hh"
#include "grease_pencil_intern.hh"
#include "paint_intern.hh"
@@ -208,30 +209,31 @@ static bool grease_pencil_sculpt_paint_poll(bContext *C)
return true;
}
static GreasePencilStrokeOperation *grease_pencil_sculpt_paint_operation(bContext &C)
static GreasePencilStrokeOperation *grease_pencil_sculpt_paint_operation(
bContext &C, const BrushStrokeMode stroke_mode)
{
const Scene &scene = *CTX_data_scene(&C);
const GpSculptPaint &gp_sculptpaint = *scene.toolsettings->gp_sculptpaint;
const Brush &brush = *BKE_paint_brush_for_read(&gp_sculptpaint.paint);
switch (eBrushGPSculptTool(brush.gpencil_sculpt_tool)) {
case GPSCULPT_TOOL_SMOOTH:
return nullptr;
return greasepencil::new_smooth_operation(stroke_mode).release();
case GPSCULPT_TOOL_THICKNESS:
return nullptr;
return greasepencil::new_thickness_operation(stroke_mode).release();
case GPSCULPT_TOOL_STRENGTH:
return nullptr;
return greasepencil::new_strength_operation(stroke_mode).release();
case GPSCULPT_TOOL_GRAB:
return nullptr;
return greasepencil::new_grab_operation(stroke_mode).release();
case GPSCULPT_TOOL_PUSH:
return nullptr;
return greasepencil::new_push_operation(stroke_mode).release();
case GPSCULPT_TOOL_TWIST:
return nullptr;
return greasepencil::new_twist_operation(stroke_mode).release();
case GPSCULPT_TOOL_PINCH:
return nullptr;
return greasepencil::new_pinch_operation(stroke_mode).release();
case GPSCULPT_TOOL_RANDOMIZE:
return nullptr;
return greasepencil::new_randomize_operation(stroke_mode).release();
case GPSCULPT_TOOL_CLONE:
return nullptr;
return greasepencil::new_clone_operation(stroke_mode).release();
}
return nullptr;
}
@@ -240,7 +242,8 @@ static bool grease_pencil_sculpt_paint_test_start(bContext *C,
wmOperator *op,
const float mouse[2])
{
GreasePencilStrokeOperation *operation = grease_pencil_sculpt_paint_operation(*C);
const BrushStrokeMode stroke_mode = BrushStrokeMode(RNA_enum_get(op->ptr, "mode"));
GreasePencilStrokeOperation *operation = grease_pencil_sculpt_paint_operation(*C, stroke_mode);
if (operation) {
stroke_start(*C, *op, float2(mouse), *operation);
return true;

View File

@@ -4,9 +4,18 @@
#pragma once
#include "DNA_scene_types.h"
#include "ED_grease_pencil.hh"
#include "paint_intern.hh"
#include "BLI_math_vector.hh"
namespace blender::bke::greasepencil {
class Drawing;
class Layer;
} // namespace blender::bke::greasepencil
namespace blender::bke::crazyspace {
struct GeometryDeformation;
}
namespace blender::ed::sculpt_paint {
@@ -25,6 +34,94 @@ class GreasePencilStrokeOperation {
namespace greasepencil {
/* Get list of drawings the tool should be operating on. */
Vector<ed::greasepencil::MutableDrawingInfo> get_drawings_for_sculpt(const bContext &C);
/* Make sure the brush has all necessary grease pencil settings. */
void init_brush(Brush &brush);
/* Index mask of all points within the brush radius. */
IndexMask brush_influence_mask(const Scene &scene,
const Brush &brush,
const float2 &mouse_position,
float pressure,
float multi_frame_falloff,
const IndexMask &selection,
Span<float2> view_positions,
Vector<float> &influences,
IndexMaskMemory &memory);
/* Influence value at point co for the brush. */
float brush_influence(const Scene &scene,
const Brush &brush,
const float2 &co,
const InputSample &sample,
float multi_frame_falloff);
/* True if influence of the brush should be inverted. */
bool is_brush_inverted(const Brush &brush, BrushStrokeMode stroke_mode);
/* Common parameters for stroke callbacks that can be passed to utility functions. */
struct GreasePencilStrokeParams {
const ToolSettings &toolsettings;
const ARegion &region;
Object &ob_orig;
Object &ob_eval;
const bke::greasepencil::Layer &layer;
int layer_index;
int frame_number;
float multi_frame_falloff;
ed::greasepencil::DrawingPlacement placement;
bke::greasepencil::Drawing &drawing;
/* Note: accessing region in worker threads will return null,
* this has to be done on the main thread and passed explicitly. */
static GreasePencilStrokeParams from_context(const Scene &scene,
const Depsgraph &depsgraph,
const ARegion &region,
const View3D &view3d,
Object &object,
int layer_index,
int frame_number,
float multi_frame_falloff,
bke::greasepencil::Drawing &drawing);
};
/* Point index mask for a drawing based on selection tool settings. */
IndexMask point_selection_mask(const GreasePencilStrokeParams &params, IndexMaskMemory &memory);
bke::crazyspace::GeometryDeformation get_drawing_deformation(
const GreasePencilStrokeParams &params);
/* Project points from layer space into 2D view space. */
Array<float2> calculate_view_positions(const GreasePencilStrokeParams &params,
const IndexMask &selection);
/* Stroke operation base class that performs various common initializations. */
class GreasePencilStrokeOperationCommon : public GreasePencilStrokeOperation {
public:
using MutableDrawingInfo = blender::ed::greasepencil::MutableDrawingInfo;
using DrawingPlacement = ed::greasepencil::DrawingPlacement;
BrushStrokeMode stroke_mode;
/* Previous mouse position for computing the direction. */
float2 prev_mouse_position;
GreasePencilStrokeOperationCommon(const BrushStrokeMode stroke_mode) : stroke_mode(stroke_mode)
{
}
bool is_inverted(const Brush &brush) const;
float2 mouse_delta(const InputSample &input_sample) const;
void init_stroke(const bContext &C, const InputSample &start_sample);
void stroke_extended(const InputSample &extension_sample);
void foreach_editable_drawing(
const bContext &C, FunctionRef<bool(const GreasePencilStrokeParams &params)> fn) const;
};
std::unique_ptr<GreasePencilStrokeOperation> new_paint_operation();
std::unique_ptr<GreasePencilStrokeOperation> new_erase_operation();
std::unique_ptr<GreasePencilStrokeOperation> new_tint_operation();
@@ -33,6 +130,15 @@ std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_draw_operation(
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_blur_operation();
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_average_operation();
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_smear_operation();
std::unique_ptr<GreasePencilStrokeOperation> new_smooth_operation(BrushStrokeMode stroke_mode);
std::unique_ptr<GreasePencilStrokeOperation> new_thickness_operation(BrushStrokeMode stroke_mode);
std::unique_ptr<GreasePencilStrokeOperation> new_strength_operation(BrushStrokeMode stroke_mode);
std::unique_ptr<GreasePencilStrokeOperation> new_randomize_operation(BrushStrokeMode stroke_mode);
std::unique_ptr<GreasePencilStrokeOperation> new_grab_operation(BrushStrokeMode stroke_mode);
std::unique_ptr<GreasePencilStrokeOperation> new_push_operation(BrushStrokeMode stroke_mode);
std::unique_ptr<GreasePencilStrokeOperation> new_pinch_operation(BrushStrokeMode stroke_mode);
std::unique_ptr<GreasePencilStrokeOperation> new_twist_operation(BrushStrokeMode stroke_mode);
std::unique_ptr<GreasePencilStrokeOperation> new_clone_operation(BrushStrokeMode stroke_mode);
} // namespace greasepencil

View File

@@ -331,13 +331,14 @@ struct PaintOperationExecutor {
* stable) fit. */
Array<float2> coords_pre_blur(smooth_window.size());
const int pre_blur_iterations = 3;
geometry::gaussian_blur_1D(coords_to_smooth,
pre_blur_iterations,
settings_->active_smooth,
true,
true,
false,
coords_pre_blur.as_mutable_span());
geometry::gaussian_blur_1D(
coords_to_smooth,
pre_blur_iterations,
VArray<float>::ForSingle(settings_->active_smooth, smooth_window.size()),
true,
true,
false,
coords_pre_blur.as_mutable_span());
/* Curve fitting. The output will be a set of handles (float2 triplets) in a flat array. */
const float max_error_threshold_px = 5.0f;

View File

@@ -0,0 +1,90 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_context.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_paint.hh"
#include "ED_curves.hh"
#include "ED_grease_pencil.hh"
#include "ED_view3d.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "grease_pencil_intern.hh"
#include "paint_intern.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class CloneOperation : public GreasePencilStrokeOperationCommon {
public:
using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon;
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override;
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override;
void on_stroke_done(const bContext & /*C*/) override {}
};
static float2 arithmetic_mean(Span<float2> values)
{
return std::accumulate(values.begin(), values.end(), float2(0)) / values.size();
}
void CloneOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample)
{
Main &bmain = *CTX_data_main(&C);
Object &object = *CTX_data_active_object(&C);
this->init_stroke(C, start_sample);
/* Note: Only one copy is created at the beginning of each stroke.
* GPv2 supposedly has 2 modes:
* - Stamp: Clone on stroke start and then transform (the transform part doesn't work)
* - Continuous: Create multiple copies during the stroke (disabled)
*
* Here we only have the GPv2 behavior that actually works for now. */
this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams &params) {
const IndexRange pasted_curves = ed::greasepencil::clipboard_paste_strokes(
bmain, object, params.drawing, false);
if (pasted_curves.is_empty()) {
return false;
}
bke::CurvesGeometry &curves = params.drawing.strokes_for_write();
const OffsetIndices<int> pasted_points_by_curve = curves.points_by_curve().slice(
pasted_curves);
const IndexRange pasted_points = IndexRange::from_begin_size(
pasted_points_by_curve[0].start(),
pasted_points_by_curve.total_size() - pasted_points_by_curve[0].start());
Array<float2> view_positions = calculate_view_positions(params, pasted_points);
const float2 center = arithmetic_mean(view_positions.as_mutable_span().slice(pasted_points));
const float2 &mouse_delta = start_sample.mouse_position - center;
MutableSpan<float3> positions = curves.positions_for_write();
threading::parallel_for(pasted_points, 4096, [&](const IndexRange range) {
for (const int point_i : range) {
positions[point_i] = params.placement.project(view_positions[point_i] + mouse_delta);
}
});
params.drawing.tag_positions_changed();
return true;
});
}
void CloneOperation::on_stroke_extended(const bContext & /*C*/,
const InputSample &extension_sample)
{
this->stroke_extended(extension_sample);
}
std::unique_ptr<GreasePencilStrokeOperation> new_clone_operation(const BrushStrokeMode stroke_mode)
{
return std::make_unique<CloneOperation>(stroke_mode);
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@@ -0,0 +1,284 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_brush.hh"
#include "BKE_colortools.hh"
#include "BKE_context.hh"
#include "BKE_crazyspace.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BLI_index_mask.hh"
#include "BLI_math_vector.hh"
#include "BLI_task.hh"
#include "DEG_depsgraph_query.hh"
#include "DNA_brush_types.h"
#include "DNA_node_tree_interface_types.h"
#include "DNA_screen_types.h"
#include "DNA_view3d_types.h"
#include "ED_grease_pencil.hh"
#include "ED_view3d.hh"
#include "grease_pencil_intern.hh"
#include <iostream>
namespace blender::ed::sculpt_paint::greasepencil {
Vector<ed::greasepencil::MutableDrawingInfo> get_drawings_for_sculpt(const bContext &C)
{
using namespace blender::bke::greasepencil;
const Scene &scene = *CTX_data_scene(&C);
Object &ob_orig = *CTX_data_active_object(&C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(ob_orig.data);
Paint &paint = *BKE_paint_get_active_from_context(&C);
const Brush &brush = *BKE_paint_brush(&paint);
const bool active_layer_only = ((brush.gpencil_settings->flag & GP_BRUSH_ACTIVE_LAYER_ONLY) !=
0);
if (active_layer_only) {
/* Apply only to the drawing at the current frame of the active layer. */
if (!grease_pencil.has_active_layer()) {
return {};
}
const Layer &active_layer = *grease_pencil.get_active_layer();
return ed::greasepencil::retrieve_editable_drawings_from_layer_with_falloff(
scene, grease_pencil, active_layer);
}
/* Apply to all editable drawings. */
return ed::greasepencil::retrieve_editable_drawings_with_falloff(scene, grease_pencil);
}
void init_brush(Brush &brush)
{
if (brush.gpencil_settings == nullptr) {
BKE_brush_init_gpencil_settings(&brush);
}
BLI_assert(brush.gpencil_settings != nullptr);
BKE_curvemapping_init(brush.gpencil_settings->curve_strength);
BKE_curvemapping_init(brush.gpencil_settings->curve_sensitivity);
BKE_curvemapping_init(brush.gpencil_settings->curve_jitter);
BKE_curvemapping_init(brush.gpencil_settings->curve_rand_pressure);
BKE_curvemapping_init(brush.gpencil_settings->curve_rand_strength);
BKE_curvemapping_init(brush.gpencil_settings->curve_rand_uv);
BKE_curvemapping_init(brush.gpencil_settings->curve_rand_hue);
BKE_curvemapping_init(brush.gpencil_settings->curve_rand_saturation);
BKE_curvemapping_init(brush.gpencil_settings->curve_rand_value);
}
static float brush_radius(const Scene &scene, const Brush &brush, const float pressure = 1.0f)
{
float radius = BKE_brush_size_get(&scene, &brush);
if (BKE_brush_use_size_pressure(&brush)) {
radius *= BKE_curvemapping_evaluateF(brush.gpencil_settings->curve_sensitivity, 0, pressure);
}
return radius;
}
float brush_influence(const Scene &scene,
const Brush &brush,
const float2 &co,
const InputSample &sample,
const float multi_frame_falloff)
{
const float radius = brush_radius(scene, brush, sample.pressure);
/* Basic strength factor from brush settings. */
const float brush_pressure = BKE_brush_use_alpha_pressure(&brush) ? sample.pressure : 1.0f;
const float influence_base = BKE_brush_alpha_get(&scene, &brush) * brush_pressure *
multi_frame_falloff;
/* Distance falloff. */
const int2 mval_i = int2(math::round(sample.mouse_position));
const float distance = math::distance(mval_i, int2(co));
/* Apply Brush curve. */
const float brush_falloff = BKE_brush_curve_strength(&brush, distance, radius);
return influence_base * brush_falloff;
}
IndexMask brush_influence_mask(const Scene &scene,
const Brush &brush,
const float2 &mouse_position,
const float pressure,
const float multi_frame_falloff,
const IndexMask &selection,
const Span<float2> view_positions,
Vector<float> &influences,
IndexMaskMemory &memory)
{
if (selection.is_empty()) {
return {};
}
const float radius = brush_radius(scene, brush, pressure);
const float radius_squared = radius * radius;
const float brush_pressure = BKE_brush_use_alpha_pressure(&brush) ? pressure : 1.0f;
const float influence_base = BKE_brush_alpha_get(&scene, &brush) * brush_pressure *
multi_frame_falloff;
const int2 mval_i = int2(math::round(mouse_position));
Array<float> all_influences(selection.min_array_size());
const IndexMask influence_mask = IndexMask::from_predicate(
selection, GrainSize(4096), memory, [&](const int point) {
/* Distance falloff. */
const float distance_squared = math::distance_squared(int2(view_positions[point]), mval_i);
if (distance_squared > radius_squared) {
all_influences[point] = 0.0f;
return false;
}
/* Apply Brush curve. */
const float brush_falloff = BKE_brush_curve_strength(
&brush, math::sqrt(distance_squared), radius);
all_influences[point] = influence_base * brush_falloff;
return all_influences[point] > 0.0f;
});
influences.reinitialize(influence_mask.size());
array_utils::gather(all_influences.as_span(), influence_mask, influences.as_mutable_span());
return influence_mask;
}
bool is_brush_inverted(const Brush &brush, const BrushStrokeMode stroke_mode)
{
/* The basic setting is the brush's setting. During runtime, the user can hold down the Ctrl key
* to invert the basic behavior. */
return bool(brush.flag & BRUSH_DIR_IN) ^ (stroke_mode == BrushStrokeMode::BRUSH_STROKE_INVERT);
}
GreasePencilStrokeParams GreasePencilStrokeParams::from_context(
const Scene &scene,
const Depsgraph &depsgraph,
const ARegion &region,
const View3D &view3d,
Object &object,
const int layer_index,
const int frame_number,
const float multi_frame_falloff,
bke::greasepencil::Drawing &drawing)
{
Object &ob_eval = *DEG_get_evaluated_object(&depsgraph, &object);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
const bke::greasepencil::Layer &layer = *grease_pencil.layers()[layer_index];
ed::greasepencil::DrawingPlacement placement(scene, region, view3d, ob_eval, layer);
return {*scene.toolsettings,
region,
object,
ob_eval,
layer,
layer_index,
frame_number,
multi_frame_falloff,
std::move(placement),
drawing};
}
IndexMask point_selection_mask(const GreasePencilStrokeParams &params, IndexMaskMemory &memory)
{
const bool is_masking = GPENCIL_ANY_SCULPT_MASK(
eGP_Sculpt_SelectMaskFlag(params.toolsettings.gpencil_selectmode_sculpt));
return (is_masking ? ed::greasepencil::retrieve_editable_and_selected_points(
params.ob_eval, params.drawing, memory) :
params.drawing.strokes().points_range());
}
bke::crazyspace::GeometryDeformation get_drawing_deformation(
const GreasePencilStrokeParams &params)
{
return bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
&params.ob_eval, params.ob_orig, params.layer_index, params.frame_number);
}
Array<float2> calculate_view_positions(const GreasePencilStrokeParams &params,
const IndexMask &selection)
{
bke::crazyspace::GeometryDeformation deformation = get_drawing_deformation(params);
Array<float2> view_positions(deformation.positions.size());
/* Compute screen space positions. */
const float4x4 transform = params.layer.to_world_space(params.ob_eval);
selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) {
eV3DProjStatus result = ED_view3d_project_float_global(
&params.region,
math::transform_point(transform, deformation.positions[point_i]),
view_positions[point_i],
V3D_PROJ_TEST_NOP);
if (result != V3D_PROJ_RET_OK) {
view_positions[point_i] = float2(0);
}
});
return view_positions;
}
bool GreasePencilStrokeOperationCommon::is_inverted(const Brush &brush) const
{
return is_brush_inverted(brush, this->stroke_mode);
}
float2 GreasePencilStrokeOperationCommon::mouse_delta(const InputSample &input_sample) const
{
return input_sample.mouse_position - this->prev_mouse_position;
}
void GreasePencilStrokeOperationCommon::foreach_editable_drawing(
const bContext &C, FunctionRef<bool(const GreasePencilStrokeParams &params)> fn) const
{
using namespace blender::bke::greasepencil;
const Scene &scene = *CTX_data_scene(&C);
const Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C);
const View3D &view3d = *CTX_wm_view3d(&C);
const ARegion &region = *CTX_wm_region(&C);
Object &object = *CTX_data_active_object(&C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
std::atomic<bool> changed = false;
const Vector<MutableDrawingInfo> drawings = get_drawings_for_sculpt(C);
threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
GreasePencilStrokeParams params = GreasePencilStrokeParams::from_context(
scene,
depsgraph,
region,
view3d,
object,
info.layer_index,
info.frame_number,
info.multi_frame_falloff,
info.drawing);
if (fn(params)) {
changed = true;
}
});
if (changed) {
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil);
}
}
void GreasePencilStrokeOperationCommon::init_stroke(const bContext &C,
const InputSample &start_sample)
{
Paint &paint = *BKE_paint_get_active_from_context(&C);
Brush &brush = *BKE_paint_brush(&paint);
init_brush(brush);
this->prev_mouse_position = start_sample.mouse_position;
}
void GreasePencilStrokeOperationCommon::stroke_extended(const InputSample &extension_sample)
{
this->prev_mouse_position = extension_sample.mouse_position;
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@@ -0,0 +1,234 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_context.hh"
#include "BKE_crazyspace.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_paint.hh"
#include "BLI_index_mask.hh"
#include "BLI_math_matrix.hh"
#include "BLI_task.hh"
#include "DEG_depsgraph_query.hh"
#include "DNA_grease_pencil_types.h"
#include "DNA_view3d_types.h"
#include "ED_grease_pencil.hh"
#include "ED_view3d.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "grease_pencil_intern.hh"
#include "paint_intern.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class GrabOperation : public GreasePencilStrokeOperationCommon {
public:
using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon;
using MutableDrawingInfo = blender::ed::greasepencil::MutableDrawingInfo;
using DrawingPlacement = ed::greasepencil::DrawingPlacement;
/* Cached point mask and influence for a particular drawing. */
struct PointWeights {
int layer_index;
int frame_number;
float multi_frame_falloff;
/* Layer space to view space projection at the start of the stroke. */
float4x4 layer_to_win;
/* Points that are grabbed at the beginning of the stroke. */
IndexMask point_mask;
/* Influence weights for grabbed points. */
Vector<float> weights;
IndexMaskMemory memory;
};
/* Cached point data for each affected drawing. */
Array<PointWeights> drawing_data;
void foreach_grabbed_drawing(const bContext &C,
FunctionRef<bool(const GreasePencilStrokeParams &params,
const IndexMask &mask,
Span<float> weights)> fn) const;
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override;
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override;
void on_stroke_done(const bContext & /*C*/) override {}
};
void GrabOperation::foreach_grabbed_drawing(
const bContext &C,
FunctionRef<bool(
const GreasePencilStrokeParams &params, const IndexMask &mask, Span<float> weights)> fn)
const
{
const Scene &scene = *CTX_data_scene(&C);
const Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C);
const ARegion &region = *CTX_wm_region(&C);
const View3D &view3d = *CTX_wm_view3d(&C);
Object &object = *CTX_data_active_object(&C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
bool changed = false;
threading::parallel_for_each(this->drawing_data.index_range(), [&](const int i) {
const PointWeights &data = this->drawing_data[i];
if (data.point_mask.is_empty()) {
return;
}
const bke::greasepencil::Layer &layer = *grease_pencil.layers()[data.layer_index];
/* If a new frame is created, could be impossible find the stroke. */
const int drawing_index = layer.drawing_index_at(data.frame_number);
if (drawing_index < 0) {
return;
}
GreasePencilDrawingBase &drawing_base = *grease_pencil.drawing(drawing_index);
if (drawing_base.type != GP_DRAWING) {
return;
}
bke::greasepencil::Drawing &drawing =
reinterpret_cast<GreasePencilDrawing &>(drawing_base).wrap();
GreasePencilStrokeParams params = GreasePencilStrokeParams::from_context(
scene,
depsgraph,
region,
view3d,
object,
data.layer_index,
data.frame_number,
data.multi_frame_falloff,
drawing);
if (fn(params, data.point_mask, data.weights)) {
changed = true;
}
});
if (changed) {
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil);
}
}
void GrabOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample)
{
const ARegion &region = *CTX_wm_region(&C);
const View3D &view3d = *CTX_wm_view3d(&C);
const RegionView3D &rv3d = *CTX_wm_region_view3d(&C);
const Scene &scene = *CTX_data_scene(&C);
Paint &paint = *BKE_paint_get_active_from_context(&C);
Brush &brush = *BKE_paint_brush(&paint);
const Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C);
Object &ob_orig = *CTX_data_active_object(&C);
Object &ob_eval = *DEG_get_evaluated_object(&depsgraph, &ob_orig);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(ob_orig.data);
init_brush(brush);
this->prev_mouse_position = start_sample.mouse_position;
const Vector<MutableDrawingInfo> drawings = get_drawings_for_sculpt(C);
this->drawing_data.reinitialize(drawings.size());
threading::parallel_for_each(drawings.index_range(), [&](const int i) {
const MutableDrawingInfo &info = drawings[i];
BLI_assert(info.layer_index >= 0);
PointWeights &data = this->drawing_data[i];
const bke::greasepencil::Layer &layer = *grease_pencil.layers()[info.layer_index];
const int drawing_index = layer.drawing_index_at(info.frame_number);
BLI_assert(drawing_index >= 0);
BLI_assert(grease_pencil.get_drawing_at(layer, info.frame_number) == &info.drawing);
ed::greasepencil::DrawingPlacement placement(scene, region, view3d, ob_eval, layer);
GreasePencilStrokeParams params = {*scene.toolsettings,
region,
ob_orig,
ob_eval,
layer,
info.layer_index,
info.frame_number,
info.multi_frame_falloff,
std::move(placement),
info.drawing};
IndexMaskMemory selection_memory;
IndexMask selection = point_selection_mask(params, selection_memory);
Array<float2> view_positions = calculate_view_positions(params, selection);
/* Cache points under brush influence. */
Vector<float> weights;
IndexMask point_mask = brush_influence_mask(scene,
brush,
start_sample.mouse_position,
start_sample.pressure,
info.multi_frame_falloff,
selection,
view_positions,
weights,
data.memory);
if (point_mask.is_empty()) {
/* Set empty point mask to skip. */
data.point_mask = {};
return;
}
data.layer_index = info.layer_index;
data.frame_number = info.frame_number;
data.multi_frame_falloff = info.multi_frame_falloff;
data.layer_to_win = ED_view3d_ob_project_mat_get(&rv3d, &ob_eval) *
layer.to_object_space(ob_eval);
data.point_mask = std::move(point_mask);
data.weights = std::move(weights);
});
}
void GrabOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample)
{
const ARegion &region = *CTX_wm_region(&C);
const RegionView3D &rv3d = *CTX_wm_region_view3d(&C);
this->foreach_grabbed_drawing(
C,
[&](const GreasePencilStrokeParams &params,
const IndexMask &mask,
const Span<float> weights) {
/* Crazyspace deformation. */
bke::crazyspace::GeometryDeformation deformation = get_drawing_deformation(params);
/* Transform mouse delta into layer space. */
const float2 mouse_delta_win = this->mouse_delta(extension_sample);
const float3 layer_origin = params.layer.to_world_space(params.ob_eval).location();
const float zfac = ED_view3d_calc_zfac(&rv3d, layer_origin);
float3 mouse_delta;
ED_view3d_win_to_delta(&region, mouse_delta_win, zfac, mouse_delta);
bke::CurvesGeometry &curves = params.drawing.strokes_for_write();
MutableSpan<float3> positions = curves.positions_for_write();
mask.foreach_index(GrainSize(1024), [&](const int point_i, const int index) {
/* Translate the point with the influence factor. */
const float3 new_pos_layer = deformation.positions[point_i] +
mouse_delta * weights[index];
const float3 new_pos_world = math::transform_point(
params.layer.to_world_space(params.ob_eval), new_pos_layer);
float2 new_pos_view;
ED_view3d_project_float_global(&region, new_pos_world, new_pos_view, V3D_PROJ_TEST_NOP);
positions[point_i] = params.placement.project(new_pos_view);
});
params.drawing.tag_positions_changed();
return true;
});
this->stroke_extended(extension_sample);
}
std::unique_ptr<GreasePencilStrokeOperation> new_grab_operation(const BrushStrokeMode stroke_mode)
{
return std::make_unique<GrabOperation>(stroke_mode);
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@@ -0,0 +1,79 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_context.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_paint.hh"
#include "ED_grease_pencil.hh"
#include "ED_view3d.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "grease_pencil_intern.hh"
#include "paint_intern.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class PinchOperation : public GreasePencilStrokeOperationCommon {
public:
using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon;
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override;
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override;
void on_stroke_done(const bContext & /*C*/) override {}
};
void PinchOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample)
{
this->init_stroke(C, start_sample);
}
void PinchOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample)
{
const Scene &scene = *CTX_data_scene(&C);
Paint &paint = *BKE_paint_get_active_from_context(&C);
const Brush &brush = *BKE_paint_brush(&paint);
const bool invert = this->is_inverted(brush);
this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams &params) {
IndexMaskMemory selection_memory;
const IndexMask selection = point_selection_mask(params, selection_memory);
if (selection.is_empty()) {
return false;
}
Array<float2> view_positions = calculate_view_positions(params, selection);
bke::CurvesGeometry &curves = params.drawing.strokes_for_write();
MutableSpan<float3> positions = curves.positions_for_write();
const float2 target = extension_sample.mouse_position;
selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) {
const float2 &co = view_positions[point_i];
const float influence = brush_influence(
scene, brush, co, extension_sample, params.multi_frame_falloff);
if (influence <= 0.0f) {
return;
}
const float scale_offset = influence * influence / 25.0f;
const float scale = invert ? 1.0 + scale_offset : 1.0f - scale_offset;
positions[point_i] = params.placement.project(target + (co - target) * scale);
});
params.drawing.tag_positions_changed();
return true;
});
this->stroke_extended(extension_sample);
}
std::unique_ptr<GreasePencilStrokeOperation> new_pinch_operation(const BrushStrokeMode stroke_mode)
{
return std::make_unique<PinchOperation>(stroke_mode);
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@@ -0,0 +1,76 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_context.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_paint.hh"
#include "ED_grease_pencil.hh"
#include "ED_view3d.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "grease_pencil_intern.hh"
#include "paint_intern.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class PushOperation : public GreasePencilStrokeOperationCommon {
public:
using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon;
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override;
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override;
void on_stroke_done(const bContext & /*C*/) override {}
};
void PushOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample)
{
this->init_stroke(C, start_sample);
}
void PushOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample)
{
const Scene &scene = *CTX_data_scene(&C);
Paint &paint = *BKE_paint_get_active_from_context(&C);
const Brush &brush = *BKE_paint_brush(&paint);
this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams &params) {
IndexMaskMemory selection_memory;
const IndexMask selection = point_selection_mask(params, selection_memory);
if (selection.is_empty()) {
return false;
}
Array<float2> view_positions = calculate_view_positions(params, selection);
bke::CurvesGeometry &curves = params.drawing.strokes_for_write();
MutableSpan<float3> positions = curves.positions_for_write();
const float2 mouse_delta = this->mouse_delta(extension_sample);
selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) {
const float2 &co = view_positions[point_i];
const float influence = brush_influence(
scene, brush, co, extension_sample, params.multi_frame_falloff);
if (influence <= 0.0f) {
return;
}
positions[point_i] = params.placement.project(co + mouse_delta * influence);
});
params.drawing.tag_positions_changed();
return true;
});
this->stroke_extended(extension_sample);
}
std::unique_ptr<GreasePencilStrokeOperation> new_push_operation(const BrushStrokeMode stroke_mode)
{
return std::make_unique<PushOperation>(stroke_mode);
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@@ -0,0 +1,153 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_hash.h"
#include "BLI_math_vector.hh"
#include "BLI_rand.hh"
#include "BLI_task.hh"
#include "BKE_context.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_paint.hh"
#include "ED_grease_pencil.hh"
#include "ED_view3d.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "grease_pencil_intern.hh"
#include "paint_intern.hh"
namespace blender::ed::sculpt_paint::greasepencil {
/* Use a hash to generate random numbers. */
static float hash_rng(uint32_t seed1, uint32_t seed2, int index)
{
return BLI_hash_int_01(BLI_hash_int_3d(seed1, seed2, uint32_t(index)));
}
class RandomizeOperation : public GreasePencilStrokeOperationCommon {
public:
using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon;
/* Get a different seed value for each stroke. */
uint32_t unique_seed() const;
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override;
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override;
void on_stroke_done(const bContext & /*C*/) override {}
};
uint32_t RandomizeOperation::unique_seed() const
{
return RandomNumberGenerator::from_random_seed().get_uint32();
}
void RandomizeOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample)
{
this->init_stroke(C, start_sample);
}
void RandomizeOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample)
{
const Scene &scene = *CTX_data_scene(&C);
Paint &paint = *BKE_paint_get_active_from_context(&C);
const Brush &brush = *BKE_paint_brush(&paint);
const int sculpt_mode_flag = brush.gpencil_settings->sculpt_mode_flag;
this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams &params) {
const uint32_t seed = this->unique_seed();
IndexMaskMemory selection_memory;
const IndexMask selection = point_selection_mask(params, selection_memory);
if (selection.is_empty()) {
return false;
}
Array<float2> view_positions = calculate_view_positions(params, selection);
bke::CurvesGeometry &curves = params.drawing.strokes_for_write();
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
bool changed = false;
if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_POSITION) {
MutableSpan<float3> positions = curves.positions_for_write();
/* Jitter is applied perpendicular to the mouse movement vector. */
const float2 forward = math::normalize(this->mouse_delta(extension_sample));
const float2 sideways = float2(-forward.y, forward.x);
selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) {
const float2 &co = view_positions[point_i];
const float influence = brush_influence(
scene, brush, co, extension_sample, params.multi_frame_falloff);
if (influence <= 0.0f) {
return;
}
const float noise = 2.0f * hash_rng(seed, 5678, point_i) - 1.0f;
positions[point_i] = params.placement.project(co + sideways * influence * noise);
});
params.drawing.tag_positions_changed();
changed = true;
}
if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_STRENGTH) {
MutableSpan<float> opacities = params.drawing.opacities_for_write();
selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) {
const float2 &co = view_positions[point_i];
const float influence = brush_influence(
scene, brush, co, extension_sample, params.multi_frame_falloff);
if (influence <= 0.0f) {
return;
}
const float noise = 2.0f * hash_rng(seed, 1212, point_i) - 1.0f;
opacities[point_i] = math::clamp(opacities[point_i] + influence * noise, 0.0f, 1.0f);
});
changed = true;
}
if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_THICKNESS) {
const MutableSpan<float> radii = params.drawing.radii_for_write();
selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) {
const float2 &co = view_positions[point_i];
const float influence = brush_influence(
scene, brush, co, extension_sample, params.multi_frame_falloff);
if (influence <= 0.0f) {
return;
}
const float noise = 2.0f * hash_rng(seed, 1212, point_i) - 1.0f;
radii[point_i] = math::max(radii[point_i] + influence * noise * 0.001f, 0.0f);
});
curves.tag_radii_changed();
changed = true;
}
if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_UV) {
bke::SpanAttributeWriter<float> rotations = attributes.lookup_or_add_for_write_span<float>(
"rotation", bke::AttrDomain::Point);
selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) {
const float2 &co = view_positions[point_i];
const float influence = brush_influence(
scene, brush, co, extension_sample, params.multi_frame_falloff);
if (influence <= 0.0f) {
return;
}
const float noise = 2.0f * hash_rng(seed, 1212, point_i) - 1.0f;
rotations.span[point_i] = math::clamp(
rotations.span[point_i] + influence * noise, -float(M_PI_2), float(M_PI_2));
});
rotations.finish();
changed = true;
}
return changed;
});
this->stroke_extended(extension_sample);
}
std::unique_ptr<GreasePencilStrokeOperation> new_randomize_operation(
const BrushStrokeMode stroke_mode)
{
return std::make_unique<RandomizeOperation>(stroke_mode);
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@@ -0,0 +1,140 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute.hh"
#include "BKE_context.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_paint.hh"
#include "BLI_virtual_array.hh"
#include "DNA_brush_enums.h"
#include "GEO_smooth_curves.hh"
#include "ED_grease_pencil.hh"
#include "ED_view3d.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "grease_pencil_intern.hh"
#include "paint_intern.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class SmoothOperation : public GreasePencilStrokeOperationCommon {
public:
using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon;
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override;
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override;
void on_stroke_done(const bContext & /*C*/) override {}
};
void SmoothOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample)
{
this->init_stroke(C, start_sample);
}
void SmoothOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample)
{
const Scene &scene = *CTX_data_scene(&C);
Paint &paint = *BKE_paint_get_active_from_context(&C);
const Brush &brush = *BKE_paint_brush(&paint);
const int sculpt_mode_flag = brush.gpencil_settings->sculpt_mode_flag;
this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams &params) {
IndexMaskMemory selection_memory;
const IndexMask selection = point_selection_mask(params, selection_memory);
if (selection.is_empty()) {
return false;
}
Array<float2> view_positions = calculate_view_positions(params, selection);
bke::CurvesGeometry &curves = params.drawing.strokes_for_write();
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
const OffsetIndices points_by_curve = curves.points_by_curve();
const VArray<bool> cyclic = curves.cyclic();
const int iterations = 2;
const VArray<float> influences = VArray<float>::ForFunc(
view_positions.size(), [&](const int64_t point_) {
return brush_influence(
scene, brush, view_positions[point_], extension_sample, params.multi_frame_falloff);
});
Array<bool> selection_array(curves.points_num());
selection.to_bools(selection_array);
const VArray<bool> selection_varray = VArray<bool>::ForSpan(selection_array);
bool changed = false;
if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_POSITION) {
MutableSpan<float3> positions = curves.positions_for_write();
geometry::smooth_curve_attribute(curves.curves_range(),
points_by_curve,
selection_varray,
cyclic,
iterations,
influences,
false,
false,
positions);
params.drawing.tag_positions_changed();
changed = true;
}
if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_STRENGTH) {
MutableSpan<float> opacities = params.drawing.opacities_for_write();
geometry::smooth_curve_attribute(curves.curves_range(),
points_by_curve,
selection_varray,
cyclic,
iterations,
influences,
true,
false,
opacities);
changed = true;
}
if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_THICKNESS) {
const MutableSpan<float> radii = params.drawing.radii_for_write();
geometry::smooth_curve_attribute(curves.curves_range(),
points_by_curve,
selection_varray,
cyclic,
iterations,
influences,
true,
false,
radii);
curves.tag_radii_changed();
changed = true;
}
if (sculpt_mode_flag & GP_SCULPT_FLAGMODE_APPLY_UV) {
bke::SpanAttributeWriter<float> rotations = attributes.lookup_or_add_for_write_span<float>(
"rotation", bke::AttrDomain::Point);
geometry::smooth_curve_attribute(curves.curves_range(),
points_by_curve,
selection_varray,
cyclic,
iterations,
influences,
true,
false,
rotations.span);
rotations.finish();
changed = true;
}
return changed;
});
this->stroke_extended(extension_sample);
}
std::unique_ptr<GreasePencilStrokeOperation> new_smooth_operation(
const BrushStrokeMode stroke_mode)
{
return std::make_unique<SmoothOperation>(stroke_mode);
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@@ -0,0 +1,73 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_task.hh"
#include "BKE_context.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_paint.hh"
#include "ED_grease_pencil.hh"
#include "ED_view3d.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "grease_pencil_intern.hh"
#include "paint_intern.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class StrengthOperation : public GreasePencilStrokeOperationCommon {
public:
using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon;
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override;
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override;
void on_stroke_done(const bContext & /*C*/) override {}
};
void StrengthOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample)
{
this->init_stroke(C, start_sample);
}
void StrengthOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample)
{
const Scene &scene = *CTX_data_scene(&C);
Paint &paint = *BKE_paint_get_active_from_context(&C);
const Brush &brush = *BKE_paint_brush(&paint);
const bool invert = this->is_inverted(brush);
this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams &params) {
IndexMaskMemory selection_memory;
const IndexMask selection = point_selection_mask(params, selection_memory);
if (selection.is_empty()) {
return false;
}
Array<float2> view_positions = calculate_view_positions(params, selection);
MutableSpan<float> opacities = params.drawing.opacities_for_write();
selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) {
float &opacity = opacities[point_i];
const float influence = brush_influence(
scene, brush, view_positions[point_i], extension_sample, params.multi_frame_falloff);
/* Brush influence mapped to opacity by a factor of 0.125. */
const float delta_opacity = (invert ? -influence : influence) * 0.125f;
opacity = std::clamp(opacity + delta_opacity, 0.0f, 1.0f);
});
return true;
});
this->stroke_extended(extension_sample);
}
std::unique_ptr<GreasePencilStrokeOperation> new_strength_operation(
const BrushStrokeMode stroke_mode)
{
return std::make_unique<StrengthOperation>(stroke_mode);
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@@ -0,0 +1,77 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_task.hh"
#include "BKE_context.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_paint.hh"
#include "ED_grease_pencil.hh"
#include "ED_view3d.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "grease_pencil_intern.hh"
#include "paint_intern.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class ThicknessOperation : public GreasePencilStrokeOperationCommon {
public:
using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon;
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override;
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override;
void on_stroke_done(const bContext & /*C*/) override {}
};
void ThicknessOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample)
{
this->init_stroke(C, start_sample);
}
void ThicknessOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample)
{
const Scene &scene = *CTX_data_scene(&C);
Paint &paint = *BKE_paint_get_active_from_context(&C);
const Brush &brush = *BKE_paint_brush(&paint);
const bool invert = this->is_inverted(brush);
this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams &params) {
IndexMaskMemory selection_memory;
const IndexMask selection = point_selection_mask(params, selection_memory);
if (selection.is_empty()) {
return false;
}
Array<float2> view_positions = calculate_view_positions(params, selection);
bke::CurvesGeometry &curves = params.drawing.strokes_for_write();
BLI_assert(view_positions.size() == curves.points_num());
MutableSpan<float> radii = params.drawing.radii_for_write();
selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) {
float &radius = radii[point_i];
const float influence = brush_influence(
scene, brush, view_positions[point_i], extension_sample, params.multi_frame_falloff);
/* Factor 1/1000 is used to map arbitrary influence value to a sensible radius. */
const float delta_radius = (invert ? -influence : influence) * 0.001f;
radius = std::max(radius + delta_radius, 0.0f);
});
curves.tag_radii_changed();
return true;
});
this->stroke_extended(extension_sample);
}
std::unique_ptr<GreasePencilStrokeOperation> new_thickness_operation(
const BrushStrokeMode stroke_mode)
{
return std::make_unique<ThicknessOperation>(stroke_mode);
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@@ -0,0 +1,86 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_context.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_paint.hh"
#include "ED_grease_pencil.hh"
#include "ED_view3d.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "grease_pencil_intern.hh"
#include "paint_intern.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class TwistOperation : public GreasePencilStrokeOperationCommon {
public:
using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon;
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override;
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override;
void on_stroke_done(const bContext & /*C*/) override {}
};
static float2 rotate_by_angle(const float2 &vec, const float angle)
{
const float cos_angle = math::cos(angle);
const float sin_angle = math::sin(angle);
return float2(vec.x * cos_angle - vec.y * sin_angle, vec.x * sin_angle + vec.y * cos_angle);
}
void TwistOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample)
{
this->init_stroke(C, start_sample);
}
void TwistOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample)
{
const Scene &scene = *CTX_data_scene(&C);
Paint &paint = *BKE_paint_get_active_from_context(&C);
const Brush &brush = *BKE_paint_brush(&paint);
const bool invert = this->is_inverted(brush);
this->foreach_editable_drawing(C, [&](const GreasePencilStrokeParams &params) {
IndexMaskMemory selection_memory;
const IndexMask selection = point_selection_mask(params, selection_memory);
if (selection.is_empty()) {
return false;
}
Array<float2> view_positions = calculate_view_positions(params, selection);
bke::CurvesGeometry &curves = params.drawing.strokes_for_write();
MutableSpan<float3> positions = curves.positions_for_write();
const float2 mouse_pos = extension_sample.mouse_position;
selection.foreach_index(GrainSize(4096), [&](const int64_t point_i) {
const float2 &co = view_positions[point_i];
const float influence = brush_influence(
scene, brush, co, extension_sample, params.multi_frame_falloff);
if (influence <= 0.0f) {
return;
}
const float angle = DEG2RADF(invert ? -1.0f : 1.0f) * influence;
positions[point_i] = params.placement.project(rotate_by_angle(co - mouse_pos, angle) +
mouse_pos);
});
params.drawing.tag_positions_changed();
return true;
});
this->stroke_extended(extension_sample);
}
std::unique_ptr<GreasePencilStrokeOperation> new_twist_operation(const BrushStrokeMode stroke_mode)
{
return std::make_unique<TwistOperation>(stroke_mode);
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@@ -14,13 +14,14 @@ namespace blender::geometry {
* 1D Gaussian-like smoothing function.
*
* \param iterations: Number of times to repeat the smoothing.
* \param influence: Influence factor for each point.
* \param smooth_ends: Smooth the first and last value.
* \param keep_shape: Changes the gaussian kernel to avoid severe deformations.
* \param is_cyclic: Propagate smoothing across the ends of the input as if they were connected.
*/
void gaussian_blur_1D(const GSpan src,
int iterations,
const float influence,
const VArray<float> &influence_by_point,
const bool smooth_ends,
const bool keep_shape,
const bool is_cyclic,
@@ -39,4 +40,17 @@ void smooth_curve_attribute(const IndexMask &curves_to_smooth,
bool keep_shape,
GMutableSpan attribute_data);
/**
* Smooths the \a attribute_data using a 1D gaussian blur.
*/
void smooth_curve_attribute(const IndexMask &curves_to_smooth,
const OffsetIndices<int> points_by_curve,
const VArray<bool> &point_selection,
const VArray<bool> &cyclic,
int iterations,
const VArray<float> &influence_by_point,
bool smooth_ends,
bool keep_shape,
GMutableSpan attribute_data);
} // namespace blender::geometry

View File

@@ -18,7 +18,7 @@ namespace blender::geometry {
template<typename T>
static void gaussian_blur_1D(const Span<T> src,
const int iterations,
const float influence,
const VArray<float> &influence_by_point,
const bool smooth_ends,
const bool keep_shape,
const bool is_cyclic,
@@ -133,19 +133,21 @@ static void gaussian_blur_1D(const Span<T> src,
}
/* Normalize the weights. */
threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) {
for (const int64_t index : range) {
if (!is_end_and_fixed(index)) {
total_weight[index] += w - w2;
dst[index] = src[index] + influence * dst[index] / total_weight[index];
devirtualize_varray(influence_by_point, [&](const auto influence_by_point) {
threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) {
for (const int64_t index : range) {
if (!is_end_and_fixed(index)) {
total_weight[index] += w - w2;
dst[index] = src[index] + influence_by_point[index] * dst[index] / total_weight[index];
}
}
}
});
});
}
void gaussian_blur_1D(const GSpan src,
const int iterations,
const float influence,
const VArray<float> &influence_by_point,
const bool smooth_ends,
const bool keep_shape,
const bool is_cyclic,
@@ -160,7 +162,7 @@ void gaussian_blur_1D(const GSpan src,
{
gaussian_blur_1D(src.typed<T>(),
iterations,
influence,
influence_by_point,
smooth_ends,
keep_shape,
is_cyclic,
@@ -174,11 +176,13 @@ void smooth_curve_attribute(const IndexMask &curves_to_smooth,
const VArray<bool> &point_selection,
const VArray<bool> &cyclic,
const int iterations,
const float influence,
const VArray<float> &influence_by_point,
const bool smooth_ends,
const bool keep_shape,
GMutableSpan attribute_data)
{
VArraySpan<float> influences(influence_by_point);
curves_to_smooth.foreach_index(GrainSize(512), [&](const int curve_i) {
Vector<std::byte> orig_data;
const IndexRange points = points_by_curve[curve_i];
@@ -196,10 +200,36 @@ void smooth_curve_attribute(const IndexMask &curves_to_smooth,
dst_data.type().copy_assign_n(dst_data.data(), orig_data.data(), range.size());
const GSpan src_data(dst_data.type(), orig_data.data(), range.size());
gaussian_blur_1D(
src_data, iterations, influence, smooth_ends, keep_shape, cyclic[curve_i], dst_data);
gaussian_blur_1D(src_data,
iterations,
VArray<float>::ForSpan(influences.slice(range)),
smooth_ends,
keep_shape,
cyclic[curve_i],
dst_data);
});
});
}
void smooth_curve_attribute(const IndexMask &curves_to_smooth,
const OffsetIndices<int> points_by_curve,
const VArray<bool> &point_selection,
const VArray<bool> &cyclic,
const int iterations,
const float influence,
const bool smooth_ends,
const bool keep_shape,
GMutableSpan attribute_data)
{
smooth_curve_attribute(curves_to_smooth,
points_by_curve,
point_selection,
cyclic,
iterations,
VArray<float>::ForSingle(influence, points_by_curve.total_size()),
smooth_ends,
keep_shape,
attribute_data);
}
} // namespace blender::geometry

View File

@@ -328,6 +328,7 @@ typedef enum eGP_Sculpt_Mode_Flag {
/* apply brush to uv data */
GP_SCULPT_FLAGMODE_APPLY_UV = (1 << 3),
} eGP_Sculpt_Mode_Flag;
ENUM_OPERATORS(eGP_Sculpt_Mode_Flag, GP_SCULPT_FLAGMODE_APPLY_UV)
typedef enum eAutomasking_flag {
BRUSH_AUTOMASKING_TOPOLOGY = (1 << 0),

View File

@@ -1013,6 +1013,7 @@ static const EnumPropertyItem *rna_Brush_direction_itemf(bContext *C,
return rna_enum_dummy_DEFAULT_items;
}
case PaintMode::SculptGPencil:
case PaintMode::SculptGreasePencil:
switch (me->gpencil_sculpt_tool) {
case GPSCULPT_TOOL_THICKNESS:
case GPSCULPT_TOOL_STRENGTH:

View File

@@ -86,6 +86,7 @@
#include "ED_anim_api.hh"
#include "ED_asset.hh"
#include "ED_gpencil_legacy.hh"
#include "ED_grease_pencil.hh"
#include "ED_keyframes_edit.hh"
#include "ED_keyframing.hh"
#include "ED_node.hh"
@@ -581,6 +582,7 @@ void WM_exit_ex(bContext *C, const bool do_python_exit, const bool do_user_exit_
BKE_mask_clipboard_free();
BKE_vfont_clipboard_free();
ED_node_clipboard_free();
ed::greasepencil::clipboard_free();
UV_clipboard_free();
wm_clipboard_free();