GPv3: Duplicate keyframes

Implementation of the duplicate and move operator for grease pencil frames.

The `ACTION_OT_duplicate` operator now creates duplicates of the selected frames and stores them in the layer runtime data.
The `TRANSFORM_OT_transform` operator is updated to take care of these duplicated frames.

Pull Request: https://projects.blender.org/blender/blender/pulls/111051
This commit is contained in:
Amelie Fondevilla 2023-09-12 12:07:15 +02:00 committed by Falk David
parent 6920c690f1
commit 127eee87ce
7 changed files with 194 additions and 46 deletions

View File

@ -260,6 +260,10 @@ struct LayerTransformData {
* i.e. each frame that is not an implicit hold. */ * i.e. each frame that is not an implicit hold. */
Map<int, int> frames_duration; Map<int, int> frames_duration;
/* Temporary copy of duplicated frames before we decide on a place to insert them.
* Used in the move+duplicate operator. */
Map<int, GreasePencilFrame> temp_frames_buffer;
FrameTransformationStatus status{TRANS_CLEAR}; FrameTransformationStatus status{TRANS_CLEAR};
}; };

View File

@ -1564,26 +1564,43 @@ void GreasePencil::remove_drawings_with_no_users()
void GreasePencil::move_frames(blender::bke::greasepencil::Layer &layer, void GreasePencil::move_frames(blender::bke::greasepencil::Layer &layer,
const blender::Map<int, int> &frame_number_destinations) const blender::Map<int, int> &frame_number_destinations)
{ {
using namespace blender; return this->move_duplicate_frames(
layer, frame_number_destinations, blender::Map<int, GreasePencilFrame>());
}
void GreasePencil::move_duplicate_frames(
blender::bke::greasepencil::Layer &layer,
const blender::Map<int, int> &frame_number_destinations,
const blender::Map<int, GreasePencilFrame> &duplicate_frames)
{
using namespace blender;
Map<int, GreasePencilFrame> layer_frames_copy = layer.frames(); Map<int, GreasePencilFrame> layer_frames_copy = layer.frames();
/* Remove all frames that have a mapping. */ /* Copy frames durations. */
for (const int frame_number : frame_number_destinations.keys()) { Map<int, int> layer_frames_durations;
layer.remove_frame(frame_number); for (const auto [frame_number, frame] : layer.frames().items()) {
if (!frame.is_implicit_hold()) {
layer_frames_durations.add(frame_number, layer.get_frame_duration_at(frame_number));
}
} }
/* Insert all frames of the transformation. */
for (const auto [src_frame_number, dst_frame_number] : frame_number_destinations.items()) { for (const auto [src_frame_number, dst_frame_number] : frame_number_destinations.items()) {
if (!layer_frames_copy.contains(src_frame_number)) { const bool use_duplicate = duplicate_frames.contains(src_frame_number);
const Map<int, GreasePencilFrame> &frame_map = use_duplicate ? duplicate_frames :
layer_frames_copy;
if (!frame_map.contains(src_frame_number)) {
continue; continue;
} }
const GreasePencilFrame src_frame = layer_frames_copy.lookup(src_frame_number); const GreasePencilFrame src_frame = frame_map.lookup(src_frame_number);
const int drawing_index = src_frame.drawing_index; const int drawing_index = src_frame.drawing_index;
const int duration = src_frame.is_implicit_hold() ? const int duration = layer_frames_durations.lookup_default(src_frame_number, 0);
0 :
layer.get_frame_duration_at(src_frame_number); if (!use_duplicate) {
layer.remove_frame(src_frame_number);
}
/* Add and overwrite the frame at the destination number. */ /* Add and overwrite the frame at the destination number. */
if (layer.frames().contains(dst_frame_number)) { if (layer.frames().contains(dst_frame_number)) {

View File

@ -94,6 +94,39 @@ bool mirror_selected_frames(GreasePencil &grease_pencil,
return changed; return changed;
} }
bool duplicate_selected_frames(GreasePencil &grease_pencil, bke::greasepencil::Layer &layer)
{
using namespace bke::greasepencil;
bool changed = false;
LayerTransformData &trans_data = layer.runtime->trans_data_;
for (auto [frame_number, frame] : layer.frames_for_write().items()) {
if (!frame.is_selected()) {
continue;
}
/* Create the duplicate drawing. */
const Drawing *drawing = grease_pencil.get_editable_drawing_at(&layer, frame_number);
if (drawing == nullptr) {
continue;
}
const int duplicated_drawing_index = grease_pencil.drawings().size();
grease_pencil.add_duplicate_drawings(1, *drawing);
/* Make a copy of the frame in the duplicates. */
GreasePencilFrame frame_duplicate = frame;
frame_duplicate.drawing_index = duplicated_drawing_index;
trans_data.temp_frames_buffer.add_overwrite(frame_number, frame_duplicate);
/* Deselect the current frame, so that only the copy is selected. */
frame.flag ^= GP_FRAME_SELECTED;
changed = true;
}
return changed;
}
bool remove_all_selected_frames(GreasePencil &grease_pencil, bke::greasepencil::Layer &layer) bool remove_all_selected_frames(GreasePencil &grease_pencil, bke::greasepencil::Layer &layer)
{ {
Vector<int> frames_to_remove; Vector<int> frames_to_remove;

View File

@ -49,6 +49,11 @@ namespace blender::ed::greasepencil {
void set_selected_frames_type(bke::greasepencil::Layer &layer, void set_selected_frames_type(bke::greasepencil::Layer &layer,
const eBezTriple_KeyframeType key_type); const eBezTriple_KeyframeType key_type);
/* Creates duplicate frames for each selected frame in the layer. The duplicates are stored in the
* LayerTransformData structure of the layer runtime data. This function also unselects the
* selected frames, while keeping the duplicates selected. */
bool duplicate_selected_frames(GreasePencil &grease_pencil, bke::greasepencil::Layer &layer);
bool mirror_selected_frames(GreasePencil &grease_pencil, bool mirror_selected_frames(GreasePencil &grease_pencil,
bke::greasepencil::Layer &layer, bke::greasepencil::Layer &layer,

View File

@ -1011,7 +1011,9 @@ static bool duplicate_action_keys(bAnimContext *ac)
changed |= ED_gpencil_layer_frame_select_check((bGPDlayer *)ale->data); changed |= ED_gpencil_layer_frame_select_check((bGPDlayer *)ale->data);
} }
else if (ale->type == ANIMTYPE_GREASE_PENCIL_LAYER) { else if (ale->type == ANIMTYPE_GREASE_PENCIL_LAYER) {
/* GPv3: To be implemented. */ changed |= blender::ed::greasepencil::duplicate_selected_frames(
*reinterpret_cast<GreasePencil *>(ale->id),
static_cast<GreasePencilLayer *>(ale->data)->wrap());
} }
else if (ale->type == ANIMTYPE_MASKLAYER) { else if (ale->type == ANIMTYPE_MASKLAYER) {
ED_masklayer_frames_duplicate((MaskLayer *)ale->data); ED_masklayer_frames_duplicate((MaskLayer *)ale->data);

View File

@ -100,7 +100,8 @@ static bool grease_pencil_layer_reset_trans_data(blender::bke::greasepencil::Lay
static bool grease_pencil_layer_update_trans_data(blender::bke::greasepencil::Layer &layer, static bool grease_pencil_layer_update_trans_data(blender::bke::greasepencil::Layer &layer,
const int src_frame_number, const int src_frame_number,
const int dst_frame_number) const int dst_frame_number,
const bool duplicated)
{ {
using namespace blender::bke::greasepencil; using namespace blender::bke::greasepencil;
LayerTransformData &trans_data = layer.runtime->trans_data_; LayerTransformData &trans_data = layer.runtime->trans_data_;
@ -118,13 +119,28 @@ static bool grease_pencil_layer_update_trans_data(blender::bke::greasepencil::La
trans_data.status = LayerTransformData::TRANS_RUNNING; trans_data.status = LayerTransformData::TRANS_RUNNING;
} }
/* Apply the transformation directly in the frame map, so that we display the transformed const bool use_duplicated = duplicated &&
trans_data.temp_frames_buffer.contains(src_frame_number);
const blender::Map<int, GreasePencilFrame> &frame_map = use_duplicated ?
(trans_data.temp_frames_buffer) :
(trans_data.frames_copy);
if (!frame_map.contains(src_frame_number)) {
return false;
}
/* Apply the transformation directly in the layer frame map, so that we display the transformed
* frame numbers. We don't want to edit the frames or remove any drawing here. This will be * frame numbers. We don't want to edit the frames or remove any drawing here. This will be
* done at once at the end of the transformation. */ * done at once at the end of the transformation. */
const GreasePencilFrame src_frame = trans_data.frames_copy.lookup(src_frame_number); const GreasePencilFrame src_frame = frame_map.lookup(src_frame_number);
const int src_duration = trans_data.frames_duration.lookup_default(src_frame_number, 0); const int src_duration = trans_data.frames_duration.lookup_default(src_frame_number, 0);
layer.remove_frame(src_frame_number);
if (!use_duplicated) {
layer.remove_frame(src_frame_number);
}
layer.remove_frame(dst_frame_number); layer.remove_frame(dst_frame_number);
GreasePencilFrame *frame = layer.add_frame( GreasePencilFrame *frame = layer.add_frame(
dst_frame_number, src_frame.drawing_index, src_duration); dst_frame_number, src_frame.drawing_index, src_duration);
*frame = src_frame; *frame = src_frame;
@ -136,7 +152,8 @@ static bool grease_pencil_layer_update_trans_data(blender::bke::greasepencil::La
static bool grease_pencil_layer_apply_trans_data(GreasePencil &grease_pencil, static bool grease_pencil_layer_apply_trans_data(GreasePencil &grease_pencil,
blender::bke::greasepencil::Layer &layer, blender::bke::greasepencil::Layer &layer,
const bool canceled) const bool canceled,
const bool duplicate)
{ {
using namespace blender::bke::greasepencil; using namespace blender::bke::greasepencil;
LayerTransformData &trans_data = layer.runtime->trans_data_; LayerTransformData &trans_data = layer.runtime->trans_data_;
@ -151,13 +168,28 @@ static bool grease_pencil_layer_apply_trans_data(GreasePencil &grease_pencil,
layer.tag_frames_map_keys_changed(); layer.tag_frames_map_keys_changed();
if (!canceled) { if (!canceled) {
/* Apply the transformation. */ /* Moves all the selected frames according to the transformation, and inserts the potential
grease_pencil.move_frames(layer, trans_data.frames_destination); * duplicate frames in the layer. */
grease_pencil.move_duplicate_frames(
layer, trans_data.frames_destination, trans_data.temp_frames_buffer);
}
if (canceled && duplicate) {
/* Duplicates were done, so we need to delete the corresponding duplicate drawings. */
for (const GreasePencilFrame &duplicate_frame : trans_data.temp_frames_buffer.values()) {
GreasePencilDrawingBase *drawing_base = grease_pencil.drawings(
duplicate_frame.drawing_index);
if (drawing_base->type == GP_DRAWING) {
reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap().remove_user();
}
}
grease_pencil.remove_drawings_with_no_users();
} }
/* Clear the frames copy. */ /* Clear the frames copy. */
trans_data.frames_copy.clear(); trans_data.frames_copy.clear();
trans_data.frames_destination.clear(); trans_data.frames_destination.clear();
trans_data.temp_frames_buffer.clear();
trans_data.status = LayerTransformData::TRANS_CLEAR; trans_data.status = LayerTransformData::TRANS_CLEAR;
return true; return true;
@ -224,9 +256,10 @@ static int count_gplayer_frames(bGPDlayer *gpl, char side, float cfra, bool is_p
} }
static int count_grease_pencil_frames(const blender::bke::greasepencil::Layer *layer, static int count_grease_pencil_frames(const blender::bke::greasepencil::Layer *layer,
char side, const char side,
float cfra, const float cfra,
bool is_prop_edit) const bool is_prop_edit,
const bool use_duplicated)
{ {
if (layer == nullptr) { if (layer == nullptr) {
return 0; return 0;
@ -235,15 +268,22 @@ static int count_grease_pencil_frames(const blender::bke::greasepencil::Layer *l
int count_selected = 0; int count_selected = 0;
int count_all = 0; int count_all = 0;
/* Only include points that occur on the right side of cfra. */ if (use_duplicated) {
for (const auto &[frame_number, frame] : layer->frames().items()) { /* Only count the frames that were duplicated. */
if (!FrameOnMouseSide(side, float(frame_number), cfra)) { count_selected += layer->runtime->trans_data_.temp_frames_buffer.size();
continue; count_all += count_selected;
}
else {
/* Only include points that occur on the right side of cfra. */
for (const auto &[frame_number, frame] : layer->frames().items()) {
if (!FrameOnMouseSide(side, float(frame_number), cfra)) {
continue;
}
if (frame.is_selected()) {
count_selected++;
}
count_all++;
} }
if (frame.is_selected()) {
count_selected++;
}
count_all++;
} }
if (is_prop_edit && count_selected > 0) { if (is_prop_edit && count_selected > 0) {
@ -413,24 +453,26 @@ static int GPLayerToTransData(TransData *td,
static int GreasePencilLayerToTransData(TransData *td, static int GreasePencilLayerToTransData(TransData *td,
TransData2D *td2d, TransData2D *td2d,
blender::bke::greasepencil::Layer *layer, blender::bke::greasepencil::Layer *layer,
char side, const char side,
float cfra, const float cfra,
bool is_prop_edit, const bool is_prop_edit,
float ypos) const float ypos,
const bool duplicate)
{ {
using namespace blender; using namespace blender;
using namespace bke::greasepencil; using namespace bke::greasepencil;
int total_trans_frames = 0; int total_trans_frames = 0;
bool any_frame_affected = false; bool any_frame_affected = false;
for (auto [frame_number, frame] : layer->frames().items()) {
const auto grease_pencil_frame_to_trans_data = [&](const int frame_number,
const bool frame_selected) {
/* We only add transform data for selected frames that are on the right side of current frame. /* We only add transform data for selected frames that are on the right side of current frame.
* If proportional edit is set, then we should also account for non selected frames. * If proportional edit is set, then we should also account for non selected frames.
*/ */
if ((!is_prop_edit && !frame.is_selected()) || !FrameOnMouseSide(side, frame_number, cfra)) { if ((!is_prop_edit && !frame_selected) || !FrameOnMouseSide(side, frame_number, cfra)) {
continue; return;
} }
any_frame_affected = true;
td2d->loc[0] = float(frame_number); td2d->loc[0] = float(frame_number);
@ -440,7 +482,7 @@ static int GreasePencilLayerToTransData(TransData *td,
td->center[0] = td->ival; td->center[0] = td->ival;
td->center[1] = ypos; td->center[1] = ypos;
if (frame.is_selected()) { if (frame_selected) {
td->flag |= TD_SELECTED; td->flag |= TD_SELECTED;
} }
/* Set a pointer to the layer in the transform data so that we can apply the transformation /* Set a pointer to the layer in the transform data so that we can apply the transformation
@ -453,6 +495,14 @@ static int GreasePencilLayerToTransData(TransData *td,
td++; td++;
td2d++; td2d++;
total_trans_frames++; total_trans_frames++;
any_frame_affected = true;
};
const blender::Map<int, GreasePencilFrame> &frame_map =
duplicate ? (layer->runtime->trans_data_.temp_frames_buffer) : (layer->frames());
for (const auto [frame_number, frame] : frame_map.items()) {
grease_pencil_frame_to_trans_data(frame_number, frame.is_selected());
} }
if (total_trans_frames == 0) { if (total_trans_frames == 0) {
@ -510,6 +560,10 @@ static void createTransActionData(bContext *C, TransInfo *t)
TransData2D *td2d = nullptr; TransData2D *td2d = nullptr;
tGPFtransdata *tfd = nullptr; tGPFtransdata *tfd = nullptr;
/* The T_DUPLICATED_KEYFRAMES flag is only set if we made some duplicates of the selected frames,
* and they are the ones that are being transformed. */
const bool use_duplicated = (t->flag & T_DUPLICATED_KEYFRAMES) != 0;
rcti *mask = &t->region->v2d.mask; rcti *mask = &t->region->v2d.mask;
rctf *datamask = &t->region->v2d.cur; rctf *datamask = &t->region->v2d.cur;
@ -572,7 +626,7 @@ static void createTransActionData(bContext *C, TransInfo *t)
else if (ale->type == ANIMTYPE_GREASE_PENCIL_LAYER) { else if (ale->type == ANIMTYPE_GREASE_PENCIL_LAYER) {
using namespace blender::bke::greasepencil; using namespace blender::bke::greasepencil;
adt_count = count_grease_pencil_frames( adt_count = count_grease_pencil_frames(
static_cast<Layer *>(ale->data), t->frame_side, cfra, is_prop_edit); static_cast<Layer *>(ale->data), t->frame_side, cfra, is_prop_edit, use_duplicated);
} }
else if (ale->type == ANIMTYPE_MASKLAYER) { else if (ale->type == ANIMTYPE_MASKLAYER) {
adt_count = count_masklayer_frames( adt_count = count_masklayer_frames(
@ -647,7 +701,8 @@ static void createTransActionData(bContext *C, TransInfo *t)
Layer *layer = static_cast<Layer *>(ale->data); Layer *layer = static_cast<Layer *>(ale->data);
int i; int i;
i = GreasePencilLayerToTransData(td, td2d, layer, t->frame_side, cfra, is_prop_edit, ypos); i = GreasePencilLayerToTransData(
td, td2d, layer, t->frame_side, cfra, is_prop_edit, ypos, use_duplicated);
td += i; td += i;
td2d += i; td2d += i;
} }
@ -715,15 +770,16 @@ static void createTransActionData(bContext *C, TransInfo *t)
using namespace blender::bke::greasepencil; using namespace blender::bke::greasepencil;
Layer *layer = static_cast<Layer *>(ale->data); Layer *layer = static_cast<Layer *>(ale->data);
for (auto [frame_number, frame] : layer->frames_for_write().items()) { const auto grease_pencil_closest_selected_frame = [&](const int frame_number,
if (frame.is_selected()) { const bool frame_selected) {
if (frame_selected) {
td->dist = td->rdist = 0.0f; td->dist = td->rdist = 0.0f;
++td; ++td;
continue; return;
} }
int closest_selected = INT_MAX; int closest_selected = INT_MAX;
for (auto [neighbor_frame_number, neighbor_frame] : layer->frames_for_write().items()) { for (const auto [neighbor_frame_number, neighbor_frame] : layer->frames().items()) {
if (!neighbor_frame.is_selected() || if (!neighbor_frame.is_selected() ||
!FrameOnMouseSide(t->frame_side, float(neighbor_frame_number), cfra)) !FrameOnMouseSide(t->frame_side, float(neighbor_frame_number), cfra))
{ {
@ -735,6 +791,18 @@ static void createTransActionData(bContext *C, TransInfo *t)
td->dist = td->rdist = closest_selected; td->dist = td->rdist = closest_selected;
++td; ++td;
};
for (const auto [frame_number, frame] : layer->frames().items()) {
grease_pencil_closest_selected_frame(frame_number, frame.is_selected());
}
if (use_duplicated) {
/* Also count for duplicated frames. */
for (const auto [frame_number, frame] :
layer->runtime->trans_data_.temp_frames_buffer.items()) {
grease_pencil_closest_selected_frame(frame_number, frame.is_selected());
}
} }
} }
else if (ale->type == ANIMTYPE_MASKLAYER) { else if (ale->type == ANIMTYPE_MASKLAYER) {
@ -886,10 +954,12 @@ static void recalcData_actedit(TransInfo *t)
transform_convert_flush_handle2D(td, td2d, 0.0f); transform_convert_flush_handle2D(td, td2d, 0.0f);
if ((t->state == TRANS_RUNNING) && ((td->flag & TD_GREASE_PENCIL_FRAME) != 0)) { if ((t->state == TRANS_RUNNING) && ((td->flag & TD_GREASE_PENCIL_FRAME) != 0)) {
const bool use_duplicated = (t->flag & T_DUPLICATED_KEYFRAMES) != 0;
grease_pencil_layer_update_trans_data( grease_pencil_layer_update_trans_data(
*static_cast<blender::bke::greasepencil::Layer *>(td->extra), *static_cast<blender::bke::greasepencil::Layer *>(td->extra),
round_fl_to_int(td->ival), round_fl_to_int(td->ival),
round_fl_to_int(td2d->loc[0])); round_fl_to_int(td2d->loc[0]),
use_duplicated);
} }
} }
@ -1175,7 +1245,8 @@ static void special_aftertrans_update__actedit(bContext *C, TransInfo *t)
grease_pencil_layer_apply_trans_data( grease_pencil_layer_apply_trans_data(
*grease_pencil, *grease_pencil,
*static_cast<blender::bke::greasepencil::Layer *>(ale->data), *static_cast<blender::bke::greasepencil::Layer *>(ale->data),
canceled); canceled,
duplicate);
break; break;
} }

View File

@ -517,6 +517,22 @@ typedef struct GreasePencil {
void move_frames(blender::bke::greasepencil::Layer &layer, void move_frames(blender::bke::greasepencil::Layer &layer,
const blender::Map<int, int> &frame_number_destinations); const blender::Map<int, int> &frame_number_destinations);
/**
* Moves and/or inserts duplicates of a set of frames in a \a layer.
*
* \param frame_number_destination describes all transformations that should be applied on the
* frame keys.
* \param duplicate_frames the frames that should be duplicated instead of moved. Keys of the map
* are the keys of the corresponding source frames. Frames will be inserted at the key given by
* the map \a frame_number_destination.
*
* If a transformation overlaps another frames, the frame will be overwritten, and the
* corresponding drawing may be removed, if it no longer has users.
*/
void move_duplicate_frames(blender::bke::greasepencil::Layer &layer,
const blender::Map<int, int> &frame_number_destinations,
const blender::Map<int, GreasePencilFrame> &duplicate_frames);
/** /**
* Returns an editable drawing on \a layer at frame \a frame_number or `nullptr` if no such * Returns an editable drawing on \a layer at frame \a frame_number or `nullptr` if no such
* drawing exists. * drawing exists.