|
|
|
@@ -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
|
|
|
|
|
* \{ */
|
|
|
|
|