save startup blend for animation tab & whatnot
This commit is contained in:
2026-04-08 12:10:18 -06:00
parent 57a652524a
commit 692e200ffe
180 changed files with 12336 additions and 3431 deletions
@@ -29,6 +29,7 @@ import fnmatch
import json
import os
import platform
import logging
import shutil
import ssl
import threading
@@ -43,6 +44,10 @@ import addon_utils
# Blender imports, used in limited cases.
import bpy
from . import tasks_queue
bk_logger = logging.getLogger(__name__)
# -----------------------------------------------------------------------------
# The main class
@@ -134,15 +139,15 @@ class SingletonUpdater:
self._select_link = select_link_function
def print_trace(self):
"""Print handled exception details when use_print_traces is set"""
"""Logs handled exception details when use_print_traces is set"""
if self._use_print_traces:
traceback.print_exc()
bk_logger.error("%s", traceback.format_exc())
def print_verbose(self, msg):
"""Print out a verbose logging message if verbose is true."""
"""Logs verbose messages if verbose is true."""
if not self._verbose:
return
print("🔄 {}: ".format(self.addon) + msg)
bk_logger.info("%s", msg)
# -------------------------------------------------------------------------
# Getters and setters
@@ -177,7 +182,7 @@ class SingletonUpdater:
def auto_reload_post_update(self, value):
try:
self._auto_reload_post_update = bool(value)
except:
except Exception:
raise ValueError("auto_reload_post_update must be a boolean value")
@property
@@ -227,7 +232,7 @@ class SingletonUpdater:
elif type(tuple_values) is not tuple:
try:
tuple(tuple_values)
except:
except Exception:
raise ValueError("current_version must be a tuple of integers")
for i in tuple_values:
if type(i) is not int:
@@ -277,7 +282,7 @@ class SingletonUpdater:
def include_branch_auto_check(self, value):
try:
self._include_branch_auto_check = bool(value)
except:
except Exception:
raise ValueError("include_branch_autocheck must be a boolean")
@property
@@ -295,7 +300,7 @@ class SingletonUpdater:
)
else:
self._include_branch_list = value
except:
except Exception:
raise ValueError("include_branch_list should be a list of valid branches")
@property
@@ -306,7 +311,7 @@ class SingletonUpdater:
def include_branches(self, value):
try:
self._include_branches = bool(value)
except:
except Exception:
raise ValueError("include_branches must be a boolean value")
@property
@@ -329,7 +334,7 @@ class SingletonUpdater:
def manual_only(self, value):
try:
self._manual_only = bool(value)
except:
except Exception:
raise ValueError("manual_only must be a boolean value")
@property
@@ -377,7 +382,7 @@ class SingletonUpdater:
def repo(self, value):
try:
self._repo = str(value)
except:
except Exception:
raise ValueError("repo must be a string value")
@property
@@ -404,8 +409,8 @@ class SingletonUpdater:
elif value is not None and not os.path.exists(value):
try:
os.makedirs(value)
except:
self.print_verbose("Error trying to staging path")
except Exception:
self.print_verbose("Error trying to create staging path")
self.print_trace()
return
self._updater_path = value
@@ -453,7 +458,7 @@ class SingletonUpdater:
def use_releases(self, value):
try:
self._use_releases = bool(value)
except:
except Exception:
raise ValueError("use_releases must be a boolean value")
@property
@@ -464,7 +469,7 @@ class SingletonUpdater:
def user(self, value):
try:
self._user = str(value)
except:
except Exception:
raise ValueError("User must be a string value")
@property
@@ -476,7 +481,7 @@ class SingletonUpdater:
try:
self._verbose = bool(value)
self.print_verbose("Verbose is enabled")
except:
except Exception:
raise ValueError("Verbose must be a boolean value")
@property
@@ -487,7 +492,7 @@ class SingletonUpdater:
def use_print_traces(self, value):
try:
self._use_print_traces = bool(value)
except:
except Exception:
raise ValueError("use_print_traces must be a boolean value")
@property
@@ -687,7 +692,7 @@ class SingletonUpdater:
request = urllib.request.Request(url)
try:
context = ssl._create_unverified_context()
except:
except Exception:
# Some blender packaged python versions don't have this, largely
# useful for local network setups otherwise minimal impact.
context = None
@@ -712,24 +717,24 @@ class SingletonUpdater:
if str(e.code) == "403":
self._error = "HTTP error (access denied)"
self._error_msg = str(e.code) + " - server error response"
print(self._error, self._error_msg)
bk_logger.error("%s %s", self._error, self._error_msg)
else:
self._error = "HTTP error"
self._error_msg = str(e.code)
print(self._error, self._error_msg)
self.print_trace()
bk_logger.error("%s %s", self._error, self._error_msg)
# self.print_trace()
self._update_ready = None
except urllib.error.URLError as e:
reason = str(e.reason)
if "TLSV1_ALERT" in reason or "SSL" in reason.upper():
self._error = "Connection rejected, download manually"
self._error_msg = reason
print(self._error, self._error_msg)
bk_logger.error("%s %s", self._error, self._error_msg)
else:
self._error = "URL error, check internet connection"
self._error_msg = reason
print(self._error, self._error_msg)
self.print_trace()
bk_logger.error("%s %s", self._error, self._error_msg)
# self.print_trace()
self._update_ready = None
return None
else:
@@ -748,7 +753,7 @@ class SingletonUpdater:
self._error = "API response has invalid JSON format"
self._error_msg = str(e.reason)
self._update_ready = None
print(self._error, self._error_msg)
bk_logger.error("%s %s", self._error, self._error_msg)
self.print_trace()
return None
else:
@@ -766,13 +771,13 @@ class SingletonUpdater:
try:
shutil.rmtree(local)
os.makedirs(local)
except:
except Exception:
error = "failed to remove existing staging directory"
self.print_trace()
else:
try:
os.makedirs(local)
except:
except Exception:
error = "failed to create staging directory"
self.print_trace()
@@ -811,8 +816,8 @@ class SingletonUpdater:
except Exception as e:
self._error = "Error retrieving download, bad link?"
self._error_msg = "Error: {}".format(e)
print("Error retrieving download, bad link?")
print("Error: {}".format(e))
bk_logger.error("Error retrieving download, bad link?")
bk_logger.error("Error: %s", e)
self.print_trace()
return False
@@ -829,7 +834,7 @@ class SingletonUpdater:
if os.path.isdir(local):
try:
shutil.rmtree(local)
except:
except Exception:
self.print_verbose(
"Failed to removed previous backup folder, continuing"
)
@@ -840,7 +845,7 @@ class SingletonUpdater:
if os.path.isdir(tempdest):
try:
shutil.rmtree(tempdest)
except:
except Exception:
self.print_verbose("Failed to remove existing temp folder, continuing")
self.print_trace()
@@ -852,15 +857,15 @@ class SingletonUpdater:
tempdest,
ignore=shutil.ignore_patterns(*self._backup_ignore_patterns),
)
except:
print("Failed to create backup, still attempting update.")
except Exception:
self.print_verbose("Failed to create backup, still attempting update.")
self.print_trace()
return
else:
try:
shutil.copytree(self._addon_root, tempdest)
except:
print("Failed to create backup, still attempting update.")
except Exception:
self.print_verbose("Failed to create backup, still attempting update.")
self.print_trace()
return
shutil.move(tempdest, local)
@@ -906,23 +911,23 @@ class SingletonUpdater:
try:
shutil.rmtree(outdir)
self.print_verbose("Source folder cleared")
except:
except Exception:
self.print_verbose("Error occurred while clearing extract dir")
self.print_trace()
# Create parent directories if needed, would not be relevant unless
# installing addon into another location or via an addon manager.
try:
os.mkdir(outdir)
except Exception as err:
print("Error occurred while making extract dir:")
print(str(err))
except Exception:
self.print_verbose("Error occurred while making extract dir")
self.print_trace()
self._error = "Install failed"
self._error_msg = "Failed to make extract directory"
return -1
if not os.path.isdir(outdir):
print("Failed to create source directory")
bk_logger.error("Failed to create source directory")
self._error = "Install failed"
self._error_msg = "Failed to create extract directory"
return -1
@@ -972,7 +977,7 @@ class SingletonUpdater:
if not os.path.isdir(unpath):
self._error = "Install failed"
self._error_msg = "Extracted path does not exist"
print("Extracted path does not exist: ", unpath)
bk_logger.error("Extracted path does not exist: %s", unpath)
return -1
if self._subfolder_path:
@@ -991,9 +996,8 @@ class SingletonUpdater:
# Smarter check for additional sub folders for a single folder
# containing the __init__.py file.
if not os.path.isfile(os.path.join(unpath, "__init__.py")):
print("Not a valid addon found")
print("Paths:")
print(dirlist)
bk_logger.error("Not a valid addon found")
bk_logger.error("Paths: %s", dirlist)
self._error = "Install failed"
self._error_msg = "No __init__ file found in new source"
return -1
@@ -1052,8 +1056,10 @@ class SingletonUpdater:
self.print_verbose(
"Clean removing file {}".format(os.path.join(base, f))
)
except Exception as e:
print(f"Error removing file {os.path.join(base, f)}: {e}")
except Exception:
bk_logger.exception(
"Error removing file %s:", os.path.join(base, f)
)
for f in folders:
if os.path.join(base, f) is self._updater_path:
continue
@@ -1064,12 +1070,14 @@ class SingletonUpdater:
os.path.join(base, f)
)
)
except Exception as e:
print(f"Error removing folder {os.path.join(base, f)}: {e}")
except Exception:
bk_logger.exception(
"Error removing folder %s:", os.path.join(base, f)
)
except Exception as err:
except Exception:
error = "failed to create clean existing addon folder"
print(error, str(err))
self.print_verbose(error)
self.print_trace()
# Walk through the base addon folder for rules on pre-removing
@@ -1087,7 +1095,7 @@ class SingletonUpdater:
os.remove(fl)
self.print_verbose("Pre-removed file " + file)
except OSError:
print("Failed to pre-remove " + file)
self.print_verbose("Failed to pre-remove " + file)
self.print_trace()
# Walk through the temp addon sub folder for replacements
@@ -1134,13 +1142,13 @@ class SingletonUpdater:
# File did not previously exist, simply move it over.
os.rename(srcFile, dest_file)
self.print_verbose("New file " + os.path.basename(dest_file))
except Exception as e:
print(f"Error replacing file {file}: {e}")
except Exception:
bk_logger.exception("Error replacing file %s:", file)
# now remove the temp staging folder and downloaded zip
try:
shutil.rmtree(staging_path)
except:
except Exception:
error = (
"Error: Failed to remove existing staging directory, "
"consider manually removing "
@@ -1152,7 +1160,7 @@ class SingletonUpdater:
# if post_update false, skip this function
# else, unload/reload addon & trigger popup
if not self._auto_reload_post_update:
print("Restart blender to reload addon and complete update")
bk_logger.info("Restart blender to reload addon and complete update")
return
self.print_verbose("Reloading addon...")
@@ -1165,12 +1173,12 @@ class SingletonUpdater:
bpy.ops.wm.addon_disable(module=self._addon_package)
bpy.ops.wm.addon_refresh()
bpy.ops.wm.addon_enable(module=self._addon_package)
print("2.7 reload complete")
bk_logger.info("2.7 reload complete")
else: # 2.8
bpy.ops.preferences.addon_disable(module=self._addon_package)
bpy.ops.preferences.addon_refresh()
bpy.ops.preferences.addon_enable(module=self._addon_package)
print("2.8 reload complete")
bk_logger.info("2.8 reload complete")
# -------------------------------------------------------------------------
# Other non-api functions and setups
@@ -1190,10 +1198,10 @@ class SingletonUpdater:
while 1:
data = url_file.read(chunk)
if not data:
# print("done.")
# bk_logger.info("done.")
break
f.write(data)
# print("Read %s bytes" % len(data))
# bk_logger.info("Read %s bytes" % len(data))
f.close()
def version_tuple_from_text(self, text):
@@ -1249,7 +1257,9 @@ class SingletonUpdater:
self.print_verbose("Skipping async check, already started")
# already running the bg thread
elif self._update_ready is None:
print("{} updater: Running background check for update".format(self.addon))
bk_logger.info(
"%s updater: Running background check for update", self.addon
)
self.start_async_check_update(False, callback)
def check_for_update_now(self, callback=None):
@@ -1266,7 +1276,7 @@ class SingletonUpdater:
self.start_async_check_update(True, callback)
def check_for_update(self, now=False):
"""Check for update not in a syncrhonous manner.
"""Check for update not in a synchronous manner.
This function is not async, will always return in sequential fashion
but should have a parent which calls it in another thread.
@@ -1449,7 +1459,7 @@ class SingletonUpdater:
res = self.stage_repository(self._update_link)
if not res:
print("Error in staging repository: " + str(res))
bk_logger.error("Error in staging repository: %s", str(res))
if callback is not None:
callback(self._addon_package, self._error_msg)
return self._error_msg
@@ -1467,7 +1477,7 @@ class SingletonUpdater:
res = self.stage_repository(self._update_link)
if not res:
print("Error in staging repository: " + str(res))
bk_logger.error("Error in staging repository: %s", str(res))
if callback:
callback(self._addon_package, self._error_msg)
return self._error_msg
@@ -1525,9 +1535,11 @@ class SingletonUpdater:
os.rename(old_json_path, json_path)
except FileNotFoundError:
pass
except Exception as err:
print("Other OS error occurred while trying to rename old JSON")
print(err)
except Exception:
self.print_verbose(
"Other OS error occurred while trying to rename old JSON"
)
self.print_trace()
return json_path
@@ -1571,7 +1583,7 @@ class SingletonUpdater:
jpath = self.get_json_path()
if not os.path.isdir(os.path.dirname(jpath)):
print(
bk_logger.error(
"State error: Directory does not exist, cannot save json: ",
os.path.basename(jpath),
)
@@ -1580,8 +1592,9 @@ class SingletonUpdater:
with open(jpath, "w") as outf:
data_out = json.dumps(self._json, indent=4)
outf.write(data_out)
except:
print("Failed to open/save data to json: ", jpath)
self.print_verbose(f"Wrote updater JSON settings to file: {jpath}")
except Exception:
self.print_verbose(f"Failed to open/save data to json: {jpath}")
self.print_trace()
self.print_verbose("Wrote out updater JSON settings with content:")
self.print_verbose(str(self._json))
@@ -1630,8 +1643,7 @@ class SingletonUpdater:
try:
self.check_for_update(now=now)
except Exception as exception:
print("Checking for update error:")
print(exception)
self.print_verbose(f"Checking for update error: {exception}")
self.print_trace()
if not self._error:
self._update_ready = False
@@ -1644,8 +1656,11 @@ class SingletonUpdater:
self._check_thread = None
if callback:
self.print_verbose("Finished check update, doing callback")
callback(self._update_ready)
self.print_verbose(
"Finished check update, queueing callback on main thread"
)
# Run the callback on Blender's main thread to avoid bpy access from a background thread.
tasks_queue.add_task((callback, (self._update_ready,)), wait=0)
self.print_verbose("BG thread: Finished check update, no callback")
def stop_async_check_update(self):