Fix #132826: Grease Pencil: Missing "Paste by Layer" option
All checks were successful
buildbot/v440-code-daily-lint Build done.
buildbot/v440-code-daily-linux-x86_64 Build done.
buildbot/v440-code-daily-darwin-arm64 Build done.
buildbot/v440-code-daily-darwin-x86_64 Build done.
buildbot/v440-code-daily-windows-amd64 Build done.
buildbot/v440-code-daily-coordinator Build done.

The Grease Pencil `Clipboard` only stored a single `CurvesGeometry`. This meant
that the `Paste by Layer` operation (from 4.2 LTS) couldn't be implemented.

This PR adds an `Array` of `ClipboardLayer`s to the  `Clipboard`. Each layer stores
the name of the Grease Pencil layer that it was copied from and the `CurvesGeometry`.

The `grease_pencil.paste` operator has a new property `type` (`ACTIVE` or `LAYER`)
that decides how to paste the strokes.

Using the `ACTIVE` type, the strokes of all the `ClipboardLayer`s get merged and then
pasted to the active layer.
Using the `LAYER` type, we first try to find layers to paste the `ClipboardLayer`s into.
We just look for matching layer names. If no matching layer is found, the strokes are
pasted into the active layer instead.

This should be consistent with how Grease Pencil used to behave in 4.2. LTS.

Pull Request: #134168
This commit is contained in:
Falk David 2025-02-07 16:17:16 +01:00 committed by Falk David
parent a0548950a0
commit 971f3e0699
4 changed files with 412 additions and 266 deletions

View File

@ -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()

View File

@ -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<ClipboardLayer> 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<std::pair<uint, int>> 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<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. */
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<MutableDrawingInfo> 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<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);
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<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.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<int> material_indices = *attributes.lookup_or_default<int>(
"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<int> clipboard_materials_remap(Main &bmain, Object &object)
{
using namespace blender::ed::greasepencil;
@ -2710,7 +2497,7 @@ static Array<int> 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<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. */
@ -2732,47 +2519,181 @@ static Array<int> 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<bke::GeometrySet> geometries,
const VArray<float4x4> &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<bke::Instances> instances = std::make_unique<bke::Instances>();
instances->resize(geometries.size());
transforms.materialize(instances->transforms_for_write());
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 bke::GeometrySet join_geometries_with_transform(Span<bke::GeometrySet> geometries,
const float4x4 &transform)
{
return join_geometries_with_transforms(
geometries, VArray<float4x4>::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<GreasePencil *>(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<const Layer *, Vector<bke::GeometrySet>> copied_curves_per_layer;
/* 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);
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<int> material_indices = *attributes.lookup_or_default<int>(
"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<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());
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<bke::GeometrySet> 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<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);
const float4x4 transform = object_to_paste_layer *
(keep_world_transform ?
object.world_to_object() * clipboard_to_world :
float4x4::identity());
const Array<float4x4> transforms = paste_back ? Span<float4x4>{transform, float4x4::identity()} :
Span<float4x4>{float4x4::identity(), transform};
bke::GeometrySet joined_curves = join_geometries_with_transforms(
geometry_sets, VArray<float4x4>::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<GreasePencil *>(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<MutableDrawingInfo> 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<Layer *> 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<MutableDrawingInfo> 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<bke::GeometrySet> 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
* \{ */

View File

@ -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.

View File

@ -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;
}