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;