7

is there a way to automate render file names and avoid accidental overwrites?

E.G. add timestamp to filename?

I find my workflow involves many fast test renders, and it drives me nuts that for each time I launch F12 if I have not renamed previous render and overwrite is enabled I lose it, if overwrite is not enabled it does not save the file automatically.

I am using the compositor file output node to at least automate file saving for images

I do not understand why there is no simple way to press F12 and have a file with a unique name (eg. timestamp added at end) be saved automatically as soon as the render is complete, it seems ridiculous that you can wait maybe hours for a render to be done and risk losing it entirely because it is accidentally overwritten or you forget that you have not saved it.

After a day passed fruitlessly googling and reading forums I humbly ask for help here!

Ottavio Rava
  • 71
  • 1
  • 2
  • 2
    Related: https://blender.stackexchange.com/questions/127573/how-do-i-stop-the-compositor-output-node-from-overwriting-my-images-on-each-new/135494#135494 – p2or Mar 23 '20 at 11:34
  • All I want is a stopper: "Are you sure about overwriting the previous file? CANCEL OK" pop up. Obviously since I see the red filename box all the time I don't even pay attention as I just written over my texture image with a render of the object I was texturing image. – Eric Huelin Jan 09 '22 at 17:08

3 Answers3

11

The following add-on generates a unique file path from a given directory and the current timestamp. It modifies both the output path (Output Properties > Output) and every base path of File Output nodes.

UI screenshot

For instance, if the directory (Output Properties > Auto-Filename > Directory) is set to C:\tmp\ and the current timestamp is 2020-03-22_01_10_43_857754 then the output path is C:\tmp\2020-03-22_01_10_43_857754\#### and the base path of the File Output nodes are C:\tmp\2020-03-22_01_10_43_857754\[name of the file output node].

bl_info = {
    "name": "Auto-Filepath",
    "author": "Robert Guetzkow",
    "version": (1, 0, 2),
    "blender": (2, 80, 0),
    "location": "Output Properties > Auto-filepath",
    "description": "Automatically sets a unique filepath for each frame based on the current timestamp.",
    "warning": "",
    "wiki_url": "",
    "category": "Render"}

import bpy import datetime from pathlib import Path from bpy.app.handlers import persistent

@persistent def update_filepath(self): if not bpy.context.scene.auto_filepath_settings.use_auto_filepath: return now = datetime.datetime.now() path = Path(bpy.context.scene.auto_filepath_settings.directory) base_path = path / now.strftime('%Y-%m-%d_%H_%M_%S_%f') bpy.context.scene.render.filepath = str(base_path / "####") bpy.context.scene.render.use_file_extension = True bpy.context.scene.render.use_overwrite = False if bpy.context.scene.use_nodes: for node in bpy.context.scene.node_tree.nodes: if node.type == "OUTPUT_FILE": node.base_path = str(base_path / node.name)

def set_directory(self, value): path = Path(value) if path.is_dir(): self["directory"] = value

def get_directory(self): return self.get("directory", bpy.context.scene.auto_filepath_settings.bl_rna.properties["directory"].default)

class AutoFilepathSettings(bpy.types.PropertyGroup): use_auto_filepath: bpy.props.BoolProperty(name="Automatic filepath generation.", description="Enable/disable automatic filepath generation. When enabled, " "this will overwrite the output path and the base path of " "all File Output nodes.", default=False)

directory: bpy.props.StringProperty(name="Directory",
                                    description="Directory where files shall be stored.",
                                    default="/",
                                    maxlen=4096,
                                    subtype="DIR_PATH",
                                    set=set_directory,
                                    get=get_directory)


class AUTOFILEPATH_PT_panel(bpy.types.Panel): bl_label = "Auto-Filepath" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "output" bl_options = {"DEFAULT_CLOSED"}

def draw_header(self, context):
    self.layout.prop(context.scene.auto_filepath_settings, "use_auto_filepath", text="")

def draw(self, context):
    layout = self.layout
    layout.prop(context.scene.auto_filepath_settings, "directory")


classes = (AutoFilepathSettings, AUTOFILEPATH_PT_panel)

def register(): for cls in classes: bpy.utils.register_class(cls) bpy.types.Scene.auto_filepath_settings = bpy.props.PointerProperty(type=AutoFilepathSettings) if update_filepath not in bpy.app.handlers.render_pre: bpy.app.handlers.render_pre.append(update_filepath)

def unregister(): for cls in classes: bpy.utils.unregister_class(cls) del bpy.types.Scene.auto_filepath_settings if update_filepath in bpy.app.handlers.render_pre: bpy.app.handlers.render_pre.remove(update_filepath)

if name == "main": register()

The add-on can also be downloaded from GitHub Gist.

Robert Gützkow
  • 25,622
  • 3
  • 47
  • 78
  • Wow this is almost exactly what I need, thank you so much!

    If I may ask, is there a way to adapt the code so the renaming occurs just to the file name itself rather than creating a sub-folder for each render?

    In any case thank you so much for your help, this is already a big problem solved!

    – Ottavio Rava Mar 22 '20 at 21:39
  • Yes, that's possible. How should the names be formatted? It would still be necessary to have unique names among multiple file output nodes. Should the node name be included in the filename? – Robert Gützkow Mar 22 '20 at 21:55
  • Ideally I would like the file naming to work in this way: render image: filename is timestamp, file is in output folder. render animation: filename is timestamp + #### framenumber, in output folder or subfolder, it makes little difference. I currently only ever use one output node, and name does not need to be included, as compositing or separate render layers are not a big part of my workflow. Thanks! – Ottavio Rava Mar 23 '20 at 07:53
  • Please note that this doesn't auto-save images render with Render > Render Image (F12). While you're not using the multiple file output nodes and perhaps not multiple inputs per file output node, the add-on should still create unique names since the solution should also work for other people that need this feature. – Robert Gützkow Mar 23 '20 at 14:31
  • If you want to have Render Animation include the timestamp in the name you could change bpy.context.scene.render.filepath = str(base_path / "####") to bpy.context.scene.render.filepath = str(base_path) + "_####". – Robert Gützkow Mar 31 '20 at 18:45
  • In the register() and unregister(), can you explain why it looks in render_pre but adds it to or removes it from render_init? Shouldn't it check and add/remove in the same place?

    for example:

    if update_filepath not in bpy.app.handlers.render_init: bpy.app.handlers.render_init.append(update_filepath)

    I might have missed something, just curious?

    – ddd Oct 07 '20 at 23:33
  • 1
    @ddd That look like an error to me, but it's currently 3am. Let me take another look tomorrow. – Robert Gützkow Oct 08 '20 at 00:42
  • 1
    @ddd Yes, that was an error. It's fixed now. – Robert Gützkow Oct 08 '20 at 09:27
  • This add-on looks to be just what I need. It doesn't seem to install on Blender 3.2. Do you know if later versions of Blender are compatible with the add-on? – Austin Berenyi Jun 17 '22 at 19:31
  • @AustinBerenyi I've tested it in Blender 3.2 and it works without any issues. You need to copy and paste the code into a file and save it with the .py file extension. Then you should be able to install it through Edit > Preferences > Add-ons > Install. – Robert Gützkow Jun 18 '22 at 07:54
1

Press F2 and click "+" next to the file name input field. This indexes .blend files. The same goes for rendered images (F3). To my knowledge your problem involves scripting or code alteration as this is not yet automatic by default.

Lukasz-40sth
  • 3,062
  • 7
  • 19
  • 30
0

Here is my take Robert's plugin. Rather than creating new folder for each rendered image or animation frame the plugin will preppend timestamps to file names. The timestamp is created when the render job is started and not when the frame is rendered thus for animation renders the timestamp will be the same for all frames and thus images can be used as image sequence. There is also additional setting that lets you put animation jobs into a separate timestamped folder.

Save the code below as .py file and install as addon in Blender. Also for the near future code will be maintained on gist: https://gist.github.com/QuietNoise/c56c5f4bf1e42238928bf2cef481cde0

bl_info = {
    "name": "Auto-Filename",
    "author": "J. Wrong",
    "version": (1, 0, 1),
    "blender": (4, 00, 0),
    "location": "Output Properties > Auto-filename",
    "description": "Automatically sets a unique filename for each frame based on the current timestamp. Inspired by original code by Robert Guetzkow",
    "warning": "",
    "wiki_url": "",
    "category": "Render"}

import bpy import datetime from pathlib import Path from bpy.app.handlers import persistent

@persistent def update_filename(self): # If auto filename generation is disabled, do nothing. if not bpy.context.scene.auto_filename_settings.use_auto_filename: return

# Get the current timestamp and Path object for the base directory.
now = datetime.datetime.now()
base_path = Path(bpy.context.scene.auto_filename_settings.directory)
foldername = now.strftime('%Y-%m-%d_%H_%M_%S_%f')
filename = foldername + " - ####"

# Should animation renders be put in a subfolder?
if bpy.context.scene.auto_filename_settings.animations_in_subfolder:
    bpy.context.scene.render.filepath = str(base_path / foldername / filename)
else:
    bpy.context.scene.render.filepath = str(base_path / filename )

if bpy.context.scene.use_nodes:
    for node in bpy.context.scene.node_tree.nodes:
        if node.type == "OUTPUT_FILE":
            node.file_slots[0].path = node.name + " - " + filename


def set_directory(self, value): path = Path(value) if path.is_dir(): self["directory"] = value

def get_directory(self): return self.get("directory", bpy.context.scene.auto_filename_settings.bl_rna.properties["directory"].default)

class AutoFilenameSettings(bpy.types.PropertyGroup): use_auto_filename: bpy.props.BoolProperty(name="Automatic filename generation.", description="Enable/disable automatic filename generation for renders", default=False)

directory: bpy.props.StringProperty(name="Directory",
                                    description="Directory where files shall be stored",
                                    default="/",
                                    maxlen=4096,
                                    subtype="DIR_PATH",
                                    set=set_directory,
                                    get=get_directory)

animations_in_subfolder: bpy.props.BoolProperty(name="Put animation jobs in subfolder.",
                                          description="Whether animation render jobs should be put in a timestamped subfolder",
                                          default=False)


class AUTOFILENAME_PT_panel(bpy.types.Panel): bl_label = "Auto-Filename" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "output" bl_options = {"DEFAULT_CLOSED"}

def draw_header(self, context):
    self.layout.prop(context.scene.auto_filename_settings, "use_auto_filename", text="")

def draw(self, context):
    layout = self.layout
    layout.prop(context.scene.auto_filename_settings, "directory")
    layout.prop(context.scene.auto_filename_settings, "animations_in_subfolder", text="Put animation jobs in subfolder")


classes = (AutoFilenameSettings, AUTOFILENAME_PT_panel)

def register(): for cls in classes: bpy.utils.register_class(cls) bpy.types.Scene.auto_filename_settings = bpy.props.PointerProperty(type=AutoFilenameSettings) if update_filename not in bpy.app.handlers.render_init: bpy.app.handlers.render_init.append(update_filename)

def unregister(): for cls in classes: bpy.utils.unregister_class(cls) del bpy.types.Scene.auto_filename_settings if update_filename in bpy.app.handlers.render_init: bpy.app.handlers.render_init.remove(update_filename)

if name == "main": register()

J. Wrong
  • 191
  • 2
  • 4