diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 7415d23ec24..bbda275b645 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -5738,7 +5738,8 @@ class VIEW3D_MT_edit_greasepencil(Menu): layout.separator() layout.operator("grease_pencil.copy", text="Copy", icon='COPYDOWN') - layout.operator("grease_pencil.paste", text="Paste", icon='PASTEDOWN') + layout.operator("grease_pencil.paste", text="Paste", icon='PASTEDOWN').type = 'ACTIVE' + layout.operator("grease_pencil.paste", text="Paste by Layer").type = 'LAYER' layout.separator() @@ -8132,9 +8133,10 @@ class VIEW3D_MT_greasepencil_edit_context_menu(Menu): col.separator() # Copy/paste - col.operator("grease_pencil.copy", text="Copy", icon='COPYDOWN') - col.operator("grease_pencil.paste", text="Paste", icon='PASTEDOWN') col.operator("grease_pencil.duplicate_move", text="Duplicate") + col.operator("grease_pencil.copy", text="Copy", icon='COPYDOWN') + col.operator("grease_pencil.paste", text="Paste", icon='PASTEDOWN').type = 'ACTIVE' + col.operator("grease_pencil.paste", text="Paste by Layer").type = 'LAYER' col.separator() 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 722a0815727..0ccacd2f0e1 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc @@ -6,6 +6,7 @@ * \ingroup edgreasepencil */ +#include "BLI_array.hh" #include "BLI_array_utils.hh" #include "BLI_assert.h" #include "BLI_index_mask.hh" @@ -2448,9 +2449,15 @@ static void GREASE_PENCIL_OT_separate(wmOperatorType *ot) /* Global clipboard for Grease Pencil curves. */ static struct Clipboard { - bke::CurvesGeometry curves; + struct ClipboardLayer { + /* Name of the layer. */ + std::string name; + /* Curves for this layer. */ + bke::CurvesGeometry curves; + }; + Array layers; /* Object transform of stored curves. */ - float4x4 transform; + float4x4 object_to_world; /* We store the material uid's of the copied curves, so we can match those when pasting the * clipboard into another object. */ Vector> materials; @@ -2470,220 +2477,6 @@ static Clipboard &ensure_grease_pencil_clipboard() return *grease_pencil_clipboard; } -static int grease_pencil_paste_strokes_exec(bContext *C, wmOperator *op) -{ - Main *bmain = CTX_data_main(C); - const Scene &scene = *CTX_data_scene(C); - Object *object = CTX_data_active_object(C); - const bke::AttrDomain selection_domain = ED_grease_pencil_edit_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. */ - if (!grease_pencil.has_active_layer()) { - BKE_report(op->reports, RPT_ERROR, "No active Grease Pencil layer"); - return OPERATOR_CANCELLED; - } - bke::greasepencil::Layer &active_layer = *grease_pencil.get_active_layer(); - if (!active_layer.is_editable()) { - BKE_report(op->reports, RPT_ERROR, "Active layer is locked or hidden"); - return OPERATOR_CANCELLED; - } - - /* Ensure active keyframe. */ - bool inserted_keyframe = false; - if (!ensure_active_keyframe(scene, grease_pencil, active_layer, false, inserted_keyframe)) { - BKE_report(op->reports, RPT_ERROR, "No Grease Pencil frame to draw on"); - return OPERATOR_CANCELLED; - } - bke::greasepencil::Drawing *target_drawing = grease_pencil.get_editable_drawing_at(active_layer, - scene.r.cfra); - if (target_drawing == nullptr) { - return OPERATOR_CANCELLED; - } - - /* Deselect everything from editable drawings. The pasted strokes are the only ones then after - * the paste. That's convenient for the user. */ - const Vector drawings = retrieve_editable_drawings(scene, grease_pencil); - threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { - bke::GSpanAttributeWriter selection_in_target = ed::curves::ensure_selection_attribute( - info.drawing.strokes_for_write(), selection_domain, CD_PROP_BOOL); - ed::curves::fill_selection_false(selection_in_target.span); - selection_in_target.finish(); - }); - - 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); - - if (inserted_keyframe) { - WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, nullptr); - } - - 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); - const bke::AttrDomain selection_domain = ED_grease_pencil_edit_selection_domain_get( - scene->toolsettings); - - Clipboard &clipboard = ensure_grease_pencil_clipboard(); - - 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.is_empty()) { - continue; - } - if (!ed::curves::has_anything_selected(curves)) { - continue; - } - - /* Get a copy of the selected geometry on this layer. */ - IndexMaskMemory memory; - bke::CurvesGeometry copied_curves; - - if (selection_domain == bke::AttrDomain::Curve) { - const IndexMask selected_curves = ed::curves::retrieve_selected_curves(curves, memory); - copied_curves = curves_copy_curve_selection(curves, selected_curves, {}); - num_copied += copied_curves.curves_num(); - } - else if (selection_domain == bke::AttrDomain::Point) { - const IndexMask selected_points = ed::curves::retrieve_selected_points(curves, memory); - copied_curves = curves_copy_point_selection(curves, selected_points, {}); - num_copied += copied_curves.points_num(); - } - - /* 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; - } - - if (!anything_copied) { - clipboard.curves.resize(0, 0); - return OPERATOR_CANCELLED; - } - - /* Merge all copied curves into one CurvesGeometry object and assign it to the clipboard. */ - 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. */ - clipboard.materials.clear(); - clipboard.materials_in_source_num = grease_pencil.material_array_num; - const bke::AttributeAccessor attributes = clipboard.curves.attributes(); - const VArraySpan material_indices = *attributes.lookup_or_default( - "material_index", bke::AttrDomain::Curve, 0); - for (const int material_index : IndexRange(grease_pencil.material_array_num)) { - if (!material_indices.contains(material_index)) { - continue; - } - const Material *material = grease_pencil.material_array[material_index]; - clipboard.materials.append({material->id.session_uid, material_index}); - } - - /* Report the numbers. */ - if (selection_domain == bke::AttrDomain::Curve) { - BKE_reportf(op->reports, RPT_INFO, "Copied %d selected curve(s)", num_copied); - } - else if (selection_domain == bke::AttrDomain::Point) { - BKE_reportf(op->reports, RPT_INFO, "Copied %d selected point(s)", num_copied); - } - - return OPERATOR_FINISHED; -} - -static bool grease_pencil_paste_strokes_poll(bContext *C) -{ - if (!editable_grease_pencil_poll(C)) { - return false; - } - - std::scoped_lock lock(grease_pencil_clipboard_lock); - /* Check for curves in the Grease Pencil clipboard. */ - return (grease_pencil_clipboard && grease_pencil_clipboard->curves.curves_num() > 0); -} - -static void GREASE_PENCIL_OT_paste(wmOperatorType *ot) -{ - ot->name = "Paste Strokes"; - ot->idname = "GREASE_PENCIL_OT_paste"; - ot->description = - "Paste Grease Pencil points or strokes from the internal clipboard to the active layer"; - - ot->exec = grease_pencil_paste_strokes_exec; - ot->poll = grease_pencil_paste_strokes_poll; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - 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) -{ - ot->name = "Copy Strokes"; - ot->idname = "GREASE_PENCIL_OT_copy"; - ot->description = "Copy the selected Grease Pencil points or strokes to the internal clipboard"; - - ot->exec = grease_pencil_copy_strokes_exec; - ot->poll = editable_grease_pencil_poll; - - ot->flag = OPTYPE_REGISTER; -} - -/** \} */ - void clipboard_free() { std::scoped_lock lock(grease_pencil_clipboard_lock); @@ -2694,12 +2487,6 @@ void clipboard_free() } } -const bke::CurvesGeometry &clipboard_curves() -{ - using namespace blender::ed::greasepencil; - return ensure_grease_pencil_clipboard().curves; -} - static Array clipboard_materials_remap(Main &bmain, Object &object) { using namespace blender::ed::greasepencil; @@ -2710,7 +2497,7 @@ static Array clipboard_materials_remap(Main &bmain, Object &object) scene_materials.add(material->id.session_uid, material); } - Clipboard &clipboard = ensure_grease_pencil_clipboard(); + const Clipboard &clipboard = ensure_grease_pencil_clipboard(); Array 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. */ @@ -2732,47 +2519,181 @@ static Array clipboard_materials_remap(Main &bmain, Object &object) return clipboard_material_remap; } -IndexRange clipboard_paste_strokes(Main &bmain, - Object &object, - bke::greasepencil::Drawing &drawing, - const float4x4 &transform, - const bool keep_world_transform, - const bool paste_back) +static bke::GeometrySet join_geometries_with_transforms(Span geometries, + const VArray &transforms) { - 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 {}; + BLI_assert(geometries.size() == transforms.size()); + + std::unique_ptr instances = std::make_unique(); + instances->resize(geometries.size()); + transforms.materialize(instances->transforms_for_write()); + 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 bke::GeometrySet join_geometries_with_transform(Span geometries, + const float4x4 &transform) +{ + return join_geometries_with_transforms( + geometries, VArray::ForSingle(transform, geometries.size())); +} + +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); + const bke::AttrDomain selection_domain = ED_grease_pencil_edit_selection_domain_get( + scene->toolsettings); + + Clipboard &clipboard = ensure_grease_pencil_clipboard(); + + int num_elements_copied = 0; + Map> copied_curves_per_layer; + + /* 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); + + if (curves.is_empty()) { + continue; + } + if (!ed::curves::has_anything_selected(curves)) { + continue; + } + + /* Get a copy of the selected geometry on this layer. */ + IndexMaskMemory memory; + bke::CurvesGeometry copied_curves; + + if (selection_domain == bke::AttrDomain::Curve) { + const IndexMask selected_curves = ed::curves::retrieve_selected_curves(curves, memory); + copied_curves = curves_copy_curve_selection(curves, selected_curves, {}); + num_elements_copied += copied_curves.curves_num(); + } + else if (selection_domain == bke::AttrDomain::Point) { + const IndexMask selected_points = ed::curves::retrieve_selected_points(curves, memory); + copied_curves = curves_copy_point_selection(curves, selected_points, {}); + num_elements_copied += copied_curves.points_num(); + } + + /* Add the layer selection to the set of copied curves. */ + copied_curves_per_layer.lookup_or_add_default(&layer).append( + bke::GeometrySet::from_curves(curves_new_nomain(std::move(copied_curves)))); + } + + if (copied_curves_per_layer.is_empty()) { + clipboard.layers.reinitialize(0); + return OPERATOR_CANCELLED; + } + + clipboard.layers.reinitialize(copied_curves_per_layer.size()); + + int i = 0; + for (auto const &[layer, geometries] : copied_curves_per_layer.items()) { + const float4x4 layer_to_object = layer->to_object_space(*object); + Clipboard::ClipboardLayer &cliplayer = clipboard.layers[i]; + + bke::GeometrySet joined_copied_curves = join_geometries_with_transform(geometries.as_span(), + layer_to_object); + cliplayer.curves = joined_copied_curves.get_curves()->geometry.wrap(); + cliplayer.name = layer->name(); + i++; + } + clipboard.object_to_world = 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. */ + clipboard.materials.clear(); + clipboard.materials_in_source_num = grease_pencil.material_array_num; + + const auto is_material_index_used = [&](const int material_index) -> bool { + for (const Clipboard::ClipboardLayer &layer : clipboard.layers) { + const bke::AttributeAccessor attributes = layer.curves.attributes(); + const VArraySpan material_indices = *attributes.lookup_or_default( + "material_index", bke::AttrDomain::Curve, 0); + if (material_indices.contains(material_index)) { + return true; + } + } + return false; + }; + + for (const int material_index : IndexRange(grease_pencil.material_array_num)) { + if (!is_material_index_used(material_index)) { + continue; + } + const Material *material = grease_pencil.material_array[material_index]; + clipboard.materials.append({material->id.session_uid, material_index}); + } + + /* Report the numbers. */ + if (selection_domain == bke::AttrDomain::Curve) { + BKE_reportf(op->reports, RPT_INFO, "Copied %d selected curve(s)", num_elements_copied); + } + else if (selection_domain == bke::AttrDomain::Point) { + BKE_reportf(op->reports, RPT_INFO, "Copied %d selected point(s)", num_elements_copied); + } + + return OPERATOR_FINISHED; +} + +static void GREASE_PENCIL_OT_copy(wmOperatorType *ot) +{ + ot->name = "Copy Strokes"; + ot->idname = "GREASE_PENCIL_OT_copy"; + ot->description = "Copy the selected Grease Pencil points or strokes to the internal clipboard"; + + ot->exec = grease_pencil_copy_strokes_exec; + ot->poll = editable_grease_pencil_poll; + + ot->flag = OPTYPE_REGISTER; +} + +static IndexRange clipboard_paste_strokes_ex(Main &bmain, + Object &object, + const bke::CurvesGeometry &curves_to_paste, + const float4x4 &object_to_paste_layer, + const float4x4 &clipboard_to_world, + const bool keep_world_transform, + const bool paste_back, + bke::greasepencil::Drawing &drawing) +{ /* Get a list of all materials in the scene. */ const Array 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()); + const IndexRange pasted_curves_range = paste_back ? IndexRange(0, curves_to_paste.curves_num()) : + IndexRange(drawing.strokes().curves_num(), + curves_to_paste.curves_num()); /* Append the geometry from the clipboard to the target layer. */ - Curves *clipboard_id = bke::curves_new_nomain(clipboard_curves); + Curves *clipboard_id = bke::curves_new_nomain(curves_to_paste); 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)}; - 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); + const float4x4 transform = object_to_paste_layer * + (keep_world_transform ? + object.world_to_object() * clipboard_to_world : + float4x4::identity()); + const Array transforms = paste_back ? Span{transform, float4x4::identity()} : + Span{float4x4::identity(), transform}; + bke::GeometrySet joined_curves = join_geometries_with_transforms( + geometry_sets, VArray::ForContainer(transforms)); drawing.strokes_for_write() = std::move(joined_curves.get_curves_for_write()->geometry.wrap()); @@ -2792,6 +2713,233 @@ IndexRange clipboard_paste_strokes(Main &bmain, return pasted_curves_range; } +enum class PasteType { + Active = 0, + ByLayer = 1, +}; + +static int grease_pencil_paste_strokes_exec(bContext *C, wmOperator *op) +{ + using namespace bke::greasepencil; + Main *bmain = CTX_data_main(C); + const Scene &scene = *CTX_data_scene(C); + Object *object = CTX_data_active_object(C); + const bke::AttrDomain selection_domain = ED_grease_pencil_edit_selection_domain_get( + scene.toolsettings); + GreasePencil &grease_pencil = *static_cast(object->data); + + const PasteType type = PasteType(RNA_enum_get(op->ptr, "type")); + + 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"); + + const Clipboard &clipboard = ensure_grease_pencil_clipboard(); + if (clipboard.layers.is_empty()) { + return OPERATOR_CANCELLED; + } + + if (type == PasteType::Active) { + Layer *active_layer = grease_pencil.get_active_layer(); + if (!active_layer) { + BKE_report(op->reports, RPT_ERROR, "No active Grease Pencil layer to paste into"); + return OPERATOR_CANCELLED; + } + if (!active_layer->is_editable()) { + BKE_report(op->reports, RPT_ERROR, "Active layer is not editable"); + return OPERATOR_CANCELLED; + } + + /* Deselect everything from editable drawings. The pasted strokes are the only ones then after + * the paste. That's convenient for the user. */ + const Vector drawings = retrieve_editable_drawings(scene, grease_pencil); + threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { + bke::GSpanAttributeWriter selection_in_target = ed::curves::ensure_selection_attribute( + info.drawing.strokes_for_write(), selection_domain, CD_PROP_BOOL); + ed::curves::fill_selection_false(selection_in_target.span); + selection_in_target.finish(); + }); + + const float4x4 object_to_layer = math::invert(active_layer->to_object_space(*object)); + + /* Ensure active keyframe. */ + bool inserted_keyframe = false; + if (!ensure_active_keyframe(scene, grease_pencil, *active_layer, false, inserted_keyframe)) { + BKE_report(op->reports, RPT_ERROR, "No Grease Pencil frame to draw on"); + return OPERATOR_CANCELLED; + } + + bke::greasepencil::Drawing *target_drawing = grease_pencil.get_editable_drawing_at( + *active_layer, scene.r.cfra); + BLI_assert(target_drawing != nullptr); + + paste_all_strokes_from_clipboard( + *bmain, *object, object_to_layer, keep_world_transform, paste_on_back, *target_drawing); + + if (inserted_keyframe) { + WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, nullptr); + } + } + else if (type == PasteType::ByLayer) { + Layer *active_layer = grease_pencil.get_active_layer(); + /* Find layers to paste strokes into. */ + Array layers_to_paste_into(clipboard.layers.size()); + for (const int clip_layer_i : clipboard.layers.index_range()) { + const Clipboard::ClipboardLayer &layer = clipboard.layers[clip_layer_i]; + bke::greasepencil::TreeNode *node = grease_pencil.find_node_by_name(layer.name); + const bool found_layer = node && node->is_layer() && node->as_layer().is_editable(); + if (found_layer) { + layers_to_paste_into[clip_layer_i] = &node->as_layer(); + continue; + } + else if (active_layer && active_layer->is_editable()) { + /* Fallback to active layer. */ + BKE_report( + op->reports, RPT_WARNING, "Couldn't find matching layer, pasting into active layer"); + layers_to_paste_into[clip_layer_i] = active_layer; + continue; + } + + if (!active_layer) { + BKE_report(op->reports, RPT_ERROR, "No active Grease Pencil layer to paste into"); + } + if (!active_layer->is_editable()) { + BKE_report(op->reports, RPT_ERROR, "Active layer is not editable"); + } + return OPERATOR_CANCELLED; + } + + /* Deselect everything from editable drawings. The pasted strokes are the only ones then after + * the paste. That's convenient for the user. */ + const Vector drawings = retrieve_editable_drawings(scene, grease_pencil); + threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { + bke::GSpanAttributeWriter selection_in_target = ed::curves::ensure_selection_attribute( + info.drawing.strokes_for_write(), selection_domain, CD_PROP_BOOL); + ed::curves::fill_selection_false(selection_in_target.span); + selection_in_target.finish(); + }); + + for (const int clip_layer_i : clipboard.layers.index_range()) { + const Clipboard::ClipboardLayer &clip_layer = clipboard.layers[clip_layer_i]; + const bke::CurvesGeometry &curves_to_paste = clip_layer.curves; + + BLI_assert(layers_to_paste_into[clip_layer_i] != nullptr); + Layer &paste_layer = *layers_to_paste_into[clip_layer_i]; + const float4x4 object_to_paste_layer = math::invert(paste_layer.to_object_space(*object)); + + /* Ensure active keyframe. */ + bool inserted_keyframe = false; + if (!ensure_active_keyframe(scene, grease_pencil, paste_layer, false, inserted_keyframe)) { + BKE_report(op->reports, RPT_ERROR, "No Grease Pencil frame to draw on"); + return OPERATOR_CANCELLED; + } + + bke::greasepencil::Drawing *target_drawing = grease_pencil.get_editable_drawing_at( + paste_layer, scene.r.cfra); + BLI_assert(target_drawing != nullptr); + + clipboard_paste_strokes_ex(*bmain, + *object, + curves_to_paste, + object_to_paste_layer, + clipboard.object_to_world, + keep_world_transform, + paste_on_back, + *target_drawing); + + if (inserted_keyframe) { + WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, nullptr); + } + } + } + else { + BLI_assert_unreachable(); + } + + DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil); + + return OPERATOR_FINISHED; +} + +static bool grease_pencil_paste_strokes_poll(bContext *C) +{ + if (!editable_grease_pencil_poll(C)) { + return false; + } + + std::scoped_lock lock(grease_pencil_clipboard_lock); + /* Check for curves in the Grease Pencil clipboard. */ + return (grease_pencil_clipboard && grease_pencil_clipboard->layers.size() > 0); +} + +static void GREASE_PENCIL_OT_paste(wmOperatorType *ot) +{ + PropertyRNA *prop; + + static const EnumPropertyItem rna_paste_items[] = { + {int(PasteType::Active), "ACTIVE", 0, "Paste to Active", ""}, + {int(PasteType::ByLayer), "LAYER", 0, "Paste by Layer", ""}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + ot->name = "Paste Strokes"; + ot->idname = "GREASE_PENCIL_OT_paste"; + ot->description = + "Paste Grease Pencil points or strokes from the internal clipboard to the active layer"; + + ot->exec = grease_pencil_paste_strokes_exec; + ot->poll = grease_pencil_paste_strokes_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum(ot->srna, "type", rna_paste_items, int(PasteType::Active), "Type", ""); + + prop = RNA_def_boolean( + ot->srna, "paste_back", false, "Paste on Back", "Add pasted strokes behind all strokes"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + + prop = RNA_def_boolean(ot->srna, + "keep_world_transform", + false, + "Keep World Transform", + "Keep the world transform of strokes from the clipboard unchanged"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/** \} */ + +IndexRange paste_all_strokes_from_clipboard(Main &bmain, + Object &object, + const float4x4 &object_to_paste_layer, + const bool keep_world_transform, + const bool paste_back, + bke::greasepencil::Drawing &drawing) +{ + Clipboard &clipboard = ensure_grease_pencil_clipboard(); + if (clipboard.layers.is_empty()) { + return {}; + } + + Vector geometries_to_join; + for (Clipboard::ClipboardLayer &layer : clipboard.layers) { + geometries_to_join.append(bke::GeometrySet::from_curves(curves_new_nomain(layer.curves))); + } + bke::GeometrySet joined_clipboard_set = geometry::join_geometries(geometries_to_join.as_span(), + {}); + BLI_assert(joined_clipboard_set.has_curves()); + const bke::CurvesGeometry &joined_clipboard_curves = + joined_clipboard_set.get_curves()->geometry.wrap(); + + return clipboard_paste_strokes_ex(bmain, + object, + joined_clipboard_curves, + object_to_paste_layer, + clipboard.object_to_world, + keep_world_transform, + paste_back, + drawing); +} + /* -------------------------------------------------------------------- */ /** \name Merge Stroke Operator * \{ */ diff --git a/source/blender/editors/include/ED_grease_pencil.hh b/source/blender/editors/include/ED_grease_pencil.hh index e7af9df77c9..b463a491324 100644 --- a/source/blender/editors/include/ED_grease_pencil.hh +++ b/source/blender/editors/include/ED_grease_pencil.hh @@ -561,19 +561,15 @@ void add_armature_envelope_weights(Scene &scene, Object &object, const Object &o void add_armature_automatic_weights(Scene &scene, Object &object, const Object &ob_armature); 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. - * \param keep_world_transform: Keep the world transform of clipboard strokes unchanged. - * \return Index range of the new curves in the drawing after pasting. + * Paste all the strokes in the clipboard layers into \a drawing. */ -IndexRange clipboard_paste_strokes(Main &bmain, - Object &object, - bke::greasepencil::Drawing &drawing, - const float4x4 &transform, - bool keep_world_transform, - bool paste_back); +IndexRange paste_all_strokes_from_clipboard(Main &bmain, + Object &object, + const float4x4 &object_to_paste_layer, + bool keep_world_transform, + bool paste_back, + bke::greasepencil::Drawing &drawing); /** * Method used by the Fill tool to fit the render buffer to strokes. 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 e50d717a341..3e51cacfbcd 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_clone.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_clone.cc @@ -59,11 +59,11 @@ void CloneOperation::on_stroke_begin(const bContext &C, const InputSample &start return false; } - /* TODO Could become a tool setting. */ + /* 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, clipboard_to_layer, keep_world_transform, false); + const float4x4 object_to_layer = math::invert(params.layer.to_object_space(object)); + const IndexRange pasted_curves = ed::greasepencil::paste_all_strokes_from_clipboard( + bmain, object, object_to_layer, keep_world_transform, false, params.drawing); if (pasted_curves.is_empty()) { return false; }