From bf2f4e54cc226d749a7041fb219a284ae2bf9a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20T=C3=B6nne?= Date: Tue, 24 Sep 2024 11:35:07 +0200 Subject: [PATCH] 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 --- .../intern/grease_pencil_edit.cc | 63 +++++++++++++++++-- .../editors/include/ED_grease_pencil.hh | 3 + .../grease_pencil_sculpt_clone.cc | 11 +++- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc index 27628f08b55..d2ba81387eb 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc @@ -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> 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(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 geometries, + Span transforms) +{ + BLI_assert(geometries.size() == transforms.size()); + + std::unique_ptr instances = std::make_unique(); + instances->resize(geometries.size()); + instances->transforms_for_write().copy_from(transforms); + MutableSpan 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(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 set_of_copied_curves; + Vector set_of_transforms; /* Collect all selected strokes/points on all editable layers. */ const Vector 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 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 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 transforms = paste_back ? + Span{clipboard_transform, + float4x4::identity()} : + Span{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. */ diff --git a/source/blender/editors/include/ED_grease_pencil.hh b/source/blender/editors/include/ED_grease_pencil.hh index 115c5b25539..d24d170b4cb 100644 --- a/source/blender/editors/include/ED_grease_pencil.hh +++ b/source/blender/editors/include/ED_grease_pencil.hh @@ -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); /** diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_clone.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_clone.cc index 1064941211d..9b98554b3f9 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_clone.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_clone.cc @@ -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(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 ¶ms, const DeltaProjectionFunc &projection_fn) { + /* Only insert on the active layer. */ + if (¶ms.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; }