From 406554c11e1be649ec6f035e7229542755d639de Mon Sep 17 00:00:00 2001 From: John Kiril Swenson Date: Sat, 6 Jul 2024 15:24:52 +0200 Subject: [PATCH] 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 --- scripts/startup/bl_ui/space_sequencer.py | 54 ++- .../blender/blenkernel/BKE_blender_version.h | 2 +- .../blenloader/intern/versioning_400.cc | 8 + source/blender/editors/transform/transform.hh | 10 + .../transform_mode_edge_seq_slide.cc | 2 +- .../transform/transform_mode_translate.cc | 7 +- .../editors/transform/transform_snap.cc | 34 +- .../editors/transform/transform_snap.hh | 3 +- .../transform/transform_snap_sequencer.cc | 403 +++++++++++++++--- source/blender/makesdna/DNA_scene_types.h | 5 + source/blender/makesrna/intern/rna_scene.cc | 18 +- source/blender/sequencer/intern/sequencer.cc | 4 +- 12 files changed, 467 insertions(+), 83 deletions(-) diff --git a/scripts/startup/bl_ui/space_sequencer.py b/scripts/startup/bl_ui/space_sequencer.py index 4ff3f0c225f..db1d9a662be 100644 --- a/scripts/startup/bl_ui/space_sequencer.py +++ b/scripts/startup/bl_ui/space_sequencer.py @@ -174,18 +174,16 @@ class SEQUENCER_HT_header(Header): if st.view_type == 'PREVIEW': layout.prop(sequencer_tool_settings, "pivot_point", text="", icon_only=True) - layout.separator_spacer() if st.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}: row = layout.row(align=True) row.prop(sequencer_tool_settings, "overlap_mode", text="") - if st.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}: - row = layout.row(align=True) - row.prop(tool_settings, "use_snap_sequencer", text="") - sub = row.row(align=True) - sub.popover(panel="SEQUENCER_PT_snapping") - layout.separator_spacer() + row = layout.row(align=True) + row.prop(tool_settings, "use_snap_sequencer", text="") + sub = row.row(align=True) + sub.popover(panel="SEQUENCER_PT_snapping") + layout.separator_spacer() if st.view_type in {'PREVIEW', 'SEQUENCER_PREVIEW'}: layout.prop(st, "display_mode", text="", icon_only=True) @@ -2876,6 +2874,46 @@ class SEQUENCER_PT_snapping(Panel): bl_region_type = 'HEADER' 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): tool_settings = context.tool_settings sequencer_tool_settings = tool_settings.sequencer_tool_settings @@ -2986,6 +3024,8 @@ classes = ( SEQUENCER_PT_annotation_onion, SEQUENCER_PT_snapping, + SEQUENCER_PT_preview_snapping, + SEQUENCER_PT_sequencer_snapping, ) if __name__ == "__main__": # only for live edit. diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 8d0d747883e..fce220cf57d 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -29,7 +29,7 @@ extern "C" { /* Blender file format 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 * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index d284e116b42..42e679a2fc0 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -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 * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. diff --git a/source/blender/editors/transform/transform.hh b/source/blender/editors/transform/transform.hh index 404acd4dd04..8a0ab0cec05 100644 --- a/source/blender/editors/transform/transform.hh +++ b/source/blender/editors/transform/transform.hh @@ -179,6 +179,14 @@ enum eTSnap { }; 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 */ enum eTConstraint { /** When set constraints are in use. */ @@ -311,6 +319,8 @@ struct TransSnap { /* Snapped Element Type (currently for objects only). */ eSnapMode source_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). */ float snap_source[3]; /** To this point (in global-space). */ diff --git a/source/blender/editors/transform/transform_mode_edge_seq_slide.cc b/source/blender/editors/transform/transform_mode_edge_seq_slide.cc index d765507149b..31bfecffbc0 100644 --- a/source/blender/editors/transform/transform_mode_edge_seq_slide.cc +++ b/source/blender/editors/transform/transform_mode_edge_seq_slide.cc @@ -156,6 +156,6 @@ TransModeInfo TransMode_seqslide = { /*transform_matrix_fn*/ nullptr, /*handle_event_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, }; diff --git a/source/blender/editors/transform/transform_mode_translate.cc b/source/blender/editors/transform/transform_mode_translate.cc index 04352af18cc..f95ef58c622 100644 --- a/source/blender/editors/transform/transform_mode_translate.cc +++ b/source/blender/editors/transform/transform_mode_translate.cc @@ -358,7 +358,12 @@ static void ApplySnapTranslation(TransInfo *t, float vec[3]) } } 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 { if (t->spacetype == SPACE_VIEW3D) { diff --git a/source/blender/editors/transform/transform_snap.cc b/source/blender/editors/transform/transform_snap.cc index ff251c4b5d9..06c5dcbf336 100644 --- a/source/blender/editors/transform/transform_snap.cc +++ b/source/blender/editors/transform/transform_snap.cc @@ -167,6 +167,10 @@ bool transformModeUseSnap(const TransInfo *t) } ToolSettings *ts = t->settings; 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; } if (t->mode == TFM_ROTATION) { @@ -357,11 +361,31 @@ void drawSnapping(TransInfo *t) immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformColor4ubv(col); float pixelx = BLI_rctf_size_x(®ion->v2d.cur) / BLI_rcti_size_x(®ion->v2d.mask); - immRectf(pos, - t->tsnap.snap_target[0] - pixelx, - region->v2d.cur.ymax, - t->tsnap.snap_target[0] + pixelx, - region->v2d.cur.ymin); + + if (region->regiontype == RGN_TYPE_PREVIEW) { + if (t->tsnap.direction & DIR_GLOBAL_X) { + immRectf(pos, + 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(); GPU_blend(GPU_BLEND_NONE); } diff --git a/source/blender/editors/transform/transform_snap.hh b/source/blender/editors/transform/transform_snap.hh index bcfc6c1498c..571565f58f5 100644 --- a/source/blender/editors/transform/transform_snap.hh +++ b/source/blender/editors/transform/transform_snap.hh @@ -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); void transform_snap_sequencer_data_free(TransSeqSnapData *data); 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` */ void snapFrameTransform( diff --git a/source/blender/editors/transform/transform_snap_sequencer.cc b/source/blender/editors/transform/transform_snap_sequencer.cc index 2c91dc986b9..05618d195ef 100644 --- a/source/blender/editors/transform/transform_snap_sequencer.cc +++ b/source/blender/editors/transform/transform_snap_sequencer.cc @@ -24,14 +24,15 @@ #include "SEQ_render.hh" #include "SEQ_sequencer.hh" #include "SEQ_time.hh" +#include "SEQ_transform.hh" #include "transform.hh" #include "transform_convert.hh" #include "transform_snap.hh" struct TransSeqSnapData { - blender::Array source_snap_points; - blender::Array target_snap_points; + blender::Array source_snap_points; + blender::Array target_snap_points; #ifdef WITH_CXX_GUARDEDALLOC MEM_CXX_CLASS_ALLOC_FUNCS("TransSeqSnapData") @@ -42,21 +43,51 @@ struct TransSeqSnapData { /** \name Snap sources * \{ */ -static int seq_get_snap_source_points_len(blender::Span snap_sources) +static blender::VectorSet query_snap_sources_timeline(const Scene *scene) +{ + blender::VectorSet 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 query_snap_sources_preview(const Scene *scene) +{ + blender::VectorSet 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 snap_sources) { 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 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, - TransSeqSnapData *snap_data, - blender::Span snap_sources) +static int cmp_fn(const void *a, const void *b) { - 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 snap_sources) +{ + + const size_t point_count_source = seq_get_snap_source_points_count_timeline(snap_sources); if (point_count_source == 0) { 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); } - snap_data->source_snap_points[i] = left; - snap_data->source_snap_points[i + 1] = right; + /* Set only the x-positions when snapping in the timeline. */ + snap_data->source_snap_points[i][0] = left; + snap_data->source_snap_points[i + 1][0] = 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), + sizeof(blender::float2), cmp_fn); return true; } +static bool seq_snap_source_points_build_preview(const Scene *scene, + TransSeqSnapData *snap_data, + const blender::Span 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 query_snap_targets(Scene *scene, - blender::Span snap_sources, - bool exclude_selected) +static blender::VectorSet query_snap_targets_timeline( + Scene *scene, const blender::Span snap_sources, const 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. */ + /* 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( @@ -134,7 +200,7 @@ static blender::VectorSet query_snap_targets(Scene *scene, blender::VectorSet snap_targets; LISTBASE_FOREACH (Sequence *, seq, seqbase) { 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)) { continue; @@ -152,11 +218,34 @@ static blender::VectorSet query_snap_targets(Scene *scene, return snap_targets; } -static int seq_get_snap_target_points_count(const Scene *scene, - short snap_mode, - blender::Span snap_targets) +static blender::VectorSet query_snap_targets_preview(Scene *scene, + const short snap_mode) { - int count = 2; /* Strip start and end are always used. */ + blender::VectorSet 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 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) { count += 2; @@ -175,12 +264,35 @@ static int seq_get_snap_target_points_count(const Scene *scene, return count; } -static bool seq_snap_target_points_build(Scene *scene, - short snap_mode, - TransSeqSnapData *snap_data, - blender::Span snap_targets) +static int seq_get_snap_target_points_count_preview(const short snap_mode, + const blender::Span 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 snap_targets) +{ + + const size_t point_count_target = seq_get_snap_target_points_count_timeline( scene, snap_mode, snap_targets); if (point_count_target == 0) { return false; @@ -190,20 +302,20 @@ static bool seq_snap_target_points_build(Scene *scene, int i = 0; 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++; } if (snap_mode & SEQ_SNAP_TO_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++; } } 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); + snap_data->target_snap_points[i][0] = SEQ_time_left_handle_frame_get(scene, seq); + snap_data->target_snap_points[i + 1][0] = SEQ_time_right_handle_frame_get(scene, seq); i += 2; 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_right_handle_frame_get(scene, seq)); - snap_data->target_snap_points[i] = content_start; - snap_data->target_snap_points[i + 1] = content_end; + snap_data->target_snap_points[i][0] = content_start; + snap_data->target_snap_points[i + 1][0] = 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), + sizeof(blender::float2), cmp_fn); 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 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 * \{ */ -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 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)); + return UI_view2d_region_to_view_x(v2d, snap_distance) - 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(__func__); Scene *scene = t->scene; - ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(scene)); short snap_mode = t->tsnap.mode; - blender::VectorSet snap_sources = SEQ_query_selected_strips(seqbase); - blender::VectorSet snap_targets = query_snap_targets(scene, snap_sources, true); + blender::VectorSet snap_sources = query_snap_sources_timeline(scene); + blender::VectorSet snap_targets = query_snap_targets_timeline( + 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)) + if (!seq_snap_source_points_build_timeline(scene, snap_data, snap_sources) || + !seq_snap_target_points_build_timeline(scene, snap_mode, snap_data, snap_targets)) { MEM_delete(snap_data); return nullptr; @@ -278,18 +447,49 @@ TransSeqSnapData *transform_snap_sequencer_data_alloc(const TransInfo *t) return snap_data; } +static TransSeqSnapData *transform_snap_sequencer_data_alloc_preview(const TransInfo *t) +{ + TransSeqSnapData *snap_data = MEM_new(__func__); + + Scene *scene = t->scene; + short snap_mode = t->tsnap.mode; + View2D *v2d = &t->region->v2d; + + blender::VectorSet snap_sources = query_snap_sources_preview(scene); + blender::VectorSet 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) { 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. */ if (t->con.mode & CON_APPLY && t->con.mode & CON_AXIS1) { 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; - for (int snap_source_frame : snap_data->source_snap_points) { - for (int snap_target_frame : snap_data->target_snap_points) { + for (const float *snap_source_point : snap_data->source_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]))); if (dist > best_dist) { continue; @@ -319,28 +521,99 @@ bool transform_snap_sequencer_calc(TransInfo *t) 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::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]; } -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; TransSeqSnapData *snap_data = MEM_new(__func__); blender::VectorSet empty_col; - blender::VectorSet snap_targets = query_snap_targets(scene, empty_col, false); + blender::VectorSet snap_targets = query_snap_targets_timeline( + 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; + snap_data->source_snap_points[0][0] = frame_1; + snap_data->source_snap_points[1][0] = 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); + /* Build arrays of snap target frames. */ + seq_snap_target_points_build_timeline(scene, snap_mode, snap_data, snap_targets); t->tsnap.seq_context = snap_data; 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; if (snap_success) { 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 { 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, ARegion *region, - int frame_1, - int frame_2, + const int frame_1, + const int frame_2, int *r_snap_distance, float *r_snap_frame) { diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index baa958be679..150b2beeb4e 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -2436,6 +2436,11 @@ enum { SEQ_SNAP_TO_CURRENT_FRAME = 1 << 1, SEQ_SNAP_TO_STRIP_HOLD = 1 << 2, 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 */ diff --git a/source/blender/makesrna/intern/rna_scene.cc b/source/blender/makesrna/intern/rna_scene.cc index a866ce761a6..c4891ade37b 100644 --- a/source/blender/makesrna/intern/rna_scene.cc +++ b/source/blender/makesrna/intern/rna_scene.cc @@ -3534,7 +3534,7 @@ static void rna_def_tool_settings(BlenderRNA *brna) 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_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_boolean_default(prop, true); 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_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); 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"); diff --git a/source/blender/sequencer/intern/sequencer.cc b/source/blender/sequencer/intern/sequencer.cc index 7a86573be2f..d46defa85bc 100644 --- a/source/blender/sequencer/intern/sequencer.cc +++ b/source/blender/sequencer/intern/sequencer.cc @@ -332,7 +332,9 @@ SequencerToolSettings *SEQ_tool_settings_init() MEM_callocN(sizeof(SequencerToolSettings), "Sequencer tool settings")); tool_settings->fit_method = SEQ_SCALE_TO_FIT; 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->overlap_mode = SEQ_OVERLAP_SHUFFLE; tool_settings->pivot_point = V3D_AROUND_LOCAL_ORIGINS;