Anim: Common Playhead snapping for all editors
This patch adds snapping options for the playhead to all animation editors. The options can be modified through a new dropdown in the editor header. All editors will show all those options, and they are shared, so toggling the option in on editor will change it for all other editors too. Some options are not working/relevant in some editors for example Strips in the Dope Sheet. However for consistency the option is still shown. This is a separate menu from the transform snapping menu because you can toggle the snapping for transform and playhead separately. Putting it in the existing snapping transform menu would imply that it can be turned off with the magnet which is not the case. Playhead snapping is explicitly disabled for the drivers editor because there is no playhead to drag around. Snapping to Frame/Second intervals takes the scene start as a starting point. That means you can snap to the n-th second of the animation even though it might not start at frame 1. The preview range is NOT taken into account by design since the use case is working on a sub-section of the animation in which case the snap target should not change. Snapping is toggled by pressing CTRL as indicated by the status bar. Snapping to Frames/Seconds is absolute, meaning no matter how far away your cursor it will snap to the closest snap point. All others only snap to things if they are close to the cursor in pixel values. When mixing those two behaviors, it prefers relative snapping. If no point is close enough to snap relative, it will fall back to absolute snapping. Based on the prototype #135913 Part of #135794 Pull Request: https://projects.blender.org/blender/blender/pulls/137278
This commit is contained in:
committed by
Christoph Lendenfeld
parent
b734eae387
commit
87a28fa117
@@ -16,6 +16,8 @@ from bl_ui.properties_data_grease_pencil import (
|
||||
GreasePencil_LayerAdjustmentsPanel,
|
||||
GreasePencil_LayerDisplayPanel,
|
||||
)
|
||||
from bl_ui.space_toolsystem_common import PlayheadSnappingPanel
|
||||
|
||||
|
||||
from rna_prop_ui import PropertyPanel
|
||||
|
||||
@@ -216,6 +218,10 @@ class DOPESHEET_HT_header(Header):
|
||||
DOPESHEET_HT_editor_buttons.draw_header(context, layout)
|
||||
|
||||
|
||||
class DOPESHEET_PT_playhead_snapping(PlayheadSnappingPanel, Panel):
|
||||
bl_space_type = 'DOPESHEET_EDITOR'
|
||||
|
||||
|
||||
# Header for "normal" dopesheet editor modes (e.g. Dope Sheet, Action, Shape Keys, etc.)
|
||||
class DOPESHEET_HT_editor_buttons:
|
||||
|
||||
@@ -277,6 +283,8 @@ class DOPESHEET_HT_editor_buttons:
|
||||
text="",
|
||||
)
|
||||
|
||||
layout.popover(panel="DOPESHEET_PT_playhead_snapping")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(tool_settings, "use_proportional_action", text="", icon_only=True)
|
||||
sub = row.row(align=True)
|
||||
@@ -1004,6 +1012,7 @@ classes = (
|
||||
DOPESHEET_PT_grease_pencil_layer_adjustments,
|
||||
DOPESHEET_PT_grease_pencil_layer_relations,
|
||||
DOPESHEET_PT_grease_pencil_layer_display,
|
||||
DOPESHEET_PT_playhead_snapping,
|
||||
)
|
||||
|
||||
if __name__ == "__main__": # only for live edit.
|
||||
|
||||
@@ -9,6 +9,12 @@ from bl_ui.space_dopesheet import (
|
||||
dopesheet_filter,
|
||||
)
|
||||
|
||||
from bl_ui.space_toolsystem_common import PlayheadSnappingPanel
|
||||
|
||||
|
||||
class GRAPH_PT_playhead_snapping(PlayheadSnappingPanel, Panel):
|
||||
bl_space_type = 'GRAPH_EDITOR'
|
||||
|
||||
|
||||
class GRAPH_HT_header(Header):
|
||||
bl_space_type = 'GRAPH_EDITOR'
|
||||
@@ -65,6 +71,7 @@ class GRAPH_HT_header(Header):
|
||||
panel="GRAPH_PT_snapping",
|
||||
text="",
|
||||
)
|
||||
layout.popover(panel="GRAPH_PT_playhead_snapping")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(tool_settings, "use_proportional_fcurve", text="", icon_only=True)
|
||||
@@ -561,6 +568,7 @@ classes = (
|
||||
GRAPH_PT_filters,
|
||||
GRAPH_PT_snapping,
|
||||
GRAPH_PT_driver_snapping,
|
||||
GRAPH_PT_playhead_snapping,
|
||||
)
|
||||
|
||||
if __name__ == "__main__": # only for live edit.
|
||||
|
||||
@@ -10,6 +10,12 @@ from bl_ui.space_dopesheet import (
|
||||
dopesheet_filter,
|
||||
)
|
||||
|
||||
from bl_ui.space_toolsystem_common import PlayheadSnappingPanel
|
||||
|
||||
|
||||
class NLA_PT_playhead_snapping(PlayheadSnappingPanel, Panel):
|
||||
bl_space_type = 'NLA_EDITOR'
|
||||
|
||||
|
||||
class NLA_HT_header(Header):
|
||||
bl_space_type = 'NLA_EDITOR'
|
||||
@@ -39,6 +45,7 @@ class NLA_HT_header(Header):
|
||||
panel="NLA_PT_snapping",
|
||||
text="",
|
||||
)
|
||||
layout.popover(panel="NLA_PT_playhead_snapping")
|
||||
|
||||
|
||||
class NLA_PT_snapping(Panel):
|
||||
@@ -408,6 +415,7 @@ classes = (
|
||||
NLA_PT_filters,
|
||||
NLA_PT_action,
|
||||
NLA_PT_snapping,
|
||||
NLA_PT_playhead_snapping,
|
||||
)
|
||||
|
||||
if __name__ == "__main__": # only for live edit.
|
||||
|
||||
@@ -18,6 +18,7 @@ from bl_ui.properties_grease_pencil_common import (
|
||||
)
|
||||
from bl_ui.space_toolsystem_common import (
|
||||
ToolActivePanelHelper,
|
||||
PlayheadSnappingPanel,
|
||||
)
|
||||
from rna_prop_ui import PropertyPanel
|
||||
|
||||
@@ -185,6 +186,7 @@ class SEQUENCER_HT_header(Header):
|
||||
row.prop(tool_settings, "use_snap_sequencer", text="")
|
||||
sub = row.row(align=True)
|
||||
sub.popover(panel="SEQUENCER_PT_snapping")
|
||||
layout.popover(panel="SEQUENCER_PT_playhead_snapping")
|
||||
layout.separator_spacer()
|
||||
|
||||
if st.view_type in {'PREVIEW', 'SEQUENCER_PREVIEW'}:
|
||||
@@ -3028,6 +3030,10 @@ class SEQUENCER_PT_custom_props(SequencerButtonsPanel, PropertyPanel, Panel):
|
||||
bl_category = "Strip"
|
||||
|
||||
|
||||
class SEQUENCER_PT_playhead_snapping(PlayheadSnappingPanel, Panel):
|
||||
bl_space_type = 'SEQUENCE_EDITOR'
|
||||
|
||||
|
||||
class SEQUENCER_PT_snapping(Panel):
|
||||
bl_space_type = 'SEQUENCE_EDITOR'
|
||||
bl_region_type = 'HEADER'
|
||||
@@ -3093,9 +3099,6 @@ class SEQUENCER_PT_sequencer_snapping(Panel):
|
||||
col.prop(sequencer_tool_settings, "snap_ignore_muted", text="Muted Strips")
|
||||
col.prop(sequencer_tool_settings, "snap_ignore_sound", text="Sound Strips")
|
||||
|
||||
col = layout.column(heading="Current Frame", align=True)
|
||||
col.prop(sequencer_tool_settings, "use_snap_current_frame_to_strips", text="Snap to Strips")
|
||||
|
||||
|
||||
classes = (
|
||||
SEQUENCER_MT_change,
|
||||
@@ -3192,6 +3195,7 @@ classes = (
|
||||
SEQUENCER_PT_snapping,
|
||||
SEQUENCER_PT_preview_snapping,
|
||||
SEQUENCER_PT_sequencer_snapping,
|
||||
SEQUENCER_PT_playhead_snapping,
|
||||
)
|
||||
|
||||
if __name__ == "__main__": # only for live edit.
|
||||
|
||||
@@ -1239,6 +1239,32 @@ def _keymap_from_item(context, item):
|
||||
return None
|
||||
|
||||
|
||||
class PlayheadSnappingPanel:
|
||||
bl_region_type = 'HEADER'
|
||||
bl_label = "Playhead"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
tool_settings = context.tool_settings
|
||||
layout = self.layout
|
||||
col = layout.column()
|
||||
|
||||
col.prop(tool_settings, "use_snap_playhead")
|
||||
col.prop(tool_settings, "playhead_snap_distance")
|
||||
col.separator()
|
||||
col.label(text="Snap Target")
|
||||
col.prop(tool_settings, "snap_playhead_element", expand=True)
|
||||
col.separator()
|
||||
|
||||
if 'FRAME' in tool_settings.snap_playhead_element:
|
||||
col.prop(tool_settings, "snap_playhead_frame_step")
|
||||
if 'SECOND' in tool_settings.snap_playhead_element:
|
||||
col.prop(tool_settings, "snap_playhead_second_step")
|
||||
|
||||
|
||||
classes = (
|
||||
WM_MT_toolsystem_submenu,
|
||||
)
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
/* Blender file format version. */
|
||||
#define BLENDER_FILE_VERSION BLENDER_VERSION
|
||||
#define BLENDER_FILE_SUBVERSION 75
|
||||
#define BLENDER_FILE_SUBVERSION 76
|
||||
|
||||
/* 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
|
||||
|
||||
@@ -4410,6 +4410,16 @@ void do_versions_after_linking_450(FileData * /*fd*/, Main *bmain)
|
||||
FOREACH_NODETREE_END;
|
||||
}
|
||||
|
||||
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 76)) {
|
||||
ToolSettings toolsettings_default = *DNA_struct_default_get(ToolSettings);
|
||||
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
|
||||
scene->toolsettings->snap_playhead_mode = toolsettings_default.snap_playhead_mode;
|
||||
scene->toolsettings->snap_step_frames = toolsettings_default.snap_step_frames;
|
||||
scene->toolsettings->snap_step_seconds = toolsettings_default.snap_step_seconds;
|
||||
scene->toolsettings->playhead_snap_distance = toolsettings_default.playhead_snap_distance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Always bump subversion in BKE_blender_version.h when adding versioning
|
||||
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
set(INC
|
||||
../asset
|
||||
../include
|
||||
../space_graph
|
||||
../../asset_system
|
||||
../../makesrna
|
||||
../../../../extern/fmtlib/include
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "UI_resources.hh"
|
||||
#include "UI_view2d.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
@@ -36,14 +37,18 @@
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include "ED_anim_api.hh"
|
||||
#include "ED_keyframes_keylist.hh"
|
||||
#include "ED_markers.hh"
|
||||
#include "ED_screen.hh"
|
||||
#include "ED_sequencer.hh"
|
||||
#include "ED_space_graph.hh"
|
||||
#include "ED_time_scrub_ui.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_build.hh"
|
||||
|
||||
#include "SEQ_iterator.hh"
|
||||
#include "SEQ_retiming.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_time.hh"
|
||||
|
||||
@@ -56,6 +61,34 @@
|
||||
/** \name Frame Change Operator
|
||||
* \{ */
|
||||
|
||||
/* Persistent data to re-use during frame change modal operations. */
|
||||
class FrameChangeModalData {
|
||||
/* Used for keyframe snapping. Is populated when needed and re-used so it doesn't have to be
|
||||
* created on every modal call. */
|
||||
public:
|
||||
AnimKeylist *keylist;
|
||||
|
||||
FrameChangeModalData()
|
||||
{
|
||||
keylist = nullptr;
|
||||
}
|
||||
|
||||
~FrameChangeModalData()
|
||||
{
|
||||
if (keylist) {
|
||||
ED_keylist_free(keylist);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* Point the playhead can snap to. */
|
||||
struct SnapTarget {
|
||||
float pos;
|
||||
/* If true, only snap if close to the point in screenspace. If false snap to this point
|
||||
* regardless of screen space distance. */
|
||||
bool use_snap_treshold;
|
||||
};
|
||||
|
||||
/* Check if the operator can be run from the current context */
|
||||
static bool change_frame_poll(bContext *C)
|
||||
{
|
||||
@@ -94,18 +127,146 @@ static bool change_frame_poll(bContext *C)
|
||||
return false;
|
||||
}
|
||||
|
||||
static int seq_snap_threshold_get_frame_distance(bContext *C)
|
||||
/* Returns the playhead snap threshold in frames. Of course that depends on the zoom level of the
|
||||
* editor. */
|
||||
static float get_snap_threshold(const ToolSettings *tool_settings, const ARegion *region)
|
||||
{
|
||||
const int snap_distance = blender::seq::tool_settings_snap_distance_get(CTX_data_scene(C));
|
||||
const ARegion *region = CTX_wm_region(C);
|
||||
return round_fl_to_int(UI_view2d_region_to_view_x(®ion->v2d, snap_distance) -
|
||||
UI_view2d_region_to_view_x(®ion->v2d, 0));
|
||||
const int snap_threshold = tool_settings->playhead_snap_distance;
|
||||
return UI_view2d_region_to_view_x(®ion->v2d, snap_threshold) -
|
||||
UI_view2d_region_to_view_x(®ion->v2d, 0);
|
||||
}
|
||||
|
||||
static void seq_frame_snap_update_best(const int position,
|
||||
const int timeline_frame,
|
||||
int *r_best_frame,
|
||||
int *r_best_distance)
|
||||
static void ensure_change_frame_keylist(bContext *C, FrameChangeModalData &op_data)
|
||||
{
|
||||
/* Only populate data once. */
|
||||
if (op_data.keylist != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ScrArea *area = CTX_wm_area(C);
|
||||
|
||||
if (area->spacetype == SPACE_SEQ) {
|
||||
/* Special case for the sequencer since it has retiming keys, but those have no bAnimListElem
|
||||
* representation. Need to manually add entries to keylist. */
|
||||
op_data.keylist = ED_keylist_create();
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
|
||||
ListBase *seqbase = blender::seq::active_seqbase_get(blender::seq::editing_get(scene));
|
||||
LISTBASE_FOREACH (Strip *, strip, seqbase) {
|
||||
sequencer_strip_to_keylist(*strip, *op_data.keylist, *scene);
|
||||
}
|
||||
ED_keylist_prepare_for_direct_access(op_data.keylist);
|
||||
return;
|
||||
}
|
||||
|
||||
bAnimContext ac;
|
||||
if (!ANIM_animdata_get_context(C, &ac)) {
|
||||
BLI_assert_unreachable();
|
||||
return;
|
||||
}
|
||||
|
||||
ListBase anim_data = {nullptr, nullptr};
|
||||
|
||||
switch (area->spacetype) {
|
||||
case SPACE_ACTION: {
|
||||
const eAnimFilter_Flags filter = ANIMFILTER_DATA_VISIBLE;
|
||||
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
|
||||
break;
|
||||
}
|
||||
|
||||
case SPACE_GRAPH:
|
||||
anim_data = blender::ed::graph::get_editable_fcurves(ac);
|
||||
break;
|
||||
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
|
||||
op_data.keylist = ED_keylist_create();
|
||||
|
||||
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
||||
switch (ale->datatype) {
|
||||
case ALE_FCURVE: {
|
||||
FCurve *fcurve = static_cast<FCurve *>(ale->data);
|
||||
fcurve_to_keylist(ale->adt, fcurve, op_data.keylist, 0, {-FLT_MAX, FLT_MAX}, true);
|
||||
break;
|
||||
}
|
||||
|
||||
case ALE_GPFRAME: {
|
||||
gpl_to_keylist(nullptr, static_cast<bGPDlayer *>(ale->data), op_data.keylist);
|
||||
break;
|
||||
}
|
||||
|
||||
case ALE_GREASE_PENCIL_CEL: {
|
||||
grease_pencil_cels_to_keylist(
|
||||
ale->adt, static_cast<const GreasePencilLayer *>(ale->data), op_data.keylist, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
ANIM_animdata_freelist(&anim_data);
|
||||
|
||||
ED_keylist_prepare_for_direct_access(op_data.keylist);
|
||||
}
|
||||
|
||||
static void append_keyframe_snap_target(bContext *C,
|
||||
FrameChangeModalData &op_data,
|
||||
const float timeline_frame,
|
||||
blender::Vector<SnapTarget> &r_targets)
|
||||
{
|
||||
ensure_change_frame_keylist(C, op_data);
|
||||
const ActKeyColumn *closest_column = ED_keylist_find_closest(op_data.keylist, timeline_frame);
|
||||
if (!closest_column) {
|
||||
return;
|
||||
}
|
||||
r_targets.append({closest_column->cfra, true});
|
||||
}
|
||||
|
||||
static void append_marker_snap_target(Scene *scene,
|
||||
const float timeline_frame,
|
||||
blender::Vector<SnapTarget> &r_targets)
|
||||
{
|
||||
if (BLI_listbase_is_empty(&scene->markers)) {
|
||||
/* This check needs to be here because `ED_markers_find_nearest_marker_time` returns the
|
||||
* current frame if there are no markers. */
|
||||
return;
|
||||
}
|
||||
const float nearest_marker = ED_markers_find_nearest_marker_time(&scene->markers,
|
||||
timeline_frame);
|
||||
r_targets.append({nearest_marker, true});
|
||||
}
|
||||
|
||||
static void append_second_snap_target(Scene *scene,
|
||||
const float timeline_frame,
|
||||
const int step,
|
||||
blender::Vector<SnapTarget> &r_targets)
|
||||
{
|
||||
const int start_frame = scene->r.sfra;
|
||||
const float snap_frame = BKE_scene_frame_snap_by_seconds(
|
||||
scene, step, timeline_frame - start_frame) +
|
||||
start_frame;
|
||||
r_targets.append({snap_frame, false});
|
||||
}
|
||||
|
||||
static void append_frame_snap_target(const Scene *scene,
|
||||
const float timeline_frame,
|
||||
const int step,
|
||||
blender::Vector<SnapTarget> &r_targets)
|
||||
{
|
||||
const int start_frame = scene->r.sfra;
|
||||
const float snap_frame = (round((timeline_frame - start_frame) / float(step)) * step) +
|
||||
start_frame;
|
||||
r_targets.append({snap_frame, false});
|
||||
}
|
||||
|
||||
static void seq_frame_snap_update_best(const float position,
|
||||
const float timeline_frame,
|
||||
float *r_best_frame,
|
||||
float *r_best_distance)
|
||||
{
|
||||
if (abs(position - timeline_frame) < *r_best_distance) {
|
||||
*r_best_distance = abs(position - timeline_frame);
|
||||
@@ -113,14 +274,15 @@ static void seq_frame_snap_update_best(const int position,
|
||||
}
|
||||
}
|
||||
|
||||
static int seq_frame_apply_snap(bContext *C, Scene *scene, const int timeline_frame)
|
||||
static void append_sequencer_strip_snap_target(blender::Span<Strip *> strips,
|
||||
const Scene *scene,
|
||||
const float timeline_frame,
|
||||
blender::Vector<SnapTarget> &r_targets)
|
||||
{
|
||||
float best_frame = FLT_MAX;
|
||||
float best_distance = FLT_MAX;
|
||||
|
||||
ListBase *seqbase = blender::seq::active_seqbase_get(blender::seq::editing_get(scene));
|
||||
|
||||
int best_frame = 0;
|
||||
int best_distance = MAXFRAME;
|
||||
for (Strip *strip : blender::seq::query_all_strips(seqbase)) {
|
||||
for (Strip *strip : strips) {
|
||||
seq_frame_snap_update_best(blender::seq::time_left_handle_frame_get(scene, strip),
|
||||
timeline_frame,
|
||||
&best_frame,
|
||||
@@ -131,11 +293,230 @@ static int seq_frame_apply_snap(bContext *C, Scene *scene, const int timeline_fr
|
||||
&best_distance);
|
||||
}
|
||||
|
||||
if (best_distance < seq_snap_threshold_get_frame_distance(C)) {
|
||||
return best_frame;
|
||||
/* best_frame will be FLT_MAX if no target was found. */
|
||||
if (best_distance != FLT_MAX) {
|
||||
r_targets.append({best_frame, true});
|
||||
}
|
||||
}
|
||||
|
||||
static void append_nla_strip_snap_target(bContext *C,
|
||||
const float timeline_frame,
|
||||
blender::Vector<SnapTarget> &r_targets)
|
||||
{
|
||||
|
||||
bAnimContext ac;
|
||||
if (!ANIM_animdata_get_context(C, &ac)) {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
return timeline_frame;
|
||||
ListBase anim_data = {nullptr, nullptr};
|
||||
eAnimFilter_Flags filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE |
|
||||
ANIMFILTER_LIST_CHANNELS | ANIMFILTER_FCURVESONLY);
|
||||
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, eAnimCont_Types(ac.datatype));
|
||||
|
||||
float best_frame = FLT_MAX;
|
||||
float best_distance = FLT_MAX;
|
||||
|
||||
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
||||
if (ale->type != ANIMTYPE_NLATRACK) {
|
||||
continue;
|
||||
}
|
||||
NlaTrack *track = static_cast<NlaTrack *>(ale->data);
|
||||
LISTBASE_FOREACH (NlaStrip *, strip, &track->strips) {
|
||||
if (abs(strip->start - timeline_frame) < best_distance) {
|
||||
best_distance = abs(strip->start - timeline_frame);
|
||||
best_frame = strip->start;
|
||||
}
|
||||
if (abs(strip->end - timeline_frame) < best_distance) {
|
||||
best_distance = abs(strip->end - timeline_frame);
|
||||
best_frame = strip->end;
|
||||
}
|
||||
}
|
||||
}
|
||||
ANIM_animdata_freelist(&anim_data);
|
||||
|
||||
/* If no strip was found, best_frame will be FLT_MAX. */
|
||||
if (best_frame != FLT_MAX) {
|
||||
r_targets.append({best_frame, true});
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- */
|
||||
|
||||
static blender::Vector<SnapTarget> seq_get_snap_targets(bContext *C,
|
||||
FrameChangeModalData &op_data,
|
||||
const float timeline_frame)
|
||||
{
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ToolSettings *tool_settings = scene->toolsettings;
|
||||
|
||||
blender::Vector<SnapTarget> targets;
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_STRIPS) {
|
||||
ListBase *seqbase = blender::seq::active_seqbase_get(blender::seq::editing_get(scene));
|
||||
append_sequencer_strip_snap_target(
|
||||
blender::seq::query_all_strips(seqbase), scene, timeline_frame, targets);
|
||||
}
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_MARKERS) {
|
||||
append_marker_snap_target(scene, timeline_frame, targets);
|
||||
}
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_KEYS) {
|
||||
append_keyframe_snap_target(C, op_data, timeline_frame, targets);
|
||||
}
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_SECOND) {
|
||||
append_second_snap_target(scene, timeline_frame, tool_settings->snap_step_seconds, targets);
|
||||
}
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_FRAME) {
|
||||
append_frame_snap_target(scene, timeline_frame, tool_settings->snap_step_frames, targets);
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
static blender::Vector<SnapTarget> nla_get_snap_targets(bContext *C, const float timeline_frame)
|
||||
{
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ToolSettings *tool_settings = scene->toolsettings;
|
||||
|
||||
blender::Vector<SnapTarget> targets;
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_STRIPS) {
|
||||
append_nla_strip_snap_target(C, timeline_frame, targets);
|
||||
}
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_MARKERS) {
|
||||
append_marker_snap_target(scene, timeline_frame, targets);
|
||||
}
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_SECOND) {
|
||||
append_second_snap_target(scene, timeline_frame, tool_settings->snap_step_seconds, targets);
|
||||
}
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_FRAME) {
|
||||
append_frame_snap_target(scene, timeline_frame, tool_settings->snap_step_frames, targets);
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
static blender::Vector<SnapTarget> action_get_snap_targets(bContext *C,
|
||||
FrameChangeModalData &op_data,
|
||||
const float timeline_frame)
|
||||
{
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ToolSettings *tool_settings = scene->toolsettings;
|
||||
|
||||
blender::Vector<SnapTarget> targets;
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_MARKERS) {
|
||||
append_marker_snap_target(scene, timeline_frame, targets);
|
||||
}
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_KEYS) {
|
||||
append_keyframe_snap_target(C, op_data, timeline_frame, targets);
|
||||
}
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_SECOND) {
|
||||
append_second_snap_target(scene, timeline_frame, tool_settings->snap_step_seconds, targets);
|
||||
}
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_FRAME) {
|
||||
append_frame_snap_target(scene, timeline_frame, tool_settings->snap_step_frames, targets);
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
static blender::Vector<SnapTarget> graph_get_snap_targets(bContext *C,
|
||||
FrameChangeModalData &op_data,
|
||||
const float timeline_frame)
|
||||
{
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ToolSettings *tool_settings = scene->toolsettings;
|
||||
|
||||
blender::Vector<SnapTarget> targets;
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_MARKERS) {
|
||||
append_marker_snap_target(scene, timeline_frame, targets);
|
||||
}
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_KEYS) {
|
||||
append_keyframe_snap_target(C, op_data, timeline_frame, targets);
|
||||
}
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_SECOND) {
|
||||
append_second_snap_target(scene, timeline_frame, tool_settings->snap_step_seconds, targets);
|
||||
}
|
||||
|
||||
if (tool_settings->snap_playhead_mode & SCE_SNAP_TO_FRAME) {
|
||||
append_frame_snap_target(scene, timeline_frame, tool_settings->snap_step_frames, targets);
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
/* ---- */
|
||||
|
||||
/* Returns a frame that is snapped to the closest point of interest defined by the area. If no
|
||||
* point of interest is nearby, the frame is returned unmodified. */
|
||||
static float apply_frame_snap(bContext *C, FrameChangeModalData &op_data, const float frame)
|
||||
{
|
||||
ScrArea *area = CTX_wm_area(C);
|
||||
|
||||
blender::Vector<SnapTarget> targets;
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
switch (area->spacetype) {
|
||||
case SPACE_SEQ:
|
||||
targets = seq_get_snap_targets(C, op_data, frame);
|
||||
break;
|
||||
case SPACE_ACTION:
|
||||
targets = action_get_snap_targets(C, op_data, frame);
|
||||
break;
|
||||
case SPACE_GRAPH:
|
||||
targets = graph_get_snap_targets(C, op_data, frame);
|
||||
break;
|
||||
case SPACE_NLA:
|
||||
targets = nla_get_snap_targets(C, frame);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
float snap_frame = FLT_MAX;
|
||||
|
||||
/* Find closest frame of all targets. */
|
||||
for (const SnapTarget &target : targets) {
|
||||
if (abs(target.pos - frame) < abs(snap_frame - frame)) {
|
||||
snap_frame = target.pos;
|
||||
}
|
||||
}
|
||||
|
||||
const ARegion *region = CTX_wm_region(C);
|
||||
if (abs(snap_frame - frame) < get_snap_threshold(scene->toolsettings, region)) {
|
||||
return snap_frame;
|
||||
}
|
||||
|
||||
snap_frame = FLT_MAX;
|
||||
/* No frame is close enough to the snap threshold. Hard snap to targets without a threshold. */
|
||||
for (const SnapTarget &target : targets) {
|
||||
if (target.use_snap_treshold) {
|
||||
continue;
|
||||
}
|
||||
if (abs(target.pos - frame) < abs(snap_frame - frame)) {
|
||||
snap_frame = target.pos;
|
||||
}
|
||||
}
|
||||
|
||||
if (snap_frame != FLT_MAX) {
|
||||
return snap_frame;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
/* Set the new frame number */
|
||||
@@ -149,12 +530,8 @@ static void change_frame_apply(bContext *C, wmOperator *op, const bool always_up
|
||||
const float old_subframe = scene->r.subframe;
|
||||
|
||||
if (do_snap) {
|
||||
if (CTX_wm_space_seq(C) && blender::seq::editing_get(scene) != nullptr) {
|
||||
frame = seq_frame_apply_snap(C, scene, frame);
|
||||
}
|
||||
else {
|
||||
frame = BKE_scene_frame_snap_by_seconds(scene, 1.0, frame);
|
||||
}
|
||||
FrameChangeModalData *op_data = static_cast<FrameChangeModalData *>(op->customdata);
|
||||
frame = apply_frame_snap(C, *op_data, frame);
|
||||
}
|
||||
|
||||
/* set the new frame number */
|
||||
@@ -224,16 +601,21 @@ static void change_frame_seq_preview_end(SpaceSeq *sseq)
|
||||
}
|
||||
}
|
||||
|
||||
static bool use_sequencer_snapping(bContext *C)
|
||||
static bool use_playhead_snapping(bContext *C)
|
||||
{
|
||||
if (!CTX_wm_space_seq(C)) {
|
||||
return false;
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ScrArea *area = CTX_wm_area(C);
|
||||
|
||||
if (area->spacetype == SPACE_GRAPH) {
|
||||
SpaceGraph *graph_editor = static_cast<SpaceGraph *>(area->spacedata.first);
|
||||
/* Snapping is disabled for driver mode. Need to evaluate if it makes sense there and what form
|
||||
* it should take. */
|
||||
if (graph_editor->mode == SIPO_MODE_DRIVERS) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
short snap_flag = blender::seq::tool_settings_snap_flag_get(scene);
|
||||
return (scene->toolsettings->snap_flag_seq & SCE_SNAP) &&
|
||||
(snap_flag & SEQ_SNAP_CURRENT_FRAME_TO_STRIPS);
|
||||
return scene->toolsettings->snap_flag_playhead & SCE_SNAP;
|
||||
}
|
||||
|
||||
static bool sequencer_skip_for_handle_tweak(const bContext *C, const wmEvent *event)
|
||||
@@ -262,6 +644,8 @@ static bool sequencer_skip_for_handle_tweak(const bContext *C, const wmEvent *ev
|
||||
static wmOperatorStatus change_frame_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
bScreen *screen = CTX_wm_screen(C);
|
||||
FrameChangeModalData *op_data = MEM_new<FrameChangeModalData>(__func__);
|
||||
op->customdata = op_data;
|
||||
|
||||
/* This check is done in case scrubbing and strip tweaking in the sequencer are bound to the same
|
||||
* event (e.g. RCS keymap where both are activated on left mouse press). Tweaking should take
|
||||
@@ -276,7 +660,7 @@ static wmOperatorStatus change_frame_invoke(bContext *C, wmOperator *op, const w
|
||||
*/
|
||||
RNA_float_set(op->ptr, "frame", frame_from_event(C, event));
|
||||
|
||||
if (use_sequencer_snapping(C)) {
|
||||
if (use_playhead_snapping(C)) {
|
||||
RNA_boolean_set(op->ptr, "snap", true);
|
||||
}
|
||||
|
||||
@@ -356,7 +740,7 @@ static wmOperatorStatus change_frame_modal(bContext *C, wmOperator *op, const wm
|
||||
case EVT_LEFTCTRLKEY:
|
||||
case EVT_RIGHTCTRLKEY:
|
||||
/* Use Ctrl key to invert snapping in sequencer. */
|
||||
if (use_sequencer_snapping(C)) {
|
||||
if (use_playhead_snapping(C)) {
|
||||
if (event->val == KM_RELEASE) {
|
||||
RNA_boolean_set(op->ptr, "snap", true);
|
||||
}
|
||||
@@ -378,10 +762,18 @@ static wmOperatorStatus change_frame_modal(bContext *C, wmOperator *op, const wm
|
||||
}
|
||||
}
|
||||
|
||||
WorkspaceStatus status(C);
|
||||
status.item(IFACE_("Toggle Snapping"), ICON_EVENT_CTRL);
|
||||
|
||||
if (ret != OPERATOR_RUNNING_MODAL) {
|
||||
ED_workspace_status_text(C, nullptr);
|
||||
bScreen *screen = CTX_wm_screen(C);
|
||||
screen->scrubbing = false;
|
||||
|
||||
FrameChangeModalData *op_data = static_cast<FrameChangeModalData *>(op->customdata);
|
||||
MEM_delete(op_data);
|
||||
op->customdata = nullptr;
|
||||
|
||||
if (RNA_boolean_get(op->ptr, "seq_solo_preview")) {
|
||||
SpaceSeq *sseq = CTX_wm_space_seq(C);
|
||||
if (sseq != nullptr) {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "DNA_mask_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BKE_fcurve.hh"
|
||||
#include "BKE_grease_pencil.hh"
|
||||
@@ -35,6 +36,8 @@
|
||||
#include "ED_anim_api.hh"
|
||||
#include "ED_keyframes_keylist.hh"
|
||||
|
||||
#include "SEQ_retiming.hh"
|
||||
|
||||
#include "ANIM_action.hh"
|
||||
|
||||
using namespace blender;
|
||||
@@ -264,6 +267,39 @@ const ActKeyColumn *ED_keylist_find_prev(const AnimKeylist *keylist, const float
|
||||
return prev_column;
|
||||
}
|
||||
|
||||
const ActKeyColumn *ED_keylist_find_closest(const AnimKeylist *keylist, const float cfra)
|
||||
{
|
||||
BLI_assert_msg(keylist->is_runtime_initialized,
|
||||
"ED_keylist_prepare_for_direct_access needs to be called before searching.");
|
||||
if (ED_keylist_is_empty(keylist)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (cfra <= keylist->runtime.key_columns.first().cfra) {
|
||||
return &keylist->runtime.key_columns.first();
|
||||
}
|
||||
if (cfra >= keylist->runtime.key_columns.last().cfra) {
|
||||
keylist->runtime.key_columns.last();
|
||||
}
|
||||
const ActKeyColumn *prev = ED_keylist_find_prev(keylist, cfra);
|
||||
BLI_assert_msg(prev != nullptr,
|
||||
"This should exist since we checked for cfra bounds just before");
|
||||
/* This could be a nullptr though. */
|
||||
const ActKeyColumn *next = prev->next;
|
||||
|
||||
if (!next) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
const float prev_delta = cfra - prev->cfra;
|
||||
const float next_delta = next->cfra - cfra;
|
||||
/* `prev_delta` and `next_delta` can both be 0 if the given `cfra` is exactly at a key column. */
|
||||
|
||||
if (prev_delta <= next_delta) {
|
||||
return prev;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
const ActKeyColumn *ED_keylist_find_any_between(const AnimKeylist *keylist,
|
||||
const Bounds<float> frame_range)
|
||||
{
|
||||
@@ -589,6 +625,50 @@ static void nupdate_ak_gpframe(ActKeyColumn *ak, void *data)
|
||||
|
||||
/* ......... */
|
||||
|
||||
/* Extra struct to pass the timeline frame since the retiming key doesn't contain that and we would
|
||||
* need the scene to get it. */
|
||||
struct SeqAllocateData {
|
||||
const SeqRetimingKey *key;
|
||||
float timeline_frame;
|
||||
};
|
||||
|
||||
/* New node callback used for building ActKeyColumns from Sequencer keys. */
|
||||
static ActKeyColumn *nalloc_ak_seqframe(void *data)
|
||||
{
|
||||
ActKeyColumn *ak = MEM_callocN<ActKeyColumn>("ActKeyColumnGPF");
|
||||
const SeqAllocateData *allocate_data = (SeqAllocateData *)data;
|
||||
const SeqRetimingKey *timing_key = allocate_data->key;
|
||||
|
||||
/* store settings based on state of BezTriple */
|
||||
ak->cfra = allocate_data->timeline_frame;
|
||||
ak->sel = (timing_key->flag & SEQ_KEY_SELECTED) ? SELECT : 0;
|
||||
ak->key_type = eBezTriple_KeyframeType::BEZT_KEYTYPE_KEYFRAME;
|
||||
|
||||
/* Count keyframes in this column. */
|
||||
ak->totkey = 1;
|
||||
/* Set as visible block. */
|
||||
ak->totblock = 1;
|
||||
ak->block.sel = ak->sel;
|
||||
ak->block.flag = 0;
|
||||
|
||||
return ak;
|
||||
}
|
||||
|
||||
/* Node updater callback used for building ActKeyColumns from Sequencer keys. */
|
||||
static void nupdate_ak_seqframe(ActKeyColumn *ak, void *data)
|
||||
{
|
||||
SeqAllocateData *allocate_data = static_cast<SeqAllocateData *>(data);
|
||||
|
||||
/* Set selection status and 'touched' status. */
|
||||
if (allocate_data->key->flag & SEQ_KEY_SELECTED) {
|
||||
ak->sel = SELECT;
|
||||
}
|
||||
|
||||
ak->totkey++;
|
||||
}
|
||||
|
||||
/* ......... */
|
||||
|
||||
/* New node callback used for building ActKeyColumns from GPencil frames */
|
||||
static ActKeyColumn *nalloc_ak_masklayshape(void *data)
|
||||
{
|
||||
@@ -1417,3 +1497,18 @@ void mask_to_keylist(bDopeSheet * /*ads*/, MaskLayer *masklay, AnimKeylist *keyl
|
||||
|
||||
update_keyblocks(keylist, nullptr, 0);
|
||||
}
|
||||
|
||||
void sequencer_strip_to_keylist(const Strip &strip, AnimKeylist &keylist, Scene &scene)
|
||||
{
|
||||
if (!blender::seq::retiming_is_active(&strip)) {
|
||||
return;
|
||||
}
|
||||
keylist_reset_last_accessed(&keylist);
|
||||
for (const SeqRetimingKey &retime_key : blender::seq::retiming_keys_get(&strip)) {
|
||||
const float cfra = blender::seq::retiming_key_timeline_frame_get(&scene, &strip, &retime_key);
|
||||
SeqAllocateData allocate_data = {&retime_key, cfra};
|
||||
keylist_add_or_update_column(
|
||||
&keylist, cfra, nalloc_ak_seqframe, nupdate_ak_seqframe, &allocate_data);
|
||||
}
|
||||
update_keyblocks(&keylist, nullptr, 0);
|
||||
}
|
||||
|
||||
@@ -154,6 +154,40 @@ TEST(keylist, find_exact)
|
||||
ED_keylist_free(keylist);
|
||||
}
|
||||
|
||||
TEST(keylist, find_closest)
|
||||
{
|
||||
AnimKeylist *keylist = create_test_keylist();
|
||||
|
||||
{
|
||||
const ActKeyColumn *closest = ED_keylist_find_closest(keylist, -1);
|
||||
EXPECT_EQ(closest->cfra, 10.0);
|
||||
}
|
||||
|
||||
{
|
||||
const ActKeyColumn *closest = ED_keylist_find_closest(keylist, 10);
|
||||
EXPECT_EQ(closest->cfra, 10.0);
|
||||
}
|
||||
|
||||
{
|
||||
const ActKeyColumn *closest = ED_keylist_find_closest(keylist, 14.999);
|
||||
EXPECT_EQ(closest->cfra, 10.0);
|
||||
}
|
||||
{
|
||||
/* When the distance between key columns is equal, the previous column is chosen */
|
||||
const ActKeyColumn *closest = ED_keylist_find_closest(keylist, 15);
|
||||
EXPECT_EQ(closest->cfra, 10.0);
|
||||
}
|
||||
{
|
||||
const ActKeyColumn *closest = ED_keylist_find_closest(keylist, 15.001);
|
||||
EXPECT_EQ(closest->cfra, 20.0);
|
||||
}
|
||||
{
|
||||
const ActKeyColumn *closest = ED_keylist_find_closest(keylist, 30.001);
|
||||
EXPECT_EQ(closest->cfra, 30.0);
|
||||
}
|
||||
ED_keylist_free(keylist);
|
||||
}
|
||||
|
||||
class KeylistSummaryTest : public testing::Test {
|
||||
public:
|
||||
Main *bmain;
|
||||
|
||||
@@ -25,6 +25,7 @@ struct ListBase;
|
||||
struct MaskLayer;
|
||||
struct Object;
|
||||
struct Scene;
|
||||
struct Strip;
|
||||
struct bAction;
|
||||
struct bActionGroup;
|
||||
struct bAnimContext;
|
||||
@@ -138,6 +139,7 @@ void ED_keylist_prepare_for_direct_access(AnimKeylist *keylist);
|
||||
const ActKeyColumn *ED_keylist_find_exact(const AnimKeylist *keylist, float cfra);
|
||||
const ActKeyColumn *ED_keylist_find_next(const AnimKeylist *keylist, float cfra);
|
||||
const ActKeyColumn *ED_keylist_find_prev(const AnimKeylist *keylist, float cfra);
|
||||
const ActKeyColumn *ED_keylist_find_closest(const AnimKeylist *keylist, float cfra);
|
||||
const ActKeyColumn *ED_keylist_find_any_between(const AnimKeylist *keylist,
|
||||
const blender::Bounds<float> frame_range);
|
||||
bool ED_keylist_is_empty(const AnimKeylist *keylist);
|
||||
@@ -280,6 +282,9 @@ void gpl_to_keylist(bDopeSheet *ads, bGPDlayer *gpl, AnimKeylist *keylist);
|
||||
/* Mask */
|
||||
void mask_to_keylist(bDopeSheet *ads, MaskLayer *masklay, AnimKeylist *keylist);
|
||||
|
||||
/* Sequencer strip data. */
|
||||
void sequencer_strip_to_keylist(const Strip &strip, AnimKeylist &keylist, Scene &scene);
|
||||
|
||||
/* ActKeyColumn API ---------------- */
|
||||
|
||||
/** Checks if #ActKeyColumn has any block data. */
|
||||
|
||||
@@ -379,8 +379,13 @@
|
||||
.snap_node_mode = SCE_SNAP_TO_GRID, \
|
||||
.snap_uv_mode = SCE_SNAP_TO_INCREMENT, \
|
||||
.snap_anim_mode = SCE_SNAP_TO_FRAME, \
|
||||
.snap_playhead_mode = SCE_SNAP_TO_KEYS | SCE_SNAP_TO_STRIPS, \
|
||||
.snap_step_frames = 2, \
|
||||
.snap_step_seconds = 1, \
|
||||
.playhead_snap_distance = 20, \
|
||||
.snap_flag = SCE_SNAP_TO_INCLUDE_EDITED | SCE_SNAP_TO_INCLUDE_NONEDITED, \
|
||||
.snap_flag_anim = SCE_SNAP, \
|
||||
.snap_flag_playhead = 0, \
|
||||
.snap_transform_mode_flag = SCE_SNAP_TRANSFORM_MODE_TRANSLATE, \
|
||||
.snap_face_nearest_steps = 1, \
|
||||
.snap_angle_increment_3d = DEG2RADF(5.0f), \
|
||||
|
||||
@@ -1750,14 +1750,15 @@ typedef struct ToolSettings {
|
||||
short snap_mode;
|
||||
short snap_uv_mode;
|
||||
short snap_anim_mode;
|
||||
short snap_playhead_mode;
|
||||
/** Generic flags (per space-type), #eSnapFlag. */
|
||||
short snap_flag;
|
||||
short snap_flag_node;
|
||||
short snap_flag_seq;
|
||||
short snap_flag_anim;
|
||||
short snap_flag_driver;
|
||||
short snap_flag_playhead;
|
||||
short snap_uv_flag;
|
||||
char _pad[2];
|
||||
/** Default snap source, #eSnapSourceOP. */
|
||||
/**
|
||||
* TODO(@gfxcoder): Rename `snap_target` to `snap_source` to avoid previous ambiguity of
|
||||
@@ -1801,7 +1802,7 @@ typedef struct ToolSettings {
|
||||
|
||||
char workspace_tool_type;
|
||||
|
||||
char _pad5[1];
|
||||
char _pad5[7];
|
||||
|
||||
/**
|
||||
* XXX: these `sculpt_paint_*` fields are deprecated, use the
|
||||
@@ -1845,6 +1846,11 @@ typedef struct ToolSettings {
|
||||
float snap_angle_increment_3d;
|
||||
float snap_angle_increment_3d_precision;
|
||||
|
||||
int16_t snap_step_seconds;
|
||||
int16_t snap_step_frames;
|
||||
/* Pixel threshold that needs to be crossed before the playhead is snapped to a point. */
|
||||
int playhead_snap_distance;
|
||||
|
||||
} ToolSettings;
|
||||
|
||||
/** \} */
|
||||
@@ -2497,10 +2503,12 @@ ENUM_OPERATORS(eSnapTargetOP, SCE_SNAP_TARGET_NOT_NONEDITED)
|
||||
typedef enum eSnapMode {
|
||||
SCE_SNAP_TO_NONE = 0,
|
||||
|
||||
/** #ToolSettings::snap_anim_mode */
|
||||
/** #ToolSettings::snap_anim_mode and #ToolSettings::snap_playhead_mode. */
|
||||
SCE_SNAP_TO_FRAME = (1 << 0),
|
||||
SCE_SNAP_TO_SECOND = (1 << 1),
|
||||
SCE_SNAP_TO_MARKERS = (1 << 2),
|
||||
SCE_SNAP_TO_KEYS = (1 << 3),
|
||||
SCE_SNAP_TO_STRIPS = (1 << 4),
|
||||
|
||||
/** #ToolSettings::snap_mode and #ToolSettings::snap_node_mode and #ToolSettings.snap_uv_mode */
|
||||
SCE_SNAP_TO_POINT = (1 << 0),
|
||||
|
||||
@@ -193,6 +193,15 @@ static const EnumPropertyItem snap_uv_element_items[] = {
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
const EnumPropertyItem rna_enum_snap_playhead_element_items[] = {
|
||||
{SCE_SNAP_TO_FRAME, "FRAME", 0, "Frames", "Snap to frame increments"},
|
||||
{SCE_SNAP_TO_SECOND, "SECOND", 0, "Seconds", "Snap to second increments"},
|
||||
{SCE_SNAP_TO_MARKERS, "MARKER", 0, "Markers", "Snap to markers"},
|
||||
{SCE_SNAP_TO_KEYS, "KEY", 0, "Keyframes", "Snap to keyframes"},
|
||||
{SCE_SNAP_TO_STRIPS, "Strip", 0, "Strips", "Snap to Strips"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static const EnumPropertyItem rna_enum_scene_display_aa_methods[] = {
|
||||
{SCE_DISPLAY_AA_OFF,
|
||||
"OFF",
|
||||
@@ -3696,6 +3705,39 @@ static void rna_def_tool_settings(BlenderRNA *brna)
|
||||
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_UNIT);
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); /* header redraw */
|
||||
|
||||
prop = RNA_def_property(srna, "use_snap_playhead", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, nullptr, "snap_flag_playhead", SCE_SNAP);
|
||||
RNA_def_property_flag(prop, PROP_DEG_SYNC_ONLY);
|
||||
RNA_def_property_ui_text(prop, "Use Snapping", "Snap playhead when scrubbing");
|
||||
RNA_def_property_boolean_default(prop, false);
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr);
|
||||
|
||||
prop = RNA_def_property(srna, "snap_playhead_element", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_bitflag_sdna(prop, nullptr, "snap_playhead_mode");
|
||||
RNA_def_property_flag(prop, PROP_DEG_SYNC_ONLY);
|
||||
RNA_def_property_flag(prop, PROP_ENUM_FLAG);
|
||||
RNA_def_property_enum_items(prop, rna_enum_snap_playhead_element_items);
|
||||
RNA_def_property_ui_text(prop, "Snap Playhead Element", "Type of element to snap to");
|
||||
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_UNIT);
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr);
|
||||
|
||||
prop = RNA_def_property(srna, "snap_playhead_frame_step", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, nullptr, "snap_step_frames");
|
||||
RNA_def_property_range(prop, 1, 32768);
|
||||
RNA_def_property_ui_text(prop, "Frame Step", "At which interval to snap to frames");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr);
|
||||
|
||||
prop = RNA_def_property(srna, "snap_playhead_second_step", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, nullptr, "snap_step_seconds");
|
||||
RNA_def_property_ui_text(prop, "Second Step", "At which interval to snap to seconds");
|
||||
RNA_def_property_range(prop, 1, 32768);
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr);
|
||||
|
||||
prop = RNA_def_property(srna, "playhead_snap_distance", PROP_INT, PROP_PIXEL);
|
||||
RNA_def_property_int_sdna(prop, nullptr, "playhead_snap_distance");
|
||||
RNA_def_property_ui_range(prop, 1, 100, 1, 1);
|
||||
RNA_def_property_ui_text(prop, "Snap Distance", "Maximum distance for snapping in pixels");
|
||||
|
||||
/* image editor uses its own set of snap modes */
|
||||
prop = RNA_def_property(srna, "snap_uv_element", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_bitflag_sdna(prop, nullptr, "snap_uv_mode");
|
||||
|
||||
Reference in New Issue
Block a user