Anim: VSE: Add overlay to visualize scene strip range when using scene syncing

This adds an overlay to the animation editors to visualize the current scene strip
in and out points in the scene timeline when the `Sync Scene Time` option in
the sequencer is used.

* Adds an overlay toggle + panel to the dopesheet.
* Adds a new theme setting (for the overlay) under
  `Common` > `Animation` > `Scene Strip Range`.
* Adds the overlay drawing to the dopesheet.

Pull Request: https://projects.blender.org/blender/blender/pulls/146165
This commit is contained in:
Falk David
2025-10-07 17:34:47 +02:00
committed by Falk David
parent b67dd35e1f
commit 0c18c1cfc2
16 changed files with 236 additions and 27 deletions

View File

@@ -312,6 +312,7 @@ const bTheme U_theme_default = {
.anim = {
.playhead = RGBA(0x4772b3ff),
.preview_range = RGBA(0xa14d0066),
.scene_strip_range = RGBA(0x00000080),
.channels = RGBA(0x194e8080),
.channels_sub = RGBA(0x0f2c4d80),
.channel_group = RGBA(0x1a332d37),

View File

@@ -283,6 +283,13 @@ class DOPESHEET_HT_editor_buttons:
icon_only=True,
panel="DOPESHEET_PT_proportional_edit",
)
overlays = st.overlays
row = layout.row(align=True)
row.prop(overlays, "show_overlays", text="", icon='OVERLAY')
sub = row.row(align=True)
sub.popover(panel="DOPESHEET_PT_overlay", text="")
sub.active = overlays.show_overlays
@classmethod
def _draw_action_selector(cls, context, layout):
@@ -1014,6 +1021,33 @@ class DOPESHEET_PT_ShapeKey(Panel):
draw_shape_key_properties(context, self.layout)
class DOPESHEET_PT_overlay(Panel):
bl_space_type = 'DOPESHEET_EDITOR'
bl_region_type = 'HEADER'
bl_label = "Overlays"
bl_ui_units_x = 13
def draw(self, _context):
pass
class DOPESHEET_PT_dopesheet_overlay(Panel):
bl_space_type = 'DOPESHEET_EDITOR'
bl_region_type = 'HEADER'
bl_parent_id = "DOPESHEET_PT_overlay"
bl_label = "Dope Sheet Overlays"
def draw(self, context):
st = context.space_data
overlay_settings = st.overlays
layout = self.layout
layout.active = overlay_settings.show_overlays
row = layout.row()
row.active = context.workspace.use_scene_time_sync
row.prop(overlay_settings, "show_scene_strip_range")
classes = (
DOPESHEET_HT_header,
DOPESHEET_HT_playback_controls,
@@ -1045,6 +1079,9 @@ classes = (
DOPESHEET_PT_grease_pencil_layer_relations,
DOPESHEET_PT_grease_pencil_layer_display,
DOPESHEET_PT_ShapeKey,
DOPESHEET_PT_overlay,
DOPESHEET_PT_dopesheet_overlay,
)
if __name__ == "__main__": # only for live edit.

View File

@@ -27,7 +27,7 @@
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 103
#define BLENDER_FILE_SUBVERSION 104
/* 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

View File

@@ -3918,6 +3918,23 @@ void blo_do_versions_500(FileData *fd, Library * /*lib*/, Main *bmain)
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 104)) {
/* Dope Sheet Editor: toggle overlays on. */
if (!DNA_struct_exists(fd->filesdna, "SpaceActionOverlays")) {
LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) {
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
LISTBASE_FOREACH (SpaceLink *, space, &area->spacedata) {
if (space->spacetype == SPACE_ACTION) {
SpaceAction *space_action = (SpaceAction *)space;
space_action->overlays.flag |= ADS_OVERLAY_SHOW_OVERLAYS;
space_action->overlays.flag |= ADS_SHOW_SCENE_STRIP_FRAME_RANGE;
}
}
}
}
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@@ -386,6 +386,10 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme)
btheme->tui.wcol_curve.roundness = U_theme_default.tui.wcol_curve.roundness;
}
if (!USER_VERSION_ATLEAST(500, 104)) {
FROM_DEFAULT_V4_UCHAR(common.anim.scene_strip_range);
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a USER_VERSION_ATLEAST check.

View File

@@ -12,8 +12,10 @@
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_sequence_types.h"
#include "DNA_space_types.h"
#include "DNA_userdef_types.h"
#include "DNA_workspace_types.h"
#include "BLI_listbase.h"
#include "BLI_math_rotation.h"
@@ -31,6 +33,7 @@
#include "ED_anim_api.hh"
#include "ED_keyframes_edit.hh"
#include "ED_keyframes_keylist.hh"
#include "ED_sequencer.hh"
#include "RNA_access.hh"
#include "RNA_path.hh"
@@ -41,6 +44,8 @@
#include "GPU_immediate.hh"
#include "GPU_state.hh"
#include "SEQ_time.hh"
/* *************************************************** */
/* CURRENT FRAME DRAWING */
@@ -103,6 +108,57 @@ void ANIM_draw_previewrange(const Scene *scene, View2D *v2d, int end_frame_width
}
}
void ANIM_draw_scene_strip_range(const bContext *C, View2D *v2d)
{
using namespace blender;
SpaceAction *space_action = CTX_wm_space_action(C);
if (!space_action || (space_action->overlays.flag & ADS_OVERLAY_SHOW_OVERLAYS) == 0 ||
(space_action->overlays.flag & ADS_SHOW_SCENE_STRIP_FRAME_RANGE) == 0)
{
return;
}
WorkSpace *workspace = CTX_wm_workspace(C);
if (!workspace) {
return;
}
if ((workspace->flags & WORKSPACE_SYNC_SCENE_TIME) == 0) {
return;
}
const Scene *sequencer_scene = workspace->sequencer_scene;
if (!sequencer_scene) {
return;
}
const Strip *scene_strip = blender::ed::vse::get_scene_strip_for_time_sync(sequencer_scene);
if (!scene_strip) {
return;
}
GPU_blend(GPU_BLEND_ALPHA);
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", blender::gpu::VertAttrType::SFLOAT_32_32);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformThemeColorShadeAlpha(TH_ANIM_SCENE_STRIP_RANGE, -25, -30);
/* ..._handle are frames in "sequencer logic", meaning that on the right_handle point in time,
* the strip is not visible any more. The last visible frame of the strip is actually on
* (right_handle-1), hence the -1 when computing the end_frame. */
const float left_handle = seq::time_left_handle_frame_get(sequencer_scene, scene_strip);
const float right_handle = seq::time_right_handle_frame_get(sequencer_scene, scene_strip);
const float start_frame = seq::give_frame_index(sequencer_scene, scene_strip, left_handle) +
scene_strip->scene->r.sfra;
const float end_frame = seq::give_frame_index(sequencer_scene, scene_strip, right_handle - 1) +
scene_strip->scene->r.sfra;
BLI_assert(start_frame < end_frame);
immRectf(pos, v2d->cur.xmin, v2d->cur.ymin, start_frame, v2d->cur.ymax);
immRectf(pos, end_frame, v2d->cur.ymin, v2d->cur.xmax, v2d->cur.ymax);
immUnbindProgram();
GPU_blend(GPU_BLEND_NONE);
}
/* *************************************************** */
/* SCENE FRAME RANGE */

View File

@@ -881,6 +881,11 @@ void ANIM_draw_cfra(const bContext *C, View2D *v2d, short flag);
*/
void ANIM_draw_previewrange(const Scene *scene, View2D *v2d, int end_frame_width);
/**
* Draw range of the current sequencer scene strip when using scene time syncing.
*/
void ANIM_draw_scene_strip_range(const bContext *C, View2D *v2d);
/** \} */
/* -------------------------------------------------------------------- */

View File

@@ -82,6 +82,11 @@ bool can_select_handle(const Scene *scene, const Strip *strip, const View2D *v2d
bool handle_is_selected(const Strip *strip, eStripHandle handle);
bool is_scene_time_sync_needed(const bContext &C);
/**
* Returns the scene strip (if any) that should be used for the scene synchronization feature.
* This is the top-most visible scene strip at the current time of the \a sequencer_scene.
*/
const Strip *get_scene_strip_for_time_sync(const Scene *sequence_scene);
void sync_active_scene_and_time_with_scene_strip(bContext &C);
} // namespace blender::ed::vse

View File

@@ -287,9 +287,10 @@ enum ThemeColorID {
TH_SKIN_ROOT,
TH_ANIM_ACTIVE, /* active action */
TH_ANIM_INACTIVE, /* no active action */
TH_ANIM_PREVIEW_RANGE, /* preview range overlay */
TH_ANIM_ACTIVE, /* active action */
TH_ANIM_INACTIVE, /* no active action */
TH_ANIM_PREVIEW_RANGE, /* preview range overlay */
TH_ANIM_SCENE_STRIP_RANGE, /* scene strip range overlay */
TH_ICON_SCENE,
TH_ICON_COLLECTION,

View File

@@ -915,6 +915,9 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid)
case TH_ANIM_PREVIEW_RANGE:
cp = btheme->common.anim.preview_range;
break;
case TH_ANIM_SCENE_STRIP_RANGE:
cp = btheme->common.anim.scene_strip_range;
break;
case TH_NLA_TWEAK:
cp = ts->nla_tweaking;

View File

@@ -72,6 +72,8 @@ static SpaceLink *action_create(const ScrArea *area, const Scene *scene)
TIME_CACHE_CLOTH | TIME_CACHE_SMOKE | TIME_CACHE_DYNAMICPAINT |
TIME_CACHE_RIGIDBODY | TIME_CACHE_SIMULATION_NODES;
saction->overlays.flag |= (ADS_OVERLAY_SHOW_OVERLAYS | ADS_SHOW_SCENE_STRIP_FRAME_RANGE);
/* header */
region = BKE_area_region_new();
@@ -263,6 +265,8 @@ static void action_main_region_draw(const bContext *C, ARegion *region)
UI_view2d_view_ortho(v2d);
ANIM_draw_previewrange(scene, v2d, 0);
ANIM_draw_scene_strip_range(C, v2d);
/* callback */
UI_view2d_view_ortho(v2d);
ED_region_draw_cb_draw(C, region, REGION_DRAW_POST_VIEW);

View File

@@ -361,27 +361,17 @@ static Scene *get_sequencer_scene_for_time_sync(const bContext &C)
return nullptr;
}
void sync_active_scene_and_time_with_scene_strip(bContext &C)
const Strip *get_scene_strip_for_time_sync(const Scene *sequencer_scene)
{
using namespace blender;
Scene *sequence_scene = get_sequencer_scene_for_time_sync(C);
if (!sequence_scene) {
return;
}
wmWindow *win = CTX_wm_window(&C);
Scene *active_scene = WM_window_get_active_scene(win);
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
Object *prev_obact = BKE_view_layer_active_object_get(view_layer);
Editing *ed = seq::editing_get(sequence_scene);
const Editing *ed = seq::editing_get(sequencer_scene);
if (!ed) {
return;
return nullptr;
}
ListBase *seqbase = seq::active_seqbase_get(ed);
const ListBase *channels = seq::channels_displayed_get(ed);
VectorSet<Strip *> query_strips = seq::query_strips_recursive_at_frame(
sequence_scene, seqbase, sequence_scene->r.cfra);
sequencer_scene, seqbase, sequencer_scene->r.cfra);
/* Ignore effect strips, sound strips and muted strips. */
query_strips.remove_if([&](const Strip *strip) {
return strip->is_effect() || strip->type == STRIP_TYPE_SOUND_RAM ||
@@ -393,20 +383,35 @@ void sync_active_scene_and_time_with_scene_strip(bContext &C)
return a->channel > b->channel;
});
/* Get the top-most scene strip. */
const Strip *scene_strip = [&]() -> const Strip * {
for (const Strip *strip : strips) {
if (strip->type == STRIP_TYPE_SCENE) {
return strip;
}
for (const Strip *strip : strips) {
if (strip->type == STRIP_TYPE_SCENE) {
return strip;
}
return nullptr;
}();
}
return nullptr;
}
void sync_active_scene_and_time_with_scene_strip(bContext &C)
{
using namespace blender;
Scene *sequencer_scene = get_sequencer_scene_for_time_sync(C);
if (!sequencer_scene) {
return;
}
wmWindow *win = CTX_wm_window(&C);
const Strip *scene_strip = get_scene_strip_for_time_sync(sequencer_scene);
if (!scene_strip || !scene_strip->scene) {
/* No scene strip with scene found. Switch to pinned scene. */
Main *bmain = CTX_data_main(&C);
WM_window_set_active_scene(bmain, &C, win, sequence_scene);
WM_window_set_active_scene(bmain, &C, win, sequencer_scene);
return;
}
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
Object *prev_obact = BKE_view_layer_active_object_get(view_layer);
Scene *active_scene = WM_window_get_active_scene(win);
if (active_scene != scene_strip->scene) {
/* Sync active scene in window. */
Main *bmain = CTX_data_main(&C);
@@ -440,7 +445,7 @@ void sync_active_scene_and_time_with_scene_strip(bContext &C)
/* Compute the scene time based on the scene strip. */
const float frame_index = seq::give_frame_index(
sequence_scene, scene_strip, sequence_scene->r.cfra) +
sequencer_scene, scene_strip, sequencer_scene->r.cfra) +
active_scene->r.sfra;
if (active_scene->r.flag & SCER_SHOW_SUBFRAME) {
active_scene->r.cfra = int(frame_index);

View File

@@ -995,6 +995,17 @@ typedef struct SpaceAction_Runtime {
char _pad0[7];
} SpaceAction_Runtime;
typedef enum SpaceActionOverlays_Flag {
ADS_OVERLAY_SHOW_OVERLAYS = (1 << 0),
ADS_SHOW_SCENE_STRIP_FRAME_RANGE = (1 << 1)
} SpaceActionOverlays_Flag;
typedef struct SpaceActionOverlays {
/** #SpaceActionOverlays_Flag */
int flag;
char _pad0[4];
} SpaceActionOverlays;
/* Action Editor Space. This is defined here instead of in DNA_space_types.h */
typedef struct SpaceAction {
struct SpaceLink *next, *prev;
@@ -1028,6 +1039,8 @@ typedef struct SpaceAction {
char cache_display;
char _pad1[6];
SpaceActionOverlays overlays;
SpaceAction_Runtime runtime;
} SpaceAction;

View File

@@ -151,6 +151,9 @@ typedef struct ThemeCommonAnim {
keyframe_jitter_selected[4], keyframe_moving_hold_selected[4],
keyframe_generated_selected[4];
unsigned char long_key[4], long_key_selected[4];
unsigned char scene_strip_range[4];
char _pad0[4];
} ThemeCommonAnim;
typedef struct ThemeCommonCurves {

View File

@@ -2542,6 +2542,20 @@ static void rna_SpaceSequenceEditor_zoom_percentage_set(PointerRNA *ptr, const f
ED_region_tag_redraw(region);
}
static PointerRNA rna_SpaceDopeSheet_overlay_get(PointerRNA *ptr)
{
return RNA_pointer_create_with_parent(*ptr, &RNA_SpaceDopeSheetOverlay, ptr->data);
}
static std::optional<std::string> rna_SpaceDopeSheetOverlay_path(const PointerRNA *ptr)
{
std::optional<std::string> editor_path = BKE_screen_path_from_screen_to_space(ptr);
if (!editor_path) {
return std::nullopt;
}
return editor_path.value() + ".overlays";
}
/* Space Node Editor */
static PointerRNA rna_SpaceNode_overlay_get(PointerRNA *ptr)
{
@@ -6717,6 +6731,32 @@ static void rna_def_space_text(BlenderRNA *brna)
RNA_api_space_text(srna);
}
static void rna_def_space_dopesheet_overlays(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "SpaceDopeSheetOverlay", nullptr);
RNA_def_struct_sdna(srna, "SpaceAction");
RNA_def_struct_nested(brna, srna, "SpaceDopeSheetEditor");
RNA_def_struct_path_func(srna, "rna_SpaceDopeSheetOverlay_path");
RNA_def_struct_ui_text(srna, "Overlay Settings", "");
prop = RNA_def_property(srna, "show_overlays", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "overlays.flag", ADS_OVERLAY_SHOW_OVERLAYS);
RNA_def_property_boolean_default(prop, true);
RNA_def_property_ui_text(prop, "Show Overlays", "Display overlays");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_NODE, nullptr);
prop = RNA_def_property(srna, "show_scene_strip_range", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "overlays.flag", ADS_SHOW_SCENE_STRIP_FRAME_RANGE);
RNA_def_property_ui_text(prop,
"Show Scene Strip Range",
"When using scene time synchronization in the sequence editor, display "
"the range of the current scene strip");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_DOPESHEET, nullptr);
}
static void rna_def_space_dopesheet(BlenderRNA *brna)
{
StructRNA *srna;
@@ -6854,6 +6894,15 @@ static void rna_def_space_dopesheet(BlenderRNA *brna)
RNA_def_property_boolean_sdna(prop, nullptr, "cache_display", TIME_CACHE_RIGIDBODY);
RNA_def_property_ui_text(prop, "Rigid Body", "Show the active object's Rigid Body cache");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_TIME, nullptr);
prop = RNA_def_property(srna, "overlays", PROP_POINTER, PROP_NONE);
RNA_def_property_flag(prop, PROP_NEVER_NULL);
RNA_def_property_struct_type(prop, "SpaceDopeSheetOverlay");
RNA_def_property_pointer_funcs(
prop, "rna_SpaceDopeSheet_overlay_get", nullptr, nullptr, nullptr);
RNA_def_property_ui_text(prop, "Overlay Settings", "Settings for display of overlays");
rna_def_space_dopesheet_overlays(brna);
}
static void rna_def_space_graph(BlenderRNA *brna)

View File

@@ -2382,6 +2382,12 @@ static void rna_def_userdef_theme_common_anim(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Preview Range", "Color of preview range overlay");
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
prop = RNA_def_property(srna, "scene_strip_range", PROP_FLOAT, PROP_COLOR_GAMMA);
RNA_def_property_float_sdna(prop, nullptr, "scene_strip_range");
RNA_def_property_array(prop, 4);
RNA_def_property_ui_text(prop, "Scene Strip Range", "Color of scene strip range overlay");
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
/* Channel properties */
prop = RNA_def_property(srna, "channels", PROP_FLOAT, PROP_COLOR_GAMMA);
RNA_def_property_array(prop, 4);