diff options
Diffstat (limited to 'release/scripts/ui/space_userpref.py')
-rw-r--r-- | release/scripts/ui/space_userpref.py | 390 |
1 files changed, 184 insertions, 206 deletions
diff --git a/release/scripts/ui/space_userpref.py b/release/scripts/ui/space_userpref.py index 4870c789aee..a1fb82b8930 100644 --- a/release/scripts/ui/space_userpref.py +++ b/release/scripts/ui/space_userpref.py @@ -20,6 +20,9 @@ import bpy import os import shutil +import addon_utils + +from bpy.props import StringProperty, BoolProperty, EnumProperty def ui_items_general(col, context): @@ -92,6 +95,7 @@ class USERPREF_HT_header(bpy.types.Header): layout.operator("wm.keyconfig_import") elif userpref.active_section == 'ADDONS': layout.operator("wm.addon_install") + layout.menu("USERPREF_MT_addons_dev_guides", text=" Addons Developer Guides", icon='INFO') elif userpref.active_section == 'THEMES': layout.operator("ui.reset_default_theme") @@ -127,7 +131,11 @@ class USERPREF_MT_splash(bpy.types.Menu): row.label("") row = split.row() row.label("Interaction:") - row.menu("USERPREF_MT_interaction_presets", text=bpy.types.USERPREF_MT_interaction_presets.bl_label) + # XXX, no redraws + # text = bpy.path.display_name(context.window_manager.keyconfigs.active.name) + # if not text: + # text = "Blender (default)" + row.menu("USERPREF_MT_keyconfigs", text="Preset") class USERPREF_PT_interface(bpy.types.Panel): @@ -152,6 +160,7 @@ class USERPREF_PT_interface(bpy.types.Panel): col = row.column() col.label(text="Display:") col.prop(view, "show_tooltips") + col.prop(view, "show_tooltips_python") col.prop(view, "show_object_info", text="Object Info") col.prop(view, "show_large_cursors") col.prop(view, "show_view_name", text="View Name") @@ -297,7 +306,6 @@ class USERPREF_PT_edit(bpy.types.Panel): sub = col.column() # sub.active = edit.use_keyframe_insert_auto # incorrect, timeline can enable - sub.prop(edit, "use_keyframe_insert_keyingset", text="Only Insert for Keying Set") sub.prop(edit, "use_keyframe_insert_available", text="Only Insert Available") col.separator() @@ -318,8 +326,7 @@ class USERPREF_PT_edit(bpy.types.Panel): row.separator() col = row.column() - row = col.row(align=True) - row.prop(edit, "sculpt_paint_overlay_color", text="Sculpt Overlay Color") + col.prop(edit, "sculpt_paint_overlay_color", text="Sculpt Overlay Color") col.separator() col.separator() @@ -359,7 +366,6 @@ class USERPREF_PT_system(bpy.types.Panel): split = layout.split() - # 1. Column column = split.column() colsplit = column.split(percentage=0.85) @@ -412,7 +418,6 @@ class USERPREF_PT_system(bpy.types.Panel): #col.prop(system, "use_textured_fonts") - # 2. Column column = split.column() colsplit = column.split(percentage=0.85) @@ -426,6 +431,8 @@ class USERPREF_PT_system(bpy.types.Panel): #col.prop(system, "use_antialiasing") col.label(text="Window Draw Method:") col.prop(system, "window_draw_method", text="") + col.label(text="Text Draw Options:") + col.prop(system, "use_text_antialiasing") col.label(text="Textures:") col.prop(system, "gl_texture_limit", text="Limit Size") col.prop(system, "texture_time_out", text="Time Out") @@ -439,7 +446,6 @@ class USERPREF_PT_system(bpy.types.Panel): col.prop(system, "prefetch_frames") col.prop(system, "memory_cache_limit") - # 3. Column column = split.column() @@ -527,7 +533,6 @@ class USERPREF_PT_theme(bpy.types.Panel): split = layout.split(percentage=0.4) - layout.separator() layout.separator() @@ -624,7 +629,6 @@ class USERPREF_PT_theme(bpy.types.Panel): colsub.row().prop(ui, "inner_key_sel") colsub.row().prop(ui, "blend") - ui = theme.user_interface col.separator() col.separator() @@ -634,7 +638,29 @@ class USERPREF_PT_theme(bpy.types.Panel): layout.separator() layout.separator() + elif theme.theme_area == 'COLOR_SETS': + col = split.column() + + for i, ui in enumerate(theme.bone_color_sets): + col.label(text="Color Set %d:" % (i + 1)) # i starts from 0 + + row = col.row() + + subsplit = row.split(percentage=0.95) + padding = subsplit.split(percentage=0.15) + colsub = padding.column() + colsub = padding.column() + colsub.row().prop(ui, "normal") + colsub.row().prop(ui, "select") + colsub.row().prop(ui, "active") + + subsplit = row.split(percentage=0.85) + + padding = subsplit.split(percentage=0.15) + colsub = padding.column() + colsub = padding.column() + colsub.row().prop(ui, "show_colored_constraints") else: self._theme_generic(split, getattr(theme, theme.theme_area.lower())) @@ -697,14 +723,16 @@ class USERPREF_PT_file(bpy.types.Panel): col.prop(paths, "use_load_ui") col.prop(paths, "use_filter_files") col.prop(paths, "show_hidden_files_datablocks") + col.prop(paths, "hide_recent_locations") + col.prop(paths, "show_thumbnails") col.separator() col.separator() - col.label(text="Auto Save:") col.prop(paths, "save_version") col.prop(paths, "recent_files") col.prop(paths, "use_save_preview_images") + col.label(text="Auto Save:") col.prop(paths, "use_auto_save_temporary_files") sub = col.column() sub.active = paths.use_auto_save_temporary_files @@ -730,8 +758,10 @@ class USERPREF_PT_input(InputKeyMapPanel): sub = col.column() sub.label(text="Presets:") subrow = sub.row(align=True) + subrow.menu("USERPREF_MT_interaction_presets", text=bpy.types.USERPREF_MT_interaction_presets.bl_label) subrow.operator("wm.interaction_preset_add", text="", icon='ZOOMIN') + subrow.operator("wm.interaction_preset_add", text="", icon='ZOOMOUT').remove_active = True sub.separator() sub.label(text="Mouse:") @@ -739,6 +769,7 @@ class USERPREF_PT_input(InputKeyMapPanel): sub1.active = (inputs.select_mouse == 'RIGHT') sub1.prop(inputs, "use_mouse_emulate_3_button") sub.prop(inputs, "use_mouse_continuous") + sub.prop(inputs, "drag_threshold") sub.label(text="Select With:") sub.row().prop(inputs, "select_mouse", expand=True) @@ -789,7 +820,6 @@ class USERPREF_PT_input(InputKeyMapPanel): #start = time.time() userpref = context.user_preferences - wm = context.window_manager inputs = userpref.inputs @@ -804,12 +834,28 @@ class USERPREF_PT_input(InputKeyMapPanel): #print("runtime", time.time() - start) +class USERPREF_MT_addons_dev_guides(bpy.types.Menu): + bl_label = "Addons develoment guides" + + # menu to open webpages with addons development guides + def draw(self, context): + layout = self.layout + layout.operator('wm.url_open', text='API Concepts' + ).url = 'http://wiki.blender.org/index.php/Dev:2.5/Py/API/Intro' + layout.operator('wm.url_open', text='Addons guidelines', + ).url = 'http://wiki.blender.org/index.php/Dev:2.5/Py/Scripts/Guidelines/Addons' + layout.operator('wm.url_open', text='How to share your addon', + ).url = 'http://wiki.blender.org/index.php/Dev:Py/Sharing' + + class USERPREF_PT_addons(bpy.types.Panel): bl_space_type = 'USER_PREFERENCES' bl_label = "Addons" bl_region_type = 'WINDOW' bl_options = {'HIDE_HEADER'} - + + _addons_cats = None + _addons_sups = None _addons_fake_modules = {} @classmethod @@ -821,101 +867,6 @@ class USERPREF_PT_addons(bpy.types.Panel): def module_get(mod_name): return USERPREF_PT_addons._addons_fake_modules[mod_name] - @staticmethod - def _addon_list(): - import os - import sys - import time - - modules = [] - loaded_modules = set() - paths = bpy.utils.script_paths("addons") - # if folder addons_contrib/ exists, scripts in there will be loaded - paths += bpy.utils.script_paths("addons_contrib") - - if bpy.app.debug: - t_main = time.time() - - # fake module importing - def fake_module(mod_name, mod_path, speedy=True): - if bpy.app.debug: - print("fake_module", mod_name, mod_path) - import ast - ModuleType = type(ast) - if speedy: - lines = [] - line_iter = iter(open(mod_path, "r")) - l = "" - while not l.startswith("bl_addon_info"): - l = line_iter.readline() - if len(l) == 0: - break - while l.rstrip(): - lines.append(l) - l = line_iter.readline() - del line_iter - data = "".join(lines) - - else: - data = open(mod_path, "r").read() - - ast_data = ast.parse(data, filename=mod_path) - body_info = None - for body in ast_data.body: - if body.__class__ == ast.Assign: - if len(body.targets) == 1: - if getattr(body.targets[0], "id", "") == "bl_addon_info": - body_info = body - break - - if body_info: - mod = ModuleType(mod_name) - mod.bl_addon_info = ast.literal_eval(body.value) - mod.__file__ = mod_path - mod.__time__ = os.path.getmtime(mod_path) - return mod - else: - return None - - modules_stale = set(USERPREF_PT_addons._addons_fake_modules.keys()) - - for path in paths: - for f in sorted(os.listdir(path)): - if f.endswith(".py"): - mod_name = f[0:-3] - mod_path = os.path.join(path, f) - elif ("." not in f) and (os.path.isfile(os.path.join(path, f, "__init__.py"))): - mod_name = f - mod_path = os.path.join(path, f, "__init__.py") - else: - mod_name = "" - mod_path = "" - - if mod_name: - if mod_name in modules_stale: - modules_stale.remove(mod_name) - mod = USERPREF_PT_addons._addons_fake_modules.get(mod_name) - if mod: - if mod.__time__ != os.path.getmtime(mod_path): - print("Reloading", mod_name) - del USERPREF_PT_addons._addons_fake_modules[mod_name] - mod = None - - if mod is None: - mod = fake_module(mod_name, mod_path) - if mod: - USERPREF_PT_addons._addons_fake_modules[mod_name] = mod - - - # just incase we get stale modules, not likely - for mod_stale in modules_stale: - del USERPREF_PT_addons._addons_fake_modules[mod_stale] - del modules_stale - - mod_list = list(USERPREF_PT_addons._addons_fake_modules.values()) - mod_list.sort(key=lambda mod: (mod.bl_addon_info['category'], mod.bl_addon_info['name'])) - return mod_list - def draw(self, context): layout = self.layout @@ -923,40 +874,52 @@ class USERPREF_PT_addons(bpy.types.Panel): used_ext = {ext.module for ext in userpref.addons} # collect the categories that can be filtered on - addons = [(mod, addon_info_get(mod)) for mod in self._addon_list()] + addons = [(mod, addon_utils.module_bl_info(mod)) for mod in addon_utils.modules(USERPREF_PT_addons._addons_fake_modules)] cats = {info["category"] for mod, info in addons} cats.discard("") - cats = ["All", "Enabled", "Disabled"] + sorted(cats) + if USERPREF_PT_addons._addons_cats != cats: + bpy.types.WindowManager.addon_filter = EnumProperty(items=[(cat, cat, "") for cat in ["All", "Enabled", "Disabled"] + sorted(cats)], name="Category", description="Filter add-ons by category") + bpy.types.WindowManager.addon_search = StringProperty(name="Search", description="Search within the selected filter") + USERPREF_PT_addons._addons_cats = cats - bpy.types.Scene.EnumProperty(items=[(cat, cat, cat + " addons") for cat in cats], - name="Category", attr="addon_filter", description="Filter add-ons by category") - bpy.types.Scene.StringProperty(name="Search", attr="addon_search", - description="Search within the selected filter") + sups_default = {'OFFICIAL', 'COMMUNITY'} + sups = sups_default | {info["support"] for mod, info in addons} + sups.discard("") + + if USERPREF_PT_addons._addons_sups != sups: + bpy.types.WindowManager.addon_support = EnumProperty(items=[(sup, sup.title(), "") for sup in reversed(sorted(sups))], name="Support", description="Display support level", default=sups_default, options={'ENUM_FLAG'}) + USERPREF_PT_addons._addons_sups = sups split = layout.split(percentage=0.2) col = split.column() - col.prop(context.scene, "addon_filter", text="Filter", expand=True) - col.prop(context.scene, "addon_search", text="", icon='VIEWZOOM') + col.prop(context.window_manager, "addon_search", text="", icon='VIEWZOOM') + col.prop(context.window_manager, "addon_filter", expand=True) + + col.label(text="Supported Level") + col.prop(context.window_manager, "addon_support", expand=True) col = split.column() - filter = context.scene.addon_filter - search = context.scene.addon_search.lower() + filter = context.window_manager.addon_filter + search = context.window_manager.addon_search.lower() + support = context.window_manager.addon_support for mod, info in addons: module_name = mod.__name__ is_enabled = module_name in used_ext + if info["support"] not in support: + continue + # check if add-on should be visible with current filters if (filter == "All") or \ (filter == info["category"]) or \ (filter == "Enabled" and is_enabled) or \ (filter == "Disabled" and not is_enabled): - if search and search not in info["name"].lower(): if info["author"]: if search not in info["author"].lower(): @@ -974,7 +937,16 @@ class USERPREF_PT_addons(bpy.types.Panel): rowsub = row.row() rowsub.active = is_enabled rowsub.label(text='%s: %s' % (info['category'], info["name"])) - if info["warning"]: rowsub.label(icon='ERROR') + if info["warning"]: + rowsub.label(icon='ERROR') + + # icon showing support level. + if info["support"] == 'OFFICIAL': + rowsub.label(icon='FILE_BLEND') + elif info["support"] == 'COMMUNITY': + rowsub.label(icon='POSE_DATA') + else: + rowsub.label(icon='QUESTION') if is_enabled: row.operator("wm.addon_disable", icon='CHECKBOX_HLT', text="", emboss=False).module = module_name @@ -998,7 +970,7 @@ class USERPREF_PT_addons(bpy.types.Panel): if info["version"]: split = colsub.row().split(percentage=0.15) split.label(text='Version:') - split.label(text='.'.join([str(x) for x in info["version"]])) + split.label(text='.'.join(str(x) for x in info["version"])) if info["warning"]: split = colsub.row().split(percentage=0.15) split.label(text="Warning:") @@ -1027,36 +999,17 @@ class USERPREF_PT_addons(bpy.types.Panel): col.column().label(text="Missing script files") module_names = {mod.__name__ for mod, info in addons} - for ext in sorted(missing_modules): + for module_name in sorted(missing_modules): + is_enabled = module_name in used_ext # Addon UI Code box = col.column().box() colsub = box.column() row = colsub.row() - row.label(text=ext, icon='ERROR') - row.operator("wm.addon_disable").module = ext - -from bpy.props import * - - -def addon_info_get(mod, info_basis={"name": "", "author": "", "version": (), "blender": (), "api": 0, "location": "", "description": "", "wiki_url": "", "tracker_url": "", "category": "", "warning": "", "show_expanded": False}): - addon_info = getattr(mod, "bl_addon_info", {}) - - # avoid re-initializing - if "_init" in addon_info: - return addon_info - - if not addon_info: - mod.bl_addon_info = addon_info - - for key, value in info_basis.items(): - addon_info.setdefault(key, value) + row.label(text=module_name, icon='ERROR') - if not addon_info["name"]: - addon_info["name"] = mod.__name__ - - addon_info["_init"] = None - return addon_info + if is_enabled: + row.operator("wm.addon_disable", icon='CHECKBOX_HLT', text="", emboss=False).module = module_name class WM_OT_addon_enable(bpy.types.Operator): @@ -1067,34 +1020,18 @@ class WM_OT_addon_enable(bpy.types.Operator): module = StringProperty(name="Module", description="Module name of the addon to enable") def execute(self, context): - module_name = self.properties.module + mod = addon_utils.enable(self.module) - # note, this still gets added to _bpy_types.TypeMap - import bpy_types as _bpy_types - _bpy_types._register_immediate = False + if mod: + # check if add-on is written for current blender version, or raise a warning + info = addon_utils.module_bl_info(mod) - try: - mod = __import__(module_name) - _bpy_types._register_module(module_name) - mod.register() - except: - import traceback - traceback.print_exc() + if info.get("blender", (0, 0, 0)) > bpy.app.version: + self.report("WARNING','This script was written for a newer version of Blender and might not function (correctly).\nThe script is enabled though.") + return {'FINISHED'} + else: return {'CANCELLED'} - ext = context.user_preferences.addons.new() - ext.module = module_name - - # check if add-on is written for current blender version, or raise a warning - info = addon_info_get(mod) - - if info.get("blender", (0, 0, 0)) > bpy.app.version: - self.report("WARNING','This script was written for a newer version of Blender and might not function (correctly).\nThe script is enabled though.") - - _bpy_types._register_immediate = True - - return {'FINISHED'} - class WM_OT_addon_disable(bpy.types.Operator): "Disable an addon" @@ -1104,27 +1041,7 @@ class WM_OT_addon_disable(bpy.types.Operator): module = StringProperty(name="Module", description="Module name of the addon to disable") def execute(self, context): - import bpy_types as _bpy_types - module_name = self.properties.module - - try: - mod = __import__(module_name) - _bpy_types._unregister_module(module_name, free=False) # dont free because we may want to enable again. - mod.unregister() - except: - import traceback - traceback.print_exc() - - addons = context.user_preferences.addons - ok = True - while ok: # incase its in more then once. - ok = False - for ext in addons: - if ext.module == module_name: - addons.remove(ext) - ok = True - break - + addon_utils.disable(self.module) return {'FINISHED'} @@ -1133,27 +1050,73 @@ class WM_OT_addon_install(bpy.types.Operator): bl_idname = "wm.addon_install" bl_label = "Install Add-On..." - module = StringProperty(name="Module", description="Module name of the addon to disable") + overwrite = BoolProperty(name="Overwrite", description="Remove existing addons with the same ID", default=True) filepath = StringProperty(name="File Path", description="File path to write file to") filter_folder = BoolProperty(name="Filter folders", description="", default=True, options={'HIDDEN'}) filter_python = BoolProperty(name="Filter python", description="", default=True, options={'HIDDEN'}) + filter_glob = StringProperty(default="*.py;*.zip", options={'HIDDEN'}) + + @staticmethod + def _module_remove(path_addons, module): + module = os.path.splitext(module)[0] + for f in os.listdir(path_addons): + f_base = os.path.splitext(f)[0] + if f_base == module: + f_full = os.path.join(path_addons, f) + + if os.path.isdir(f_full): + os.rmdir(f_full) + else: + os.remove(f_full) def execute(self, context): import traceback import zipfile - pyfile = self.properties.filepath + pyfile = self.filepath + + # dont 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) + + if not path_addons: + self.report({'ERROR'}, "Failed to get addons path") + return {'CANCELLED'} + + # 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 addon search path: %r" % addon_path) + return {'CANCELLED'} + del addon_path + del pyfile_dir + # done checking for exceptional case - path_addons = bpy.utils.script_paths("addons")[-1] + contents = set(os.listdir(path_addons)) #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'} - #extract the file to "addons" - file_to_extract.extractall(path_addons) + if self.overwrite: + for f in file_to_extract.namelist(): + __class__._module_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'} @@ -1161,8 +1124,10 @@ class WM_OT_addon_install(bpy.types.Operator): else: path_dest = os.path.join(path_addons, os.path.basename(pyfile)) - if os.path.exists(path_dest): - self.report({'WARNING'}, "File already installed to '%s'\n" % path_dest) + if self.overwrite: + __class__._module_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 @@ -1173,18 +1138,30 @@ class WM_OT_addon_install(bpy.types.Operator): traceback.print_exc() return {'CANCELLED'} + # disable any addons we may have enabled previously and removed. + # this is unlikely but do just incase. bug [#23978] + addons_new = set(os.listdir(path_addons)) - contents + for new_addon in addons_new: + addon_utils.disable(os.path.splitext(new_addon)[0]) + + # possible the zip contains multiple addons, we could disallow this + # but for now just use the first + for mod in addon_utils.modules(USERPREF_PT_addons._addons_fake_modules): + 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 + # TODO, should not be a warning. # self.report({'WARNING'}, "File installed to '%s'\n" % path_dest) return {'FINISHED'} def invoke(self, context, event): - paths = bpy.utils.script_paths("addons") - if not paths: - self.report({'ERROR'}, "No 'addons' path could be found in " + str(bpy.utils.script_paths())) - return {'CANCELLED'} - wm = context.window_manager - wm.add_fileselect(self) + wm.fileselect_add(self) return {'RUNNING_MODAL'} @@ -1196,7 +1173,7 @@ class WM_OT_addon_expand(bpy.types.Operator): module = StringProperty(name="Module", description="Module name of the addon to expand") def execute(self, context): - module_name = self.properties.module + module_name = self.module # unlikely to fail, module should have already been imported try: @@ -1207,16 +1184,17 @@ class WM_OT_addon_expand(bpy.types.Operator): traceback.print_exc() return {'CANCELLED'} - info = addon_info_get(mod) + info = addon_utils.module_bl_info(mod) info["show_expanded"] = not info["show_expanded"] return {'FINISHED'} def register(): - pass + bpy.utils.register_module(__name__) + def unregister(): - pass + bpy.utils.unregister_module(__name__) if __name__ == "__main__": register() |