bugfix [#24087] Blender can not install add-ons unless running with root priviledges
[blender.git] / release / scripts / modules / bpy / 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 """
22 This module contains utility functions specific to blender but
23 not assosiated with blenders internal data.
24 """
25
26 import bpy as _bpy
27 import os as _os
28 import sys as _sys
29
30 from _bpy import blend_paths, user_resource
31 from _bpy import script_paths as _bpy_script_paths
32
33
34 def _test_import(module_name, loaded_modules):
35     import traceback
36     import time
37     if module_name in loaded_modules:
38         return None
39     if "." in module_name:
40         print("Ignoring '%s', can't import files containing multiple periods." % module_name)
41         return None
42
43     t = time.time()
44     try:
45         mod = __import__(module_name)
46     except:
47         traceback.print_exc()
48         return None
49
50     if _bpy.app.debug:
51         print("time %s %.4f" % (module_name, time.time() - t))
52
53     loaded_modules.add(mod.__name__)  # should match mod.__name__ too
54     return mod
55
56
57 def _sys_path_ensure(path):
58     if path not in _sys.path:  # reloading would add twice
59         _sys.path.insert(0, path)
60
61
62 def modules_from_path(path, loaded_modules):
63     """
64     Load all modules in a path and return them as a list.
65
66     :arg path: this path is scanned for scripts and packages.
67     :type path: string
68     :arg loaded_modules: already loaded module names, files matching these names will be ignored.
69     :type loaded_modules: set
70     :return: all loaded modules.
71     :rtype: list
72     """
73     import traceback
74     import time
75
76     modules = []
77
78     for mod_name, mod_path in _bpy.path.module_names(path):
79         mod = _test_import(mod_name, loaded_modules)
80         if mod:
81             modules.append(mod)
82
83     return modules
84
85
86 _global_loaded_modules = []  # store loaded module names for reloading.
87 import bpy_types as _bpy_types  # keep for comparisons, never ever reload this.
88
89
90 def load_scripts(reload_scripts=False, refresh_scripts=False):
91     """
92     Load scripts and run each modules register function.
93
94     :arg reload_scripts: Causes all scripts to have their unregister method called before loading.
95     :type reload_scripts: bool
96     :arg refresh_scripts: only load scripts which are not already loaded as modules.
97     :type refresh_scripts: bool
98     """
99     import traceback
100     import time
101
102     # must be set back to True on exits
103     _bpy_types._register_immediate = False
104
105     t_main = time.time()
106
107     loaded_modules = set()
108
109     if refresh_scripts:
110         original_modules = _sys.modules.values()
111
112     if reload_scripts:
113         _bpy_types.TypeMap.clear()
114         _bpy_types.PropertiesMap.clear()
115
116         # just unload, dont change user defaults, this means we can sync to reload.
117         # note that they will only actually reload of the modification time changes.
118         # this `wont` work for packages so... its not perfect.
119         for module_name in [ext.module for ext in _bpy.context.user_preferences.addons]:
120             addon_disable(module_name, default_set=False)
121
122     def register_module_call(mod):
123         _bpy_types._register_module(mod.__name__)
124         register = getattr(mod, "register", None)
125         if register:
126             try:
127                 register()
128             except:
129                 traceback.print_exc()
130         else:
131             print("\nWarning! '%s' has no register function, this is now a requirement for registerable scripts." % mod.__file__)
132
133     def unregister_module_call(mod):
134         _bpy_types._unregister_module(mod.__name__)
135         unregister = getattr(mod, "unregister", None)
136         if unregister:
137             try:
138                 unregister()
139             except:
140                 traceback.print_exc()
141
142     def test_reload(mod):
143         # reloading this causes internal errors
144         # because the classes from this module are stored internally
145         # possibly to refresh internal references too but for now, best not to.
146         if mod == _bpy_types:
147             return mod
148
149         try:
150             return reload(mod)
151         except:
152             traceback.print_exc()
153
154     def test_register(mod):
155
156         if refresh_scripts and mod in original_modules:
157             return
158
159         if reload_scripts and mod:
160             print("Reloading:", mod)
161             mod = test_reload(mod)
162
163         if mod:
164             register_module_call(mod)
165             _global_loaded_modules.append(mod.__name__)
166
167     if reload_scripts:
168
169         # module names -> modules
170         _global_loaded_modules[:] = [_sys.modules[mod_name] for mod_name in _global_loaded_modules]
171
172         # loop over and unload all scripts
173         _global_loaded_modules.reverse()
174         for mod in _global_loaded_modules:
175             unregister_module_call(mod)
176
177         for mod in _global_loaded_modules:
178             test_reload(mod)
179
180         _global_loaded_modules[:] = []
181
182     user_path = user_script_path()
183
184     for base_path in script_paths():
185         for path_subdir in ("", "ui", "op", "io", "keyingsets", "modules"):
186             path = _os.path.join(base_path, path_subdir)
187             if _os.path.isdir(path):
188                 _sys_path_ensure(path)
189
190                 # only add this to sys.modules, dont run
191                 if path_subdir == "modules":
192                     continue
193
194                 if user_path != base_path and path_subdir == "":
195                     continue  # avoid loading 2.4x scripts
196
197                 for mod in modules_from_path(path, loaded_modules):
198                     test_register(mod)
199
200     _bpy_types._register_immediate = True
201
202     # deal with addons seperately
203     addon_reset_all()
204
205
206     # run the active integration preset
207     filepath = preset_find(_bpy.context.user_preferences.inputs.active_keyconfig, "keyconfig")
208     if filepath:
209         keyconfig_set(filepath)
210
211
212     if reload_scripts:
213         import gc
214         print("gc.collect() -> %d" % gc.collect())
215
216     if _bpy.app.debug:
217         print("Python Script Load Time %.4f" % (time.time() - t_main))
218
219
220 # base scripts
221 _scripts = _os.path.join(_os.path.dirname(__file__), _os.path.pardir, _os.path.pardir)
222 _scripts = (_os.path.normpath(_scripts), )
223
224
225 def user_script_path():
226     path = _bpy.context.user_preferences.filepaths.script_directory
227
228     if path:
229         path = _os.path.normpath(path)
230         return path
231     else:
232         return None
233
234
235 def script_paths(subdir=None, user=True):
236     """
237     Returns a list of valid script paths from the home directory and user preferences.
238
239     Accepts any number of string arguments which are joined to make a path.
240     """
241     scripts = list(_scripts)
242
243     # add user scripts dir
244     if user:
245         user_script_path = _bpy.context.user_preferences.filepaths.script_directory
246     else:
247         user_script_path = None
248
249     for path in _bpy_script_paths() + (user_script_path, ):
250         if path:
251             path = _os.path.normpath(path)
252             if path not in scripts and _os.path.isdir(path):
253                 scripts.append(path)
254
255     if not subdir:
256         return scripts
257
258     script_paths = []
259     for path in scripts:
260         path_subdir = _os.path.join(path, subdir)
261         if _os.path.isdir(path_subdir):
262             script_paths.append(path_subdir)
263
264     return script_paths
265
266
267 _presets = _os.path.join(_scripts[0], "presets")  # FIXME - multiple paths
268
269
270 def preset_paths(subdir):
271     """
272     Returns a list of paths for a specific preset.
273     """
274     dirs = []
275     for path in script_paths("presets"):
276         directory = _os.path.join(path, subdir)
277         if _os.path.isdir(directory):
278             dirs.append(directory)
279     return dirs
280
281
282 def smpte_from_seconds(time, fps=None):
283     """
284     Returns an SMPTE formatted string from the time in seconds: "HH:MM:SS:FF".
285
286     If the *fps* is not given the current scene is used.
287     """
288     import math
289
290     if fps is None:
291         fps = _bpy.context.scene.render.fps
292
293     hours = minutes = seconds = frames = 0
294
295     if time < 0:
296         time = - time
297         neg = "-"
298     else:
299         neg = ""
300
301     if time >= 3600.0:  # hours
302         hours = int(time / 3600.0)
303         time = time % 3600.0
304     if time >= 60.0:  # mins
305         minutes = int(time / 60.0)
306         time = time % 60.0
307
308     seconds = int(time)
309     frames = int(round(math.floor(((time - seconds) * fps))))
310
311     return "%s%02d:%02d:%02d:%02d" % (neg, hours, minutes, seconds, frames)
312
313
314 def smpte_from_frame(frame, fps=None, fps_base=None):
315     """
316     Returns an SMPTE formatted string from the frame: "HH:MM:SS:FF".
317
318     If *fps* and *fps_base* are not given the current scene is used.
319     """
320
321     if fps is None:
322         fps = _bpy.context.scene.render.fps
323
324     if fps_base is None:
325         fps_base = _bpy.context.scene.render.fps_base
326
327     return smpte_from_seconds((frame * fps_base) / fps, fps)
328
329
330 def addon_check(module_name):
331     """
332     Returns the loaded state of the addon.
333
334     :arg module_name: The name of the addon and module.
335     :type module_name: string
336     :return: (loaded_default, loaded_state)
337     :rtype: tuple of booleans
338     """
339     loaded_default = module_name in _bpy.context.user_preferences.addons
340
341     mod = _sys.modules.get(module_name)
342     loaded_state = mod and getattr(mod, "__addon_enabled__", Ellipsis)
343
344     if loaded_state is Ellipsis:
345         print("Warning: addon-module %r found module but without"
346                " __addon_enabled__ field, possible name collision from file: %r" %
347                (module_name, getattr(mod, "__file__", "<unknown>")))
348
349         loaded_state = False
350
351     return loaded_default, loaded_state
352
353
354 def addon_enable(module_name, default_set=True):
355     """
356     Enables an addon by name.
357
358     :arg module_name: The name of the addon and module.
359     :type module_name: string
360     :return: the loaded module or None on failier.
361     :rtype: module
362     """
363     # note, this still gets added to _bpy_types.TypeMap
364
365     import os
366     import sys
367     import bpy_types as _bpy_types
368
369
370     _bpy_types._register_immediate = False
371
372     def handle_error():
373         import traceback
374         traceback.print_exc()
375         _bpy_types._register_immediate = True
376
377
378     # reload if the mtime changes
379     mod = sys.modules.get(module_name)
380     if mod:
381         mod.__addon_enabled__ = False
382         mtime_orig = getattr(mod, "__time__", 0)
383         mtime_new = os.path.getmtime(mod.__file__)
384         if mtime_orig != mtime_new:
385             print("module changed on disk:", mod.__file__, "reloading...")
386
387             try:
388                 reload(mod)
389             except:
390                 handle_error()
391                 del sys.modules[module_name]
392                 return None
393             mod.__addon_enabled__ = False
394
395     # Split registering up into 3 steps so we can undo if it fails par way through
396     # 1) try import
397     try:
398         mod = __import__(module_name)
399         mod.__time__ = os.path.getmtime(mod.__file__)
400         mod.__addon_enabled__ = False
401     except:
402         handle_error()
403         return None
404
405     # 2) try register collected modules
406     try:
407         _bpy_types._register_module(module_name)
408     except:
409         handle_error()
410         del sys.modules[module_name]
411         return None
412
413     # 3) try run the modules register function
414     try:
415         mod.register()
416     except:
417         handle_error()
418         _bpy_types._unregister_module(module_name)
419         del sys.modules[module_name]
420         return None
421
422     # * OK loaded successfully! *
423     if default_set:
424         # just incase its enabled alredy
425         ext = _bpy.context.user_preferences.addons.get(module_name)
426         if not ext:
427             ext = _bpy.context.user_preferences.addons.new()
428             ext.module = module_name
429     
430     _bpy_types._register_immediate = True
431
432     mod.__addon_enabled__ = True
433
434     print("\tbpy.utils.addon_enable", mod.__name__)
435
436     return mod
437
438
439 def addon_disable(module_name, default_set=True):
440     """
441     Disables an addon by name.
442
443     :arg module_name: The name of the addon and module.
444     :type module_name: string
445     """
446     import traceback
447     import bpy_types as _bpy_types
448
449     mod = _sys.modules.get(module_name)
450
451     # possible this addon is from a previous session and didnt load a module this time.
452     # so even if the module is not found, still disable the addon in the user prefs.
453     if mod:
454         mod.__addon_enabled__ = False
455
456         try:
457             _bpy_types._unregister_module(module_name, free=False)  # dont free because we may want to enable again.
458             mod.unregister()
459         except:
460             traceback.print_exc()
461     else:
462         print("addon_disable", module_name, "not loaded")
463
464     # could be in more then once, unlikely but better do this just incase.
465     addons = _bpy.context.user_preferences.addons
466
467     if default_set:
468         while module_name in addons:
469             addon = addons.get(module_name)
470             if addon:
471                 addons.remove(addon)
472     
473     print("\tbpy.utils.addon_disable", module_name)
474
475
476 def addon_reset_all():
477     """
478     Sets the addon state based on the user preferences.
479     """
480
481     paths = script_paths("addons") + script_paths("addons_contrib")
482
483     for path in paths:
484         _sys_path_ensure(path)
485         for mod_name, mod_path in _bpy.path.module_names(path):
486             is_enabled, is_loaded = addon_check(mod_name)
487             if is_enabled == is_loaded:
488                 pass
489             elif is_enabled:
490                 addon_enable(mod_name)
491             elif is_loaded:
492                 print("\taddon_reset_all unloading", mod_name)
493                 addon_disable(mod_name)
494
495 def preset_find(name, preset_path, display_name=False):
496     if not name:
497         return None
498     
499     for directory in preset_paths(preset_path):
500         
501         if display_name:
502             filename = ""
503             for fn in _os.listdir(directory):
504                 if fn.endswith(".py") and name == _bpy.path.display_name(fn):
505                     filename = fn
506                     break
507         else:
508             filename = name + ".py"
509
510         if filename:
511             filepath = _os.path.join(directory, filename)
512             if _os.path.exists(filepath):
513                 return filepath
514
515
516 def keyconfig_set(filepath):
517     from os.path import basename, splitext
518
519     print("loading preset:", filepath)
520     keyconfigs = _bpy.context.window_manager.keyconfigs
521     kc_orig = keyconfigs.active
522
523     keyconfigs_old = keyconfigs[:]
524
525     try:
526         exec(compile(open(filepath).read(), filepath, 'exec'), {"__file__": filepath})
527     except:
528         import traceback
529         traceback.print_exc()
530
531     kc_new = [kc for kc in keyconfigs if kc not in keyconfigs_old][0]
532
533     kc_new.name = ""
534
535     # remove duplicates
536     name = splitext(basename(filepath))[0]
537     while True:
538         kc_dupe = keyconfigs.get(name)
539         if kc_dupe:
540             keyconfigs.remove(kc_dupe)
541         else:
542             break
543     
544     kc_new.name = name
545     keyconfigs.active = kc_new
546
547