VSE: Add snapping in preview area

Adds snapping in the VSE preview area.

Source points are the four corners and origins of all selected, visible
image quads. Targets can be preview borders, preview center, or
corners/origins of other strips.

Pull Request: https://projects.blender.org/blender/blender/pulls/122759
This commit is contained in:
John Kiril Swenson 2024-07-06 15:24:52 +02:00 committed by Aras Pranckevicius
parent 8e8160283e
commit 406554c11e
12 changed files with 467 additions and 83 deletions

@ -174,18 +174,16 @@ class SEQUENCER_HT_header(Header):
if st.view_type == 'PREVIEW': if st.view_type == 'PREVIEW':
layout.prop(sequencer_tool_settings, "pivot_point", text="", icon_only=True) layout.prop(sequencer_tool_settings, "pivot_point", text="", icon_only=True)
layout.separator_spacer()
if st.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}: if st.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}:
row = layout.row(align=True) row = layout.row(align=True)
row.prop(sequencer_tool_settings, "overlap_mode", text="") row.prop(sequencer_tool_settings, "overlap_mode", text="")
if st.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}: row = layout.row(align=True)
row = layout.row(align=True) row.prop(tool_settings, "use_snap_sequencer", text="")
row.prop(tool_settings, "use_snap_sequencer", text="") sub = row.row(align=True)
sub = row.row(align=True) sub.popover(panel="SEQUENCER_PT_snapping")
sub.popover(panel="SEQUENCER_PT_snapping") layout.separator_spacer()
layout.separator_spacer()
if st.view_type in {'PREVIEW', 'SEQUENCER_PREVIEW'}: if st.view_type in {'PREVIEW', 'SEQUENCER_PREVIEW'}:
layout.prop(st, "display_mode", text="", icon_only=True) layout.prop(st, "display_mode", text="", icon_only=True)
@ -2876,6 +2874,46 @@ class SEQUENCER_PT_snapping(Panel):
bl_region_type = 'HEADER' bl_region_type = 'HEADER'
bl_label = "" bl_label = ""
def draw(self, _context):
pass
class SEQUENCER_PT_preview_snapping(Panel):
bl_space_type = 'SEQUENCE_EDITOR'
bl_region_type = 'HEADER'
bl_parent_id = "SEQUENCER_PT_snapping"
bl_label = "Preview Snapping"
@classmethod
def poll(cls, context):
st = context.space_data
return st.view_type in {'PREVIEW', 'SEQUENCER_PREVIEW'}
def draw(self, context):
tool_settings = context.tool_settings
sequencer_tool_settings = tool_settings.sequencer_tool_settings
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
col = layout.column(heading="Snap to", align=True)
col.prop(sequencer_tool_settings, "snap_to_borders")
col.prop(sequencer_tool_settings, "snap_to_center")
col.prop(sequencer_tool_settings, "snap_to_strips_preview")
class SEQUENCER_PT_sequencer_snapping(Panel):
bl_space_type = 'SEQUENCE_EDITOR'
bl_region_type = 'HEADER'
bl_parent_id = "SEQUENCER_PT_snapping"
bl_label = "Sequencer Snapping"
@classmethod
def poll(cls, context):
st = context.space_data
return st.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}
def draw(self, context): def draw(self, context):
tool_settings = context.tool_settings tool_settings = context.tool_settings
sequencer_tool_settings = tool_settings.sequencer_tool_settings sequencer_tool_settings = tool_settings.sequencer_tool_settings
@ -2986,6 +3024,8 @@ classes = (
SEQUENCER_PT_annotation_onion, SEQUENCER_PT_annotation_onion,
SEQUENCER_PT_snapping, SEQUENCER_PT_snapping,
SEQUENCER_PT_preview_snapping,
SEQUENCER_PT_sequencer_snapping,
) )
if __name__ == "__main__": # only for live edit. if __name__ == "__main__": # only for live edit.

@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */ /* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION #define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 6 #define BLENDER_FILE_SUBVERSION 7
/* Minimum Blender version that supports reading file written with the current /* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to * version. Older Blender versions will test this and cancel loading the file, showing a warning to

@ -4299,6 +4299,14 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
} }
} }
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 7)) {
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
SequencerToolSettings *sequencer_tool_settings = SEQ_tool_settings_ensure(scene);
sequencer_tool_settings->snap_mode |= SEQ_SNAP_TO_PREVIEW_BORDERS |
SEQ_SNAP_TO_PREVIEW_CENTER |
SEQ_SNAP_TO_STRIPS_PREVIEW;
}
}
/** /**
* Always bump subversion in BKE_blender_version.h when adding versioning * Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

@ -179,6 +179,14 @@ enum eTSnap {
}; };
ENUM_OPERATORS(eTSnap, SNAP_MULTI_POINTS) ENUM_OPERATORS(eTSnap, SNAP_MULTI_POINTS)
/** #TransSnap.direction */
enum eSnapDir {
DIR_GLOBAL_X = (1 << 0),
DIR_GLOBAL_Y = (1 << 1),
DIR_GLOBAL_Z = (1 << 2),
};
ENUM_OPERATORS(eSnapDir, DIR_GLOBAL_Z)
/** #TransCon.mode, #TransInfo.con.mode */ /** #TransCon.mode, #TransInfo.con.mode */
enum eTConstraint { enum eTConstraint {
/** When set constraints are in use. */ /** When set constraints are in use. */
@ -311,6 +319,8 @@ struct TransSnap {
/* Snapped Element Type (currently for objects only). */ /* Snapped Element Type (currently for objects only). */
eSnapMode source_type; eSnapMode source_type;
eSnapMode target_type; eSnapMode target_type;
/* For independent snapping in different directions (currently used only by VSE preview). */
eSnapDir direction;
/** Snapping from this point (in global-space). */ /** Snapping from this point (in global-space). */
float snap_source[3]; float snap_source[3];
/** To this point (in global-space). */ /** To this point (in global-space). */

@ -156,6 +156,6 @@ TransModeInfo TransMode_seqslide = {
/*transform_matrix_fn*/ nullptr, /*transform_matrix_fn*/ nullptr,
/*handle_event_fn*/ nullptr, /*handle_event_fn*/ nullptr,
/*snap_distance_fn*/ nullptr, /*snap_distance_fn*/ nullptr,
/*snap_apply_fn*/ transform_snap_sequencer_apply_translate, /*snap_apply_fn*/ transform_snap_sequencer_apply_seqslide,
/*draw_fn*/ nullptr, /*draw_fn*/ nullptr,
}; };

@ -358,7 +358,12 @@ static void ApplySnapTranslation(TransInfo *t, float vec[3])
} }
} }
else if (t->spacetype == SPACE_SEQ) { else if (t->spacetype == SPACE_SEQ) {
transform_snap_sequencer_apply_translate(t, vec); if (t->region->regiontype == RGN_TYPE_PREVIEW) {
transform_snap_sequencer_image_apply_translate(t, vec);
}
else {
transform_snap_sequencer_apply_seqslide(t, vec);
}
} }
else { else {
if (t->spacetype == SPACE_VIEW3D) { if (t->spacetype == SPACE_VIEW3D) {

@ -167,6 +167,10 @@ bool transformModeUseSnap(const TransInfo *t)
} }
ToolSettings *ts = t->settings; ToolSettings *ts = t->settings;
if (t->mode == TFM_TRANSLATION) { if (t->mode == TFM_TRANSLATION) {
/* VSE preview snapping should also not depend on the 3D viewport. */
if (t->spacetype == SPACE_SEQ) {
return true;
}
return (ts->snap_transform_mode_flag & SCE_SNAP_TRANSFORM_MODE_TRANSLATE) != 0; return (ts->snap_transform_mode_flag & SCE_SNAP_TRANSFORM_MODE_TRANSLATE) != 0;
} }
if (t->mode == TFM_ROTATION) { if (t->mode == TFM_ROTATION) {
@ -357,11 +361,31 @@ void drawSnapping(TransInfo *t)
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformColor4ubv(col); immUniformColor4ubv(col);
float pixelx = BLI_rctf_size_x(&region->v2d.cur) / BLI_rcti_size_x(&region->v2d.mask); float pixelx = BLI_rctf_size_x(&region->v2d.cur) / BLI_rcti_size_x(&region->v2d.mask);
immRectf(pos,
t->tsnap.snap_target[0] - pixelx, if (region->regiontype == RGN_TYPE_PREVIEW) {
region->v2d.cur.ymax, if (t->tsnap.direction & DIR_GLOBAL_X) {
t->tsnap.snap_target[0] + pixelx, immRectf(pos,
region->v2d.cur.ymin); t->tsnap.snap_target[0] - pixelx,
region->v2d.cur.ymax,
t->tsnap.snap_target[0] + pixelx,
region->v2d.cur.ymin);
}
if (t->tsnap.direction & DIR_GLOBAL_Y) {
immRectf(pos,
region->v2d.cur.xmin,
t->tsnap.snap_target[1] - pixelx,
region->v2d.cur.xmax,
t->tsnap.snap_target[1] + pixelx);
}
}
else {
immRectf(pos,
t->tsnap.snap_target[0] - pixelx,
region->v2d.cur.ymax,
t->tsnap.snap_target[0] + pixelx,
region->v2d.cur.ymin);
}
immUnbindProgram(); immUnbindProgram();
GPU_blend(GPU_BLEND_NONE); GPU_blend(GPU_BLEND_NONE);
} }

@ -67,7 +67,8 @@ float transform_snap_distance_len_squared_fn(TransInfo *t, const float p1[3], co
TransSeqSnapData *transform_snap_sequencer_data_alloc(const TransInfo *t); TransSeqSnapData *transform_snap_sequencer_data_alloc(const TransInfo *t);
void transform_snap_sequencer_data_free(TransSeqSnapData *data); void transform_snap_sequencer_data_free(TransSeqSnapData *data);
bool transform_snap_sequencer_calc(TransInfo *t); bool transform_snap_sequencer_calc(TransInfo *t);
void transform_snap_sequencer_apply_translate(TransInfo *t, float *vec); void transform_snap_sequencer_apply_seqslide(TransInfo *t, float *vec);
void transform_snap_sequencer_image_apply_translate(TransInfo *t, float *vec);
/* `transform_snap_animation.cc` */ /* `transform_snap_animation.cc` */
void snapFrameTransform( void snapFrameTransform(

@ -24,14 +24,15 @@
#include "SEQ_render.hh" #include "SEQ_render.hh"
#include "SEQ_sequencer.hh" #include "SEQ_sequencer.hh"
#include "SEQ_time.hh" #include "SEQ_time.hh"
#include "SEQ_transform.hh"
#include "transform.hh" #include "transform.hh"
#include "transform_convert.hh" #include "transform_convert.hh"
#include "transform_snap.hh" #include "transform_snap.hh"
struct TransSeqSnapData { struct TransSeqSnapData {
blender::Array<int> source_snap_points; blender::Array<blender::float2> source_snap_points;
blender::Array<int> target_snap_points; blender::Array<blender::float2> target_snap_points;
#ifdef WITH_CXX_GUARDEDALLOC #ifdef WITH_CXX_GUARDEDALLOC
MEM_CXX_CLASS_ALLOC_FUNCS("TransSeqSnapData") MEM_CXX_CLASS_ALLOC_FUNCS("TransSeqSnapData")
@ -42,21 +43,51 @@ struct TransSeqSnapData {
/** \name Snap sources /** \name Snap sources
* \{ */ * \{ */
static int seq_get_snap_source_points_len(blender::Span<Sequence *> snap_sources) static blender::VectorSet<Sequence *> query_snap_sources_timeline(const Scene *scene)
{
blender::VectorSet<Sequence *> snap_sources;
ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(scene));
snap_sources = SEQ_query_selected_strips(seqbase);
return snap_sources;
}
static blender::VectorSet<Sequence *> query_snap_sources_preview(const Scene *scene)
{
blender::VectorSet<Sequence *> snap_sources;
Editing *ed = SEQ_editing_get(scene);
ListBase *channels = SEQ_channels_displayed_get(ed);
snap_sources = SEQ_query_rendered_strips(scene, channels, ed->seqbasep, scene->r.cfra, 0);
snap_sources.remove_if([&](Sequence *seq) { return (seq->flag & SELECT) == 0; });
return snap_sources;
}
static int seq_get_snap_source_points_count_timeline(const blender::Span<Sequence *> snap_sources)
{ {
return snap_sources.size() * 2; return snap_sources.size() * 2;
} }
static int cmp_fn(const void *a, const void *b) static int seq_get_snap_source_points_count_preview(const blender::Span<Sequence *> snap_sources)
{ {
return (*(int *)a - *(int *)b); /* Source points are four corners and the center of an image quad. */
return snap_sources.size() * 5;
} }
static bool seq_snap_source_points_build(const Scene *scene, static int cmp_fn(const void *a, const void *b)
TransSeqSnapData *snap_data,
blender::Span<Sequence *> snap_sources)
{ {
const size_t point_count_source = seq_get_snap_source_points_len(snap_sources); return round_fl_to_int((*(blender::float2 *)a)[0] - (*(blender::float2 *)b)[0]);
}
static bool seq_snap_source_points_build_timeline(const Scene *scene,
TransSeqSnapData *snap_data,
const blender::Span<Sequence *> snap_sources)
{
const size_t point_count_source = seq_get_snap_source_points_count_timeline(snap_sources);
if (point_count_source == 0) { if (point_count_source == 0) {
return false; return false;
} }
@ -76,20 +107,56 @@ static bool seq_snap_source_points_build(const Scene *scene,
right = SEQ_time_right_handle_frame_get(scene, seq); right = SEQ_time_right_handle_frame_get(scene, seq);
} }
snap_data->source_snap_points[i] = left; /* Set only the x-positions when snapping in the timeline. */
snap_data->source_snap_points[i + 1] = right; snap_data->source_snap_points[i][0] = left;
snap_data->source_snap_points[i + 1][0] = right;
i += 2; i += 2;
BLI_assert(i <= snap_data->source_snap_points.size()); BLI_assert(i <= snap_data->source_snap_points.size());
} }
qsort(snap_data->source_snap_points.data(), qsort(snap_data->source_snap_points.data(),
snap_data->source_snap_points.size(), snap_data->source_snap_points.size(),
sizeof(int), sizeof(blender::float2),
cmp_fn); cmp_fn);
return true; return true;
} }
static bool seq_snap_source_points_build_preview(const Scene *scene,
TransSeqSnapData *snap_data,
const blender::Span<Sequence *> snap_sources)
{
const size_t point_count_source = seq_get_snap_source_points_count_preview(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) {
float seq_image_quad[4][2];
SEQ_image_transform_final_quad_get(scene, seq, seq_image_quad);
for (int j = 0; j < 4; j++) {
snap_data->source_snap_points[i][0] = seq_image_quad[j][0];
snap_data->source_snap_points[i][1] = seq_image_quad[j][1];
i++;
}
/* Add origins last */
float image_origin[2];
SEQ_image_transform_origin_offset_pixelspace_get(scene, seq, image_origin);
snap_data->source_snap_points[i][0] = image_origin[0];
snap_data->source_snap_points[i][1] = image_origin[1];
i++;
BLI_assert(i <= snap_data->source_snap_points.size());
}
return true;
}
/** \} */ /** \} */
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
@ -115,17 +182,16 @@ static void query_strip_effects_fn(const Scene *scene,
} }
} }
static blender::VectorSet<Sequence *> query_snap_targets(Scene *scene, static blender::VectorSet<Sequence *> query_snap_targets_timeline(
blender::Span<Sequence *> snap_sources, Scene *scene, const blender::Span<Sequence *> snap_sources, const bool exclude_selected)
bool exclude_selected)
{ {
Editing *ed = SEQ_editing_get(scene); Editing *ed = SEQ_editing_get(scene);
ListBase *seqbase = SEQ_active_seqbase_get(ed); ListBase *seqbase = SEQ_active_seqbase_get(ed);
ListBase *channels = SEQ_channels_displayed_get(ed); ListBase *channels = SEQ_channels_displayed_get(ed);
const short snap_flag = SEQ_tool_settings_snap_flag_get(scene); 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 /* Effects will always change position with strip to which they are connected and they don't
* to be selected. Remove such strips from `snap_targets` collection. */ * have to be selected. Remove such strips from `snap_targets` collection. */
blender::VectorSet effects_of_snap_sources = snap_sources; blender::VectorSet effects_of_snap_sources = snap_sources;
SEQ_iterator_set_expand(scene, seqbase, effects_of_snap_sources, query_strip_effects_fn); SEQ_iterator_set_expand(scene, seqbase, effects_of_snap_sources, query_strip_effects_fn);
effects_of_snap_sources.remove_if( effects_of_snap_sources.remove_if(
@ -134,7 +200,7 @@ static blender::VectorSet<Sequence *> query_snap_targets(Scene *scene,
blender::VectorSet<Sequence *> snap_targets; blender::VectorSet<Sequence *> snap_targets;
LISTBASE_FOREACH (Sequence *, seq, seqbase) { LISTBASE_FOREACH (Sequence *, seq, seqbase) {
if (exclude_selected && seq->flag & SELECT) { if (exclude_selected && seq->flag & SELECT) {
continue; /* Selected are being transformed. */ continue; /* Selected are being transformed if there is no drag and drop. */
} }
if (SEQ_render_is_muted(channels, seq) && (snap_flag & SEQ_SNAP_IGNORE_MUTED)) { if (SEQ_render_is_muted(channels, seq) && (snap_flag & SEQ_SNAP_IGNORE_MUTED)) {
continue; continue;
@ -152,11 +218,34 @@ static blender::VectorSet<Sequence *> query_snap_targets(Scene *scene,
return snap_targets; return snap_targets;
} }
static int seq_get_snap_target_points_count(const Scene *scene, static blender::VectorSet<Sequence *> query_snap_targets_preview(Scene *scene,
short snap_mode, const short snap_mode)
blender::Span<Sequence *> snap_targets)
{ {
int count = 2; /* Strip start and end are always used. */ blender::VectorSet<Sequence *> snap_targets;
/* We don't need to calculate strip snap targets if the option is unselected. */
if ((snap_mode & SEQ_SNAP_TO_STRIPS_PREVIEW) == 0) {
return snap_targets;
}
Editing *ed = SEQ_editing_get(scene);
ListBase *channels = SEQ_channels_displayed_get(ed);
snap_targets = SEQ_query_rendered_strips(scene, channels, ed->seqbasep, scene->r.cfra, 0);
snap_targets.remove_if([&](Sequence *seq) { return (seq->flag & SELECT) == 1; });
return snap_targets;
}
static int seq_get_snap_target_points_count_timeline(const Scene *scene,
const short snap_mode,
const blender::Span<Sequence *> snap_targets)
{
int count = 0;
if (snap_mode & SEQ_SNAP_TO_STRIPS) {
count += 2; /* Strip start and end are always used. */
}
if (snap_mode & SEQ_SNAP_TO_STRIP_HOLD) { if (snap_mode & SEQ_SNAP_TO_STRIP_HOLD) {
count += 2; count += 2;
@ -175,12 +264,35 @@ static int seq_get_snap_target_points_count(const Scene *scene,
return count; return count;
} }
static bool seq_snap_target_points_build(Scene *scene, static int seq_get_snap_target_points_count_preview(const short snap_mode,
short snap_mode, const blender::Span<Sequence *> snap_targets)
TransSeqSnapData *snap_data,
blender::Span<Sequence *> snap_targets)
{ {
const size_t point_count_target = seq_get_snap_target_points_count( int count = 0;
if (snap_mode & SEQ_SNAP_TO_PREVIEW_BORDERS) {
/* Opposite corners of the view have enough information to snap to all four corners. */
count += 2;
}
if (snap_mode & SEQ_SNAP_TO_PREVIEW_CENTER) {
count++;
}
if (snap_mode & SEQ_SNAP_TO_STRIPS_PREVIEW) {
/* Snap to other strips' corners and center. */
count += snap_targets.size() * 5;
}
return count;
}
static bool seq_snap_target_points_build_timeline(const Scene *scene,
const short snap_mode,
TransSeqSnapData *snap_data,
const blender::Span<Sequence *> snap_targets)
{
const size_t point_count_target = seq_get_snap_target_points_count_timeline(
scene, snap_mode, snap_targets); scene, snap_mode, snap_targets);
if (point_count_target == 0) { if (point_count_target == 0) {
return false; return false;
@ -190,20 +302,20 @@ static bool seq_snap_target_points_build(Scene *scene,
int i = 0; int i = 0;
if (snap_mode & SEQ_SNAP_TO_CURRENT_FRAME) { if (snap_mode & SEQ_SNAP_TO_CURRENT_FRAME) {
snap_data->target_snap_points[i] = scene->r.cfra; snap_data->target_snap_points[i][0] = scene->r.cfra;
i++; i++;
} }
if (snap_mode & SEQ_SNAP_TO_MARKERS) { if (snap_mode & SEQ_SNAP_TO_MARKERS) {
LISTBASE_FOREACH (TimeMarker *, marker, &scene->markers) { LISTBASE_FOREACH (TimeMarker *, marker, &scene->markers) {
snap_data->target_snap_points[i] = marker->frame; snap_data->target_snap_points[i][0] = marker->frame;
i++; i++;
} }
} }
for (Sequence *seq : snap_targets) { 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][0] = SEQ_time_left_handle_frame_get(scene, seq);
snap_data->target_snap_points[i + 1] = SEQ_time_right_handle_frame_get(scene, seq); snap_data->target_snap_points[i + 1][0] = SEQ_time_right_handle_frame_get(scene, seq);
i += 2; i += 2;
if (snap_mode & SEQ_SNAP_TO_STRIP_HOLD) { if (snap_mode & SEQ_SNAP_TO_STRIP_HOLD) {
@ -223,53 +335,110 @@ static bool seq_snap_target_points_build(Scene *scene,
SEQ_time_left_handle_frame_get(scene, seq), SEQ_time_left_handle_frame_get(scene, seq),
SEQ_time_right_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][0] = content_start;
snap_data->target_snap_points[i + 1] = content_end; snap_data->target_snap_points[i + 1][0] = content_end;
i += 2; i += 2;
} }
} }
BLI_assert(i <= snap_data->target_snap_points.size()); BLI_assert(i <= snap_data->target_snap_points.size());
qsort(snap_data->target_snap_points.data(), qsort(snap_data->target_snap_points.data(),
snap_data->target_snap_points.size(), snap_data->target_snap_points.size(),
sizeof(int), sizeof(blender::float2),
cmp_fn); cmp_fn);
return true; return true;
} }
static bool seq_snap_target_points_build_preview(const Scene *scene,
const View2D *v2d,
const short snap_mode,
TransSeqSnapData *snap_data,
const blender::Span<Sequence *> snap_targets)
{
const size_t point_count_target = seq_get_snap_target_points_count_preview(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_PREVIEW_BORDERS) {
snap_data->target_snap_points[i][0] = v2d->tot.xmin;
snap_data->target_snap_points[i][1] = v2d->tot.ymin;
snap_data->target_snap_points[i + 1][0] = v2d->tot.xmax;
snap_data->target_snap_points[i + 1][1] = v2d->tot.ymax;
i += 2;
}
if (snap_mode & SEQ_SNAP_TO_PREVIEW_CENTER) {
snap_data->target_snap_points[i][0] = 0;
snap_data->target_snap_points[i][1] = 0;
i++;
}
if (snap_mode & SEQ_SNAP_TO_STRIPS_PREVIEW) {
for (Sequence *seq : snap_targets) {
float seq_image_quad[4][2];
SEQ_image_transform_final_quad_get(scene, seq, seq_image_quad);
for (int j = 0; j < 4; j++) {
snap_data->target_snap_points[i][0] = seq_image_quad[j][0];
snap_data->target_snap_points[i][1] = seq_image_quad[j][1];
i++;
}
float image_origin[2];
SEQ_image_transform_origin_offset_pixelspace_get(scene, seq, image_origin);
snap_data->target_snap_points[i][0] = image_origin[0];
snap_data->target_snap_points[i][1] = image_origin[1];
i++;
}
}
BLI_assert(i <= snap_data->target_snap_points.size());
return true;
}
/** \} */ /** \} */
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
/** \name Snap utilities /** \name Snap utilities
* \{ */ * \{ */
static int seq_snap_threshold_get_frame_distance(const TransInfo *t) static float seq_snap_threshold_get_view_distance(const TransInfo *t)
{ {
const int snap_distance = SEQ_tool_settings_snap_distance_get(t->scene); const int snap_distance = SEQ_tool_settings_snap_distance_get(t->scene);
const View2D *v2d = &t->region->v2d; const View2D *v2d = &t->region->v2d;
return round_fl_to_int(UI_view2d_region_to_view_x(v2d, snap_distance) - return UI_view2d_region_to_view_x(v2d, snap_distance) - UI_view2d_region_to_view_x(v2d, 0);
UI_view2d_region_to_view_x(v2d, 0)); }
static int seq_snap_threshold_get_frame_distance(const TransInfo *t)
{
return round_fl_to_int(seq_snap_threshold_get_view_distance(t));
} }
/** \} */ /** \} */
TransSeqSnapData *transform_snap_sequencer_data_alloc(const TransInfo *t) static TransSeqSnapData *transform_snap_sequencer_data_alloc_timeline(const TransInfo *t)
{ {
if (ELEM(t->data_type, &TransConvertType_SequencerImage, &TransConvertType_SequencerRetiming)) {
return nullptr;
}
TransSeqSnapData *snap_data = MEM_new<TransSeqSnapData>(__func__); TransSeqSnapData *snap_data = MEM_new<TransSeqSnapData>(__func__);
Scene *scene = t->scene; Scene *scene = t->scene;
ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(scene));
short snap_mode = t->tsnap.mode; short snap_mode = t->tsnap.mode;
blender::VectorSet<Sequence *> snap_sources = SEQ_query_selected_strips(seqbase); blender::VectorSet<Sequence *> snap_sources = query_snap_sources_timeline(scene);
blender::VectorSet<Sequence *> snap_targets = query_snap_targets(scene, snap_sources, true); blender::VectorSet<Sequence *> snap_targets = query_snap_targets_timeline(
scene, snap_sources, true);
/* Build arrays of snap points. */ /* Build arrays of snap points. */
if (!seq_snap_source_points_build(scene, snap_data, snap_sources) || if (!seq_snap_source_points_build_timeline(scene, snap_data, snap_sources) ||
!seq_snap_target_points_build(scene, snap_mode, snap_data, snap_targets)) !seq_snap_target_points_build_timeline(scene, snap_mode, snap_data, snap_targets))
{ {
MEM_delete(snap_data); MEM_delete(snap_data);
return nullptr; return nullptr;
@ -278,18 +447,49 @@ TransSeqSnapData *transform_snap_sequencer_data_alloc(const TransInfo *t)
return snap_data; return snap_data;
} }
static TransSeqSnapData *transform_snap_sequencer_data_alloc_preview(const TransInfo *t)
{
TransSeqSnapData *snap_data = MEM_new<TransSeqSnapData>(__func__);
Scene *scene = t->scene;
short snap_mode = t->tsnap.mode;
View2D *v2d = &t->region->v2d;
blender::VectorSet<Sequence *> snap_sources = query_snap_sources_preview(scene);
blender::VectorSet<Sequence *> snap_targets = query_snap_targets_preview(scene, snap_mode);
/* Build arrays of snap points. */
if (!seq_snap_source_points_build_preview(scene, snap_data, snap_sources) ||
!seq_snap_target_points_build_preview(scene, v2d, snap_mode, snap_data, snap_targets))
{
MEM_delete(snap_data);
return nullptr;
}
return snap_data;
}
TransSeqSnapData *transform_snap_sequencer_data_alloc(const TransInfo *t)
{
if (t->data_type == &TransConvertType_SequencerRetiming) {
return nullptr;
}
if (t->data_type == &TransConvertType_Sequencer) {
return transform_snap_sequencer_data_alloc_timeline(t);
}
else {
return transform_snap_sequencer_data_alloc_preview(t);
}
}
void transform_snap_sequencer_data_free(TransSeqSnapData *data) void transform_snap_sequencer_data_free(TransSeqSnapData *data)
{ {
MEM_delete(data); MEM_delete(data);
} }
bool transform_snap_sequencer_calc(TransInfo *t) static bool transform_snap_sequencer_calc_timeline(TransInfo *t, const TransSeqSnapData *snap_data)
{ {
const TransSeqSnapData *snap_data = t->tsnap.seq_context;
if (snap_data == nullptr) {
return false;
}
/* Prevent snapping when constrained to Y axis. */ /* Prevent snapping when constrained to Y axis. */
if (t->con.mode & CON_APPLY && t->con.mode & CON_AXIS1) { if (t->con.mode & CON_APPLY && t->con.mode & CON_AXIS1) {
return false; return false;
@ -297,8 +497,10 @@ bool transform_snap_sequencer_calc(TransInfo *t)
int best_dist = MAXFRAME, best_target_frame = 0, best_source_frame = 0; int best_dist = MAXFRAME, best_target_frame = 0, best_source_frame = 0;
for (int snap_source_frame : snap_data->source_snap_points) { for (const float *snap_source_point : snap_data->source_snap_points) {
for (int snap_target_frame : snap_data->target_snap_points) { for (const float *snap_target_point : snap_data->target_snap_points) {
int snap_source_frame = snap_source_point[0];
int snap_target_frame = snap_target_point[0];
int dist = abs(snap_target_frame - (snap_source_frame + round_fl_to_int(t->values[0]))); int dist = abs(snap_target_frame - (snap_source_frame + round_fl_to_int(t->values[0])));
if (dist > best_dist) { if (dist > best_dist) {
continue; continue;
@ -319,28 +521,99 @@ bool transform_snap_sequencer_calc(TransInfo *t)
return true; return true;
} }
void transform_snap_sequencer_apply_translate(TransInfo *t, float *vec) static bool transform_snap_sequencer_calc_preview(TransInfo *t, const TransSeqSnapData *snap_data)
{
/* Store best snap candidates in x and y directions separately. */
blender::float2 best_dist(std::numeric_limits<float>::max());
blender::float2 best_target_point(0.0f);
blender::float2 best_source_point(0.0f);
for (const float *snap_source_point : snap_data->source_snap_points) {
for (const float *snap_target_point : snap_data->target_snap_points) {
/* First update snaps in x direction, then y direction. */
for (int i = 0; i < 2; i++) {
int dist = abs(snap_target_point[i] - (snap_source_point[i] + t->values[i]));
if (dist > best_dist[i]) {
continue;
}
best_dist[i] = dist;
best_target_point[i] = snap_target_point[i];
best_source_point[i] = snap_source_point[i];
}
}
}
t->tsnap.direction &= ~(DIR_GLOBAL_X | DIR_GLOBAL_Y);
float thr = seq_snap_threshold_get_view_distance(t);
if (best_dist[0] <= thr) {
t->tsnap.snap_target[0] = best_target_point[0];
t->tsnap.snap_source[0] = best_source_point[0];
t->tsnap.direction |= DIR_GLOBAL_X;
}
if (best_dist[1] <= thr) {
t->tsnap.snap_target[1] = best_target_point[1];
t->tsnap.snap_source[1] = best_source_point[1];
t->tsnap.direction |= DIR_GLOBAL_Y;
}
return (best_dist[0] <= thr || best_dist[1] <= thr);
}
bool transform_snap_sequencer_calc(TransInfo *t)
{
const TransSeqSnapData *snap_data = t->tsnap.seq_context;
if (snap_data == nullptr) {
return false;
}
if (t->data_type == &TransConvertType_Sequencer) {
return transform_snap_sequencer_calc_timeline(t, snap_data);
}
else {
return transform_snap_sequencer_calc_preview(t, snap_data);
}
}
void transform_snap_sequencer_apply_seqslide(TransInfo *t, float *vec)
{ {
*vec = t->tsnap.snap_target[0] - t->tsnap.snap_source[0]; *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) void transform_snap_sequencer_image_apply_translate(TransInfo *t, float vec[2])
{
/* Apply snap along x and y axes independently. */
if (t->tsnap.direction & DIR_GLOBAL_X) {
vec[0] = t->tsnap.snap_target[0] - t->tsnap.snap_source[0];
}
if (t->tsnap.direction & DIR_GLOBAL_Y) {
vec[1] = t->tsnap.snap_target[1] - t->tsnap.snap_source[1];
}
}
static int transform_snap_sequencer_to_closest_strip_ex(TransInfo *t,
const int frame_1,
const int frame_2)
{ {
Scene *scene = t->scene; Scene *scene = t->scene;
TransSeqSnapData *snap_data = MEM_new<TransSeqSnapData>(__func__); TransSeqSnapData *snap_data = MEM_new<TransSeqSnapData>(__func__);
blender::VectorSet<Sequence *> empty_col; blender::VectorSet<Sequence *> empty_col;
blender::VectorSet<Sequence *> snap_targets = query_snap_targets(scene, empty_col, false); blender::VectorSet<Sequence *> snap_targets = query_snap_targets_timeline(
scene, empty_col, false);
BLI_assert(frame_1 <= frame_2); BLI_assert(frame_1 <= frame_2);
snap_data->source_snap_points.reinitialize(2); snap_data->source_snap_points.reinitialize(2);
snap_data->source_snap_points[0] = frame_1; snap_data->source_snap_points[0][0] = frame_1;
snap_data->source_snap_points[1] = frame_2; snap_data->source_snap_points[1][0] = frame_2;
short snap_mode = t->tsnap.mode; short snap_mode = t->tsnap.mode;
/* Build arrays of snap points. */ /* Build arrays of snap target frames. */
seq_snap_target_points_build(scene, snap_mode, snap_data, snap_targets); seq_snap_target_points_build_timeline(scene, snap_mode, snap_data, snap_targets);
t->tsnap.seq_context = snap_data; t->tsnap.seq_context = snap_data;
bool snap_success = transform_snap_sequencer_calc(t); bool snap_success = transform_snap_sequencer_calc(t);
@ -350,7 +623,7 @@ static int transform_snap_sequencer_to_closest_strip_ex(TransInfo *t, int frame_
float snap_offset = 0; float snap_offset = 0;
if (snap_success) { if (snap_success) {
t->tsnap.status |= (SNAP_TARGET_FOUND | SNAP_SOURCE_FOUND); t->tsnap.status |= (SNAP_TARGET_FOUND | SNAP_SOURCE_FOUND);
transform_snap_sequencer_apply_translate(t, &snap_offset); transform_snap_sequencer_apply_seqslide(t, &snap_offset);
} }
else { else {
t->tsnap.status &= ~(SNAP_TARGET_FOUND | SNAP_SOURCE_FOUND); t->tsnap.status &= ~(SNAP_TARGET_FOUND | SNAP_SOURCE_FOUND);
@ -361,8 +634,8 @@ static int transform_snap_sequencer_to_closest_strip_ex(TransInfo *t, int frame_
bool ED_transform_snap_sequencer_to_closest_strip_calc(Scene *scene, bool ED_transform_snap_sequencer_to_closest_strip_calc(Scene *scene,
ARegion *region, ARegion *region,
int frame_1, const int frame_1,
int frame_2, const int frame_2,
int *r_snap_distance, int *r_snap_distance,
float *r_snap_frame) float *r_snap_frame)
{ {

@ -2436,6 +2436,11 @@ enum {
SEQ_SNAP_TO_CURRENT_FRAME = 1 << 1, SEQ_SNAP_TO_CURRENT_FRAME = 1 << 1,
SEQ_SNAP_TO_STRIP_HOLD = 1 << 2, SEQ_SNAP_TO_STRIP_HOLD = 1 << 2,
SEQ_SNAP_TO_MARKERS = 1 << 3, SEQ_SNAP_TO_MARKERS = 1 << 3,
/* Preview snapping. */
SEQ_SNAP_TO_PREVIEW_BORDERS = 1 << 4,
SEQ_SNAP_TO_PREVIEW_CENTER = 1 << 5,
SEQ_SNAP_TO_STRIPS_PREVIEW = 1 << 6,
}; };
/** #SequencerToolSettings::snap_flag */ /** #SequencerToolSettings::snap_flag */

@ -3534,7 +3534,7 @@ static void rna_def_tool_settings(BlenderRNA *brna)
prop = RNA_def_property(srna, "use_snap_sequencer", PROP_BOOLEAN, PROP_NONE); prop = RNA_def_property(srna, "use_snap_sequencer", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "snap_flag_seq", SCE_SNAP); RNA_def_property_boolean_sdna(prop, nullptr, "snap_flag_seq", SCE_SNAP);
RNA_def_property_flag(prop, PROP_DEG_SYNC_ONLY); RNA_def_property_flag(prop, PROP_DEG_SYNC_ONLY);
RNA_def_property_ui_text(prop, "Use Snapping", "Snap to strip edges or current frame"); RNA_def_property_ui_text(prop, "Use Snapping", "Snap strips during transform");
RNA_def_property_ui_icon(prop, ICON_SNAP_OFF, 1); RNA_def_property_ui_icon(prop, ICON_SNAP_OFF, 1);
RNA_def_property_boolean_default(prop, true); RNA_def_property_boolean_default(prop, true);
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); /* Publish message-bus. */ RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); /* Publish message-bus. */
@ -4218,6 +4218,22 @@ static void rna_def_sequencer_tool_settings(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Markers", "Snap to markers"); RNA_def_property_ui_text(prop, "Markers", "Snap to markers");
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); /* header redraw */ RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); /* header redraw */
prop = RNA_def_property(srna, "snap_to_borders", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "snap_mode", SEQ_SNAP_TO_PREVIEW_BORDERS);
RNA_def_property_ui_text(prop, "Borders", "Snap to preview borders");
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); /* header redraw */
prop = RNA_def_property(srna, "snap_to_center", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "snap_mode", SEQ_SNAP_TO_PREVIEW_CENTER);
RNA_def_property_ui_text(prop, "Center", "Snap to preview center");
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); /* header redraw */
prop = RNA_def_property(srna, "snap_to_strips_preview", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "snap_mode", SEQ_SNAP_TO_STRIPS_PREVIEW);
RNA_def_property_ui_text(
prop, "Other Strips", "Snap to borders and origins of deselected, visible strips");
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); /* header redraw */
prop = RNA_def_property(srna, "snap_ignore_muted", PROP_BOOLEAN, PROP_NONE); prop = RNA_def_property(srna, "snap_ignore_muted", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "snap_flag", SEQ_SNAP_IGNORE_MUTED); RNA_def_property_boolean_sdna(prop, nullptr, "snap_flag", SEQ_SNAP_IGNORE_MUTED);
RNA_def_property_ui_text(prop, "Ignore Muted Strips", "Don't snap to hidden strips"); RNA_def_property_ui_text(prop, "Ignore Muted Strips", "Don't snap to hidden strips");

@ -332,7 +332,9 @@ SequencerToolSettings *SEQ_tool_settings_init()
MEM_callocN(sizeof(SequencerToolSettings), "Sequencer tool settings")); MEM_callocN(sizeof(SequencerToolSettings), "Sequencer tool settings"));
tool_settings->fit_method = SEQ_SCALE_TO_FIT; tool_settings->fit_method = SEQ_SCALE_TO_FIT;
tool_settings->snap_mode = SEQ_SNAP_TO_STRIPS | SEQ_SNAP_TO_CURRENT_FRAME | tool_settings->snap_mode = SEQ_SNAP_TO_STRIPS | SEQ_SNAP_TO_CURRENT_FRAME |
SEQ_SNAP_TO_STRIP_HOLD | SEQ_SNAP_TO_MARKERS; SEQ_SNAP_TO_STRIP_HOLD | SEQ_SNAP_TO_MARKERS |
SEQ_SNAP_TO_PREVIEW_BORDERS | SEQ_SNAP_TO_PREVIEW_CENTER |
SEQ_SNAP_TO_STRIPS_PREVIEW;
tool_settings->snap_distance = 15; tool_settings->snap_distance = 15;
tool_settings->overlap_mode = SEQ_OVERLAP_SHUFFLE; tool_settings->overlap_mode = SEQ_OVERLAP_SHUFFLE;
tool_settings->pivot_point = V3D_AROUND_LOCAL_ORIGINS; tool_settings->pivot_point = V3D_AROUND_LOCAL_ORIGINS;