VSE: Add render options for sequencer scene and active scene

Addresses #146305.

Ever since moving to the "sequencer scene" paradigm, attempting to
render an image or animation when a sequencer with strips is present
often seems to outright ignore the sequencer in most cases. This is
because the sequencer scene usually differs from the active scene (which
is the true render target), so one must first switch their active scene
to the sequencer scene before rendering.  This is confusing and
seems like a regression in behavior. To improve clarity, this patch does
the following:

When a sequencer scene with at least one strip (and the sequencer step
enabled in the pipeline) exists in the current workspace, new options
"Render Sequencer Image" and "Render Sequencer Animation" appear.  These
options may be invoked by alt-F12 and ctrl-alt-F12, respectively.

Additionally, if such a valid sequencer scene is the same as the active
scene, then only the regular render options are listed, since in this
case they are identical to the sequencer render operators, meaning F12
still works predictably.

To switch back and forth between sequencer and main scene render
outputs, a new toggle has been added to the image editor to "Show
Sequencer Scene" output. This button only appears for the render result
if there is a valid sequencer scene that differs from the active scene.

Pull Request: https://projects.blender.org/blender/blender/pulls/146934
This commit is contained in:
John Kiril Swenson
2025-10-07 18:12:08 +02:00
committed by John Kiril Swenson
parent 0c18c1cfc2
commit 76c03744a8
10 changed files with 133 additions and 26 deletions

View File

@@ -829,6 +829,10 @@ def km_screen(params):
{"properties": [("use_viewport", True)]}),
("render.render", {"type": 'F12', "value": 'PRESS', "ctrl": True},
{"properties": [("animation", True), ("use_viewport", True)]}),
("render.render", {"type": 'F12', "value": 'PRESS', "alt": True},
{"properties": [("use_sequencer_scene", True), ("use_viewport", True)]}),
("render.render", {"type": 'F12', "value": 'PRESS', "ctrl": True, "alt": True},
{"properties": [("animation", True), ("use_sequencer_scene", True), ("use_viewport", True)]}),
("render.view_cancel", {"type": 'ESC', "value": 'PRESS'}, None),
("render.view_show", {"type": 'F11', "value": 'PRESS'}, None),
("render.play_rendered_anim", {"type": 'F11', "value": 'PRESS', "ctrl": True}, None),

View File

@@ -289,6 +289,10 @@ def km_screen_editing(params):
{"properties": [("use_viewport", True)]}),
("render.render", {"type": 'RET', "value": 'PRESS', "ctrl": True, "alt": True},
{"properties": [("animation", True), ("use_viewport", True)]}),
("render.render", {"type": 'F12', "value": 'PRESS', "alt": True},
{"properties": [("use_sequencer_scene", True), ("use_viewport", True)]}),
("render.render", {"type": 'F12', "value": 'PRESS', "ctrl": True, "alt": True},
{"properties": [("animation", True), ("use_sequencer_scene", True), ("use_viewport", True)]}),
("render.view_cancel", {"type": 'ESC', "value": 'PRESS'}, None),
])

View File

@@ -931,6 +931,13 @@ class IMAGE_HT_header(Header):
layout.prop_search(mesh.uv_layers, "active", mesh, "uv_layers", text="")
if ima:
seq_scene = context.sequencer_scene
scene = context.scene
if show_render and seq_scene and (seq_scene != scene):
row = layout.row()
row.prop(sima, "show_sequencer_scene", text="")
if ima.is_stereo_3d:
row = layout.row()
row.prop(sima, "show_stereo_3d", text="")

View File

@@ -467,6 +467,11 @@ class TOPBAR_MT_render(Menu):
layout = self.layout
rd = context.scene.render
scene = context.scene
seq_scene = context.sequencer_scene
strips = getattr(context, "strips", ())
can_render_seq = seq_scene and seq_scene.render.use_sequencer and strips
layout.operator("render.render", text="Render Image", icon='RENDER_STILL').use_viewport = True
props = layout.operator("render.render", text="Render Animation", icon='RENDER_ANIMATION')
@@ -475,6 +480,18 @@ class TOPBAR_MT_render(Menu):
layout.separator()
if can_render_seq and (seq_scene != scene):
props = layout.operator("render.render", text="Render Sequencer Image", icon='RENDER_STILL')
props.use_viewport = True
props.use_sequencer_scene = True
props = layout.operator("render.render", text="Render Sequencer Animation", icon='RENDER_ANIMATION')
props.animation = True
props.use_viewport = True
props.use_sequencer_scene = True
layout.separator()
layout.operator("sound.mixdown", text="Render Audio...")
layout.separator()

View File

@@ -33,6 +33,7 @@ struct ImageUsage {
bool last_tile_drawing;
const void *last_image = nullptr;
const void *last_scene = nullptr;
ImageUsage() = default;
ImageUsage(const ::Image *image, const ::ImageUser *image_user, bool do_tile_drawing)
@@ -43,6 +44,7 @@ struct ImageUsage {
colorspace_settings = image->colorspace_settings;
alpha_mode = image->alpha_mode;
last_image = static_cast<const void *>(image);
last_scene = image_user ? static_cast<const void *>(image_user->scene) : nullptr;
last_tile_drawing = do_tile_drawing;
}

View File

@@ -325,16 +325,20 @@ static void get_render_operator_frame_range(wmOperator *render_operator,
/* executes blocking render */
static wmOperatorStatus screen_render_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
RenderEngineType *re_type = RE_engines_find(scene->r.engine);
ViewLayer *active_layer = CTX_data_view_layer(C);
ViewLayer *single_layer = nullptr;
Render *re;
Image *ima;
View3D *v3d = CTX_wm_view3d(C);
Main *mainp = CTX_data_main(C);
const bool is_animation = RNA_boolean_get(op->ptr, "animation");
const bool is_write_still = RNA_boolean_get(op->ptr, "write_still");
const bool use_sequencer_scene = RNA_boolean_get(op->ptr, "use_sequencer_scene");
Scene *scene = use_sequencer_scene ? CTX_data_sequencer_scene(C) : CTX_data_scene(C);
ViewLayer *active_layer = use_sequencer_scene ? BKE_view_layer_default_render(scene) :
CTX_data_view_layer(C);
RenderEngineType *re_type = RE_engines_find(scene->r.engine);
Object *camera_override = v3d ? V3D_CAMERA_LOCAL(v3d) : nullptr;
/* Cannot do render if there is not this function. */
@@ -342,6 +346,11 @@ static wmOperatorStatus screen_render_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
if (use_sequencer_scene && !RE_seq_render_active(scene, &scene->r)) {
BKE_report(op->reports, RPT_ERROR, "No sequencer scene with video strips to render");
return OPERATOR_CANCELLED;
}
if (!is_animation && render_operator_has_custom_frame_range(op)) {
BKE_report(op->reports, RPT_ERROR, "Frame start/end specified in a non-animation render");
return OPERATOR_CANCELLED;
@@ -1000,27 +1009,35 @@ static wmOperatorStatus screen_render_invoke(bContext *C, wmOperator *op, const
{
/* new render clears all callbacks */
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *active_layer = CTX_data_view_layer(C);
ViewLayer *single_layer = nullptr;
RenderEngineType *re_type = RE_engines_find(scene->r.engine);
Render *re;
wmJob *wm_job;
RenderJob *rj;
Image *ima;
ScrArea *area;
const bool is_animation = RNA_boolean_get(op->ptr, "animation");
const bool is_write_still = RNA_boolean_get(op->ptr, "write_still");
const bool use_viewport = RNA_boolean_get(op->ptr, "use_viewport");
const bool use_sequencer_scene = RNA_boolean_get(op->ptr, "use_sequencer_scene");
View3D *v3d = use_viewport ? CTX_wm_view3d(C) : nullptr;
Scene *scene = use_sequencer_scene ? CTX_data_sequencer_scene(C) : CTX_data_scene(C);
ViewLayer *active_layer = use_sequencer_scene ? BKE_view_layer_default_render(scene) :
CTX_data_view_layer(C);
RenderEngineType *re_type = RE_engines_find(scene->r.engine);
Object *camera_override = v3d ? V3D_CAMERA_LOCAL(v3d) : nullptr;
const char *name;
ScrArea *area;
/* Cannot do render if there is not this function. */
if (re_type->render == nullptr) {
return OPERATOR_CANCELLED;
}
if (use_sequencer_scene && !RE_seq_render_active(scene, &scene->r)) {
BKE_report(op->reports, RPT_ERROR, "No sequencer scene with video strips to render");
return OPERATOR_CANCELLED;
}
if (!is_animation && render_operator_has_custom_frame_range(op)) {
BKE_report(op->reports, RPT_ERROR, "Frame start/end specified in a non-animation render");
return OPERATOR_CANCELLED;
@@ -1088,7 +1105,7 @@ static wmOperatorStatus screen_render_invoke(bContext *C, wmOperator *op, const
rj->main = bmain;
rj->scene = scene;
rj->current_scene = rj->scene;
rj->view_layer = CTX_data_view_layer(C);
rj->view_layer = active_layer;
rj->single_layer = single_layer;
rj->camera_override = camera_override;
rj->anim = is_animation;
@@ -1134,6 +1151,7 @@ static wmOperatorStatus screen_render_invoke(bContext *C, wmOperator *op, const
}
/* setup job */
const char *name;
if (RE_seq_render_active(scene, &scene->r)) {
name = RPT_("Rendering sequence...");
}
@@ -1195,13 +1213,23 @@ static wmOperatorStatus screen_render_invoke(bContext *C, wmOperator *op, const
return OPERATOR_RUNNING_MODAL;
}
static std::string screen_render_get_description(bContext * /*C*/,
wmOperatorType * /*ot*/,
PointerRNA *ptr)
{
const bool use_sequencer_scene = RNA_boolean_get(ptr, "use_sequencer_scene");
if (use_sequencer_scene) {
return TIP_("Render active sequencer scene");
}
return TIP_("Render active scene");
}
void RENDER_OT_render(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Render";
ot->description = "Render active scene";
ot->idname = "RENDER_OT_render";
/* API callbacks. */
@@ -1209,6 +1237,7 @@ void RENDER_OT_render(wmOperatorType *ot)
ot->modal = screen_render_modal;
ot->cancel = screen_render_cancel;
ot->exec = screen_render_exec;
ot->get_description = screen_render_get_description;
/* This isn't needed, causes failure in background mode. */
#if 0
@@ -1233,6 +1262,12 @@ void RENDER_OT_render(wmOperatorType *ot)
"Use 3D Viewport",
"When inside a 3D viewport, use layers and camera of the viewport");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"use_sequencer_scene",
false,
"Use Sequencer Scene",
"Render the sequencer scene instead of the active scene");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_string(ot->srna,
"layer",
nullptr,
@@ -1273,28 +1308,34 @@ void RENDER_OT_render(wmOperatorType *ot)
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
Scene *ED_render_job_get_scene(const bContext *C)
static RenderJobBase *render_job_get(const bContext *C)
{
wmWindowManager *wm = CTX_wm_manager(C);
RenderJobBase *rj = (RenderJobBase *)WM_jobs_customdata_from_type(
wm, CTX_data_scene(C), WM_JOB_TYPE_RENDER);
RenderJobBase *rj;
if (rj) {
return rj->scene;
/* Try to find job tied to active scene first. */
rj = static_cast<RenderJobBase *>(
WM_jobs_customdata_from_type(wm, CTX_data_scene(C), WM_JOB_TYPE_RENDER));
/* If not found, attempt to find job tied to sequencer scene. */
if (rj == nullptr) {
return static_cast<RenderJobBase *>(
WM_jobs_customdata_from_type(wm, CTX_data_sequencer_scene(C), WM_JOB_TYPE_RENDER));
}
return nullptr;
return rj;
}
Scene *ED_render_job_get_scene(const bContext *C)
{
RenderJobBase *rj = render_job_get(C);
return rj ? rj->scene : nullptr;
}
Scene *ED_render_job_get_current_scene(const bContext *C)
{
wmWindowManager *wm = CTX_wm_manager(C);
RenderJobBase *rj = (RenderJobBase *)WM_jobs_customdata_from_type(
wm, CTX_data_scene(C), WM_JOB_TYPE_RENDER);
if (rj) {
return rj->current_scene;
}
return nullptr;
RenderJobBase *rj = render_job_get(C);
return rj ? rj->current_scene : nullptr;
}
/* Motion blur curve preset */

View File

@@ -60,7 +60,7 @@ static void draw_render_info(
Render *re = RE_GetSceneRender(scene);
Scene *stats_scene = ED_render_job_get_scene(C);
if (stats_scene == nullptr) {
stats_scene = CTX_data_scene(C);
stats_scene = scene;
}
RenderResult *rr = BKE_image_acquire_renderresult(stats_scene, ima);

View File

@@ -76,13 +76,16 @@ static void image_scopes_tag_refresh(ScrArea *area)
static void image_user_refresh_scene(const bContext *C, SpaceImage *sima)
{
/* Update scene image user for acquiring render results. */
sima->iuser.scene = CTX_data_scene(C);
sima->iuser.scene = (sima->iuser.flag & IMA_SHOW_SEQUENCER_SCENE) ? CTX_data_sequencer_scene(C) :
CTX_data_scene(C);
if (sima->image && sima->image->type == IMA_TYPE_R_RESULT) {
/* While rendering, prefer scene that is being rendered. */
Scene *render_scene = ED_render_job_get_current_scene(C);
if (render_scene) {
sima->iuser.scene = render_scene;
SET_FLAG_FROM_TEST(
sima->iuser.flag, render_scene == CTX_data_sequencer_scene(C), IMA_SHOW_SEQUENCER_SCENE);
}
}

View File

@@ -115,7 +115,7 @@ typedef struct ImageTile {
/** #ImageUser::flag */
enum {
IMA_ANIM_ALWAYS = 1 << 0,
// IMA_UNUSED_1 = 1 << 1,
IMA_SHOW_SEQUENCER_SCENE = 1 << 1,
// IMA_UNUSED_2 = 1 << 2,
IMA_NEED_FRAME_RECALC = 1 << 3,
IMA_SHOW_STEREO = 1 << 4,

View File

@@ -1819,6 +1819,24 @@ static void rna_SpaceImageEditor_show_stereo_update(Main * /*bmain*/,
}
}
static void rna_SpaceImageEditor_show_sequencer_scene_set(PointerRNA *ptr, bool value)
{
SpaceImage *sima = ptr->data_as<SpaceImage>();
if (value) {
sima->iuser.flag |= IMA_SHOW_SEQUENCER_SCENE;
}
else {
sima->iuser.flag &= ~IMA_SHOW_SEQUENCER_SCENE;
}
}
static bool rna_SpaceImageEditor_show_sequencer_scene_get(PointerRNA *ptr)
{
SpaceImage *sima = ptr->data_as<SpaceImage>();
return (sima->iuser.flag & IMA_SHOW_SEQUENCER_SCENE) != 0;
}
static bool rna_SpaceImageEditor_show_render_get(PointerRNA *ptr)
{
SpaceImage *sima = (SpaceImage *)(ptr->data);
@@ -6081,6 +6099,17 @@ static void rna_def_space_image(BlenderRNA *brna)
RNA_def_property_update(
prop, NC_SPACE | ND_SPACE_IMAGE, "rna_SpaceImageEditor_show_stereo_update");
prop = RNA_def_property(srna, "show_sequencer_scene", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_funcs(prop,
"rna_SpaceImageEditor_show_sequencer_scene_get",
"rna_SpaceImageEditor_show_sequencer_scene_set");
RNA_def_property_ui_text(
prop,
"Show Sequencer Scene",
"Display the render result for the sequencer scene instead of the active scene");
RNA_def_property_ui_icon(prop, ICON_SEQ_SEQUENCER, 0);
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, nullptr);
/* uv */
prop = RNA_def_property(srna, "uv_editor", PROP_POINTER, PROP_NONE);
RNA_def_property_flag(prop, PROP_NEVER_NULL);