From 76c03744a809365af98f4d8356ebaeb0a5e2e831 Mon Sep 17 00:00:00 2001 From: John Kiril Swenson Date: Tue, 7 Oct 2025 18:12:08 +0200 Subject: [PATCH] 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 --- .../keyconfig/keymap_data/blender_default.py | 4 + .../keymap_data/industry_compatible_data.py | 4 + scripts/startup/bl_ui/space_image.py | 7 ++ scripts/startup/bl_ui/space_topbar.py | 17 ++++ .../blender/draw/engines/image/image_usage.hh | 2 + .../blender/editors/render/render_internal.cc | 87 ++++++++++++++----- .../blender/editors/space_image/image_draw.cc | 2 +- .../editors/space_image/space_image.cc | 5 +- source/blender/makesdna/DNA_image_types.h | 2 +- source/blender/makesrna/intern/rna_space.cc | 29 +++++++ 10 files changed, 133 insertions(+), 26 deletions(-) diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index e23e52eba61..2291b60f966 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -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), diff --git a/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py index 08bff5411ea..f1370a53b85 100644 --- a/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -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), ]) diff --git a/scripts/startup/bl_ui/space_image.py b/scripts/startup/bl_ui/space_image.py index c4c130e7908..2278cd4fa32 100644 --- a/scripts/startup/bl_ui/space_image.py +++ b/scripts/startup/bl_ui/space_image.py @@ -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="") diff --git a/scripts/startup/bl_ui/space_topbar.py b/scripts/startup/bl_ui/space_topbar.py index 0b8997c75e7..f9d73b51075 100644 --- a/scripts/startup/bl_ui/space_topbar.py +++ b/scripts/startup/bl_ui/space_topbar.py @@ -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() diff --git a/source/blender/draw/engines/image/image_usage.hh b/source/blender/draw/engines/image/image_usage.hh index a249e790597..324ed44602d 100644 --- a/source/blender/draw/engines/image/image_usage.hh +++ b/source/blender/draw/engines/image/image_usage.hh @@ -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(image); + last_scene = image_user ? static_cast(image_user->scene) : nullptr; last_tile_drawing = do_tile_drawing; } diff --git a/source/blender/editors/render/render_internal.cc b/source/blender/editors/render/render_internal.cc index 690cef91e3a..89c861343a2 100644 --- a/source/blender/editors/render/render_internal.cc +++ b/source/blender/editors/render/render_internal.cc @@ -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( + 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( + 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 */ diff --git a/source/blender/editors/space_image/image_draw.cc b/source/blender/editors/space_image/image_draw.cc index 310007e245a..dc333efc482 100644 --- a/source/blender/editors/space_image/image_draw.cc +++ b/source/blender/editors/space_image/image_draw.cc @@ -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); diff --git a/source/blender/editors/space_image/space_image.cc b/source/blender/editors/space_image/space_image.cc index d2a9e18f2ff..6603796b1fb 100644 --- a/source/blender/editors/space_image/space_image.cc +++ b/source/blender/editors/space_image/space_image.cc @@ -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); } } diff --git a/source/blender/makesdna/DNA_image_types.h b/source/blender/makesdna/DNA_image_types.h index 2ffae5955c4..0d4937c83b5 100644 --- a/source/blender/makesdna/DNA_image_types.h +++ b/source/blender/makesdna/DNA_image_types.h @@ -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, diff --git a/source/blender/makesrna/intern/rna_space.cc b/source/blender/makesrna/intern/rna_space.cc index 868c447d67e..c15a114d3f0 100644 --- a/source/blender/makesrna/intern/rna_space.cc +++ b/source/blender/makesrna/intern/rna_space.cc @@ -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(); + + 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(); + 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);