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. */
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};
};

View File

@ -1564,26 +1564,43 @@ void GreasePencil::remove_drawings_with_no_users()
void GreasePencil::move_frames(blender::bke::greasepencil::Layer &layer,
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();
/* Remove all frames that have a mapping. */
for (const int frame_number : frame_number_destinations.keys()) {
layer.remove_frame(frame_number);
/* Copy frames durations. */
Map<int, int> layer_frames_durations;
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()) {
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;
}
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 duration = src_frame.is_implicit_hold() ?
0 :
layer.get_frame_duration_at(src_frame_number);
const int duration = layer_frames_durations.lookup_default(src_frame_number, 0);
if (!use_duplicate) {
layer.remove_frame(src_frame_number);
}
/* Add and overwrite the frame at the destination number. */
if (layer.frames().contains(dst_frame_number)) {

View File

@ -94,6 +94,39 @@ bool mirror_selected_frames(GreasePencil &grease_pencil,
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)
{
Vector<int> frames_to_remove;

View File

@ -49,6 +49,11 @@ namespace blender::ed::greasepencil {
void set_selected_frames_type(bke::greasepencil::Layer &layer,
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,
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);
}
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) {
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,
const int src_frame_number,
const int dst_frame_number)
const int dst_frame_number,
const bool duplicated)
{
using namespace blender::bke::greasepencil;
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;
}
/* 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
* 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);
layer.remove_frame(src_frame_number);
if (!use_duplicated) {
layer.remove_frame(src_frame_number);
}
layer.remove_frame(dst_frame_number);
GreasePencilFrame *frame = layer.add_frame(
dst_frame_number, src_frame.drawing_index, src_duration);
*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,
blender::bke::greasepencil::Layer &layer,
const bool canceled)
const bool canceled,
const bool duplicate)
{
using namespace blender::bke::greasepencil;
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();
if (!canceled) {
/* Apply the transformation. */
grease_pencil.move_frames(layer, trans_data.frames_destination);
/* Moves all the selected frames according to the transformation, and inserts the potential
* 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. */
trans_data.frames_copy.clear();
trans_data.frames_destination.clear();
trans_data.temp_frames_buffer.clear();
trans_data.status = LayerTransformData::TRANS_CLEAR;
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,
char side,
float cfra,
bool is_prop_edit)
const char side,
const float cfra,
const bool is_prop_edit,
const bool use_duplicated)
{
if (layer == nullptr) {
return 0;
@ -235,15 +268,22 @@ static int count_grease_pencil_frames(const blender::bke::greasepencil::Layer *l
int count_selected = 0;
int count_all = 0;
/* 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 (use_duplicated) {
/* Only count the frames that were duplicated. */
count_selected += layer->runtime->trans_data_.temp_frames_buffer.size();
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) {
@ -413,24 +453,26 @@ static int GPLayerToTransData(TransData *td,
static int GreasePencilLayerToTransData(TransData *td,
TransData2D *td2d,
blender::bke::greasepencil::Layer *layer,
char side,
float cfra,
bool is_prop_edit,
float ypos)
const char side,
const float cfra,
const bool is_prop_edit,
const float ypos,
const bool duplicate)
{
using namespace blender;
using namespace bke::greasepencil;
int total_trans_frames = 0;
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.
* 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)) {
continue;
if ((!is_prop_edit && !frame_selected) || !FrameOnMouseSide(side, frame_number, cfra)) {
return;
}
any_frame_affected = true;
td2d->loc[0] = float(frame_number);
@ -440,7 +482,7 @@ static int GreasePencilLayerToTransData(TransData *td,
td->center[0] = td->ival;
td->center[1] = ypos;
if (frame.is_selected()) {
if (frame_selected) {
td->flag |= TD_SELECTED;
}
/* 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++;
td2d++;
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) {
@ -510,6 +560,10 @@ static void createTransActionData(bContext *C, TransInfo *t)
TransData2D *td2d = 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;
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) {
using namespace blender::bke::greasepencil;
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) {
adt_count = count_masklayer_frames(
@ -647,7 +701,8 @@ static void createTransActionData(bContext *C, TransInfo *t)
Layer *layer = static_cast<Layer *>(ale->data);
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;
td2d += i;
}
@ -715,15 +770,16 @@ static void createTransActionData(bContext *C, TransInfo *t)
using namespace blender::bke::greasepencil;
Layer *layer = static_cast<Layer *>(ale->data);
for (auto [frame_number, frame] : layer->frames_for_write().items()) {
if (frame.is_selected()) {
const auto grease_pencil_closest_selected_frame = [&](const int frame_number,
const bool frame_selected) {
if (frame_selected) {
td->dist = td->rdist = 0.0f;
++td;
continue;
return;
}
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() ||
!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;
};
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) {
@ -886,10 +954,12 @@ static void recalcData_actedit(TransInfo *t)
transform_convert_flush_handle2D(td, td2d, 0.0f);
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(
*static_cast<blender::bke::greasepencil::Layer *>(td->extra),
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,
*static_cast<blender::bke::greasepencil::Layer *>(ale->data),
canceled);
canceled,
duplicate);
break;
}

View File

@ -517,6 +517,22 @@ typedef struct GreasePencil {
void move_frames(blender::bke::greasepencil::Layer &layer,
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
* drawing exists.