pep8 checker, mostly pedantic style changes but also found an error in mesh_utils...
[blender.git] / release / scripts / modules / addon_utils.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 __all__ = (
22     "paths",
23     "modules",
24     "check",
25     "enable",
26     "disable",
27     "reset_all",
28     "module_bl_info",
29 )
30
31 import bpy as _bpy
32
33
34 def paths():
35     # RELEASE SCRIPTS: official scripts distributed in Blender releases
36     paths = _bpy.utils.script_paths("addons")
37
38     # CONTRIB SCRIPTS: good for testing but not official scripts yet
39     # if folder addons_contrib/ exists, scripts in there will be loaded too
40     paths += _bpy.utils.script_paths("addons_contrib")
41
42     # EXTERN SCRIPTS: external projects scripts
43     # if folder addons_extern/ exists, scripts in there will be loaded too
44     paths += _bpy.utils.script_paths("addons_extern")
45
46     return paths
47
48
49 def modules(module_cache):
50     import os
51     import sys
52     import time
53
54     path_list = paths()
55
56     # fake module importing
57     def fake_module(mod_name, mod_path, speedy=True):
58         if _bpy.app.debug:
59             print("fake_module", mod_path, mod_name)
60         import ast
61         ModuleType = type(ast)
62         file_mod = open(mod_path, "r", encoding='UTF-8')
63         if speedy:
64             lines = []
65             line_iter = iter(file_mod)
66             l = ""
67             while not l.startswith("bl_info"):
68                 l = line_iter.readline()
69                 if len(l) == 0:
70                     break
71             while l.rstrip():
72                 lines.append(l)
73                 l = line_iter.readline()
74             data = "".join(lines)
75
76         else:
77             data = file_mod.read()
78
79         file_mod.close()
80
81         try:
82             ast_data = ast.parse(data, filename=mod_path)
83         except:
84             print("Syntax error 'ast.parse' can't read %r" % mod_path)
85             import traceback
86             traceback.print_exc()
87             ast_data = None
88
89         body_info = None
90
91         if ast_data:
92             for body in ast_data.body:
93                 if body.__class__ == ast.Assign:
94                     if len(body.targets) == 1:
95                         if getattr(body.targets[0], "id", "") == "bl_info":
96                             body_info = body
97                             break
98
99         if body_info:
100             try:
101                 mod = ModuleType(mod_name)
102                 mod.bl_info = ast.literal_eval(body.value)
103                 mod.__file__ = mod_path
104                 mod.__time__ = os.path.getmtime(mod_path)
105             except:
106                 print("AST error in module %s" % mod_name)
107                 import traceback
108                 traceback.print_exc()
109                 raise
110
111             return mod
112         else:
113             return None
114
115     modules_stale = set(module_cache.keys())
116
117     for path in path_list:
118         for mod_name, mod_path in _bpy.path.module_names(path):
119             modules_stale -= {mod_name}
120             mod = module_cache.get(mod_name)
121             if mod:
122                 if mod.__time__ != os.path.getmtime(mod_path):
123                     print("reloading addon:", mod_name, mod.__time__, os.path.getmtime(mod_path), mod_path)
124                     del module_cache[mod_name]
125                     mod = None
126
127             if mod is None:
128                 mod = fake_module(mod_name, mod_path)
129                 if mod:
130                     module_cache[mod_name] = mod
131
132     # just incase we get stale modules, not likely
133     for mod_stale in modules_stale:
134         del module_cache[mod_stale]
135     del modules_stale
136
137     mod_list = list(module_cache.values())
138     mod_list.sort(key=lambda mod: (mod.bl_info['category'], mod.bl_info['name']))
139     return mod_list
140
141
142 def check(module_name):
143     """
144     Returns the loaded state of the addon.
145
146     :arg module_name: The name of the addon and module.
147     :type module_name: string
148     :return: (loaded_default, loaded_state)
149     :rtype: tuple of booleans
150     """
151     import sys
152     loaded_default = module_name in _bpy.context.user_preferences.addons
153
154     mod = sys.modules.get(module_name)
155     loaded_state = mod and getattr(mod, "__addon_enabled__", Ellipsis)
156
157     if loaded_state is Ellipsis:
158         print("Warning: addon-module %r found module but without"
159                " __addon_enabled__ field, possible name collision from file: %r" %
160                (module_name, getattr(mod, "__file__", "<unknown>")))
161
162         loaded_state = False
163
164     return loaded_default, loaded_state
165
166
167 def enable(module_name, default_set=True):
168     """
169     Enables an addon by name.
170
171     :arg module_name: The name of the addon and module.
172     :type module_name: string
173     :return: the loaded module or None on failier.
174     :rtype: module
175     """
176     # note, this still gets added to _bpy_types.TypeMap
177
178     import os
179     import sys
180     import bpy_types as _bpy_types
181     import imp
182
183     def handle_error():
184         import traceback
185         traceback.print_exc()
186
187     # reload if the mtime changes
188     mod = sys.modules.get(module_name)
189     if mod:
190         mod.__addon_enabled__ = False
191         mtime_orig = getattr(mod, "__time__", 0)
192         mtime_new = os.path.getmtime(mod.__file__)
193         if mtime_orig != mtime_new:
194             print("module changed on disk:", mod.__file__, "reloading...")
195
196             try:
197                 imp.reload(mod)
198             except:
199                 handle_error()
200                 del sys.modules[module_name]
201                 return None
202             mod.__addon_enabled__ = False
203
204     # Split registering up into 3 steps so we can undo if it fails par way through
205     # 1) try import
206     try:
207         mod = __import__(module_name)
208         mod.__time__ = os.path.getmtime(mod.__file__)
209         mod.__addon_enabled__ = False
210     except:
211         handle_error()
212         return None
213
214     # 2) try register collected modules
215     # removed, addons need to handle own registration now.
216
217     # 3) try run the modules register function
218     try:
219         mod.register()
220     except:
221         handle_error()
222         del sys.modules[module_name]
223         return None
224
225     # * OK loaded successfully! *
226     if default_set:
227         # just incase its enabled alredy
228         ext = _bpy.context.user_preferences.addons.get(module_name)
229         if not ext:
230             ext = _bpy.context.user_preferences.addons.new()
231             ext.module = module_name
232
233     mod.__addon_enabled__ = True
234
235     if _bpy.app.debug:
236         print("\taddon_utils.enable", mod.__name__)
237
238     return mod
239
240
241 def disable(module_name, default_set=True):
242     """
243     Disables an addon by name.
244
245     :arg module_name: The name of the addon and module.
246     :type module_name: string
247     """
248     import sys
249     import bpy_types as _bpy_types
250
251     mod = sys.modules.get(module_name)
252
253     # possible this addon is from a previous session and didnt load a module this time.
254     # so even if the module is not found, still disable the addon in the user prefs.
255     if mod:
256         mod.__addon_enabled__ = False
257
258         try:
259             mod.unregister()
260         except:
261             import traceback
262             traceback.print_exc()
263     else:
264         print("addon_utils.disable", module_name, "not loaded")
265
266     # could be in more then once, unlikely but better do this just incase.
267     addons = _bpy.context.user_preferences.addons
268
269     if default_set:
270         while module_name in addons:
271             addon = addons.get(module_name)
272             if addon:
273                 addons.remove(addon)
274
275     if _bpy.app.debug:
276         print("\taddon_utils.disable", module_name)
277
278
279 def reset_all(reload_scripts=False):
280     """
281     Sets the addon state based on the user preferences.
282     """
283     import sys
284     import imp
285
286     # RELEASE SCRIPTS: official scripts distributed in Blender releases
287     paths_list = paths()
288
289     for path in paths_list:
290         _bpy.utils._sys_path_ensure(path)
291         for mod_name, mod_path in _bpy.path.module_names(path):
292             is_enabled, is_loaded = check(mod_name)
293
294             # first check if reload is needed before changing state.
295             if reload_scripts:
296                 mod = sys.modules.get(mod_name)
297                 if mod:
298                     imp.reload(mod)
299
300             if is_enabled == is_loaded:
301                 pass
302             elif is_enabled:
303                 enable(mod_name)
304             elif is_loaded:
305                 print("\taddon_utils.reset_all unloading", mod_name)
306                 disable(mod_name)
307
308
309 def module_bl_info(mod, info_basis={"name": "", "author": "", "version": (), "blender": (), "api": 0, "location": "", "description": "", "wiki_url": "", "tracker_url": "", "support": 'COMMUNITY', "category": "", "warning": "", "show_expanded": False}):
310     addon_info = getattr(mod, "bl_info", {})
311
312     # avoid re-initializing
313     if "_init" in addon_info:
314         return addon_info
315
316     if not addon_info:
317         mod.bl_info = addon_info
318
319     for key, value in info_basis.items():
320         addon_info.setdefault(key, value)
321
322     if not addon_info["name"]:
323         addon_info["name"] = mod.__name__
324
325     addon_info["_init"] = None
326     return addon_info