tornavis/source/blender/editors/transform/transform_snap_sequencer.cc

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

386 lines
12 KiB
C++
Raw Normal View History

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edtransform
*/
#include <cstdlib>
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BKE_context.hh"
#include "ED_screen.hh"
#include "ED_transform.hh"
#include "UI_view2d.hh"
#include "SEQ_channels.hh"
#include "SEQ_effects.hh"
#include "SEQ_iterator.hh"
#include "SEQ_relations.hh"
#include "SEQ_render.hh"
#include "SEQ_sequencer.hh"
#include "SEQ_time.hh"
#include "transform.hh"
#include "transform_convert.hh"
#include "transform_snap.hh"
struct TransSeqSnapData {
blender::Array<int> source_snap_points;
blender::Array<int> target_snap_points;
#ifdef WITH_CXX_GUARDEDALLOC
MEM_CXX_CLASS_ALLOC_FUNCS("TransSeqSnapData")
#endif
};
/* -------------------------------------------------------------------- */
/** \name Snap sources
* \{ */
static int seq_get_snap_source_points_len(blender::Span<Sequence *> snap_sources)
{
return snap_sources.size() * 2;
}
static int cmp_fn(const void *a, const void *b)
{
return (*(int *)a - *(int *)b);
}
static bool seq_snap_source_points_build(const Scene *scene,
TransSeqSnapData *snap_data,
blender::Span<Sequence *> snap_sources)
{
const size_t point_count_source = seq_get_snap_source_points_len(snap_sources);
if (point_count_source == 0) {
return false;
}
snap_data->source_snap_points.reinitialize(point_count_source);
int i = 0;
for (Sequence *seq : snap_sources) {
int left = 0, right = 0;
if (seq->flag & SEQ_LEFTSEL) {
left = right = SEQ_time_left_handle_frame_get(scene, seq);
}
else if (seq->flag & SEQ_RIGHTSEL) {
left = right = SEQ_time_right_handle_frame_get(scene, seq);
}
else {
left = SEQ_time_left_handle_frame_get(scene, seq);
right = SEQ_time_right_handle_frame_get(scene, seq);
}
snap_data->source_snap_points[i] = left;
snap_data->source_snap_points[i + 1] = right;
i += 2;
BLI_assert(i <= snap_data->source_snap_points.size());
}
qsort(snap_data->source_snap_points.data(),
snap_data->source_snap_points.size(),
sizeof(int),
cmp_fn);
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Snap targets
* \{ */
/* Add effect strips directly or indirectly connected to `seq_reference` to `collection`. */
static void query_strip_effects_fn(const Scene *scene,
Sequence *seq_reference,
ListBase *seqbase,
blender::VectorSet<Sequence *> &strips)
{
if (strips.contains(seq_reference)) {
return; /* Strip is already in set, so all effects connected to it are as well. */
}
strips.add(seq_reference);
/* Find all strips connected to `seq_reference`. */
LISTBASE_FOREACH (Sequence *, seq_test, seqbase) {
if (SEQ_relation_is_effect_of_strip(seq_test, seq_reference)) {
query_strip_effects_fn(scene, seq_test, seqbase, strips);
}
}
}
static blender::VectorSet<Sequence *> query_snap_targets(Scene *scene,
blender::Span<Sequence *> snap_sources,
bool exclude_selected)
{
Editing *ed = SEQ_editing_get(scene);
ListBase *seqbase = SEQ_active_seqbase_get(ed);
ListBase *channels = SEQ_channels_displayed_get(ed);
const short snap_flag = SEQ_tool_settings_snap_flag_get(scene);
/* Effects will always change position with strip to which they are connected and they don't have
* to be selected. Remove such strips from `snap_targets` collection. */
blender::VectorSet effects_of_snap_sources = snap_sources;
SEQ_iterator_set_expand(scene, seqbase, effects_of_snap_sources, query_strip_effects_fn);
effects_of_snap_sources.remove_if(
[&](Sequence *seq) { return SEQ_effect_get_num_inputs(seq->type) == 0; });
blender::VectorSet<Sequence *> snap_targets;
LISTBASE_FOREACH (Sequence *, seq, seqbase) {
if (exclude_selected && seq->flag & SELECT) {
continue; /* Selected are being transformed. */
}
if (SEQ_render_is_muted(channels, seq) && (snap_flag & SEQ_SNAP_IGNORE_MUTED)) {
continue;
}
if (seq->type == SEQ_TYPE_SOUND_RAM && (snap_flag & SEQ_SNAP_IGNORE_SOUND)) {
continue;
}
if (effects_of_snap_sources.contains(seq)) {
continue;
}
snap_targets.add(seq);
}
return snap_targets;
}
static int seq_get_snap_target_points_count(short snap_mode,
blender::Span<Sequence *> snap_targets)
{
int count = 2; /* Strip start and end are always used. */
if (snap_mode & SEQ_SNAP_TO_STRIP_HOLD) {
count += 2;
}
count *= snap_targets.size();
if (snap_mode & SEQ_SNAP_TO_CURRENT_FRAME) {
count++;
}
return count;
}
static bool seq_snap_target_points_build(Scene *scene,
short snap_mode,
TransSeqSnapData *snap_data,
blender::Span<Sequence *> snap_targets)
{
const size_t point_count_target = seq_get_snap_target_points_count(snap_mode, snap_targets);
if (point_count_target == 0) {
return false;
}
snap_data->target_snap_points.reinitialize(point_count_target);
int i = 0;
if (snap_mode & SEQ_SNAP_TO_CURRENT_FRAME) {
snap_data->target_snap_points[i] = scene->r.cfra;
i++;
}
for (Sequence *seq : snap_targets) {
snap_data->target_snap_points[i] = SEQ_time_left_handle_frame_get(scene, seq);
snap_data->target_snap_points[i + 1] = SEQ_time_right_handle_frame_get(scene, seq);
i += 2;
if (snap_mode & SEQ_SNAP_TO_STRIP_HOLD) {
int content_start = min_ii(SEQ_time_left_handle_frame_get(scene, seq),
2022-10-05 20:24:37 +02:00
SEQ_time_start_frame_get(seq));
int content_end = max_ii(SEQ_time_right_handle_frame_get(scene, seq),
2022-10-05 20:24:37 +02:00
SEQ_time_content_end_frame_get(scene, seq));
/* Effects and single image strips produce incorrect content length. Skip these strips. */
if ((seq->type & SEQ_TYPE_EFFECT) != 0 || seq->len == 1) {
content_start = SEQ_time_left_handle_frame_get(scene, seq);
content_end = SEQ_time_right_handle_frame_get(scene, seq);
}
VSE: Make time operations self-contained This patch makes it possible to manipulate strips without need to use update functions to recalculate effect and meta strips. Prior to this change function `SEQ_time_update_sequence` had to be used to update mainly effects and meta strips. This was implemented in a way that relied on sorted list of strips, which can't always be done and in rare cases this approach failed. In case of meta strips, `seqbase` had to be passed and compared with "active" one to determine whether meta strip should be updated or not. This is especially weak system that is prone to bugs when functions are used by python API functions. Finally, other strip types had startdisp` and `enddisp` fields updated by this function and a lot of code relied on these fields even if strip start, length and offsets are available. This is completely unnecessary. Implemented changes: All effects and meta strips are updated when strip handles are moved or strip is translated, without need to call any update function. Function `SEQ_time_update_sequence` has been split to `SEQ_time_update_meta_strip_range` and `seq_time_update_effects_strip_range`. These functions should be only used within sequencer module code. Meta update is used for versioning, which is only reason for it not being declared internally. Sequence fields `startdisp` and `enddisp` are now only used for effects to store strip start and end points. These fields should be used only internally within sequencer module code. Use function `SEQ_time_*_handle_frame_get` to get strip start and end points. To update effects and meta strips with reasonable performance, cache for "parent" meta strip and attached effects is added to `SequenceLookup` cache, so it shares invalidation mechanisms. All caches are populated during single iteration. There should be no functional changes. Differential Revision: https://developer.blender.org/D14990
2022-06-02 01:39:40 +02:00
CLAMP(content_start,
SEQ_time_left_handle_frame_get(scene, seq),
SEQ_time_right_handle_frame_get(scene, seq));
CLAMP(content_end,
SEQ_time_left_handle_frame_get(scene, seq),
SEQ_time_right_handle_frame_get(scene, seq));
snap_data->target_snap_points[i] = content_start;
snap_data->target_snap_points[i + 1] = content_end;
i += 2;
}
}
BLI_assert(i <= snap_data->target_snap_points.size());
qsort(snap_data->target_snap_points.data(),
snap_data->target_snap_points.size(),
sizeof(int),
cmp_fn);
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Snap utilities
* \{ */
static int seq_snap_threshold_get_frame_distance(const TransInfo *t)
{
const int snap_distance = SEQ_tool_settings_snap_distance_get(t->scene);
const View2D *v2d = &t->region->v2d;
return round_fl_to_int(UI_view2d_region_to_view_x(v2d, snap_distance) -
UI_view2d_region_to_view_x(v2d, 0));
}
/** \} */
TransSeqSnapData *transform_snap_sequencer_data_alloc(const TransInfo *t)
{
VSE: Improve retiming UI Currently retiming is quite awkward, when you need to retime multiple strips strips in sync. It is possible to use meta strips, but this is still not great. This is resolved by implementing selection. General changes: Gizmos are removed, since they are designed to operate only on active strip and don't support selection. Transform operator code is implemented for retiming data, which allows more sophisticated manipulation. Instead of drawing marker-like symbols, keyframes are drawn to represent retiming data. Retiming handles are now called keys. To have consistent names, DNA structures have been renamed. Retiming data is drawn on strip as overlay. UI changes: Retiming tool is removed. To edit retiming data, press Ctrl + R, select a key and move it. When retiming is edited, retiming menu and context menu shows more relevant features, like making transitions. Strip and retiming key selection can not be combined. It is possible to use box select operator to select keys, if any key is selected. Otherwise strips are selected. Adding retiming keys is possible with I shortcut or from menu. Retiming keys are always drawn at strip left and right boundary. These keys do not really exist until they are selected. This is to simplify retiming of strips that are resized. These keys are called "fake keys" in code. API changes: Functions, properties and types related to retiming handles are renamed to retiming keys: retiming_handle_add() -> retiming_key_add() retiming_handle_move() -> retiming_key_move() retiming_handle_remove() -> retiming_key_remove() retiming_handles -> retiming_keys RetimingHandle -> RetimingKey Retiming editing "mode" is activated by setting `Sequence.show_retiming_keys`. Pull Request: https://projects.blender.org/blender/blender/pulls/109044
2023-09-27 01:45:59 +02:00
if (ELEM(t->data_type, &TransConvertType_SequencerImage, &TransConvertType_SequencerRetiming)) {
return nullptr;
}
TransSeqSnapData *snap_data = MEM_new<TransSeqSnapData>(__func__);
Scene *scene = t->scene;
ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(scene));
short snap_mode = t->tsnap.mode;
blender::VectorSet<Sequence *> snap_sources = SEQ_query_selected_strips(seqbase);
blender::VectorSet<Sequence *> snap_targets = query_snap_targets(scene, snap_sources, true);
/* Build arrays of snap points. */
if (!seq_snap_source_points_build(scene, snap_data, snap_sources) ||
!seq_snap_target_points_build(scene, snap_mode, snap_data, snap_targets))
{
MEM_delete(snap_data);
return nullptr;
}
return snap_data;
}
void transform_snap_sequencer_data_free(TransSeqSnapData *data)
{
MEM_delete(data);
}
bool transform_snap_sequencer_calc(TransInfo *t)
{
const TransSeqSnapData *snap_data = t->tsnap.seq_context;
if (snap_data == nullptr) {
return false;
}
/* Prevent snapping when constrained to Y axis. */
if (t->con.mode & CON_APPLY && t->con.mode & CON_AXIS1) {
return false;
}
int best_dist = MAXFRAME, best_target_frame = 0, best_source_frame = 0;
for (int frame_src : snap_data->source_snap_points) {
int snap_source_frame = frame_src + round_fl_to_int(t->values[0]);
for (int snap_target_frame : snap_data->target_snap_points) {
int dist = abs(snap_target_frame - snap_source_frame);
if (dist > best_dist) {
continue;
}
best_dist = dist;
best_target_frame = snap_target_frame;
best_source_frame = snap_source_frame;
}
}
if (best_dist > seq_snap_threshold_get_frame_distance(t)) {
return false;
}
t->tsnap.snap_target[0] = best_target_frame;
t->tsnap.snap_source[0] = best_source_frame;
return true;
}
void transform_snap_sequencer_apply_translate(TransInfo *t, float *vec)
{
*vec += t->tsnap.snap_target[0] - t->tsnap.snap_source[0];
}
static int transform_snap_sequencer_to_closest_strip_ex(TransInfo *t, int frame_1, int frame_2)
{
Scene *scene = t->scene;
TransSeqSnapData *snap_data = MEM_new<TransSeqSnapData>(__func__);
blender::VectorSet<Sequence *> empty_col;
blender::VectorSet<Sequence *> snap_targets = query_snap_targets(scene, empty_col, false);
BLI_assert(frame_1 <= frame_2);
snap_data->source_snap_points.reinitialize(2);
snap_data->source_snap_points[0] = frame_1;
snap_data->source_snap_points[1] = frame_2;
short snap_mode = t->tsnap.mode;
/* Build arrays of snap points. */
seq_snap_target_points_build(scene, snap_mode, snap_data, snap_targets);
t->tsnap.seq_context = snap_data;
bool snap_success = transform_snap_sequencer_calc(t);
transform_snap_sequencer_data_free(snap_data);
t->tsnap.seq_context = nullptr;
float snap_offset = 0;
if (snap_success) {
t->tsnap.status |= (SNAP_TARGET_FOUND | SNAP_SOURCE_FOUND);
transform_snap_sequencer_apply_translate(t, &snap_offset);
}
else {
t->tsnap.status &= ~(SNAP_TARGET_FOUND | SNAP_SOURCE_FOUND);
}
return snap_offset;
}
bool ED_transform_snap_sequencer_to_closest_strip_calc(Scene *scene,
ARegion *region,
int frame_1,
int frame_2,
int *r_snap_distance,
float *r_snap_frame)
{
TransInfo t = {nullptr};
t.scene = scene;
t.region = region;
t.values[0] = 0;
t.data_type = &TransConvertType_Sequencer;
t.tsnap.mode = eSnapMode(SEQ_tool_settings_snap_mode_get(scene));
*r_snap_distance = transform_snap_sequencer_to_closest_strip_ex(&t, frame_1, frame_2);
*r_snap_frame = t.tsnap.snap_target[0];
return validSnap(&t);
}
void ED_draw_sequencer_snap_point(ARegion *region, const float snap_point)
{
/* Reuse the snapping drawing code from the transform system. */
TransInfo t = {nullptr};
t.mode = TFM_SEQ_SLIDE;
t.modifiers = MOD_SNAP;
t.spacetype = SPACE_SEQ;
t.tsnap.flag = SCE_SNAP;
t.tsnap.status = (SNAP_TARGET_FOUND | SNAP_SOURCE_FOUND);
t.tsnap.snap_target[0] = snap_point;
t.region = region;
drawSnapping(&t);
}