Fix #134490: Grease Pencil: Clone brush in sculpt mode not working

The main issue was that the code was using
`foreach_editable_drawing_with_automask` which shouldn't have been
the case since there is no "masking" to be done when pasting strokes.

Additionally the code was computing the view position of every
pasted point and then computing the offset to move the points to the
mouse position.

Instead, we compute the center of the 3d bounds of the pasted strokes
and then project only the center to the view. From there we can offset
all the points to move them under the mouse cursor.

It's a bit weird to do this projection of the center to the view and
I believe we mainly do it to reuse the `DeltaProjectionFunc`.
There are cases where the projection of the center to the view will fail
which can lead to unexpected behavior on the user end. For now we
just leave the pasted strokes where they were copied from, which
isn't ideal. But this can be improved later.

Pull Request: https://projects.blender.org/blender/blender/pulls/134589
This commit is contained in:
Falk David
2025-02-17 10:38:02 +01:00
committed by Falk David
parent 0faba244a5
commit 1a071ef755
3 changed files with 69 additions and 16 deletions
@@ -201,6 +201,11 @@ class GreasePencilStrokeOperationCommon : public GreasePencilStrokeOperation {
const IndexMask &points,
const DeltaProjectionFunc &projection_fn)> fn) const;
void foreach_editable_drawing(
const bContext &C,
FunctionRef<bool(const GreasePencilStrokeParams &params,
const DeltaProjectionFunc &projection_fn)> fn) const;
/** Used in vertex paint mode. */
void foreach_editable_drawing(
const bContext &C, FunctionRef<bool(const GreasePencilStrokeParams &params)> fn) const;
@@ -529,6 +529,49 @@ void GreasePencilStrokeOperationCommon::foreach_editable_drawing(
}
}
void GreasePencilStrokeOperationCommon::foreach_editable_drawing(
const bContext &C,
FunctionRef<bool(const GreasePencilStrokeParams &params,
const DeltaProjectionFunc &projection_fn)> fn) const
{
using namespace blender::bke::greasepencil;
const Scene &scene = *CTX_data_scene(&C);
Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C);
ARegion &region = *CTX_wm_region(&C);
RegionView3D &rv3d = *CTX_wm_region_view3d(&C);
Object &object = *CTX_data_active_object(&C);
Object &object_eval = *DEG_get_evaluated_object(&depsgraph, &object);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
bool changed = false;
const Vector<MutableDrawingInfo> drawings = get_drawings_for_stroke_operation(C);
for (const int64_t i : drawings.index_range()) {
const MutableDrawingInfo &info = drawings[i];
const Layer &layer = grease_pencil.layer(info.layer_index);
GreasePencilStrokeParams params = GreasePencilStrokeParams::from_context(
scene,
depsgraph,
region,
rv3d,
object,
info.layer_index,
info.frame_number,
info.multi_frame_falloff,
info.drawing);
const DeltaProjectionFunc projection_fn = get_screen_projection_fn(params, object_eval, layer);
if (fn(params, projection_fn)) {
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::foreach_editable_drawing(
const bContext &C,
const GrainSize grain_size,
@@ -7,6 +7,8 @@
#include "BKE_grease_pencil.hh"
#include "BKE_paint.hh"
#include "BLI_bounds.hh"
#include "ED_curves.hh"
#include "ED_grease_pencil.hh"
#include "ED_view3d.hh"
@@ -30,11 +32,6 @@ class CloneOperation : public GreasePencilStrokeOperationCommon {
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);
@@ -49,11 +46,8 @@ void CloneOperation::on_stroke_begin(const bContext &C, const InputSample &start
* - Continuous: Create multiple copies during the stroke (disabled)
*
* Here we only have the GPv2 behavior that actually works for now. */
this->foreach_editable_drawing_with_automask(
C,
[&](const GreasePencilStrokeParams &params,
const IndexMask & /*point_mask*/,
const DeltaProjectionFunc &projection_fn) {
this->foreach_editable_drawing(
C, [&](const GreasePencilStrokeParams &params, const DeltaProjectionFunc &projection_fn) {
/* Only insert on the active layer. */
if (&params.layer != grease_pencil.get_active_layer()) {
return false;
@@ -68,7 +62,6 @@ void CloneOperation::on_stroke_begin(const bContext &C, const InputSample &start
return false;
}
const bke::crazyspace::GeometryDeformation deformation = get_drawing_deformation(params);
bke::CurvesGeometry &curves = params.drawing.strokes_for_write();
const OffsetIndices<int> pasted_points_by_curve = curves.points_by_curve().slice(
pasted_curves);
@@ -78,15 +71,27 @@ void CloneOperation::on_stroke_begin(const bContext &C, const InputSample &start
return false;
}
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;
const Bounds<float3> bounds = *bounds::min_max(curves.positions().slice(pasted_points));
const float4x4 transform = params.layer.to_world_space(params.ob_eval);
/* FIXME: Projecting the center of the bounds to the view can sometimes fail. This might
* result in unexpected behavior on the user end. Figure out a way to not rely on view
* space here and compute the translation offset in layer space instead. */
float2 view_center(0.0f);
if (ED_view3d_project_float_global(&params.region,
math::transform_point(transform, bounds.center()),
view_center,
V3D_PROJ_TEST_NOP) != V3D_PROJ_RET_OK)
{
return false;
}
const float2 &mouse_delta = start_sample.mouse_position - view_center;
const bke::crazyspace::GeometryDeformation deformation = get_drawing_deformation(params);
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] = compute_orig_delta(
positions[point_i] += compute_orig_delta(
projection_fn, deformation, point_i, mouse_delta);
}
});