1

I see via this question: How do I get blender to stop creating so many files?

That we can alter how many files are saved, and can turn off the feature.

My problem is that I still want the feature, but I don't want the .blend1, blend2.... files cluttering up my immediate workspace.

Is there a way to override this autosave functionality so it puts all my .blend1 Save Versions in another directory.

Say. I'm Editing a file called MyScene.blend... every 2 minutes it saves not to immediate directory but to

~/BlendSave/MyScene.blend1

I'm sure this can be done with an external file monitoring program, but it would be nice for blender to provide this functionality out of the box. I would have expected in Preferences -> File Paths for there to be a setting for this? (Yes there is the 'Temporary Files' setting, but blend1 files still show up in immediate work directory)

Ryu S.
  • 245
  • 3
  • 7
  • 1
    At the moment there is no way to change this behavior. It's hard wired in the code. – Marty Fouts Aug 23 '21 at 16:13
  • not sure if a custom script like that could help your mission: https://superuser.com/questions/226828/how-to-monitor-a-folder-and-trigger-a-command-line-action-when-a-file-is-created – Sanbaldo Aug 23 '21 at 17:37
  • I sync the folder to Google Drive using the desktop app and rely on their file version history, then turn this feature off in Blender. This does require you to fully upload every version of the file you want to have access to, which can become taxing. – Allen Simpson Aug 23 '21 at 20:32
  • I really don't think blender is doing anything more sophisticated than renaming your previous file before creating a new one. E.g. If this file name already exists in the same folder, rename and create new. – Allen Simpson Aug 23 '21 at 20:40

1 Answers1

2

I was also frustrated at there not being an option to redirect the automatic save versions to output to another path and the addon I found wasn't very customizable without manual work, so I created my own.

This addon allows setting a custom path for .blend file save versions to be moved to, with support for using environment variables and a few other variables such as the date, a UTC timestamp, the name of the current .blend file, etc.

The addon itself is pretty simple; it simply checks every time the current file is saved for the newest .blend# file with the same name, then copies it to the user-specified path, overwriting the oldest save version in the destination path if it's at the max amount of save versions (which can be set in the base Blender preferences at Edit > Preferences... > Save & Load > Save Versions). It then removes all .blend# files in the original directory to clean up.

As an example, if you have my_file.blend1 and my_file.blend2, it will copy my_file.blend2 (if it actually is the newest) to the path you set in the preferences and then remove my_file.blend1 and the original my_file.blend2.

This happens because I've witnessed a few stragglers that sometimes get left behind when a file is saved rapidly and simply checking for and moving .blend1 didn't seem to be consistent. It could be a logic error on my part, but I personally like the auto-cleanup anyway.

If you'd like to change this behavior to only remove the original .blend save version that was copied, replace the code (on lines 122-123)

for file in unmoved_save_vers:
    os.remove(file)

with

os.remove(saved_ver_file_path)

That will leave any other .blend# version files intact, though from my experience that means you'll occasionally have to manually delete some if you save too rapidly.

This is the save path I use myself (and the default), but you're free to change it to whatever you wish in the addon's preferences!

%temp%\Blender Backup Saves\{year}-{month}-{day}\{filename}\

So far I've used the addon with no problems but my python skills are rusty and I'm relatively new to using python with Blender so let me know if you spot or encounter any issues.

custom_save_versions_path_v1.0.0.zip

You can install it like most other Blender addons by going to Edit > Preferences... > Add-ons > Install... and selecting the .zip file, then enabling it. The single setting (to set the save path) is in the same panel.

Here's the entire code for the addon. You can install this manually yourself if you'd like by copy-pasting this into a file named __init__.py and putting it in a folder, then zipping that folder and installing it as an addon via the instructions above.

bl_info = {
    "name": "Custom Save Versions Path",
    "description": "Allows you to set and use a custom directory for Blender's save version files (.blend1, .blend2, etc).",
    "author": "Unordinal",
    "version": (1, 0, 0),
    "blender": (3, 6, 0),
    "location": "",
    "warning": "",
    "category": "System"
}

import bpy from bpy.app.handlers import persistent from bpy.types import Operator, AddonPreferences from bpy.props import StringProperty import os import re import shutil from stat import * import datetime from datetime import datetime

def get_custom_save_vers_path(): prefs = bpy.context.preferences.addons[name].preferences

current_datetime = datetime.utcnow()
epoch = datetime(1970, 1, 1)
timestamp = (current_datetime - epoch).total_seconds()

current_date = datetime.today()
year = current_date.year
month = current_date.month
day = current_date.day

file_path = bpy.data.filepath
file_directory = os.path.dirname(file_path)
file_name = os.path.splitext(bpy.path.basename(bpy.data.filepath))[0]

final_save_path = str(os.path.expandvars(prefs.save_path)).format(
    year=str(year),
    month=str(month),
    day=str(day),
    filedir=file_directory,
    filename=file_name,
    timestamp=str(int(timestamp))
)

return final_save_path

def get_save_ver_num_to_use(save_vers: list[str], max_save_vers: int): if not save_vers: return '1'

base_save_name = os.path.splitext(save_vers[0])[0]
for n in range(1, max_save_vers + 1):
    save_ver_name = base_save_name + f".blend{n}"
    if not os.path.isfile(save_ver_name):
        return str(n)

return get_oldest_file(save_vers)[-1]

def get_save_vers_in_directory(directory: str, blend_file_name: str=None): if not blend_file_name or len(blend_file_name) == 0: blend_file_name = ".blend"

save_ver_regex = re.compile(r".*%s\d+" % re.escape(blend_file_name))
return [os.path.join(directory, file) for file in os.listdir(directory) if save_ver_regex.match(file)]

def get_oldest_file(file_list: str) -> str: return min(file_list, key=os.path.getmtime)

def get_newest_file(file_list: str) -> str: return max(file_list, key=os.path.getmtime)

def clean_up_old_save_vers(save_vers: list[str], max_save_vers: int): if not save_vers: return

save_vers_over_max = len(save_vers) - max_save_vers
for _ in range(save_vers_over_max):
    oldest_file = get_oldest_file(save_vers)
    os.remove(oldest_file)
    save_vers.remove(oldest_file)

@persistent def save_version_move_handler(saved_file_path): saved_file_name = os.path.basename(saved_file_path)

custom_save_vers_path = get_custom_save_vers_path()
if not os.path.exists(custom_save_vers_path):
    os.makedirs(custom_save_vers_path)

max_save_vers = bpy.context.preferences.filepaths.save_version
save_vers = get_save_vers_in_directory(custom_save_vers_path, saved_file_name)
clean_up_old_save_vers(save_vers, max_save_vers)

if max_save_vers == 0:
    return

unmoved_save_vers = get_save_vers_in_directory(os.path.dirname(saved_file_path), saved_file_name) # C:\Users\meee\Blender\Scenes\test.blend1, [...].blend2, etc
saved_ver_file_path = get_newest_file(unmoved_save_vers) if unmoved_save_vers else None
if not saved_ver_file_path:
    return

saved_ver_base_path = os.path.join(custom_save_vers_path, saved_file_name)
saved_ver_num_to_use = get_save_ver_num_to_use(save_vers, max_save_vers)
saved_ver_output_path = saved_ver_base_path + saved_ver_num_to_use

# This creates a new file over the max if there's a gap in the numbering after a change by the user to the number of save versions.
# Won't fix since the old save over the max will get cleaned up next time the file is saved anyway.

FAIL_COPY_STR = "Failed to copy save version file '%s' to the destination '%s'" % (saved_ver_file_path, saved_ver_output_path)
try:
    dest_save_ver = shutil.copy(saved_ver_file_path, saved_ver_output_path)
    os.utime(dest_save_ver)

    if not os.path.isfile(dest_save_ver):
        print(FAIL_COPY_STR)
        return

    # The save version file was copied successfully, remove any originals.
    for file in unmoved_save_vers:
        os.remove(file)

except Exception as err:
    print(f"{FAIL_COPY_STR}: {err}")

class CustomSaveVersionsPathPreferences(AddonPreferences): bl_idname = name

save_path: StringProperty(
    name="Save Path",
    subtype='DIR_PATH', #https://docs.blender.org/api/current/bpy_types_enum_items/property_subtype_items.html#rna-enum-property-subtype-items
    default="%temp%\\Blender Backup Saves\\{year}-{month}-{day}\\{filename}\\",
    description="""
        The path to move newly-created save versions to.
            Year: {year}
            Month: {month}
            Day: {day}
            UTC timestamp: {timestamp}
            .blend file directory: {filedir}
            .blend file name: {filename}
    """
)

def draw(self, context):
    layout = self.layout
    layout.label(text="Use 'Preferences > Save & Load > Save Versions' to set how many versions are saved.")
    layout.prop(self, "save_path")

def register(): bpy.utils.register_class(CustomSaveVersionsPathPreferences) bpy.app.handlers.save_post.append(save_version_move_handler)

def unregister(): bpy.utils.unregister_class(CustomSaveVersionsPathPreferences) bpy.app.handlers.save_post.remove(save_version_move_handler)

if name == "main": register()

Unordinal
  • 121
  • 3