# MIT License import os import ssl import bpy import time import json import urllib import shutil import pathlib import zipfile import addon_utils from threading import Thread from collections import OrderedDict from bpy.app.handlers import persistent from .tools.translations import t from .tools.common import wrap_dynamic_enum_items from . import CATS_VERSION, dev_branch no_ver_check = False fake_update = False is_checking_for_update = False checked_on_startup = False version_list = None current_version = [] current_version_str = '' update_needed = False latest_version = None latest_version_str = '' used_updater_panel = False update_finished = False remind_me_later = False is_ignored_version = False confirm_update_to = '' show_error = '' main_dir = os.path.dirname(__file__) downloads_dir = os.path.join(main_dir, "downloads") resources_dir = os.path.join(main_dir, "resources") ignore_ver_file = os.path.join(resources_dir, "ignore_version.txt") no_auto_ver_check_file = os.path.join(resources_dir, "no_auto_ver_check.txt") # Get package name, important for panel in user preferences package_name = '' for mod in addon_utils.modules(): if mod.bl_info['name'] == 'Cats Blender Plugin': package_name = mod.__name__ # Icons for UI ICON_URL = 'URL' class CheckForUpdateButton(bpy.types.Operator): bl_idname = 'cats_updater.check_for_update' bl_label = t('CheckForUpdateButton.label') bl_description = t('CheckForUpdateButton.desc') bl_options = {'INTERNAL'} @classmethod def poll(cls, context): return not is_checking_for_update def execute(self, context): global used_updater_panel used_updater_panel = True check_for_update_background() return {'FINISHED'} class UpdateToLatestButton(bpy.types.Operator): bl_idname = 'cats_updater.update_latest' bl_label = t('UpdateToLatestButton.label') bl_description = t('UpdateToLatestButton.desc') bl_options = {'INTERNAL'} @classmethod def poll(cls, context): return update_needed def execute(self, context): global confirm_update_to, used_updater_panel confirm_update_to = 'latest' used_updater_panel = True bpy.ops.cats_updater.confirm_update_panel('INVOKE_DEFAULT') return {'FINISHED'} class UpdateToSelectedButton(bpy.types.Operator): bl_idname = 'cats_updater.update_selected' bl_label = t('UpdateToSelectedButton.label') bl_description = t('UpdateToSelectedButton.desc') bl_options = {'INTERNAL'} @classmethod def poll(cls, context): if is_checking_for_update or not version_list: return False return True def execute(self, context): global confirm_update_to, used_updater_panel confirm_update_to = context.scene.cats_updater_version_list used_updater_panel = True bpy.ops.cats_updater.confirm_update_panel('INVOKE_DEFAULT') return {'FINISHED'} class UpdateToDevButton(bpy.types.Operator): bl_idname = 'cats_updater.update_dev' bl_label = t('UpdateToDevButton.label') bl_description = t('UpdateToDevButton.desc') bl_options = {'INTERNAL'} def execute(self, context): global confirm_update_to, used_updater_panel confirm_update_to = 'dev' used_updater_panel = True bpy.ops.cats_updater.confirm_update_panel('INVOKE_DEFAULT') return {'FINISHED'} class RemindMeLaterButton(bpy.types.Operator): bl_idname = 'cats_updater.remind_me_later' bl_label = t('RemindMeLaterButton.label') bl_description = t('RemindMeLaterButton.desc') bl_options = {'INTERNAL'} def execute(self, context): global remind_me_later remind_me_later = True self.report({'INFO'}, t('RemindMeLaterButton.success')) return {'FINISHED'} class IgnoreThisVersionButton(bpy.types.Operator): bl_idname = 'cats_updater.ignore_this_version' bl_label = t('IgnoreThisVersionButton.label') bl_description = t('IgnoreThisVersionButton.desc') bl_options = {'INTERNAL'} def execute(self, context): set_ignored_version() self.report({'INFO'}, t('IgnoreThisVersionButton.success', name=latest_version_str)) return {'FINISHED'} class ShowPatchnotesPanel(bpy.types.Operator): bl_idname = 'cats_updater.show_patchnotes' bl_label = t('ShowPatchnotesPanel.label') bl_description = t('ShowPatchnotesPanel.desc') bl_options = {'INTERNAL'} @classmethod def poll(cls, context): if is_checking_for_update or not version_list: return False return True def execute(self, context): return {'FINISHED'} def invoke(self, context, event): global used_updater_panel used_updater_panel = True dpi_value = get_user_preferences().system.dpi return context.window_manager.invoke_props_dialog(self, width=int(dpi_value * 8.2)) def check(self, context): # Important for changing options return True def draw(self, context): layout = self.layout col = layout.column(align=True) row = col.row(align=True) row.prop(context.scene, 'cats_updater_version_list') if context.scene.cats_updater_version_list: version = version_list.get(context.scene.cats_updater_version_list) col.separator() row = col.row(align=True) row.label(text=t('ShowPatchnotesPanel.releaseDate', date=version[2])) col.separator() for line in version[1].replace('**', '').split('\r\n'): row = col.row(align=True) row.scale_y = 0.75 row.label(text=line) col.separator() class ConfirmUpdatePanel(bpy.types.Operator): bl_idname = 'cats_updater.confirm_update_panel' bl_label = t('ConfirmUpdatePanel.label') bl_description = t('ConfirmUpdatePanel.desc') bl_options = {'INTERNAL'} show_patchnotes = False def execute(self, context): print('UPDATE TO ' + confirm_update_to) if confirm_update_to == 'dev': update_now(dev=True) elif confirm_update_to == 'latest': update_now(latest=True) else: update_now(version=confirm_update_to) return {'FINISHED'} def invoke(self, context, event): dpi_value = get_user_preferences().system.dpi return context.window_manager.invoke_props_dialog(self, width=int(dpi_value * 4.1)) def check(self, context): # Important for changing options return True def draw(self, context): layout = self.layout col = layout.column(align=True) version_str = confirm_update_to if confirm_update_to == 'latest': version_str = latest_version_str elif confirm_update_to == 'dev': version_str = 'Dev' col.separator() row = col.row(align=True) row.label(text='Version: ' + version_str) if confirm_update_to == 'dev': col.separator() col.separator() row = col.row(align=True) row.scale_y = 0.75 row.label(text=t('ConfirmUpdatePanel.warn.dev1')) row = col.row(align=True) row.scale_y = 0.75 row.label(text=t('ConfirmUpdatePanel.warn.dev2')) row = col.row(align=True) row.scale_y = 0.75 row.label(text=t('ConfirmUpdatePanel.warn.dev3')) row = col.row(align=True) row.scale_y = 0.75 row.label(text=t('ConfirmUpdatePanel.warn.dev4')) row = col.row(align=True) row.scale_y = 0.75 row.label(text=t('ConfirmUpdatePanel.warn.dev5')) else: row.operator(ShowPatchnotesPanel.bl_idname, text=t('ConfirmUpdatePanel.ShowPatchnotesPanel.label')) col.separator() col.separator() # col.separator() row = col.row(align=True) row.scale_y = 0.65 # row.label(text='Update now to ' + version_str + ':', icon=ICON_URL) row.label(text=t('ConfirmUpdatePanel.updateNow'), icon=ICON_URL) class UpdateCompletePanel(bpy.types.Operator): bl_idname = 'cats_updater.update_complete_panel' bl_label = t('UpdateCompletePanel.label') bl_description = t('UpdateCompletePanel.desc') bl_options = {'INTERNAL'} show_patchnotes = False def execute(self, context): return {'FINISHED'} def invoke(self, context, event): dpi_value = get_user_preferences().system.dpi return context.window_manager.invoke_props_dialog(self, width=int(dpi_value * 4.1)) def check(self, context): # Important for changing options return True def draw(self, context): layout = self.layout col = layout.column(align=True) if update_finished: row = col.row(align=True) row.scale_y = 0.9 row.label(text=t('UpdateCompletePanel.success1'), icon='FILE_TICK') row = col.row(align=True) row.scale_y = 0.9 row.label(text=t('UpdateCompletePanel.success2'), icon='BLANK1') else: row = col.row(align=True) row.scale_y = 0.9 row.label(text=t('UpdateCompletePanel.failure1'), icon='CANCEL') row = col.row(align=True) row.scale_y = 0.9 row.label(text=t('UpdateCompletePanel.failure2'), icon='BLANK1') class UpdateNotificationPopup(bpy.types.Operator): bl_idname = 'cats_updater.update_notification_popup' bl_label = t('UpdateNotificationPopup.label') bl_description = t('UpdateNotificationPopup.desc') bl_options = {'INTERNAL'} def execute(self, context): action = context.scene.cats_update_action if action == 'UPDATE': update_now(latest=True) elif action == 'IGNORE': set_ignored_version() else: # Remind later aka defer global remind_me_later remind_me_later = True ui_refresh() return {'FINISHED'} def invoke(self, context, event): dpi_value = get_user_preferences().system.dpi return context.window_manager.invoke_props_dialog(self, width=int(dpi_value * 4.6)) # def invoke(self, context, event): # return context.window_manager.invoke_props_dialog(self) def check(self, context): # Important for changing options return True def draw(self, context): layout = self.layout col = layout.column(align=True) row = layout_split(col, factor=0.55, align=True) row.scale_y = 1.05 row.label(text=t('UpdateNotificationPopup.newUpdate', name=latest_version_str), icon='SOLO_ON') row.operator(ShowPatchnotesPanel.bl_idname, text=t('UpdateNotificationPopup.ShowPatchnotesPanel.label')) col.separator() col.separator() col.separator() row = col.row(align=True) row.prop(context.scene, 'cats_update_action', expand=True) def check_for_update_background(check_on_startup=False): global is_checking_for_update, checked_on_startup if check_on_startup and checked_on_startup: # print('ALREADY CHECKED ON STARTUP') return if is_checking_for_update: # print('ALREADY CHECKING') return checked_on_startup = True if check_on_startup and os.path.isfile(no_auto_ver_check_file): print('AUTO CHECK DISABLED VIA FILE') return is_checking_for_update = True thread = Thread(target=check_for_update, args=[]) thread.start() def check_for_update(): print('Checking for Cats update...') # Get all releases from Github if not get_github_releases('teamneoneko'): finish_update_checking(error=t('check_for_update.cantCheck')) return # Check if an update is needed global update_needed, is_ignored_version update_needed = check_for_update_available() is_ignored_version = check_ignored_version() # Update needed, show the notification popup if it wasn't checked through the UI if update_needed: print('Update found!') if not used_updater_panel and not is_ignored_version: prepare_to_show_update_notification() else: print('No update found.') # Finish update checking, update the UI finish_update_checking() def get_github_releases(repo): global version_list version_list = OrderedDict() if fake_update: print('FAKE INSTALL!') version = 'v-99-99-99' version_tag = version.replace('-', '.') if version_tag.startswith('v.'): version_tag = version_tag[2:] if version_tag.startswith('v'): version_tag = version_tag[1:] version_list[version_tag] = ['', 'Put exiting new stuff here', 'Today'] version_list['12.34.56.78'] = ['', 'Nothing new to see', 'A week ago probably'] return True try: ssl._create_default_https_context = ssl._create_unverified_context with urllib.request.urlopen('https://api.github.com/repos/' + repo + '/Cats-Blender-Plugin-Unofficial-/releases') as url: data = json.loads(url.read().decode()) except urllib.error.URLError: print('URL ERROR') return False if not data: return False if bpy.app.version >= (4, 2) and bpy.app.version < (4, 3): tag_prefix = "4.2." for version in data: full_tag = version.get('tag_name') if not full_tag.startswith(tag_prefix): continue version_tag = full_tag[len(tag_prefix):] # Normalize version_tag version_tag = version_tag.replace('-', '.') # Store full tag version_list[full_tag] = [ version['zipball_url'], version['body'], version['published_at'].split('T')[0] ] return True def check_for_update_available(): if not version_list: return False global latest_version, latest_version_str latest_version = [] for version in version_list.keys(): latest_version_str = version for i in version.split('.'): if i.isdigit(): latest_version.append(int(i)) if latest_version: break # print(latest_version, '>', current_version) if latest_version > current_version: return True def finish_update_checking(error=''): global is_checking_for_update, show_error is_checking_for_update = False # Only show error if the update panel was used before if used_updater_panel: show_error = error ui_refresh() def ui_refresh(): # A way to refresh the ui refreshed = False while not refreshed: if hasattr(bpy.data, 'window_managers'): for windowManager in bpy.data.window_managers: for window in windowManager.windows: for area in window.screen.areas: area.tag_redraw() refreshed = True # print('Refreshed UI') else: time.sleep(0.5) def get_update_post(): if hasattr(bpy.app.handlers, 'scene_update_post'): return bpy.app.handlers.scene_update_post else: return bpy.app.handlers.depsgraph_update_post def prepare_to_show_update_notification(): # This is necessary to show a popup directly after startup # You will get a nasty error otherwise # This will add the function to the scene_update_post and it will be executed every frame. that's why it needs to be removed again asap # print('PREPARE TO SHOW UI') if show_update_notification not in get_update_post(): get_update_post().append(show_update_notification) @persistent def show_update_notification(scene): # One argument in necessary for some reason # print('SHOWING UI NOW!!!!') # # Immediately remove this from handlers again if show_update_notification in get_update_post(): get_update_post().remove(show_update_notification) # Show notification popup atr = UpdateNotificationPopup.bl_idname.split(".") getattr(getattr(bpy.ops, atr[0]), atr[1])('INVOKE_DEFAULT') def update_now(version=None, latest=False, dev=False): if fake_update: finish_update() return if dev: print('UPDATE TO DEVELOPMENT') update_link = 'https://github.com/teamneoneko/Cats-Blender-Plugin-Unofficial-/archive/blender-42-dev.zip' elif latest or not version: print('UPDATE TO ' + latest_version_str) update_link = version_list.get(latest_version_str)[0] bpy.context.scene.cats_updater_version_list = latest_version_str else: print('UPDATE TO ' + version) update_link = version_list[version][0] download_file(update_link) def download_file(update_url): # Load all the directories and files update_zip_file = os.path.join(downloads_dir, "cats-update.zip") # Remove existing download folder if os.path.isdir(downloads_dir): print("DOWNLOAD FOLDER EXISTED") shutil.rmtree(downloads_dir) # Create download folder pathlib.Path(downloads_dir).mkdir(exist_ok=True) # Download zip print('DOWNLOAD FILE') try: ssl._create_default_https_context = ssl._create_unverified_context urllib.request.urlretrieve(update_url, update_zip_file) except urllib.error.URLError: print("FILE COULD NOT BE DOWNLOADED") shutil.rmtree(downloads_dir) finish_update(error=t('download_file.cantConnect')) return print('DOWNLOAD FINISHED') # If zip is not downloaded, abort if not os.path.isfile(update_zip_file): print("ZIP NOT FOUND!") shutil.rmtree(downloads_dir) finish_update(error=t('download_file.cantFindZip')) return # Extract the downloaded zip print('EXTRACTING ZIP') with zipfile.ZipFile(update_zip_file, "r") as zip_ref: zip_ref.extractall(downloads_dir) print('EXTRACTED') # Delete the extracted zip file print('REMOVING ZIP FILE') os.remove(update_zip_file) # Detect the extracted folders and files print('SEARCHING FOR INIT 1') def searchInit(path): print('SEARCHING IN ' + path) files = os.listdir(path) if "__init__.py" in files: print('FOUND') return path folders = [f for f in os.listdir(path) if os.path.isdir(os.path.join(path, f))] if len(folders) != 1: print(len(folders), 'FOLDERS DETECTED') return None print('GOING DEEPER') return searchInit(os.path.join(path, folders[0])) print('SEARCHING FOR INIT 2') extracted_zip_dir = searchInit(downloads_dir) if not extracted_zip_dir: print("INIT NOT FOUND!") shutil.rmtree(downloads_dir) # finish_reloading() finish_update(error=t('download_file.cantFindCATS')) return # Remove old addon files clean_addon_dir() # Move the extracted files to their correct places def move_files(from_dir, to_dir): print('MOVE FILES TO DIR:', to_dir) files = os.listdir(from_dir) for file in files: file_dir = os.path.join(from_dir, file) target_dir = os.path.join(to_dir, file) print('MOVE', file_dir) # If file exists if os.path.isfile(file_dir) and os.path.isfile(target_dir): os.remove(target_dir) shutil.move(file_dir, to_dir) print('REMOVED AND MOVED', file) elif os.path.isdir(file_dir) and os.path.isdir(target_dir): move_files(file_dir, target_dir) else: shutil.move(file_dir, to_dir) print('MOVED', file) move_files(extracted_zip_dir, main_dir) # Delete download folder print('DELETE DOWNLOADS DIR') shutil.rmtree(downloads_dir) # Finish the update finish_update() def finish_update(error=''): global update_finished, show_error show_error = error if not error: update_finished = True bpy.ops.cats_updater.update_complete_panel('INVOKE_DEFAULT') ui_refresh() print("UPDATE DONE!") def clean_addon_dir(): print("CLEAN ADDON FOLDER") # first remove root files and folders (except update folder, important folders and resource folder) files = [f for f in os.listdir(main_dir) if os.path.isfile(os.path.join(main_dir, f))] folders = [f for f in os.listdir(main_dir) if os.path.isdir(os.path.join(main_dir, f))] for f in files: file = os.path.join(main_dir, f) try: os.remove(file) print("Clean removing file {}".format(file)) except OSError: print("Failed to pre-remove file " + file) for f in folders: folder = os.path.join(main_dir, f) if f.startswith('.') or f == 'resources' or f == 'downloads': continue try: shutil.rmtree(folder) print("Clean removing folder and contents {}".format(folder)) except OSError: print("Failed to pre-remove folder " + folder) # then remove resource files and folders (except settings and google dict) resources_folder = os.path.join(main_dir, 'resources') files = [f for f in os.listdir(resources_folder) if os.path.isfile(os.path.join(resources_folder, f))] folders = [f for f in os.listdir(resources_folder) if os.path.isdir(os.path.join(resources_folder, f))] for f in files: if f == 'settings.json' or f == 'dictionary_google.json': continue file = os.path.join(resources_folder, f) try: os.remove(file) print("Clean removing file {}".format(file)) except OSError: print("Failed to pre-remove " + file) for f in folders: folder = os.path.join(resources_folder, f) try: shutil.rmtree(folder) print("Clean removing folder and contents {}".format(folder)) except OSError: print("Failed to pre-remove folder " + folder) def set_ignored_version(): # Create resources folder pathlib.Path(resources_dir).mkdir(exist_ok=True) # Create ignore file with open(ignore_ver_file, 'w', encoding="utf8") as outfile: outfile.write(latest_version_str) # Set ignored status global is_ignored_version is_ignored_version = True print('IGNORE VERSION ' + latest_version_str) def check_ignored_version(): if not os.path.isfile(ignore_ver_file): # print('IGNORE FILE NOT FOUND') return False # Read ignore file with open(ignore_ver_file, 'r', encoding="utf8") as outfile: version = outfile.read() # Check if the latest version matches the one in the ignore file if latest_version_str == version: print('Update ignored.') return True # Delete ignore version file if the latest version is not the version in the file try: os.remove(ignore_ver_file) except OSError: print("FAILED TO REMOVE IGNORE VERSION FILE") return False def get_version_list(self, context): choices = [] if version_list: for version in version_list.keys(): choices.append((version, version, version)) return choices def get_user_preferences(): return bpy.context.user_preferences if hasattr(bpy.context, 'user_preferences') else bpy.context.preferences def layout_split(layout, factor=0.0, align=False): return layout.split(factor=factor, align=align) def draw_update_notification_panel(layout): if not update_needed or remind_me_later or is_ignored_version: # pass return col = layout.column(align=True) if update_finished: col.separator() row = col.row(align=True) row.label(text=t('draw_update_notification_panel.success'), icon='ERROR') col.separator() return row = col.row(align=True) row.scale_y = 0.75 row.label(text=t('draw_update_notification_panel.newUpdate', name=latest_version_str), icon='SOLO_ON') col.separator() row = col.row(align=True) row.scale_y = 1.3 row.operator(UpdateToLatestButton.bl_idname, text=t('draw_update_notification_panel.UpdateToLatestButton.label')) row = col.row(align=True) row.scale_y = 1 row.operator(RemindMeLaterButton.bl_idname, text=t('draw_update_notification_panel.RemindMeLaterButton.label')) row.operator(IgnoreThisVersionButton.bl_idname, text=t('draw_update_notification_panel.IgnoreThisVersionButton.label')) def draw_updater_panel(context, layout, user_preferences=False): col = layout.column(align=True) scale_big = 2 scale_small = 1.2 row = col.row(align=True) row.scale_y = 0.8 row.label(text=t('draw_updater_panel.updateLabel') if not user_preferences else t('draw_updater_panel.updateLabel_alt'), icon=ICON_URL) col.separator() if update_finished: col.separator() row = col.row(align=True) row.label(text=t('draw_updater_panel.success'), icon='ERROR') col.separator() return if show_error: row = col.row(align=True) row.label(text=show_error, icon='ERROR') col.separator() if is_checking_for_update: if not used_updater_panel: row = col.row(align=True) row.scale_y = scale_big row.operator(CheckForUpdateButton.bl_idname, text=t('draw_updater_panel.CheckForUpdateButton.label')) else: split = col.row(align=True) row = split.row(align=True) row.scale_y = scale_big row.operator(CheckForUpdateButton.bl_idname, text=t('draw_updater_panel.CheckForUpdateButton.label')) row = split.row(align=True) row.alignment = 'RIGHT' row.scale_y = scale_big row.operator(CheckForUpdateButton.bl_idname, text="", icon='FILE_REFRESH') elif update_needed: split = col.row(align=True) row = split.row(align=True) row.scale_y = scale_big row.operator(UpdateToLatestButton.bl_idname, text=t('draw_updater_panel.UpdateToLatestButton.label', name=latest_version_str)) row = split.row(align=True) row.alignment = 'RIGHT' row.scale_y = scale_big row.operator(CheckForUpdateButton.bl_idname, text="", icon='FILE_REFRESH') elif not used_updater_panel or not version_list: row = col.row(align=True) row.scale_y = scale_big row.operator(CheckForUpdateButton.bl_idname, text=t('draw_updater_panel.CheckForUpdateButton.label_alt')) else: split = col.row(align=True) row = split.row(align=True) row.scale_y = scale_big row.operator(UpdateToLatestButton.bl_idname, text=t('draw_updater_panel.UpdateToLatestButton.label_alt')) row = split.row(align=True) row.alignment = 'RIGHT' row.scale_y = scale_big row.operator(CheckForUpdateButton.bl_idname, text="", icon='FILE_REFRESH') # col.separator() # col.separator() # col.separator() # row = layout_split(col, factor=0.6, align=True) # row.scale_y = 0.9 # row.active = True if not is_checking_for_update and version_list else False # row.label(text="Select Version:") # row.prop(context.scene, 'cats_updater_version_list', text='') # # row = layout_split(col, factor=0.6, align=True) # row.scale_y = scale_small # row.operator(UpdateToSelectedButton.bl_idname, text='Install Selected Version') # row.operator(ShowPatchnotesPanel.bl_idname, text='Show Patchnotes') col.separator() col.separator() split = col.row(align=True) row = layout_split(split, factor=0.55, align=True) row.scale_y = scale_small row.active = True if not is_checking_for_update and version_list else False row.operator(UpdateToSelectedButton.bl_idname, text=t('draw_updater_panel.UpdateToSelectedButton.label')) row.prop(context.scene, 'cats_updater_version_list', text='') row = split.row(align=True) row.scale_y = scale_small row.operator(ShowPatchnotesPanel.bl_idname, text="", icon='WORDWRAP_ON') # topsplit = layout_split(col, factor=0.55, align=True) # # split = topsplit.row(align=True) # row = split.row(align=True) # row.scale_y = scale_small # row.active = True if not is_checking_for_update and version_list else False # row.operator(UpdateToSelectedButton.bl_idname, text='Install Version:') # # row = split.row(align=True) # row.alignment = 'RIGHT' # row.scale_y = scale_small # row.operator(ShowPatchnotesPanel.bl_idname, text="", icon='WORDWRAP_ON') # # row = topsplit.row(align=True) # row.scale_y = scale_small # row.prop(context.scene, 'cats_updater_version_list', text='') row = col.row(align=True) row.scale_y = scale_small row.operator(UpdateToDevButton.bl_idname, text=t('draw_updater_panel.UpdateToDevButton.label')) col.separator() row = col.row(align=True) row.scale_y = 0.65 row.label(text=t('draw_updater_panel.currentVersion', name=current_version_str)) # demo bare-bones preferences class DemoPreferences(bpy.types.AddonPreferences): bl_idname = package_name def draw(self, context): layout = self.layout draw_updater_panel(context, layout, user_preferences=True) to_register = [ CheckForUpdateButton, UpdateToLatestButton, UpdateToSelectedButton, UpdateToDevButton, RemindMeLaterButton, IgnoreThisVersionButton, ShowPatchnotesPanel, ConfirmUpdatePanel, UpdateCompletePanel, UpdateNotificationPopup, DemoPreferences, ] def register(dev_branch, version_str): # print('REGISTER CATS UPDATER') global current_version, fake_update, current_version_str # If not dev branch, always disable fake update! if not dev_branch: fake_update = False current_version_str = version_str # Get current version current_version = [] version_parts = CATS_VERSION.split(".") for part in version_parts: current_version.append(int(part)) bpy.types.Scene.cats_updater_version_list = bpy.props.EnumProperty( name=t('bpy.types.Scene.cats_updater_version_list.label'), description=t('bpy.types.Scene.cats_updater_version_list.desc'), items=wrap_dynamic_enum_items(get_version_list, 'cats_updater_version_list', sort=False) ) bpy.types.Scene.cats_update_action = bpy.props.EnumProperty( name=t('bpy.types.Scene.cats_update_action.label'), description=t('bpy.types.Scene.cats_update_action.desc'), items=[ ("UPDATE", t('bpy.types.Scene.cats_update_action.update.label'), t('bpy.types.Scene.cats_update_action.update.desc')), ("IGNORE", t('bpy.types.Scene.cats_update_action.ignore.label'), t( 'bpy.types.Scene.cats_update_action.ignore.desc')), ("DEFER", t('bpy.types.Scene.cats_update_action.defer.label'), t( 'bpy.types.Scene.cats_update_action.defer.desc')) ] ) # Register all Updater classes count = 0 for cls in to_register: try: bpy.utils.register_class(cls) count += 1 except ValueError: pass # print('Registered', count, 'CATS updater classes.') if count < len(to_register): print('Skipped', len(to_register) - count, 'CATS updater classes.') def unregister(): # Unregister all Updater classes for cls in reversed(to_register): try: bpy.utils.unregister_class(cls) except RuntimeError: pass if hasattr(bpy.types.Scene, 'cats_updater_version_list'): del bpy.types.Scene.cats_updater_version_list