Fix #134920: File Output writes frame for single render

The File Output node always appends the frame number even if the render
is not an animation. This patch makes it such that the frame number is
only written if the render is an animation. The user can use a frame
variable to manually append the frame number if needed.

The command line rendering interface already uses animation rendering in
all cases, so it should not be affected. However, rendering using the
render.render() operator with animation=False will see the behavior
change, however, setting animation=False and start_frame and end_frame
to the same frame number should be sufficient to restore the old
behavior.

Pull Request: https://projects.blender.org/blender/blender/pulls/141209
This commit is contained in:
Omar Emara
2025-09-12 09:01:31 +02:00
committed by Omar Emara
parent 320092d0e2
commit b10d767c4e
4 changed files with 27 additions and 4 deletions

View File

@@ -90,6 +90,10 @@ class FileOutput {
* data from each of the evaluations of each view, for instance, to save all views in a single file
* for the File Output node, see the file_outputs_ member for more information. */
class RenderContext {
public:
/* True if the render context represents an animation render. */
bool is_animation_render = false;
private:
/* A mapping between file outputs and their image file paths. Those are constructed in the
* get_file_output method and saved in the save_file_outputs method. See those methods for more

View File

@@ -169,6 +169,7 @@ static Vector<path_templates::Error> compute_image_path(const StringRefNull dire
const ImageFormatData &format,
const Scene &scene,
const bNode &node,
const bool is_animation_render,
char *r_image_path)
{
char base_path[FILE_MAX] = "";
@@ -188,7 +189,7 @@ static Vector<path_templates::Error> compute_image_path(const StringRefNull dire
frame_number,
&format,
scene.r.scemode & R_EXTENSION,
true,
is_animation_render,
BKE_scene_multiview_view_suffix_get(&scene.r, view));
}
@@ -238,8 +239,16 @@ static void output_path_layout(uiLayout *layout,
{
char image_path[FILE_MAX];
const Vector<path_templates::Error> path_errors = compute_image_path(
directory, file_name, file_name_suffix, view, scene.r.cfra, format, scene, node, image_path);
const Vector<path_templates::Error> path_errors = compute_image_path(directory,
file_name,
file_name_suffix,
view,
scene.r.cfra,
format,
scene,
node,
false,
image_path);
if (path_errors.is_empty()) {
layout->label(image_path, ICON_FILE_IMAGE);
@@ -729,6 +738,7 @@ class FileOutputOperation : public NodeOperation {
format,
this->context().get_scene(),
this->bnode(),
this->is_animation_render(),
r_image_path);
if (!path_errors.is_empty()) {
@@ -782,6 +792,14 @@ class FileOutputOperation : public NodeOperation {
domain.transformation.location() = float2(0.0f);
return domain;
}
bool is_animation_render()
{
if (!this->context().render_context()) {
return false;
}
return this->context().render_context()->is_animation_render;
}
};
static NodeOperation *get_compositor_operation(Context &context, DNode node)

View File

@@ -1366,6 +1366,7 @@ static void do_render_compositor(Render *re)
CLOG_STR_INFO(&LOG, "Executing compositor");
blender::compositor::RenderContext compositor_render_context;
compositor_render_context.is_animation_render = re->flag & R_ANIMATION;
LISTBASE_FOREACH (RenderView *, rv, &re->result->views) {
COM_execute(re,
&re->r,

View File

@@ -209,7 +209,7 @@ class FileOutputTest(unittest.TestCase):
# Set output directory for all existing file output nodes.
set_directory(bpy.data.scenes[0].compositing_node_group, f'{curr_out_dir}/')
bpy.data.scenes[0].render.compositor_device = f'{self.execution_device}'
bpy.ops.render.render()
bpy.ops.render.render(animation=True, start_frame=1, end_frame=1)
if __name__ == "__main__":