diff options
author | Campbell Barton <ideasman42@gmail.com> | 2019-02-11 15:17:05 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2019-02-11 15:24:09 +0300 |
commit | 9ec944bbab7a5ba75a526387f8eab52af7f6405e (patch) | |
tree | 288acac6313348c38dbd3a9317336e121516425d /release | |
parent | 55c281415b6743b253156802ba6269fb47c84ad7 (diff) |
Cleanup: split user preferences out of wm.py
These operations are for handling preference related tasks so move into
into a preferences file.
Operators still need to be renamed.
Diffstat (limited to 'release')
-rw-r--r-- | release/scripts/startup/bl_operators/__init__.py | 5 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/userpref.py | 1090 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/wm.py | 1047 |
3 files changed, 1093 insertions, 1049 deletions
diff --git a/release/scripts/startup/bl_operators/__init__.py b/release/scripts/startup/bl_operators/__init__.py index 4d9038684d1..bb92e070d00 100644 --- a/release/scripts/startup/bl_operators/__init__.py +++ b/release/scripts/startup/bl_operators/__init__.py @@ -35,14 +35,15 @@ _modules = [ "mask", "mesh", "node", - "object_align", "object", - "object_randomize_transform", + "object_align", "object_quick_effects", + "object_randomize_transform", "presets", "rigidbody", "screen_play_rendered_anim", "sequencer", + "userpref", "uvcalc_follow_active", "uvcalc_lightmap", "uvcalc_smart_project", diff --git a/release/scripts/startup/bl_operators/userpref.py b/release/scripts/startup/bl_operators/userpref.py new file mode 100644 index 00000000000..072899320e4 --- /dev/null +++ b/release/scripts/startup/bl_operators/userpref.py @@ -0,0 +1,1090 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +# TODO, use PREFERENCES_OT_* prefix for operators. + +import bpy +from bpy.types import ( + Operator, + OperatorFileListElement +) +from bpy.props import ( + BoolProperty, + EnumProperty, + IntProperty, + StringProperty, + CollectionProperty, +) + +from bpy.app.translations import pgettext_tip as tip_ + + +class WM_OT_keyconfig_activate(Operator): + bl_idname = "wm.keyconfig_activate" + bl_label = "Activate Keyconfig" + + filepath: StringProperty( + subtype='FILE_PATH', + ) + + def execute(self, context): + if bpy.utils.keyconfig_set(self.filepath, report=self.report): + return {'FINISHED'} + else: + return {'CANCELLED'} + + +class WM_OT_copy_prev_settings(Operator): + """Copy settings from previous version""" + bl_idname = "wm.copy_prev_settings" + bl_label = "Copy Previous Settings" + + @staticmethod + def previous_version(): + ver = bpy.app.version + ver_old = ((ver[0] * 100) + ver[1]) - 1 + return ver_old // 100, ver_old % 100 + + @staticmethod + def _old_path(): + ver = bpy.app.version + ver_old = ((ver[0] * 100) + ver[1]) - 1 + return bpy.utils.resource_path('USER', ver_old // 100, ver_old % 100) + + @staticmethod + def _new_path(): + return bpy.utils.resource_path('USER') + + @classmethod + def poll(cls, context): + import os + + old = cls._old_path() + new = cls._new_path() + + # Disable operator in case config path is overriden with environment + # variable. That case has no automatic per-version configuration. + userconfig_path = os.path.normpath(bpy.utils.user_resource('CONFIG')) + new_userconfig_path = os.path.normpath(os.path.join(new, "config")) + if userconfig_path != new_userconfig_path: + return False + + # Enable operator if new config path does not exist yet. + if os.path.isdir(old) and not os.path.isdir(new): + return True + + # Enable operator also if there are no new user preference yet. + old_userpref = os.path.join(old, "config", "userpref.blend") + new_userpref = os.path.join(new, "config", "userpref.blend") + return os.path.isfile(old_userpref) and not os.path.isfile(new_userpref) + + def execute(self, context): + import shutil + + shutil.copytree(self._old_path(), self._new_path(), symlinks=True) + + # reload recent-files.txt + bpy.ops.wm.read_history() + + # don't loose users work if they open the splash later. + if bpy.data.is_saved is bpy.data.is_dirty is False: + bpy.ops.wm.read_homefile() + else: + self.report({'INFO'}, "Reload Start-Up file to restore settings") + + return {'FINISHED'} + + +class WM_OT_keyconfig_test(Operator): + """Test key-config for conflicts""" + bl_idname = "wm.keyconfig_test" + bl_label = "Test Key Configuration for Conflicts" + + def execute(self, context): + from bpy_extras import keyconfig_utils + + wm = context.window_manager + kc = wm.keyconfigs.default + + if keyconfig_utils.keyconfig_test(kc): + print("CONFLICT") + + return {'FINISHED'} + + +class WM_OT_keyconfig_import(Operator): + """Import key configuration from a python script""" + bl_idname = "wm.keyconfig_import" + bl_label = "Import Key Configuration..." + + filepath: StringProperty( + subtype='FILE_PATH', + default="keymap.py", + ) + filter_folder: BoolProperty( + name="Filter folders", + default=True, + options={'HIDDEN'}, + ) + filter_text: BoolProperty( + name="Filter text", + default=True, + options={'HIDDEN'}, + ) + filter_python: BoolProperty( + name="Filter python", + default=True, + options={'HIDDEN'}, + ) + keep_original: BoolProperty( + name="Keep original", + description="Keep original file after copying to configuration folder", + default=True, + ) + + def execute(self, context): + import os + from os.path import basename + import shutil + + if not self.filepath: + self.report({'ERROR'}, "Filepath not set") + return {'CANCELLED'} + + config_name = basename(self.filepath) + + path = bpy.utils.user_resource('SCRIPTS', os.path.join("presets", "keyconfig"), create=True) + path = os.path.join(path, config_name) + + try: + if self.keep_original: + shutil.copy(self.filepath, path) + else: + shutil.move(self.filepath, path) + except Exception as ex: + self.report({'ERROR'}, "Installing keymap failed: %s" % ex) + return {'CANCELLED'} + + # sneaky way to check we're actually running the code. + if bpy.utils.keyconfig_set(path, report=self.report): + return {'FINISHED'} + else: + return {'CANCELLED'} + + def invoke(self, context, event): + wm = context.window_manager + wm.fileselect_add(self) + return {'RUNNING_MODAL'} + +# This operator is also used by interaction presets saving - AddPresetBase + + +class WM_OT_keyconfig_export(Operator): + """Export key configuration to a python script""" + bl_idname = "wm.keyconfig_export" + bl_label = "Export Key Configuration..." + + all: BoolProperty( + name="All Keymaps", + default=False, + description="Write all keymaps (not just user modified)", + ) + filepath: StringProperty( + subtype='FILE_PATH', + default="keymap.py", + ) + filter_folder: BoolProperty( + name="Filter folders", + default=True, + options={'HIDDEN'}, + ) + filter_text: BoolProperty( + name="Filter text", + default=True, + options={'HIDDEN'}, + ) + filter_python: BoolProperty( + name="Filter python", + default=True, + options={'HIDDEN'}, + ) + + def execute(self, context): + from bl_keymap_utils.io import keyconfig_export_as_data + + if not self.filepath: + raise Exception("Filepath not set") + + if not self.filepath.endswith(".py"): + self.filepath += ".py" + + wm = context.window_manager + + keyconfig_export_as_data( + wm, + wm.keyconfigs.active, + self.filepath, + all_keymaps=self.all, + ) + + return {'FINISHED'} + + def invoke(self, context, event): + wm = context.window_manager + wm.fileselect_add(self) + return {'RUNNING_MODAL'} + + +class WM_OT_keymap_restore(Operator): + """Restore key map(s)""" + bl_idname = "wm.keymap_restore" + bl_label = "Restore Key Map(s)" + + all: BoolProperty( + name="All Keymaps", + description="Restore all keymaps to default", + ) + + def execute(self, context): + wm = context.window_manager + + if self.all: + for km in wm.keyconfigs.user.keymaps: + km.restore_to_default() + else: + km = context.keymap + km.restore_to_default() + + return {'FINISHED'} + + +class WM_OT_keyitem_restore(Operator): + """Restore key map item""" + bl_idname = "wm.keyitem_restore" + bl_label = "Restore Key Map Item" + + item_id: IntProperty( + name="Item Identifier", + description="Identifier of the item to remove", + ) + + @classmethod + def poll(cls, context): + keymap = getattr(context, "keymap", None) + return keymap + + def execute(self, context): + km = context.keymap + kmi = km.keymap_items.from_id(self.item_id) + + if (not kmi.is_user_defined) and kmi.is_user_modified: + km.restore_item_to_default(kmi) + + return {'FINISHED'} + + +class WM_OT_keyitem_add(Operator): + """Add key map item""" + bl_idname = "wm.keyitem_add" + bl_label = "Add Key Map Item" + + def execute(self, context): + km = context.keymap + + if km.is_modal: + km.keymap_items.new_modal("", 'A', 'PRESS') + else: + km.keymap_items.new("none", 'A', 'PRESS') + + # clear filter and expand keymap so we can see the newly added item + if context.space_data.filter_text != "": + context.space_data.filter_text = "" + km.show_expanded_items = True + km.show_expanded_children = True + + return {'FINISHED'} + + +class WM_OT_keyitem_remove(Operator): + """Remove key map item""" + bl_idname = "wm.keyitem_remove" + bl_label = "Remove Key Map Item" + + item_id: IntProperty( + name="Item Identifier", + description="Identifier of the item to remove", + ) + + @classmethod + def poll(cls, context): + return hasattr(context, "keymap") + + def execute(self, context): + km = context.keymap + kmi = km.keymap_items.from_id(self.item_id) + km.keymap_items.remove(kmi) + return {'FINISHED'} + + +class WM_OT_keyconfig_remove(Operator): + """Remove key config""" + bl_idname = "wm.keyconfig_remove" + bl_label = "Remove Key Config" + + @classmethod + def poll(cls, context): + wm = context.window_manager + keyconf = wm.keyconfigs.active + return keyconf and keyconf.is_user_defined + + def execute(self, context): + wm = context.window_manager + keyconfig = wm.keyconfigs.active + wm.keyconfigs.remove(keyconfig) + return {'FINISHED'} + + +# ----------------------------------------------------------------------------- +# Add-on Operators + +class WM_OT_addon_enable(Operator): + """Enable an add-on""" + bl_idname = "wm.addon_enable" + bl_label = "Enable Add-on" + + module: StringProperty( + name="Module", + description="Module name of the add-on to enable", + ) + + def execute(self, context): + import addon_utils + + err_str = "" + + def err_cb(ex): + import traceback + nonlocal err_str + err_str = traceback.format_exc() + print(err_str) + + mod = addon_utils.enable(self.module, default_set=True, handle_error=err_cb) + + if mod: + info = addon_utils.module_bl_info(mod) + + info_ver = info.get("blender", (0, 0, 0)) + + if info_ver > bpy.app.version: + self.report( + {'WARNING'}, + "This script was written Blender " + "version %d.%d.%d and might not " + "function (correctly), " + "though it is enabled" % + info_ver + ) + return {'FINISHED'} + else: + + if err_str: + self.report({'ERROR'}, err_str) + + return {'CANCELLED'} + + +class WM_OT_addon_disable(Operator): + """Disable an add-on""" + bl_idname = "wm.addon_disable" + bl_label = "Disable Add-on" + + module: StringProperty( + name="Module", + description="Module name of the add-on to disable", + ) + + def execute(self, context): + import addon_utils + + err_str = "" + + def err_cb(ex): + import traceback + nonlocal err_str + err_str = traceback.format_exc() + print(err_str) + + addon_utils.disable(self.module, default_set=True, handle_error=err_cb) + + if err_str: + self.report({'ERROR'}, err_str) + + return {'FINISHED'} + + +class WM_OT_theme_install(Operator): + """Load and apply a Blender XML theme file""" + bl_idname = "wm.theme_install" + bl_label = "Install Theme..." + + overwrite: BoolProperty( + name="Overwrite", + description="Remove existing theme file if exists", + default=True, + ) + filepath: StringProperty( + subtype='FILE_PATH', + ) + filter_folder: BoolProperty( + name="Filter folders", + default=True, + options={'HIDDEN'}, + ) + filter_glob: StringProperty( + default="*.xml", + options={'HIDDEN'}, + ) + + def execute(self, context): + import os + import shutil + import traceback + + xmlfile = self.filepath + + path_themes = bpy.utils.user_resource('SCRIPTS', "presets/interface_theme", create=True) + + if not path_themes: + self.report({'ERROR'}, "Failed to get themes path") + return {'CANCELLED'} + + path_dest = os.path.join(path_themes, os.path.basename(xmlfile)) + + if not self.overwrite: + if os.path.exists(path_dest): + self.report({'WARNING'}, "File already installed to %r\n" % path_dest) + return {'CANCELLED'} + + try: + shutil.copyfile(xmlfile, path_dest) + bpy.ops.script.execute_preset( + filepath=path_dest, + menu_idname="USERPREF_MT_interface_theme_presets", + ) + + except: + traceback.print_exc() + return {'CANCELLED'} + + return {'FINISHED'} + + def invoke(self, context, event): + wm = context.window_manager + wm.fileselect_add(self) + return {'RUNNING_MODAL'} + + +class WM_OT_addon_refresh(Operator): + """Scan add-on directories for new modules""" + bl_idname = "wm.addon_refresh" + bl_label = "Refresh" + + def execute(self, context): + import addon_utils + + addon_utils.modules_refresh() + + return {'FINISHED'} + + +# Note: shares some logic with WM_OT_app_template_install +# but not enough to de-duplicate. Fixed here may apply to both. +class WM_OT_addon_install(Operator): + """Install an add-on""" + bl_idname = "wm.addon_install" + bl_label = "Install Add-on from File..." + + overwrite: BoolProperty( + name="Overwrite", + description="Remove existing add-ons with the same ID", + default=True, + ) + target: EnumProperty( + name="Target Path", + items=(('DEFAULT', "Default", ""), + ('PREFS', "User Prefs", "")), + ) + + filepath: StringProperty( + subtype='FILE_PATH', + ) + filter_folder: BoolProperty( + name="Filter folders", + default=True, + options={'HIDDEN'}, + ) + filter_python: BoolProperty( + name="Filter python", + default=True, + options={'HIDDEN'}, + ) + filter_glob: StringProperty( + default="*.py;*.zip", + options={'HIDDEN'}, + ) + + def execute(self, context): + import addon_utils + import traceback + import zipfile + import shutil + import os + + pyfile = self.filepath + + if self.target == 'DEFAULT': + # don't use bpy.utils.script_paths("addons") because we may not be able to write to it. + path_addons = bpy.utils.user_resource('SCRIPTS', "addons", create=True) + else: + path_addons = context.preferences.filepaths.script_directory + if path_addons: + path_addons = os.path.join(path_addons, "addons") + + if not path_addons: + self.report({'ERROR'}, "Failed to get add-ons path") + return {'CANCELLED'} + + if not os.path.isdir(path_addons): + try: + os.makedirs(path_addons, exist_ok=True) + except: + traceback.print_exc() + + # Check if we are installing from a target path, + # doing so causes 2+ addons of same name or when the same from/to + # location is used, removal of the file! + addon_path = "" + pyfile_dir = os.path.dirname(pyfile) + for addon_path in addon_utils.paths(): + if os.path.samefile(pyfile_dir, addon_path): + self.report({'ERROR'}, "Source file is in the add-on search path: %r" % addon_path) + return {'CANCELLED'} + del addon_path + del pyfile_dir + # done checking for exceptional case + + addons_old = {mod.__name__ for mod in addon_utils.modules()} + + # check to see if the file is in compressed format (.zip) + if zipfile.is_zipfile(pyfile): + try: + file_to_extract = zipfile.ZipFile(pyfile, 'r') + except: + traceback.print_exc() + return {'CANCELLED'} + + if self.overwrite: + for f in file_to_extract.namelist(): + module_filesystem_remove(path_addons, f) + else: + for f in file_to_extract.namelist(): + path_dest = os.path.join(path_addons, os.path.basename(f)) + if os.path.exists(path_dest): + self.report({'WARNING'}, "File already installed to %r\n" % path_dest) + return {'CANCELLED'} + + try: # extract the file to "addons" + file_to_extract.extractall(path_addons) + except: + traceback.print_exc() + return {'CANCELLED'} + + else: + path_dest = os.path.join(path_addons, os.path.basename(pyfile)) + + if self.overwrite: + module_filesystem_remove(path_addons, os.path.basename(pyfile)) + elif os.path.exists(path_dest): + self.report({'WARNING'}, "File already installed to %r\n" % path_dest) + return {'CANCELLED'} + + # if not compressed file just copy into the addon path + try: + shutil.copyfile(pyfile, path_dest) + except: + traceback.print_exc() + return {'CANCELLED'} + + addons_new = {mod.__name__ for mod in addon_utils.modules()} - addons_old + addons_new.discard("modules") + + # disable any addons we may have enabled previously and removed. + # this is unlikely but do just in case. bug [#23978] + for new_addon in addons_new: + addon_utils.disable(new_addon, default_set=True) + + # possible the zip contains multiple addons, we could disallow this + # but for now just use the first + for mod in addon_utils.modules(refresh=False): + if mod.__name__ in addons_new: + info = addon_utils.module_bl_info(mod) + + # show the newly installed addon. + context.window_manager.addon_filter = 'All' + context.window_manager.addon_search = info["name"] + break + + # in case a new module path was created to install this addon. + bpy.utils.refresh_script_paths() + + # print message + msg = ( + tip_("Modules Installed (%s) from %r into %r") % + (", ".join(sorted(addons_new)), pyfile, path_addons) + ) + print(msg) + self.report({'INFO'}, msg) + + return {'FINISHED'} + + def invoke(self, context, event): + wm = context.window_manager + wm.fileselect_add(self) + return {'RUNNING_MODAL'} + + +class WM_OT_addon_remove(Operator): + """Delete the add-on from the file system""" + bl_idname = "wm.addon_remove" + bl_label = "Remove Add-on" + + module: StringProperty( + name="Module", + description="Module name of the add-on to remove", + ) + + @staticmethod + def path_from_addon(module): + import os + import addon_utils + + for mod in addon_utils.modules(): + if mod.__name__ == module: + filepath = mod.__file__ + if os.path.exists(filepath): + if os.path.splitext(os.path.basename(filepath))[0] == "__init__": + return os.path.dirname(filepath), True + else: + return filepath, False + return None, False + + def execute(self, context): + import addon_utils + import os + + path, isdir = WM_OT_addon_remove.path_from_addon(self.module) + if path is None: + self.report({'WARNING'}, "Add-on path %r could not be found" % path) + return {'CANCELLED'} + + # in case its enabled + addon_utils.disable(self.module, default_set=True) + + import shutil + if isdir and (not os.path.islink(path)): + shutil.rmtree(path) + else: + os.remove(path) + + addon_utils.modules_refresh() + + context.area.tag_redraw() + return {'FINISHED'} + + # lame confirmation check + def draw(self, context): + self.layout.label(text="Remove Add-on: %r?" % self.module) + path, _isdir = WM_OT_addon_remove.path_from_addon(self.module) + self.layout.label(text="Path: %r" % path) + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self, width=600) + + +class WM_OT_addon_expand(Operator): + """Display information and preferences for this add-on""" + bl_idname = "wm.addon_expand" + bl_label = "" + bl_options = {'INTERNAL'} + + module: StringProperty( + name="Module", + description="Module name of the add-on to expand", + ) + + def execute(self, context): + import addon_utils + + module_name = self.module + + mod = addon_utils.addons_fake_modules.get(module_name) + if mod is not None: + info = addon_utils.module_bl_info(mod) + info["show_expanded"] = not info["show_expanded"] + + return {'FINISHED'} + + +class WM_OT_addon_userpref_show(Operator): + """Show add-on preferences""" + bl_idname = "wm.addon_userpref_show" + bl_label = "" + bl_options = {'INTERNAL'} + + module: StringProperty( + name="Module", + description="Module name of the add-on to expand", + ) + + def execute(self, context): + import addon_utils + + module_name = self.module + + _modules = addon_utils.modules(refresh=False) + mod = addon_utils.addons_fake_modules.get(module_name) + if mod is not None: + info = addon_utils.module_bl_info(mod) + info["show_expanded"] = True + + context.preferences.active_section = 'ADDONS' + context.window_manager.addon_filter = 'All' + context.window_manager.addon_search = info["name"] + bpy.ops.screen.userpref_show('INVOKE_DEFAULT') + + return {'FINISHED'} + + +# Note: shares some logic with WM_OT_addon_install +# but not enough to de-duplicate. Fixes here may apply to both. +class WM_OT_app_template_install(Operator): + """Install an application-template""" + bl_idname = "wm.app_template_install" + bl_label = "Install Template from File..." + + overwrite: BoolProperty( + name="Overwrite", + description="Remove existing template with the same ID", + default=True, + ) + + filepath: StringProperty( + subtype='FILE_PATH', + ) + filter_folder: BoolProperty( + name="Filter folders", + default=True, + options={'HIDDEN'}, + ) + filter_glob: StringProperty( + default="*.zip", + options={'HIDDEN'}, + ) + + def execute(self, context): + import traceback + import zipfile + import os + + filepath = self.filepath + + path_app_templates = bpy.utils.user_resource( + 'SCRIPTS', os.path.join("startup", "bl_app_templates_user"), + create=True, + ) + + if not path_app_templates: + self.report({'ERROR'}, "Failed to get add-ons path") + return {'CANCELLED'} + + if not os.path.isdir(path_app_templates): + try: + os.makedirs(path_app_templates, exist_ok=True) + except: + traceback.print_exc() + + app_templates_old = set(os.listdir(path_app_templates)) + + # check to see if the file is in compressed format (.zip) + if zipfile.is_zipfile(filepath): + try: + file_to_extract = zipfile.ZipFile(filepath, 'r') + except: + traceback.print_exc() + return {'CANCELLED'} + + if self.overwrite: + for f in file_to_extract.namelist(): + module_filesystem_remove(path_app_templates, f) + else: + for f in file_to_extract.namelist(): + path_dest = os.path.join(path_app_templates, os.path.basename(f)) + if os.path.exists(path_dest): + self.report({'WARNING'}, "File already installed to %r\n" % path_dest) + return {'CANCELLED'} + + try: # extract the file to "bl_app_templates_user" + file_to_extract.extractall(path_app_templates) + except: + traceback.print_exc() + return {'CANCELLED'} + + else: + # Only support installing zipfiles + self.report({'WARNING'}, "Expected a zip-file %r\n" % filepath) + return {'CANCELLED'} + + app_templates_new = set(os.listdir(path_app_templates)) - app_templates_old + + # in case a new module path was created to install this addon. + bpy.utils.refresh_script_paths() + + # print message + msg = ( + tip_("Template Installed (%s) from %r into %r") % + (", ".join(sorted(app_templates_new)), filepath, path_app_templates) + ) + print(msg) + self.report({'INFO'}, msg) + + return {'FINISHED'} + + def invoke(self, context, event): + wm = context.window_manager + wm.fileselect_add(self) + return {'RUNNING_MODAL'} + + +# ----------------------------------------------------------------------------- +# Studio Light Operations + +class WM_OT_studiolight_install(Operator): + """Install a user defined studio light""" + bl_idname = "wm.studiolight_install" + bl_label = "Install Custom Studio Light" + + files: CollectionProperty( + name="File Path", + type=OperatorFileListElement, + ) + directory: StringProperty( + subtype='DIR_PATH', + ) + filter_folder: BoolProperty( + name="Filter folders", + default=True, + options={'HIDDEN'}, + ) + filter_glob: StringProperty( + default="*.png;*.jpg;*.hdr;*.exr", + options={'HIDDEN'}, + ) + type: EnumProperty( + items=( + ('MATCAP', "MatCap", ""), + ('WORLD', "World", ""), + ('STUDIO', "Studio", ""), + ) + ) + + def execute(self, context): + import os + import shutil + prefs = context.preferences + + path_studiolights = os.path.join("studiolights", self.type.lower()) + path_studiolights = bpy.utils.user_resource('DATAFILES', path_studiolights, create=True) + if not path_studiolights: + self.report({'ERROR'}, "Failed to create Studio Light path") + return {'CANCELLED'} + + for e in self.files: + shutil.copy(os.path.join(self.directory, e.name), path_studiolights) + prefs.studio_lights.load(os.path.join(path_studiolights, e.name), self.type) + + # print message + msg = ( + tip_("StudioLight Installed %r into %r") % + (", ".join(e.name for e in self.files), path_studiolights) + ) + print(msg) + self.report({'INFO'}, msg) + return {'FINISHED'} + + def invoke(self, context, event): + wm = context.window_manager + + if self.type == 'STUDIO': + self.filter_glob = "*.sl" + + wm.fileselect_add(self) + return {'RUNNING_MODAL'} + + +class WM_OT_studiolight_new(Operator): + """Save custom studio light from the studio light editor settings""" + bl_idname = 'wm.studiolight_new' + bl_label = "Save custom Studio light" + + filename: StringProperty( + name="Name", + default="StudioLight", + ) + + ask_overide = False + + def execute(self, context): + import os + prefs = context.preferences + wm = context.window_manager + filename = bpy.path.ensure_ext(self.filename, ".sl") + + path_studiolights = bpy.utils.user_resource('DATAFILES', os.path.join("studiolights", "studio"), create=True) + if not path_studiolights: + self.report({'ERROR'}, "Failed to get Studio Light path") + return {'CANCELLED'} + + filepath_final = os.path.join(path_studiolights, filename) + if os.path.isfile(filepath_final): + if not self.ask_overide: + self.ask_overide = True + return wm.invoke_props_dialog(self, width=600) + else: + for studio_light in prefs.studio_lights: + if studio_light.name == filename: + bpy.ops.wm.studiolight_uninstall(index=studio_light.index) + + prefs.studio_lights.new(path=filepath_final) + + # print message + msg = ( + tip_("StudioLight Installed %r into %r") % + (self.filename, str(path_studiolights)) + ) + print(msg) + self.report({'INFO'}, msg) + return {'FINISHED'} + + def draw(self, context): + layout = self.layout + if self.ask_overide: + layout.label(text="Warning, file already exists. Overwrite existing file?") + else: + layout.prop(self, "filename") + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self, width=600) + + +class WM_OT_studiolight_uninstall(Operator): + """Delete Studio Light""" + bl_idname = 'wm.studiolight_uninstall' + bl_label = "Uninstall Studio Light" + index: bpy.props.IntProperty() + + def execute(self, context): + import os + prefs = context.preferences + for studio_light in prefs.studio_lights: + if studio_light.index == self.index: + for filepath in ( + studio_light.path, + studio_light.path_irr_cache, + studio_light.path_sh_cache, + ): + if filepath and os.path.exists(filepath): + os.unlink(filepath) + prefs.studio_lights.remove(studio_light) + return {'FINISHED'} + return {'CANCELLED'} + + +class WM_OT_studiolight_copy_settings(Operator): + """Copy Studio Light settings to the Studio light editor""" + bl_idname = 'wm.studiolight_copy_settings' + bl_label = "Copy Studio Light settings" + index: bpy.props.IntProperty() + + def execute(self, context): + prefs = context.preferences + system = prefs.system + for studio_light in prefs.studio_lights: + if studio_light.index == self.index: + system.light_ambient = studio_light.light_ambient + for sys_light, light in zip(system.solid_lights, studio_light.solid_lights): + sys_light.use = light.use + sys_light.diffuse_color = light.diffuse_color + sys_light.specular_color = light.specular_color + sys_light.smooth = light.smooth + sys_light.direction = light.direction + return {'FINISHED'} + return {'CANCELLED'} + + +class WM_OT_studiolight_userpref_show(Operator): + """Show light preferences""" + bl_idname = "wm.studiolight_userpref_show" + bl_label = "" + bl_options = {'INTERNAL'} + + def execute(self, context): + context.preferences.active_section = 'LIGHTS' + bpy.ops.screen.userpref_show('INVOKE_DEFAULT') + return {'FINISHED'} + + +classes = ( + WM_OT_addon_disable, + WM_OT_addon_enable, + WM_OT_addon_expand, + WM_OT_addon_install, + WM_OT_addon_refresh, + WM_OT_addon_remove, + WM_OT_addon_userpref_show, + WM_OT_app_template_install, + WM_OT_copy_prev_settings, + WM_OT_keyconfig_activate, + WM_OT_keyconfig_export, + WM_OT_keyconfig_import, + WM_OT_keyconfig_remove, + WM_OT_keyconfig_test, + WM_OT_keyitem_add, + WM_OT_keyitem_remove, + WM_OT_keyitem_restore, + WM_OT_keymap_restore, + WM_OT_theme_install, + WM_OT_studiolight_install, + WM_OT_studiolight_new, + WM_OT_studiolight_uninstall, + WM_OT_studiolight_copy_settings, + WM_OT_studiolight_userpref_show, +) diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index 8d663c5b3a5..3c6e8196c02 100644 --- a/release/scripts/startup/bl_operators/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -22,7 +22,6 @@ import bpy from bpy.types import ( Menu, Operator, - OperatorFileListElement ) from bpy.props import ( BoolProperty, @@ -30,11 +29,8 @@ from bpy.props import ( FloatProperty, IntProperty, StringProperty, - CollectionProperty, ) -from bpy.app.translations import pgettext_tip as tip_ - # FIXME, we need a way to detect key repeat events. # unfortunately checking event previous values isn't reliable. use_toolbar_release_hack = True @@ -1413,21 +1409,6 @@ class WM_OT_properties_remove(Operator): return {'FINISHED'} -class WM_OT_keyconfig_activate(Operator): - bl_idname = "wm.keyconfig_activate" - bl_label = "Activate Keyconfig" - - filepath: StringProperty( - subtype='FILE_PATH', - ) - - def execute(self, context): - if bpy.utils.keyconfig_set(self.filepath, report=self.report): - return {'FINISHED'} - else: - return {'CANCELLED'} - - class WM_OT_sysinfo(Operator): """Generate system information, saved into a text file""" @@ -1456,315 +1437,6 @@ class WM_OT_sysinfo(Operator): return {'RUNNING_MODAL'} -class WM_OT_copy_prev_settings(Operator): - """Copy settings from previous version""" - bl_idname = "wm.copy_prev_settings" - bl_label = "Copy Previous Settings" - - @staticmethod - def previous_version(): - ver = bpy.app.version - ver_old = ((ver[0] * 100) + ver[1]) - 1 - return ver_old // 100, ver_old % 100 - - @staticmethod - def _old_path(): - ver = bpy.app.version - ver_old = ((ver[0] * 100) + ver[1]) - 1 - return bpy.utils.resource_path('USER', ver_old // 100, ver_old % 100) - - @staticmethod - def _new_path(): - return bpy.utils.resource_path('USER') - - @classmethod - def poll(cls, context): - import os - - old = cls._old_path() - new = cls._new_path() - - # Disable operator in case config path is overriden with environment - # variable. That case has no automatic per-version configuration. - userconfig_path = os.path.normpath(bpy.utils.user_resource('CONFIG')) - new_userconfig_path = os.path.normpath(os.path.join(new, "config")) - if userconfig_path != new_userconfig_path: - return False - - # Enable operator if new config path does not exist yet. - if os.path.isdir(old) and not os.path.isdir(new): - return True - - # Enable operator also if there are no new user preference yet. - old_userpref = os.path.join(old, "config", "userpref.blend") - new_userpref = os.path.join(new, "config", "userpref.blend") - return os.path.isfile(old_userpref) and not os.path.isfile(new_userpref) - - def execute(self, context): - import shutil - - shutil.copytree(self._old_path(), self._new_path(), symlinks=True) - - # reload recent-files.txt - bpy.ops.wm.read_history() - - # don't loose users work if they open the splash later. - if bpy.data.is_saved is bpy.data.is_dirty is False: - bpy.ops.wm.read_homefile() - else: - self.report({'INFO'}, "Reload Start-Up file to restore settings") - - return {'FINISHED'} - - -class WM_OT_keyconfig_test(Operator): - """Test key-config for conflicts""" - bl_idname = "wm.keyconfig_test" - bl_label = "Test Key Configuration for Conflicts" - - def execute(self, context): - from bpy_extras import keyconfig_utils - - wm = context.window_manager - kc = wm.keyconfigs.default - - if keyconfig_utils.keyconfig_test(kc): - print("CONFLICT") - - return {'FINISHED'} - - -class WM_OT_keyconfig_import(Operator): - """Import key configuration from a python script""" - bl_idname = "wm.keyconfig_import" - bl_label = "Import Key Configuration..." - - filepath: StringProperty( - subtype='FILE_PATH', - default="keymap.py", - ) - filter_folder: BoolProperty( - name="Filter folders", - default=True, - options={'HIDDEN'}, - ) - filter_text: BoolProperty( - name="Filter text", - default=True, - options={'HIDDEN'}, - ) - filter_python: BoolProperty( - name="Filter python", - default=True, - options={'HIDDEN'}, - ) - keep_original: BoolProperty( - name="Keep original", - description="Keep original file after copying to configuration folder", - default=True, - ) - - def execute(self, context): - import os - from os.path import basename - import shutil - - if not self.filepath: - self.report({'ERROR'}, "Filepath not set") - return {'CANCELLED'} - - config_name = basename(self.filepath) - - path = bpy.utils.user_resource('SCRIPTS', os.path.join("presets", "keyconfig"), create=True) - path = os.path.join(path, config_name) - - try: - if self.keep_original: - shutil.copy(self.filepath, path) - else: - shutil.move(self.filepath, path) - except Exception as ex: - self.report({'ERROR'}, "Installing keymap failed: %s" % ex) - return {'CANCELLED'} - - # sneaky way to check we're actually running the code. - if bpy.utils.keyconfig_set(path, report=self.report): - return {'FINISHED'} - else: - return {'CANCELLED'} - - def invoke(self, context, event): - wm = context.window_manager - wm.fileselect_add(self) - return {'RUNNING_MODAL'} - -# This operator is also used by interaction presets saving - AddPresetBase - - -class WM_OT_keyconfig_export(Operator): - """Export key configuration to a python script""" - bl_idname = "wm.keyconfig_export" - bl_label = "Export Key Configuration..." - - all: BoolProperty( - name="All Keymaps", - default=False, - description="Write all keymaps (not just user modified)", - ) - filepath: StringProperty( - subtype='FILE_PATH', - default="keymap.py", - ) - filter_folder: BoolProperty( - name="Filter folders", - default=True, - options={'HIDDEN'}, - ) - filter_text: BoolProperty( - name="Filter text", - default=True, - options={'HIDDEN'}, - ) - filter_python: BoolProperty( - name="Filter python", - default=True, - options={'HIDDEN'}, - ) - - def execute(self, context): - from bl_keymap_utils.io import keyconfig_export_as_data - - if not self.filepath: - raise Exception("Filepath not set") - - if not self.filepath.endswith(".py"): - self.filepath += ".py" - - wm = context.window_manager - - keyconfig_export_as_data( - wm, - wm.keyconfigs.active, - self.filepath, - all_keymaps=self.all, - ) - - return {'FINISHED'} - - def invoke(self, context, event): - wm = context.window_manager - wm.fileselect_add(self) - return {'RUNNING_MODAL'} - - -class WM_OT_keymap_restore(Operator): - """Restore key map(s)""" - bl_idname = "wm.keymap_restore" - bl_label = "Restore Key Map(s)" - - all: BoolProperty( - name="All Keymaps", - description="Restore all keymaps to default", - ) - - def execute(self, context): - wm = context.window_manager - - if self.all: - for km in wm.keyconfigs.user.keymaps: - km.restore_to_default() - else: - km = context.keymap - km.restore_to_default() - - return {'FINISHED'} - - -class WM_OT_keyitem_restore(Operator): - """Restore key map item""" - bl_idname = "wm.keyitem_restore" - bl_label = "Restore Key Map Item" - - item_id: IntProperty( - name="Item Identifier", - description="Identifier of the item to remove", - ) - - @classmethod - def poll(cls, context): - keymap = getattr(context, "keymap", None) - return keymap - - def execute(self, context): - km = context.keymap - kmi = km.keymap_items.from_id(self.item_id) - - if (not kmi.is_user_defined) and kmi.is_user_modified: - km.restore_item_to_default(kmi) - - return {'FINISHED'} - - -class WM_OT_keyitem_add(Operator): - """Add key map item""" - bl_idname = "wm.keyitem_add" - bl_label = "Add Key Map Item" - - def execute(self, context): - km = context.keymap - - if km.is_modal: - km.keymap_items.new_modal("", 'A', 'PRESS') - else: - km.keymap_items.new("none", 'A', 'PRESS') - - # clear filter and expand keymap so we can see the newly added item - if context.space_data.filter_text != "": - context.space_data.filter_text = "" - km.show_expanded_items = True - km.show_expanded_children = True - - return {'FINISHED'} - - -class WM_OT_keyitem_remove(Operator): - """Remove key map item""" - bl_idname = "wm.keyitem_remove" - bl_label = "Remove Key Map Item" - - item_id: IntProperty( - name="Item Identifier", - description="Identifier of the item to remove", - ) - - @classmethod - def poll(cls, context): - return hasattr(context, "keymap") - - def execute(self, context): - km = context.keymap - kmi = km.keymap_items.from_id(self.item_id) - km.keymap_items.remove(kmi) - return {'FINISHED'} - - -class WM_OT_keyconfig_remove(Operator): - """Remove key config""" - bl_idname = "wm.keyconfig_remove" - bl_label = "Remove Key Config" - - @classmethod - def poll(cls, context): - wm = context.window_manager - keyconf = wm.keyconfigs.active - return keyconf and keyconf.is_user_defined - - def execute(self, context): - wm = context.window_manager - keyconfig = wm.keyconfigs.active - wm.keyconfigs.remove(keyconfig) - return {'FINISHED'} - class WM_OT_operator_cheat_sheet(Operator): """List all the Operators in a text-block, useful for scripting""" @@ -1795,81 +1467,6 @@ class WM_OT_operator_cheat_sheet(Operator): # ----------------------------------------------------------------------------- # Add-on Operators -class WM_OT_addon_enable(Operator): - """Enable an add-on""" - bl_idname = "wm.addon_enable" - bl_label = "Enable Add-on" - - module: StringProperty( - name="Module", - description="Module name of the add-on to enable", - ) - - def execute(self, context): - import addon_utils - - err_str = "" - - def err_cb(ex): - import traceback - nonlocal err_str - err_str = traceback.format_exc() - print(err_str) - - mod = addon_utils.enable(self.module, default_set=True, handle_error=err_cb) - - if mod: - info = addon_utils.module_bl_info(mod) - - info_ver = info.get("blender", (0, 0, 0)) - - if info_ver > bpy.app.version: - self.report( - {'WARNING'}, - "This script was written Blender " - "version %d.%d.%d and might not " - "function (correctly), " - "though it is enabled" % - info_ver - ) - return {'FINISHED'} - else: - - if err_str: - self.report({'ERROR'}, err_str) - - return {'CANCELLED'} - - -class WM_OT_addon_disable(Operator): - """Disable an add-on""" - bl_idname = "wm.addon_disable" - bl_label = "Disable Add-on" - - module: StringProperty( - name="Module", - description="Module name of the add-on to disable", - ) - - def execute(self, context): - import addon_utils - - err_str = "" - - def err_cb(ex): - import traceback - nonlocal err_str - err_str = traceback.format_exc() - print(err_str) - - addon_utils.disable(self.module, default_set=True, handle_error=err_cb) - - if err_str: - self.report({'ERROR'}, err_str) - - return {'FINISHED'} - - class WM_OT_owner_enable(Operator): """Enable workspace owner ID""" bl_idname = "wm.owner_enable" @@ -1901,449 +1498,6 @@ class WM_OT_owner_disable(Operator): return {'FINISHED'} -class WM_OT_theme_install(Operator): - """Load and apply a Blender XML theme file""" - bl_idname = "wm.theme_install" - bl_label = "Install Theme..." - - overwrite: BoolProperty( - name="Overwrite", - description="Remove existing theme file if exists", - default=True, - ) - filepath: StringProperty( - subtype='FILE_PATH', - ) - filter_folder: BoolProperty( - name="Filter folders", - default=True, - options={'HIDDEN'}, - ) - filter_glob: StringProperty( - default="*.xml", - options={'HIDDEN'}, - ) - - def execute(self, context): - import os - import shutil - import traceback - - xmlfile = self.filepath - - path_themes = bpy.utils.user_resource('SCRIPTS', "presets/interface_theme", create=True) - - if not path_themes: - self.report({'ERROR'}, "Failed to get themes path") - return {'CANCELLED'} - - path_dest = os.path.join(path_themes, os.path.basename(xmlfile)) - - if not self.overwrite: - if os.path.exists(path_dest): - self.report({'WARNING'}, "File already installed to %r\n" % path_dest) - return {'CANCELLED'} - - try: - shutil.copyfile(xmlfile, path_dest) - bpy.ops.script.execute_preset( - filepath=path_dest, - menu_idname="USERPREF_MT_interface_theme_presets", - ) - - except: - traceback.print_exc() - return {'CANCELLED'} - - return {'FINISHED'} - - def invoke(self, context, event): - wm = context.window_manager - wm.fileselect_add(self) - return {'RUNNING_MODAL'} - - -class WM_OT_addon_refresh(Operator): - """Scan add-on directories for new modules""" - bl_idname = "wm.addon_refresh" - bl_label = "Refresh" - - def execute(self, context): - import addon_utils - - addon_utils.modules_refresh() - - return {'FINISHED'} - - -# Note: shares some logic with WM_OT_app_template_install -# but not enough to de-duplicate. Fixed here may apply to both. -class WM_OT_addon_install(Operator): - """Install an add-on""" - bl_idname = "wm.addon_install" - bl_label = "Install Add-on from File..." - - overwrite: BoolProperty( - name="Overwrite", - description="Remove existing add-ons with the same ID", - default=True, - ) - target: EnumProperty( - name="Target Path", - items=(('DEFAULT', "Default", ""), - ('PREFS', "User Prefs", "")), - ) - - filepath: StringProperty( - subtype='FILE_PATH', - ) - filter_folder: BoolProperty( - name="Filter folders", - default=True, - options={'HIDDEN'}, - ) - filter_python: BoolProperty( - name="Filter python", - default=True, - options={'HIDDEN'}, - ) - filter_glob: StringProperty( - default="*.py;*.zip", - options={'HIDDEN'}, - ) - - def execute(self, context): - import addon_utils - import traceback - import zipfile - import shutil - import os - - pyfile = self.filepath - - if self.target == 'DEFAULT': - # don't use bpy.utils.script_paths("addons") because we may not be able to write to it. - path_addons = bpy.utils.user_resource('SCRIPTS', "addons", create=True) - else: - path_addons = context.preferences.filepaths.script_directory - if path_addons: - path_addons = os.path.join(path_addons, "addons") - - if not path_addons: - self.report({'ERROR'}, "Failed to get add-ons path") - return {'CANCELLED'} - - if not os.path.isdir(path_addons): - try: - os.makedirs(path_addons, exist_ok=True) - except: - traceback.print_exc() - - # Check if we are installing from a target path, - # doing so causes 2+ addons of same name or when the same from/to - # location is used, removal of the file! - addon_path = "" - pyfile_dir = os.path.dirname(pyfile) - for addon_path in addon_utils.paths(): - if os.path.samefile(pyfile_dir, addon_path): - self.report({'ERROR'}, "Source file is in the add-on search path: %r" % addon_path) - return {'CANCELLED'} - del addon_path - del pyfile_dir - # done checking for exceptional case - - addons_old = {mod.__name__ for mod in addon_utils.modules()} - - # check to see if the file is in compressed format (.zip) - if zipfile.is_zipfile(pyfile): - try: - file_to_extract = zipfile.ZipFile(pyfile, 'r') - except: - traceback.print_exc() - return {'CANCELLED'} - - if self.overwrite: - for f in file_to_extract.namelist(): - module_filesystem_remove(path_addons, f) - else: - for f in file_to_extract.namelist(): - path_dest = os.path.join(path_addons, os.path.basename(f)) - if os.path.exists(path_dest): - self.report({'WARNING'}, "File already installed to %r\n" % path_dest) - return {'CANCELLED'} - - try: # extract the file to "addons" - file_to_extract.extractall(path_addons) - except: - traceback.print_exc() - return {'CANCELLED'} - - else: - path_dest = os.path.join(path_addons, os.path.basename(pyfile)) - - if self.overwrite: - module_filesystem_remove(path_addons, os.path.basename(pyfile)) - elif os.path.exists(path_dest): - self.report({'WARNING'}, "File already installed to %r\n" % path_dest) - return {'CANCELLED'} - - # if not compressed file just copy into the addon path - try: - shutil.copyfile(pyfile, path_dest) - except: - traceback.print_exc() - return {'CANCELLED'} - - addons_new = {mod.__name__ for mod in addon_utils.modules()} - addons_old - addons_new.discard("modules") - - # disable any addons we may have enabled previously and removed. - # this is unlikely but do just in case. bug [#23978] - for new_addon in addons_new: - addon_utils.disable(new_addon, default_set=True) - - # possible the zip contains multiple addons, we could disallow this - # but for now just use the first - for mod in addon_utils.modules(refresh=False): - if mod.__name__ in addons_new: - info = addon_utils.module_bl_info(mod) - - # show the newly installed addon. - context.window_manager.addon_filter = 'All' - context.window_manager.addon_search = info["name"] - break - - # in case a new module path was created to install this addon. - bpy.utils.refresh_script_paths() - - # print message - msg = ( - tip_("Modules Installed (%s) from %r into %r") % - (", ".join(sorted(addons_new)), pyfile, path_addons) - ) - print(msg) - self.report({'INFO'}, msg) - - return {'FINISHED'} - - def invoke(self, context, event): - wm = context.window_manager - wm.fileselect_add(self) - return {'RUNNING_MODAL'} - - -class WM_OT_addon_remove(Operator): - """Delete the add-on from the file system""" - bl_idname = "wm.addon_remove" - bl_label = "Remove Add-on" - - module: StringProperty( - name="Module", - description="Module name of the add-on to remove", - ) - - @staticmethod - def path_from_addon(module): - import os - import addon_utils - - for mod in addon_utils.modules(): - if mod.__name__ == module: - filepath = mod.__file__ - if os.path.exists(filepath): - if os.path.splitext(os.path.basename(filepath))[0] == "__init__": - return os.path.dirname(filepath), True - else: - return filepath, False - return None, False - - def execute(self, context): - import addon_utils - import os - - path, isdir = WM_OT_addon_remove.path_from_addon(self.module) - if path is None: - self.report({'WARNING'}, "Add-on path %r could not be found" % path) - return {'CANCELLED'} - - # in case its enabled - addon_utils.disable(self.module, default_set=True) - - import shutil - if isdir and (not os.path.islink(path)): - shutil.rmtree(path) - else: - os.remove(path) - - addon_utils.modules_refresh() - - context.area.tag_redraw() - return {'FINISHED'} - - # lame confirmation check - def draw(self, context): - self.layout.label(text="Remove Add-on: %r?" % self.module) - path, _isdir = WM_OT_addon_remove.path_from_addon(self.module) - self.layout.label(text="Path: %r" % path) - - def invoke(self, context, event): - wm = context.window_manager - return wm.invoke_props_dialog(self, width=600) - - -class WM_OT_addon_expand(Operator): - """Display information and preferences for this add-on""" - bl_idname = "wm.addon_expand" - bl_label = "" - bl_options = {'INTERNAL'} - - module: StringProperty( - name="Module", - description="Module name of the add-on to expand", - ) - - def execute(self, context): - import addon_utils - - module_name = self.module - - mod = addon_utils.addons_fake_modules.get(module_name) - if mod is not None: - info = addon_utils.module_bl_info(mod) - info["show_expanded"] = not info["show_expanded"] - - return {'FINISHED'} - - -class WM_OT_addon_userpref_show(Operator): - """Show add-on preferences""" - bl_idname = "wm.addon_userpref_show" - bl_label = "" - bl_options = {'INTERNAL'} - - module: StringProperty( - name="Module", - description="Module name of the add-on to expand", - ) - - def execute(self, context): - import addon_utils - - module_name = self.module - - _modules = addon_utils.modules(refresh=False) - mod = addon_utils.addons_fake_modules.get(module_name) - if mod is not None: - info = addon_utils.module_bl_info(mod) - info["show_expanded"] = True - - context.preferences.active_section = 'ADDONS' - context.window_manager.addon_filter = 'All' - context.window_manager.addon_search = info["name"] - bpy.ops.screen.userpref_show('INVOKE_DEFAULT') - - return {'FINISHED'} - - -# Note: shares some logic with WM_OT_addon_install -# but not enough to de-duplicate. Fixes here may apply to both. -class WM_OT_app_template_install(Operator): - """Install an application-template""" - bl_idname = "wm.app_template_install" - bl_label = "Install Template from File..." - - overwrite: BoolProperty( - name="Overwrite", - description="Remove existing template with the same ID", - default=True, - ) - - filepath: StringProperty( - subtype='FILE_PATH', - ) - filter_folder: BoolProperty( - name="Filter folders", - default=True, - options={'HIDDEN'}, - ) - filter_glob: StringProperty( - default="*.zip", - options={'HIDDEN'}, - ) - - def execute(self, context): - import traceback - import zipfile - import os - - filepath = self.filepath - - path_app_templates = bpy.utils.user_resource( - 'SCRIPTS', os.path.join("startup", "bl_app_templates_user"), - create=True, - ) - - if not path_app_templates: - self.report({'ERROR'}, "Failed to get add-ons path") - return {'CANCELLED'} - - if not os.path.isdir(path_app_templates): - try: - os.makedirs(path_app_templates, exist_ok=True) - except: - traceback.print_exc() - - app_templates_old = set(os.listdir(path_app_templates)) - - # check to see if the file is in compressed format (.zip) - if zipfile.is_zipfile(filepath): - try: - file_to_extract = zipfile.ZipFile(filepath, 'r') - except: - traceback.print_exc() - return {'CANCELLED'} - - if self.overwrite: - for f in file_to_extract.namelist(): - module_filesystem_remove(path_app_templates, f) - else: - for f in file_to_extract.namelist(): - path_dest = os.path.join(path_app_templates, os.path.basename(f)) - if os.path.exists(path_dest): - self.report({'WARNING'}, "File already installed to %r\n" % path_dest) - return {'CANCELLED'} - - try: # extract the file to "bl_app_templates_user" - file_to_extract.extractall(path_app_templates) - except: - traceback.print_exc() - return {'CANCELLED'} - - else: - # Only support installing zipfiles - self.report({'WARNING'}, "Expected a zip-file %r\n" % filepath) - return {'CANCELLED'} - - app_templates_new = set(os.listdir(path_app_templates)) - app_templates_old - - # in case a new module path was created to install this addon. - bpy.utils.refresh_script_paths() - - # print message - msg = ( - tip_("Template Installed (%s) from %r into %r") % - (", ".join(sorted(app_templates_new)), filepath, path_app_templates) - ) - print(msg) - self.report({'INFO'}, msg) - - return {'FINISHED'} - - def invoke(self, context, event): - wm = context.window_manager - wm.fileselect_add(self) - return {'RUNNING_MODAL'} - class WM_OT_tool_set_by_name(Operator): """Set the tool by name (for keymaps)""" @@ -2430,183 +1584,6 @@ class WM_OT_toolbar(Operator): return {'FINISHED'} -# Studio Light operations -class WM_OT_studiolight_install(Operator): - """Install a user defined studio light""" - bl_idname = "wm.studiolight_install" - bl_label = "Install Custom Studio Light" - - files: CollectionProperty( - name="File Path", - type=OperatorFileListElement, - ) - directory: StringProperty( - subtype='DIR_PATH', - ) - filter_folder: BoolProperty( - name="Filter folders", - default=True, - options={'HIDDEN'}, - ) - filter_glob: StringProperty( - default="*.png;*.jpg;*.hdr;*.exr", - options={'HIDDEN'}, - ) - type: EnumProperty( - items=( - ('MATCAP', "MatCap", ""), - ('WORLD', "World", ""), - ('STUDIO', "Studio", ""), - ) - ) - - def execute(self, context): - import os - import shutil - prefs = context.preferences - - path_studiolights = os.path.join("studiolights", self.type.lower()) - path_studiolights = bpy.utils.user_resource('DATAFILES', path_studiolights, create=True) - if not path_studiolights: - self.report({'ERROR'}, "Failed to create Studio Light path") - return {'CANCELLED'} - - for e in self.files: - shutil.copy(os.path.join(self.directory, e.name), path_studiolights) - prefs.studio_lights.load(os.path.join(path_studiolights, e.name), self.type) - - # print message - msg = ( - tip_("StudioLight Installed %r into %r") % - (", ".join(e.name for e in self.files), path_studiolights) - ) - print(msg) - self.report({'INFO'}, msg) - return {'FINISHED'} - - def invoke(self, context, event): - wm = context.window_manager - - if self.type == 'STUDIO': - self.filter_glob = "*.sl" - - wm.fileselect_add(self) - return {'RUNNING_MODAL'} - - -class WM_OT_studiolight_new(Operator): - """Save custom studio light from the studio light editor settings""" - bl_idname = 'wm.studiolight_new' - bl_label = "Save custom Studio light" - - filename: StringProperty( - name="Name", - default="StudioLight", - ) - - ask_overide = False - - def execute(self, context): - import os - prefs = context.preferences - wm = context.window_manager - filename = bpy.path.ensure_ext(self.filename, ".sl") - - path_studiolights = bpy.utils.user_resource('DATAFILES', os.path.join("studiolights", "studio"), create=True) - if not path_studiolights: - self.report({'ERROR'}, "Failed to get Studio Light path") - return {'CANCELLED'} - - filepath_final = os.path.join(path_studiolights, filename) - if os.path.isfile(filepath_final): - if not self.ask_overide: - self.ask_overide = True - return wm.invoke_props_dialog(self, width=600) - else: - for studio_light in prefs.studio_lights: - if studio_light.name == filename: - bpy.ops.wm.studiolight_uninstall(index=studio_light.index) - - prefs.studio_lights.new(path=filepath_final) - - # print message - msg = ( - tip_("StudioLight Installed %r into %r") % - (self.filename, str(path_studiolights)) - ) - print(msg) - self.report({'INFO'}, msg) - return {'FINISHED'} - - def draw(self, context): - layout = self.layout - if self.ask_overide: - layout.label(text="Warning, file already exists. Overwrite existing file?") - else: - layout.prop(self, "filename") - - def invoke(self, context, event): - wm = context.window_manager - return wm.invoke_props_dialog(self, width=600) - - -class WM_OT_studiolight_uninstall(Operator): - """Delete Studio Light""" - bl_idname = 'wm.studiolight_uninstall' - bl_label = "Uninstall Studio Light" - index: bpy.props.IntProperty() - - def execute(self, context): - import os - prefs = context.preferences - for studio_light in prefs.studio_lights: - if studio_light.index == self.index: - for filepath in ( - studio_light.path, - studio_light.path_irr_cache, - studio_light.path_sh_cache, - ): - if filepath and os.path.exists(filepath): - os.unlink(filepath) - prefs.studio_lights.remove(studio_light) - return {'FINISHED'} - return {'CANCELLED'} - - -class WM_OT_studiolight_copy_settings(Operator): - """Copy Studio Light settings to the Studio light editor""" - bl_idname = 'wm.studiolight_copy_settings' - bl_label = "Copy Studio Light settings" - index: bpy.props.IntProperty() - - def execute(self, context): - prefs = context.preferences - system = prefs.system - for studio_light in prefs.studio_lights: - if studio_light.index == self.index: - system.light_ambient = studio_light.light_ambient - for sys_light, light in zip(system.solid_lights, studio_light.solid_lights): - sys_light.use = light.use - sys_light.diffuse_color = light.diffuse_color - sys_light.specular_color = light.specular_color - sys_light.smooth = light.smooth - sys_light.direction = light.direction - return {'FINISHED'} - return {'CANCELLED'} - - -class WM_OT_studiolight_userpref_show(Operator): - """Show light preferences""" - bl_idname = "wm.studiolight_userpref_show" - bl_label = "" - bl_options = {'INTERNAL'} - - def execute(self, context): - context.preferences.active_section = 'LIGHTS' - bpy.ops.screen.userpref_show('INVOKE_DEFAULT') - return {'FINISHED'} - - class WM_MT_splash(Menu): bl_label = "Splash" @@ -2804,14 +1781,6 @@ class WM_OT_drop_blend_file(Operator): classes = ( - WM_OT_addon_disable, - WM_OT_addon_enable, - WM_OT_addon_expand, - WM_OT_addon_install, - WM_OT_addon_refresh, - WM_OT_addon_remove, - WM_OT_addon_userpref_show, - WM_OT_app_template_install, WM_OT_context_collection_boolean_set, WM_OT_context_cycle_array, WM_OT_context_cycle_enum, @@ -2830,19 +1799,9 @@ classes = ( WM_OT_context_set_value, WM_OT_context_toggle, WM_OT_context_toggle_enum, - WM_OT_copy_prev_settings, WM_OT_doc_view, WM_OT_doc_view_manual, WM_OT_drop_blend_file, - WM_OT_keyconfig_activate, - WM_OT_keyconfig_export, - WM_OT_keyconfig_import, - WM_OT_keyconfig_remove, - WM_OT_keyconfig_test, - WM_OT_keyitem_add, - WM_OT_keyitem_remove, - WM_OT_keyitem_restore, - WM_OT_keymap_restore, WM_OT_operator_cheat_sheet, WM_OT_operator_pie_enum, WM_OT_path_open, @@ -2851,15 +1810,9 @@ classes = ( WM_OT_properties_edit, WM_OT_properties_remove, WM_OT_sysinfo, - WM_OT_theme_install, WM_OT_owner_disable, WM_OT_owner_enable, WM_OT_url_open, - WM_OT_studiolight_install, - WM_OT_studiolight_new, - WM_OT_studiolight_uninstall, - WM_OT_studiolight_copy_settings, - WM_OT_studiolight_userpref_show, WM_OT_tool_set_by_name, WM_OT_toolbar, WM_MT_splash, |