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:
parent
6920c690f1
commit
127eee87ce
|
@ -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};
|
||||
};
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue