Index: space_userpref.py =================================================================== --- space_userpref.py (revision 27386) +++ space_userpref.py (working copy) @@ -177,6 +177,149 @@ op.path = "*.py" +class USERPREF_MT_addon_filter(bpy.types.Menu): + bl_label = "Filter scripts by category" + + def draw(self, context): + layout = self.layout + + layout.operator("wm.addon_filter", text="All").filter = 'All' + layout.operator("wm.addon_filter", text="Disabled").filter = 'Disabled' + layout.operator("wm.addon_filter", text="Enabled").filter = 'Enabled' + + layout.separator() + + layout.menu("USERPREF_MT_addon_filter_animation") + layout.menu("USERPREF_MT_addon_filter_game_engine") + layout.menu("USERPREF_MT_addon_filter_io") + layout.operator("wm.addon_filter", text="Lighting").filter = 'Lighting' + layout.menu("USERPREF_MT_addon_filter_materials") + layout.operator("wm.addon_filter", text="Misc").filter = 'Misc' + layout.menu("USERPREF_MT_addon_filter_modelling") + layout.menu("USERPREF_MT_addon_filter_modifiers") + layout.menu("USERPREF_MT_addon_filter_output") + layout.menu("USERPREF_MT_addon_filter_physics") + layout.menu("USERPREF_MT_addon_filter_pyconstraints") + layout.operator("wm.addon_filter", text="Pydrivers").filter = 'Pydrivers' + layout.menu("USERPREF_MT_addon_filter_textures") + + +class USERPREF_MT_addon_filter_animation(bpy.types.Menu): + bl_label = "Animation" + + def draw(self, context): + layout = self.layout + layout.operator("wm.addon_filter", text="All").filter = 'Animation - All' + layout.separator() + layout.operator("wm.addon_filter", text="Armature").filter = 'Animation - Armature' + layout.operator("wm.addon_filter", text="Weighting").filter = 'Animation - Weighting' + + +class USERPREF_MT_addon_filter_game_engine(bpy.types.Menu): + bl_label = "Game engine" + + def draw(self, context): + layout = self.layout + layout.operator("wm.addon_filter", text="All").filter = 'Game engine - All' + layout.separator() + layout.operator("wm.addon_filter", text="Logic").filter = 'Game engine - Logic' + layout.operator("wm.addon_filter", text="Physics").filter = 'Game engine - Physics' + + +class USERPREF_MT_addon_filter_io(bpy.types.Menu): + bl_label = "I/O" + + def draw(self, context): + layout = self.layout + layout.operator("wm.addon_filter", text="All").filter = 'I/O - All' + layout.separator() + layout.operator("wm.addon_filter", text="Export").filter = 'I/O - Export' + layout.operator("wm.addon_filter", text="Import").filter = 'I/O - Import' + + +class USERPREF_MT_addon_filter_materials(bpy.types.Menu): + bl_label = "Materials" + + def draw(self, context): + layout = self.layout + layout.operator("wm.addon_filter", text="All").filter = 'Materials - All' + layout.separator() + layout.operator("wm.addon_filter", text="Pynodes").filter = 'Materials - Pynodes' + + +class USERPREF_MT_addon_filter_modelling(bpy.types.Menu): + bl_label = "Modelling" + + def draw(self, context): + layout = self.layout + layout.operator("wm.addon_filter", text="All").filter = 'Modelling - All' + layout.separator() + layout.operator("wm.addon_filter", text="Add mesh").filter = 'Modelling - Add mesh' + layout.operator("wm.addon_filter", text="Mesh").filter = 'Modelling - Mesh' + layout.operator("wm.addon_filter", text="Object").filter = 'Modelling - Object' + + +class USERPREF_MT_addon_filter_modifiers(bpy.types.Menu): + bl_label = "Modifiers" + + def draw(self, context): + layout = self.layout + layout.operator("wm.addon_filter", text="All").filter = 'Modifiers - All' + layout.separator() + layout.operator("wm.addon_filter", text="Deform").filter = 'Modifiers - Deform' + layout.operator("wm.addon_filter", text="Generate").filter = 'Modifiers - Generate' + layout.operator("wm.addon_filter", text="Simulate").filter = 'Modifiers - Simulate' + + +class USERPREF_MT_addon_filter_output(bpy.types.Menu): + bl_label = "Output" + + def draw(self, context): + layout = self.layout + layout.operator("wm.addon_filter", text="All").filter = 'Output - All' + layout.separator() + layout.operator("wm.addon_filter", text="Compositing").filter = 'Output - Compositing' + layout.operator("wm.addon_filter", text="Rendering").filter = 'Output - Rendering' + layout.operator("wm.addon_filter", text="Sequencer").filter = 'Output - Sequencer' + + +class USERPREF_MT_addon_filter_physics(bpy.types.Menu): + bl_label = "Physics" + + def draw(self, context): + layout = self.layout + layout.operator("wm.addon_filter", text="All").filter = 'Physics - All' + layout.separator() + layout.operator("wm.addon_filter", text="Cloth").filter = 'Physics - Cloth' + layout.operator("wm.addon_filter", text="Collision").filter = 'Physics - Collision' + layout.operator("wm.addon_filter", text="Fluid").filter = 'Physics - Fluid' + layout.operator("wm.addon_filter", text="Force field").filter = 'Physics - Force field' + layout.operator("wm.addon_filter", text="Smoke").filter = 'Physics - Smoke' + layout.operator("wm.addon_filter", text="Soft body").filter = 'Physics - Soft body' + + +class USERPREF_MT_addon_filter_pyconstraints(bpy.types.Menu): + bl_label = "Pyconstraints" + + def draw(self, context): + layout = self.layout + layout.operator("wm.addon_filter", text="All").filter = 'Pyconstraints - All' + layout.separator() + layout.operator("wm.addon_filter", text="Object").filter = 'Pyconstraints - Object' + layout.operator("wm.addon_filter", text="Special").filter = 'Pyconstraints - Special' + + +class USERPREF_MT_addon_filter_textures(bpy.types.Menu): + bl_label = "Textures" + + def draw(self, context): + layout = self.layout + layout.operator("wm.addon_filter", text="All").filter = 'Textures - All' + layout.separator() + layout.operator("wm.addon_filter", text="Pynodes").filter = 'Textures - Pynodes' + layout.operator("wm.addon_filter", text="UV").filter = 'Textures - UV' + + class USERPREF_PT_tabs(bpy.types.Panel): bl_label = "" bl_space_type = 'USER_PREFERENCES' @@ -1384,24 +1527,177 @@ # del sys.path[0] return modules + + def _attributes(self, mod): + # collect, check and process all attributes of the add-on + module_name = mod.__name__ + if not hasattr(mod, 'expanded'): + mod.expanded = False + + script = hasattr(mod, '__script__') + author = hasattr(mod, '__author__') + version = hasattr(mod, '__version__') + blender = hasattr(mod, '__blender__') + category = hasattr(mod, '__category__') + url = hasattr(mod, '__url__') + email = hasattr(mod, '__email__') + bpydoc = hasattr(mod, '__bpydoc__') + if script: + script = str(mod.__script__) + else: + script = module_name + if version: + version = str(mod.__version__) + if author: + if type(mod.__author__).__name__ == 'list': + if len(mod.__author__) == 0: + author = False + else: + author = "" + for i in mod.__author__: + author += str(i) + ", " + author = author[:-2] + else: + author = str(mod.__author__) + if blender: + blender = str(mod.__blender__) + subcategory = False + if category: + if type(mod.__category__).__name__ != 'list': + category = str(mod.__category__).lower() + else: + category = str(mod.__category__[0]).lower() + if len(mod.__category__)>1: + subcategory = str(mod.__category__[1]).lower() + links = [] + if url: + if type(mod.__url__).__name__ != 'list': + mod.__url__ = [str(mod.__url__)] + for i in mod.__url__: + link = str(i).rsplit(',', 1) + if len(link)>1: + link_desc = link[0].strip() + link = link[1].strip() + else: + link_desc = False + link = link[0].strip() + if link.lower() == 'blender': + link = 'http://www.blender.org/forum/viewforum.php?f=9' + if link.lower() == 'blenderartists': + link = 'http://blenderartists.org/forum/forumdisplay.php?f=11' + links.append([link, link_desc]) + emails = [] + if email: + if type(mod.__email__).__name__ != 'list': + mod.__email__ = [str(mod.__email__)] + for i in mod.__email__: + mail = str(i).rsplit(',', 1) + if len(mail)>1: + mail_desc = mail[0].strip() + mail = mail[1].strip() + else: + mail_desc = False + mail = mail[0].strip() + if mail.lower() == 'python': + mail = 'bf-python:blender*org' + mail = 'mailto:'+mail.replace(':','@').replace('*','.')+"?subject="+script + emails.append([mail, mail_desc]) + if bpydoc: + bpydoc = str(mod.__bpydoc__).splitlines() + return module_name, script, author, version, blender, category, subcategory, url, email, bpydoc, links, emails def draw(self, context): layout = self.layout userpref = context.user_preferences used_ext = {ext.module for ext in userpref.addons} + + # different displaying options + bpy.types.Scene.StringProperty(name="Category", attr="addon_filter", default="All", + description="Filter the selected category") + bpy.types.Scene.StringProperty(name="Search", attr="addon_search", + description="Search within the selected category") + filter = context.scene.addon_filter + filter = filter.rsplit('-',1) + if len(filter)>1: + subfilter = filter[1].strip().lower() + filter = filter[0].strip().lower() + if subfilter == 'all': + subfilter = False + else: + filter = filter[0].strip().lower() + subfilter = False + search = context.scene.addon_search + col = layout.column() + split = col.row().split(percentage=0.5) + split.menu("USERPREF_MT_addon_filter", text=context.scene.addon_filter) + split.prop(context.scene, "addon_search", text="Search", icon='VIEWZOOM') + col.separator() for mod in self._addon_list(): + module_name, script, author, version, blender, category, subcategory, url, email, bpydoc, links,\ + emails = self._attributes(mod) + + # check if add-on should be visible with current filters + if filter!='all' and filter!=category and not (module_name in used_ext and filter=='enabled')\ + and not (module_name not in used_ext and filter=='disabled'): + continue + if subfilter and subfilter!=subcategory: + continue + if search and script.lower().find(search.lower())<0: + if author: + if author.lower().find(search.lower())<0: + continue + else: + continue + box = col.box() - row = box.row() - text = mod.__doc__ - if not text: - text = mod.__name__ - row.label(text=text) - module_name = mod.__name__ - row.operator("wm.addon_disable" if module_name in used_ext else "wm.addon_enable").module = module_name + subcol = box.column(align=True) + subrow = subcol.row() + if mod.expanded: + subrow.operator("wm.addon_expand", icon="TRIA_DOWN").module = module_name + elif author or version or url or email: + subrow.operator("wm.addon_expand", icon="TRIA_RIGHT").module = module_name + else: + test = subrow.column() + test.enabled = False + test.operator("wm.addon_expand", icon="TRIA_RIGHT").module = module_name + subrow.label(text=script) + subrow.operator("wm.addon_disable" if module_name in used_ext else "wm.addon_enable").module = module_name + + if mod.expanded: + # extra information, only available when add-on is expanded + if author: + subsplit = subcol.row().split(percentage=0.15) + subsplit.label(text='Author:') + subsplit.label(text=author) + if version: + subsplit = subcol.row().split(percentage=0.15) + subsplit.label(text='Version:') + subsplit.label(text=version) + if url: + subsplit = subcol.row().split(percentage=0.15) + subsplit.label(text="Links:") + for i in range(len(links)): + if links[i][1]: + subsplit.operator("wm.addon_links", text=links[i][1]).link = links[i][0] + else: + subsplit.operator("wm.addon_links", text="Link "+str(i+1)).link = links[i][0] + if email: + subsplit = subcol.row().split(percentage=0.15) + subsplit.label(text="Email:") + for i in range(len(emails)): + if emails[i][1]: + subsplit.operator("wm.addon_links", text=emails[i][1]).link = emails[i][0] + else: + subsplit.operator("wm.addon_links", text="Email "+str(i+1)).link = emails[i][0] + if bpydoc: + subcol = box.column(align=True) + subcol.label(text='Description: '+bpydoc[0]) + for line in bpydoc[1:]: + subcol.label(text=line) from bpy.props import * @@ -1425,6 +1721,23 @@ mod.register() except: traceback.print_exc() + + # check if add-on is written for current blender version, or raise a warning + version = hasattr(mod, '__blender__') + if version: + version = str(mod.__blender__).split('.',2) + for i in range(len(version)): + try: + version[i] = int(version[i]) + except: + break + if version[i]>bpy.app.version[i]: + self.report('WARNING','This script was written for a newer version of Blender \ +and might not function (correctly).\nThe script is enabled though.') + elif version[i]==bpy.app.version[i]: + continue + else: + break return {'FINISHED'} @@ -1521,6 +1834,55 @@ return {'RUNNING_MODAL'} +class WM_OT_addon_expand(bpy.types.Operator): + "Display more information on this add-on" + bl_idname = "wm.addon_expand" + bl_label = "" + + module = StringProperty(name="Module", description="Module name of the addon to expand") + + def execute(self, context): + import traceback + module_name = self.properties.module + + try: + mod = __import__(module_name) + except: + traceback.print_exc() + + if mod.expanded: + mod.expanded = False + else: + mod.expanded = True + + return {'FINISHED'} + + +class WM_OT_addon_filter(bpy.types.Operator): + "Filter scripts by category" + bl_idname = "wm.addon_filter" + bl_label = "" + + filter = StringProperty(name="Filter", description="Filter category") + + def execute(self, context): + context.scene.addon_filter = self.properties.filter + return {'FINISHED'} + + +class WM_OT_addon_links(bpy.types.Operator): + "Open in webbrowser" + bl_idname = "wm.addon_links" + bl_label = "" + + link = StringProperty(name="Link", description="Link to open") + + def execute(self, context): + import webbrowser + webbrowser.open(self.properties.link) + return {'FINISHED'} + + class WM_OT_keyconfig_test(bpy.types.Operator): "Test keyconfig for conflicts" bl_idname = "wm.keyconfig_test" @@ -1902,6 +2264,19 @@ classes = [ USERPREF_HT_header, + + USERPREF_MT_addon_filter, + USERPREF_MT_addon_filter_animation, + USERPREF_MT_addon_filter_game_engine, + USERPREF_MT_addon_filter_io, + USERPREF_MT_addon_filter_materials, + USERPREF_MT_addon_filter_modelling, + USERPREF_MT_addon_filter_modifiers, + USERPREF_MT_addon_filter_output, + USERPREF_MT_addon_filter_physics, + USERPREF_MT_addon_filter_pyconstraints, + USERPREF_MT_addon_filter_textures, + USERPREF_PT_tabs, USERPREF_PT_interface, USERPREF_PT_theme, @@ -1914,6 +2289,9 @@ WM_OT_addon_enable, WM_OT_addon_disable, WM_OT_addon_install, + WM_OT_addon_expand, + WM_OT_addon_filter, + WM_OT_addon_links, WM_OT_keyconfig_export, WM_OT_keyconfig_import, @@ -1938,4 +2316,4 @@ unregister(cls) if __name__ == "__main__": - register() + register() \ No newline at end of file