GPv3: Take layer transform into account when using clipboard

This affects the edit-mode copy/paste operators as well as the sculpt
mode _Clone_ tool. These copy and paste to/from the clipboard, but were
not applying the layer transform in the process. All curves were stored
in their respective local layer space. Since everything gets merged
these transforms get lost when storing to the clipboard.

Pasting curves from the clipboard was also not applying the target layer
transform. Also the clipboard content was getting pasted into every
editable drawing.

Now clipboard curves are always stored in world space. Pasting only
happens on the active layer, and the active layer inverse transform is
applied so visual transform of curves shouldn't change through copy and
paste.

Pull Request: https://projects.blender.org/blender/blender/pulls/127917
This commit is contained in:
Lukas Tönne
2024-09-24 11:35:07 +02:00
parent 6ea0b8cdb2
commit bf2f4e54cc
3 changed files with 72 additions and 5 deletions

View File

@@ -30,6 +30,7 @@
#include "BKE_curves_utils.hh"
#include "BKE_deform.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_material.h"
@@ -53,10 +54,12 @@
#include "ED_view3d.hh"
#include "GEO_join_geometries.hh"
#include "GEO_realize_instances.hh"
#include "GEO_reorder.hh"
#include "GEO_set_curve_type.hh"
#include "GEO_smooth_curves.hh"
#include "GEO_subdivide_curves.hh"
#include "UI_interface_c.hh"
#include "UI_resources.hh"
@@ -2181,6 +2184,8 @@ static void GREASE_PENCIL_OT_separate(wmOperatorType *ot)
/* Global clipboard for Grease Pencil curves. */
static struct Clipboard {
bke::CurvesGeometry curves;
/* Object transform of stored curves. */
float4x4 transform;
/* 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;
@@ -2208,6 +2213,7 @@ static int grease_pencil_paste_strokes_exec(bContext *C, wmOperator *op)
const bke::AttrDomain selection_domain = ED_grease_pencil_selection_domain_get(
scene.toolsettings);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
const bool keep_world_transform = RNA_boolean_get(op->ptr, "keep_world_transform");
const bool paste_on_back = RNA_boolean_get(op->ptr, "paste_back");
/* Get active layer in the target object. */
@@ -2243,7 +2249,9 @@ static int grease_pencil_paste_strokes_exec(bContext *C, wmOperator *op)
selection_in_target.finish();
});
clipboard_paste_strokes(*bmain, *object, *target_drawing, paste_on_back);
const float4x4 object_to_layer = math::invert(active_layer.to_object_space(*object));
clipboard_paste_strokes(
*bmain, *object, *target_drawing, object_to_layer, keep_world_transform, paste_on_back);
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
@@ -2255,8 +2263,29 @@ static int grease_pencil_paste_strokes_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
static bke::GeometrySet join_geometries_with_transform(Span<bke::GeometrySet> geometries,
Span<float4x4> transforms)
{
BLI_assert(geometries.size() == transforms.size());
std::unique_ptr<bke::Instances> instances = std::make_unique<bke::Instances>();
instances->resize(geometries.size());
instances->transforms_for_write().copy_from(transforms);
MutableSpan<int> handles = instances->reference_handles_for_write();
for (const int i : geometries.index_range()) {
handles[i] = instances->add_new_reference(bke::InstanceReference{geometries[i]});
}
geometry::RealizeInstancesOptions options;
options.keep_original_ids = true;
options.realize_instance_attributes = false;
return realize_instances(bke::GeometrySet::from_instances(instances.release()), options);
}
static int grease_pencil_copy_strokes_exec(bContext *C, wmOperator *op)
{
using bke::greasepencil::Layer;
const Scene *scene = CTX_data_scene(C);
const Object *object = CTX_data_active_object(C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
@@ -2268,11 +2297,14 @@ static int grease_pencil_copy_strokes_exec(bContext *C, wmOperator *op)
bool anything_copied = false;
int num_copied = 0;
Vector<bke::GeometrySet> set_of_copied_curves;
Vector<float4x4> set_of_transforms;
/* Collect all selected strokes/points on all editable layers. */
const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
for (const MutableDrawingInfo &drawing_info : drawings) {
const bke::CurvesGeometry &curves = drawing_info.drawing.strokes();
const Layer &layer = *grease_pencil.layer(drawing_info.layer_index);
const float4x4 layer_to_object = layer.to_object_space(*object);
if (curves.curves_num() == 0) {
continue;
@@ -2299,6 +2331,7 @@ static int grease_pencil_copy_strokes_exec(bContext *C, wmOperator *op)
/* Add the layer selection to the set of copied curves. */
Curves *layer_curves = curves_new_nomain(std::move(copied_curves));
set_of_copied_curves.append(bke::GeometrySet::from_curves(layer_curves));
set_of_transforms.append(layer_to_object);
anything_copied = true;
}
@@ -2308,8 +2341,10 @@ static int grease_pencil_copy_strokes_exec(bContext *C, wmOperator *op)
}
/* Merge all copied curves into one CurvesGeometry object and assign it to the clipboard. */
bke::GeometrySet joined_copied_curves = geometry::join_geometries(set_of_copied_curves, {});
bke::GeometrySet joined_copied_curves = join_geometries_with_transform(set_of_copied_curves,
set_of_transforms);
clipboard.curves = std::move(joined_copied_curves.get_curves_for_write()->geometry.wrap());
clipboard.transform = object->object_to_world();
/* Store the session uid of the materials used by the curves in the clipboard. We use the uid to
* remap the material indices when pasting. */
@@ -2363,6 +2398,11 @@ static void GREASE_PENCIL_OT_paste(wmOperatorType *ot)
ot->prop = RNA_def_boolean(
ot->srna, "paste_back", false, "Paste on Back", "Add pasted strokes behind all strokes");
RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE);
ot->prop = RNA_def_boolean(ot->srna,
"keep_world_transform",
false,
"Keep World Transform",
"Keep the world transform of strokes from the clipboard unchanged");
}
static void GREASE_PENCIL_OT_copy(wmOperatorType *ot)
@@ -2430,9 +2470,13 @@ static Array<int> clipboard_materials_remap(Main &bmain, Object &object)
IndexRange clipboard_paste_strokes(Main &bmain,
Object &object,
bke::greasepencil::Drawing &drawing,
const float4x4 &transform,
const bool keep_world_transform,
const bool paste_back)
{
const bke::CurvesGeometry &clipboard_curves = ed::greasepencil::clipboard_curves();
const Clipboard &clipboard = ensure_grease_pencil_clipboard();
const bke::CurvesGeometry &clipboard_curves = clipboard.curves;
const float4x4 clipboard_to_world = clipboard.transform;
if (clipboard_curves.curves_num() <= 0) {
return {};
}
@@ -2450,10 +2494,21 @@ IndexRange clipboard_paste_strokes(Main &bmain,
/* 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, {});
const float4x4 clipboard_transform = transform *
(keep_world_transform ?
object.world_to_object() * clipboard_to_world :
float4x4::identity());
const Array<float4x4> transforms = paste_back ?
Span<float4x4>{clipboard_transform,
float4x4::identity()} :
Span<float4x4>{float4x4::identity(), clipboard_transform};
bke::GeometrySet joined_curves = join_geometries_with_transform(geometry_sets, transforms);
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. */

View File

@@ -512,11 +512,14 @@ 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.
* \param keep_world_transform: Keep the world transform of clipboard strokes unchanged.
* \return Index range of the new curves in the drawing after pasting.
*/
IndexRange clipboard_paste_strokes(Main &bmain,
Object &object,
bke::greasepencil::Drawing &drawing,
const float4x4 &transform,
bool keep_world_transform,
bool paste_back);
/**

View File

@@ -37,6 +37,7 @@ void CloneOperation::on_stroke_begin(const bContext &C, const InputSample &start
{
Main &bmain = *CTX_data_main(&C);
Object &object = *CTX_data_active_object(&C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
this->init_stroke(C, start_sample);
@@ -48,8 +49,16 @@ void CloneOperation::on_stroke_begin(const bContext &C, const InputSample &start
* Here we only have the GPv2 behavior that actually works for now. */
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;
}
/* TODO Could become a tool setting. */
const bool keep_world_transform = false;
const float4x4 clipboard_to_layer = math::invert(params.layer.to_world_space(object));
const IndexRange pasted_curves = ed::greasepencil::clipboard_paste_strokes(
bmain, object, params.drawing, false);
bmain, object, params.drawing, clipboard_to_layer, keep_world_transform, false);
if (pasted_curves.is_empty()) {
return false;
}