cleanup for python scripts - unused vars and imports
[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
52     path_list = paths()
53
54     # fake module importing
55     def fake_module(mod_name, mod_path, speedy=True):
56         if _bpy.app.debug:
57             print("fake_module", mod_path, mod_name)
58         import ast
59         ModuleType = type(ast)
60         file_mod = open(mod_path, "r", encoding='UTF-8')
61         if speedy:
62             lines = []
63             line_iter = iter(file_mod)
64             l = ""
65             while not l.startswith("bl_info"):
66                 l = line_iter.readline()
67                 if len(l) == 0:
68                     break
69             while l.rstrip():
70                 lines.append(l)
71                 l = line_iter.readline()
72             data = "".join(lines)
73
74         else:
75             data = file_mod.read()
76
77         file_mod.close()
78
79         try:
80             ast_data = ast.parse(data, filename=mod_path)
81         except:
82             print("Syntax error 'ast.parse' can't read %r" % mod_path)
83             import traceback
84             traceback.print_exc()
85             ast_data = None
86
87         body_info = None
88
89         if ast_data:
90             for body in ast_data.body:
91                 if body.__class__ == ast.Assign:
92                     if len(body.targets) == 1:
93                         if getattr(body.targets[0], "id", "") == "bl_info":
94                             body_info = body
95                             break
96
97         if body_info:
98             try:
99                 mod = ModuleType(mod_name)
100                 mod.bl_info = ast.literal_eval(body.value)
101                 mod.__file__ = mod_path
102                 mod.__time__ = os.path.getmtime(mod_path)
103             except:
104                 print("AST error in module %s" % mod_name)
105                 import traceback
106                 traceback.print_exc()
107                 raise
108
109             return mod
110         else:
111             return None
112
113     modules_stale = set(module_cache.keys())
114
115     for path in path_list:
116         for mod_name, mod_path in _bpy.path.module_names(path):
117             modules_stale -= {mod_name}
118             mod = module_cache.get(mod_name)
119             if mod:
120                 if mod.__time__ != os.path.getmtime(mod_path):
121                     print("reloading addon:", mod_name, mod.__time__, os.path.getmtime(mod_path), mod_path)
122                     del module_cache[mod_name]
123                     mod = None
124
125             if mod is None:
126                 mod = fake_module(mod_name, mod_path)
127                 if mod:
128                     module_cache[mod_name] = mod
129
130     # just incase we get stale modules, not likely
131     for mod_stale in modules_stale:
132         del module_cache[mod_stale]
133     del modules_stale
134
135     mod_list = list(module_cache.values())
136     mod_list.sort(key=lambda mod: (mod.bl_info['category'], mod.bl_info['name']))
137     return mod_list
138
139
140 def check(module_name):
141     """
142     Returns the loaded state of the addon.
143
144     :arg module_name: The name of the addon and module.
145     :type module_name: string
146     :return: (loaded_default, loaded_state)
147     :rtype: tuple of booleans
148     """
149     import sys
150     loaded_default = module_name in _bpy.context.user_preferences.addons
151
152     mod = sys.modules.get(module_name)
153     loaded_state = mod and getattr(mod, "__addon_enabled__", Ellipsis)
154
155     if loaded_state is Ellipsis:
156         print("Warning: addon-module %r found module but without"
157                " __addon_enabled__ field, possible name collision from file: %r" %
158                (module_name, getattr(mod, "__file__", "<unknown>")))
159
160         loaded_state = False
161
162     return loaded_default, loaded_state
163
164
165 def enable(module_name, default_set=True):
166     """
167     Enables an addon by name.
168
169     :arg module_name: The name of the addon and module.
170     :type module_name: string
171     :return: the loaded module or None on failier.
172     :rtype: module
173     """
174
175     import os
176     import sys
177     import imp
178
179     def handle_error():
180         import traceback
181         traceback.print_exc()
182
183     # reload if the mtime changes
184     mod = sys.modules.get(module_name)
185     if mod:
186         mod.__addon_enabled__ = False
187         mtime_orig = getattr(mod, "__time__", 0)
188         mtime_new = os.path.getmtime(mod.__file__)
189         if mtime_orig != mtime_new:
190             print("module changed on disk:", mod.__file__, "reloading...")
191
192             try:
193                 imp.reload(mod)
194             except:
195                 handle_error()
196                 del sys.modules[module_name]
197                 return None
198             mod.__addon_enabled__ = False
199
200     # Split registering up into 3 steps so we can undo if it fails par way through
201     # 1) try import
202     try:
203         mod = __import__(module_name)
204         mod.__time__ = os.path.getmtime(mod.__file__)
205         mod.__addon_enabled__ = False
206     except:
207         handle_error()
208         return None
209
210     # 2) try register collected modules
211     # removed, addons need to handle own registration now.
212
213     # 3) try run the modules register function
214     try:
215         mod.register()
216     except:
217         handle_error()
218         del sys.modules[module_name]
219         return None
220
221     # * OK loaded successfully! *
222     if default_set:
223         # just incase its enabled alredy
224         ext = _bpy.context.user_preferences.addons.get(module_name)
225         if not ext:
226             ext = _bpy.context.user_preferences.addons.new()
227             ext.module = module_name
228
229     mod.__addon_enabled__ = True
230
231     if _bpy.app.debug:
232         print("\taddon_utils.enable", mod.__name__)
233
234     return mod
235
236
237 def disable(module_name, default_set=True):
238     """
239     Disables an addon by name.
240
241     :arg module_name: The name of the addon and module.
242     :type module_name: string
243     """
244     import sys
245     mod = sys.modules.get(module_name)
246
247     # possible this addon is from a previous session and didnt load a module this time.
248     # so even if the module is not found, still disable the addon in the user prefs.
249     if mod:
250         mod.__addon_enabled__ = False
251
252         try:
253             mod.unregister()
254         except:
255             import traceback
256             traceback.print_exc()
257     else:
258         print("addon_utils.disable", module_name, "not loaded")
259
260     # could be in more then once, unlikely but better do this just incase.
261     addons = _bpy.context.user_preferences.addons
262
263     if default_set:
264         while module_name in addons:
265             addon = addons.get(module_name)
266             if addon:
267                 addons.remove(addon)
268
269     if _bpy.app.debug:
270         print("\taddon_utils.disable", module_name)
271
272
273 def reset_all(reload_scripts=False):
274     """
275     Sets the addon state based on the user preferences.
276     """
277     import sys
278     import imp
279
280     # RELEASE SCRIPTS: official scripts distributed in Blender releases
281     paths_list = paths()
282
283     for path in paths_list:
284         _bpy.utils._sys_path_ensure(path)
285         for mod_name, mod_path in _bpy.path.module_names(path):
286             is_enabled, is_loaded = check(mod_name)
287
288             # first check if reload is needed before changing state.
289             if reload_scripts:
290                 mod = sys.modules.get(mod_name)
291                 if mod:
292                     imp.reload(mod)
293
294             if is_enabled == is_loaded:
295                 pass
296             elif is_enabled:
297                 enable(mod_name)
298             elif is_loaded:
299                 print("\taddon_utils.reset_all unloading", mod_name)
300                 disable(mod_name)
301
302
303 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}):
304     addon_info = getattr(mod, "bl_info", {})
305
306     # avoid re-initializing
307     if "_init" in addon_info:
308         return addon_info
309
310     if not addon_info:
311         mod.bl_info = addon_info
312
313     for key, value in info_basis.items():
314         addon_info.setdefault(key, value)
315
316     if not addon_info["name"]:
317         addon_info["name"] = mod.__name__
318
319     addon_info["_init"] = None
320     return addon_info