update addons (for 5.2)

This commit is contained in:
Nathan
2026-07-14 14:44:58 -06:00
parent a232ea1c9c
commit e3310263f3
246 changed files with 21097 additions and 9607 deletions
@@ -1,3 +1,5 @@
### Incremental Auto-Save
[Incremental Auto-Save](https://extensions.blender.org/add-ons/incremental-auto-save/) makes Blender's autosaves more configurable and reliable.
![Addon Preferences UI](/docs/incremental_autosave.png "Addon Preferences UI")
@@ -13,4 +15,4 @@
- Save modified images: For texture painting workflows, this will auto-save your images as well as the .blend file.
- Save Mesh/Sculpt: If you spend a long time in Edit/Sculpt mode and Blender crashes, you will lose your work, because the mesh data is not written back into the object until you go to Object Mode. The add-on will periodically enter object mode for you, without interrupting any Sculpt/Paint/Transform operations.
It goes without saying that this add-on can introduce periodic lag. This is simply the price of saving data to a disk. A slow disk or a large file will exacerbate this, and all you can do is adjust your save interval according to what you can put up with.
It goes without saying that this add-on can introduce periodic lag. This is simply the price of saving data to a disk. A slow disk or a large file will exacerbate this, and all you can do is adjust your save interval according to what you can put up with.
@@ -1,10 +1,13 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import annotations
import json
import os
import tempfile
from datetime import datetime
from pathlib import Path
from typing import Any
import bpy
from bl_ui.generic_ui_list import draw_ui_list
@@ -15,15 +18,23 @@ from bpy.props import (
IntProperty,
StringProperty,
)
from bpy.types import AddonPreferences, PropertyGroup, UIList
from bpy.types import (
AddonPreferences,
Context,
PropertyGroup,
UILayout,
UIList,
bpy_prop_collection,
)
from rna_prop_ui import IDPropertyGroup
# The add-on is confirmed to work with Blender 3.5 in 2026.
# As long as that remains true, there's no point removing this bl_info block.
bl_info = {
"name": "Incremental Autosave",
"author": "Demeter Dzadik",
"version": (1, 1, 1),
"blender": (2, 90, 0),
"version": (1, 1, 2),
"blender": (3, 5, 0),
"location": "blender",
"description": "Autosaves in a way where subsequent autosaves don't overwrite previous ones",
"category": "System",
@@ -38,7 +49,14 @@ LAUNCH_TIME = datetime.now()
class INCSAVE_UL_file_paths(UIList):
def draw_item(
self, context, layout, data, item, icon_value, active_data, active_propname
self,
_context: Context,
layout: UILayout,
_data: object,
item: AutoSavePath,
_icon_value: int,
_active_data: object,
_active_propname: str,
):
filepath: AutoSavePath = item
@@ -51,12 +69,14 @@ class INCSAVE_UL_file_paths(UIList):
row.prop(filepath, "path", text="")
def get_addon_prefs(context=None):
def get_addon_prefs(
context: Context | None = None,
) -> IncrementalAutoSavePreferences:
context = context or bpy.context
return context.preferences.addons[__name__].preferences
def update_prefs_on_file(self=None, context=None):
def update_prefs_on_file(_self=None, context: Context | None = None):
prefs = get_addon_prefs(context)
if not type(prefs).loading:
prefs.save_prefs_to_file()
@@ -71,7 +91,7 @@ class PrefsFileSaveLoadMixin:
import bpy, json
from pathlib import Path
class MyAddonPrefs(PrefsFileSaveLoadMixin, bpy.types.AddonPreferences):
class MyAddonPrefs(PrefsFileSaveLoadMixin, AddonPreferences):
some_prop: bpy.props.IntProperty(update=update_prefs_on_file)
def register():
@@ -91,7 +111,7 @@ class PrefsFileSaveLoadMixin:
@staticmethod
def register_autoload_from_file(delay=0.1):
def timer_func(_scene=None):
def timer_func(_scene: object = None):
prefs = get_addon_prefs()
prefs.load_prefs_from_file()
@@ -105,7 +125,7 @@ class PrefsFileSaveLoadMixin:
ret = {}
rna_class = None
if isinstance(propgroup, bpy.types.AddonPreferences):
if isinstance(propgroup, AddonPreferences):
prop_dict = {
key: getattr(propgroup, key)
for key in propgroup.bl_rna.properties.keys()
@@ -113,9 +133,7 @@ class PrefsFileSaveLoadMixin:
}
else:
property_group_class_name = type(propgroup).__name__
rna_class = bpy.types.PropertyGroup.bl_rna_get_subclass_py(
property_group_class_name
)
rna_class = PropertyGroup.bl_rna_get_subclass_py(property_group_class_name)
if not hasattr(rna_class, "properties"):
rna_class = None
prop_dict = {
@@ -127,7 +145,7 @@ class PrefsFileSaveLoadMixin:
for key, value in prop_dict.items():
if key in type(self).omit_from_disk:
continue
if type(value) in (list, bpy.types.bpy_prop_collection_idprop):
if type(value) is list or isinstance(value, bpy_prop_collection):
ret[key] = [self.prefs_to_dict_recursive(elem) for elem in value]
elif type(value) is IDPropertyGroup:
ret[key] = self.prefs_to_dict_recursive(value)
@@ -143,7 +161,11 @@ class PrefsFileSaveLoadMixin:
ret[key] = value
return ret
def apply_prefs_from_dict_recursive(self, propgroup, data):
def apply_prefs_from_dict_recursive(
self,
propgroup: IDPropertyGroup,
data: dict[str, Any],
):
for key, value in data.items():
if not hasattr(propgroup, key):
# Property got removed or renamed in the implementation.
@@ -165,7 +187,7 @@ class PrefsFileSaveLoadMixin:
addon_name = __package__.split(".")[-1]
return Path(bpy.utils.user_resource("CONFIG")) / Path(addon_name + ".txt")
def save_prefs_to_file(self, _context=None):
def save_prefs_to_file(self, _context: Context | None = None):
data_dict = self.prefs_to_dict_recursive(propgroup=self)
with open(self.get_prefs_filepath(), "w") as f:
@@ -190,7 +212,11 @@ class PrefsFileSaveLoadMixin:
class AutoSavePath(PropertyGroup):
name: StringProperty(update=update_prefs_on_file)
name: StringProperty(
name="Autosave Path Identifier",
description="Name of this autosave path",
update=update_prefs_on_file,
)
path: StringProperty(
name="Autosave Path",
description="An autosave path. If this filepath doesn't exist, the next existing one will be used. If none are valid, the Blender auto-save path will be used. If that's not valid either, the OS default temp folder will be used",
@@ -221,7 +247,7 @@ class IncrementalAutoSavePreferences(PrefsFileSaveLoadMixin, AddonPreferences):
update=update_prefs_on_file,
)
max_save_files: bpy.props.IntProperty(
max_save_files: IntProperty(
name="Max Backups Per File",
description="Maximum number of backups to save for each file, 0 means unlimited. Otherwise, the oldest file will be deleted after reaching the limit",
default=10,
@@ -229,7 +255,7 @@ class IncrementalAutoSavePreferences(PrefsFileSaveLoadMixin, AddonPreferences):
max=100,
update=update_prefs_on_file,
)
compress_files: bpy.props.BoolProperty(
compress_files: BoolProperty(
name="Compress Files",
description="Save backups with compression enabled",
default=True,
@@ -263,7 +289,7 @@ class IncrementalAutoSavePreferences(PrefsFileSaveLoadMixin, AddonPreferences):
assert bpy.data.filepath
return os.sep.join((os.path.dirname(bpy.data.filepath), "Autosaves"))
def get_valid_autosave_path(self, context) -> str:
def get_valid_autosave_path(self, context: Context) -> str:
"""Return an autosave path that will always actually exist, no matter how desperate."""
prefs = get_addon_prefs(context)
if bpy.data.filepath and prefs.relative_to_blend:
@@ -287,12 +313,19 @@ class IncrementalAutoSavePreferences(PrefsFileSaveLoadMixin, AddonPreferences):
sys_temp = tempfile.gettempdir()
return sys_temp
def draw(self, context):
def draw(self, context: Context):
layout = self.layout.column()
layout.use_property_decorate = False
layout.use_property_split = True
header, panel = layout.panel("Incremental Autosave: Paths", default_closed=True)
if hasattr(layout, "panel"):
header, panel = layout.panel(
"Incremental Autosave: Paths", default_closed=True
)
else:
header = layout.row()
panel = layout
header.label(text="Autosave Paths")
if panel:
split = panel.split(factor=0.4)
@@ -384,7 +417,7 @@ def save_file():
if addon_prefs.save_images:
save_images()
if addon_prefs.save_sculpt:
save_sculpt()
realize_sculpt_or_edit_mesh()
bpy.ops.wm.save_as_mainfile(
filepath=backup_file, copy=True, compress=addon_prefs.compress_files
)
@@ -409,25 +442,36 @@ def save_images():
print(f"Incremental Autosave: Failed to save dirty image: {img.name}")
def save_sculpt():
"""We just need to enter object mode and then go back to the original mode."""
def realize_sculpt_or_edit_mesh():
"""We just need to enter object mode and then go back to the original mode,
to write the mesh data to the object.
"""
context = bpy.context
if context.mode == "EDIT_MESH":
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="EDIT")
if context.mode == "SCULPT":
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="SCULPT")
try:
if context.mode == "EDIT_MESH":
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="EDIT")
if context.mode == "SCULPT":
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="SCULPT")
except RuntimeError:
# This can happen if we happen to catch user during some modal operator that's
# not accounted for by save_after_modal_operation().
print(
"Incremental Autosave Error: Mesh could not be written to Object due to running modal operators: ",
[op.bl_idname for op in context.window.modal_operators],
)
print("You could report this as a bug!")
@persistent
def save_before_close(_dummy=None):
def save_before_close(_dummy: object = None):
# is_dirty means there are changes that haven't been saved to disk
if bpy.data.is_dirty and get_addon_prefs().save_before_close:
save_file()
def create_autosave_on_timer():
def create_autosave_on_timer() -> int:
now = datetime.now()
delta = now - LAUNCH_TIME
prefs = get_addon_prefs()
@@ -439,7 +483,7 @@ def create_autosave_on_timer():
return prefs.save_interval * 60
def save_after_modal_operation():
def save_after_modal_operation() -> float | None:
"""Mesh edits, sculpt strokes, and texture paint strokes trigger a depsgraph update,
so it's better to make a save after one of those operations, rather than on a timer.
"""
@@ -462,7 +506,7 @@ def save_after_modal_operation():
@persistent
def register_autosave_timer(_dummy=None):
def register_autosave_timer(_dummy: object = None):
bpy.app.timers.register(create_autosave_on_timer)
@@ -1,12 +1,12 @@
schema_version = "1.0.0"
id = "incremental_auto_save"
version = "1.1.1"
version = "1.1.2"
name = "Incremental Auto-Save"
tagline = "Improvements to Blender's Autosave"
maintainer = "Demeter Dzadik <demeter@blender.org>"
type = "add-on"
website = "https://projects.blender.org/Mets/incremental-autosave"
website = "https://projects.blender.org/Mets/incremental-autosave#incremental-auto-save"
tags = ["System"]
blender_version_min = "4.2.0"
@@ -15,7 +15,7 @@ license = [
"SPDX:GPL-3.0-or-later"
]
copyright = [
"2019-2024 Demeter Dzadik"
"2019-2026 Demeter Dzadik"
]
[permissions]
files = "Save preferences & .blends in chosen directories"