remove reload() from builtins since python3 no longer uses this.
[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 from _bpy import blend_paths
27 from _bpy import script_paths as _bpy_script_paths
28 from _bpy import user_resource as _user_resource
29
30 import bpy as _bpy
31 import os as _os
32 import sys as _sys
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         import imp
144         # reloading this causes internal errors
145         # because the classes from this module are stored internally
146         # possibly to refresh internal references too but for now, best not to.
147         if mod == _bpy_types:
148             return mod
149
150         try:
151             return imp.reload(mod)
152         except:
153             traceback.print_exc()
154
155     def test_register(mod):
156
157         if refresh_scripts and mod in original_modules:
158             return
159
160         if reload_scripts and mod:
161             print("Reloading:", mod)
162             mod = test_reload(mod)
163
164         if mod:
165             register_module_call(mod)
166             _global_loaded_modules.append(mod.__name__)
167
168     if reload_scripts:
169
170         # module names -> modules
171         _global_loaded_modules[:] = [_sys.modules[mod_name] for mod_name in _global_loaded_modules]
172
173         # loop over and unload all scripts
174         _global_loaded_modules.reverse()
175         for mod in _global_loaded_modules:
176             unregister_module_call(mod)
177
178         for mod in _global_loaded_modules:
179             test_reload(mod)
180
181         _global_loaded_modules[:] = []
182
183     user_path = user_script_path()
184
185     for base_path in script_paths():
186         for path_subdir in ("", "ui", "op", "io", "keyingsets", "modules"):
187             path = _os.path.join(base_path, path_subdir)
188             if _os.path.isdir(path):
189                 _sys_path_ensure(path)
190
191                 # only add this to sys.modules, dont run
192                 if path_subdir == "modules":
193                     continue
194
195                 if user_path != base_path and path_subdir == "":
196                     continue  # avoid loading 2.4x scripts
197
198                 for mod in modules_from_path(path, loaded_modules):
199                     test_register(mod)
200
201     _bpy_types._register_immediate = True
202
203     # deal with addons seperately
204     addon_reset_all(reload_scripts)
205
206
207     # run the active integration preset
208     filepath = preset_find(_bpy.context.user_preferences.inputs.active_keyconfig, "keyconfig")
209     if filepath:
210         keyconfig_set(filepath)
211
212
213     if reload_scripts:
214         import gc
215         print("gc.collect() -> %d" % gc.collect())
216
217     if _bpy.app.debug:
218         print("Python Script Load Time %.4f" % (time.time() - t_main))
219
220
221 # base scripts
222 _scripts = _os.path.join(_os.path.dirname(__file__), _os.path.pardir, _os.path.pardir)
223 _scripts = (_os.path.normpath(_scripts), )
224
225
226 def user_script_path():
227     path = _bpy.context.user_preferences.filepaths.script_directory
228
229     if path:
230         path = _os.path.normpath(path)
231         return path
232     else:
233         return None
234
235
236 def script_paths(subdir=None, user=True):
237     """
238     Returns a list of valid script paths from the home directory and user preferences.
239
240     Accepts any number of string arguments which are joined to make a path.
241     """
242     scripts = list(_scripts)
243
244     # add user scripts dir
245     if user:
246         user_script_path = _bpy.context.user_preferences.filepaths.script_directory
247     else:
248         user_script_path = None
249
250     for path in _bpy_script_paths() + (user_script_path, ):
251         if path:
252             path = _os.path.normpath(path)
253             if path not in scripts and _os.path.isdir(path):
254                 scripts.append(path)
255
256     if not subdir:
257         return scripts
258
259     script_paths = []
260     for path in scripts:
261         path_subdir = _os.path.join(path, subdir)
262         if _os.path.isdir(path_subdir):
263             script_paths.append(path_subdir)
264
265     return script_paths
266
267
268 _presets = _os.path.join(_scripts[0], "presets")  # FIXME - multiple paths
269
270
271 def preset_paths(subdir):
272     """
273     Returns a list of paths for a specific preset.
274     """
275     dirs = []
276     for path in script_paths("presets"):
277         directory = _os.path.join(path, subdir)
278         if _os.path.isdir(directory):
279             dirs.append(directory)
280     return dirs
281
282
283 def smpte_from_seconds(time, fps=None):
284     """
285     Returns an SMPTE formatted string from the time in seconds: "HH:MM:SS:FF".
286
287     If the *fps* is not given the current scene is used.
288     """
289     import math
290
291     if fps is None:
292         fps = _bpy.context.scene.render.fps
293
294     hours = minutes = seconds = frames = 0
295
296     if time < 0:
297         time = - time
298         neg = "-"
299     else:
300         neg = ""
301
302     if time >= 3600.0:  # hours
303         hours = int(time / 3600.0)
304         time = time % 3600.0
305     if time >= 60.0:  # mins
306         minutes = int(time / 60.0)
307         time = time % 60.0
308
309     seconds = int(time)
310     frames = int(round(math.floor(((time - seconds) * fps))))
311
312     return "%s%02d:%02d:%02d:%02d" % (neg, hours, minutes, seconds, frames)
313
314
315 def smpte_from_frame(frame, fps=None, fps_base=None):
316     """
317     Returns an SMPTE formatted string from the frame: "HH:MM:SS:FF".
318
319     If *fps* and *fps_base* are not given the current scene is used.
320     """
321
322     if fps is None:
323         fps = _bpy.context.scene.render.fps
324
325     if fps_base is None:
326         fps_base = _bpy.context.scene.render.fps_base
327
328     return smpte_from_seconds((frame * fps_base) / fps, fps)
329
330
331 def addon_check(module_name):
332     """
333     Returns the loaded state of the addon.
334
335     :arg module_name: The name of the addon and module.
336     :type module_name: string
337     :return: (loaded_default, loaded_state)
338     :rtype: tuple of booleans
339     """
340     loaded_default = module_name in _bpy.context.user_preferences.addons
341
342     mod = _sys.modules.get(module_name)
343     loaded_state = mod and getattr(mod, "__addon_enabled__", Ellipsis)
344
345     if loaded_state is Ellipsis:
346         print("Warning: addon-module %r found module but without"
347                " __addon_enabled__ field, possible name collision from file: %r" %
348                (module_name, getattr(mod, "__file__", "<unknown>")))
349
350         loaded_state = False
351
352     return loaded_default, loaded_state
353
354
355 def addon_enable(module_name, default_set=True):
356     """
357     Enables an addon by name.
358
359     :arg module_name: The name of the addon and module.
360     :type module_name: string
361     :return: the loaded module or None on failier.
362     :rtype: module
363     """
364     # note, this still gets added to _bpy_types.TypeMap
365
366     import os
367     import sys
368     import bpy_types as _bpy_types
369     import imp
370
371
372     _bpy_types._register_immediate = False
373
374     def handle_error():
375         import traceback
376         traceback.print_exc()
377         _bpy_types._register_immediate = True
378
379
380     # reload if the mtime changes
381     mod = sys.modules.get(module_name)
382     if mod:
383         mod.__addon_enabled__ = False
384         mtime_orig = getattr(mod, "__time__", 0)
385         mtime_new = os.path.getmtime(mod.__file__)
386         if mtime_orig != mtime_new:
387             print("module changed on disk:", mod.__file__, "reloading...")
388
389             try:
390                 imp.reload(mod)
391             except:
392                 handle_error()
393                 del sys.modules[module_name]
394                 return None
395             mod.__addon_enabled__ = False
396
397     # Split registering up into 3 steps so we can undo if it fails par way through
398     # 1) try import
399     try:
400         mod = __import__(module_name)
401         mod.__time__ = os.path.getmtime(mod.__file__)
402         mod.__addon_enabled__ = False
403     except:
404         handle_error()
405         return None
406
407     # 2) try register collected modules
408     try:
409         _bpy_types._register_module(module_name)
410     except:
411         handle_error()
412         del sys.modules[module_name]
413         return None
414
415     # 3) try run the modules register function
416     try:
417         mod.register()
418     except:
419         handle_error()
420         _bpy_types._unregister_module(module_name)
421         del sys.modules[module_name]
422         return None
423
424     # * OK loaded successfully! *
425     if default_set:
426         # just incase its enabled alredy
427         ext = _bpy.context.user_preferences.addons.get(module_name)
428         if not ext:
429             ext = _bpy.context.user_preferences.addons.new()
430             ext.module = module_name
431     
432     _bpy_types._register_immediate = True
433
434     mod.__addon_enabled__ = True
435
436     print("\tbpy.utils.addon_enable", mod.__name__)
437
438     return mod
439
440
441 def addon_disable(module_name, default_set=True):
442     """
443     Disables an addon by name.
444
445     :arg module_name: The name of the addon and module.
446     :type module_name: string
447     """
448     import traceback
449     import bpy_types as _bpy_types
450
451     mod = _sys.modules.get(module_name)
452
453     # possible this addon is from a previous session and didnt load a module this time.
454     # so even if the module is not found, still disable the addon in the user prefs.
455     if mod:
456         mod.__addon_enabled__ = False
457
458         try:
459             _bpy_types._unregister_module(module_name, free=False)  # dont free because we may want to enable again.
460             mod.unregister()
461         except:
462             traceback.print_exc()
463     else:
464         print("addon_disable", module_name, "not loaded")
465
466     # could be in more then once, unlikely but better do this just incase.
467     addons = _bpy.context.user_preferences.addons
468
469     if default_set:
470         while module_name in addons:
471             addon = addons.get(module_name)
472             if addon:
473                 addons.remove(addon)
474     
475     print("\tbpy.utils.addon_disable", module_name)
476
477
478 def addon_reset_all(reload_scripts=False):
479     """
480     Sets the addon state based on the user preferences.
481     """
482     import imp
483
484     # RELEASE SCRIPTS: official scripts distributed in Blender releases
485     paths = script_paths("addons")
486     
487     # CONTRIB SCRIPTS: good for testing but not official scripts yet
488     paths += script_paths("addons_contrib")
489     
490     # EXTERN SCRIPTS: external projects scripts
491     paths += script_paths("addons_extern")
492
493     for path in paths:
494         _sys_path_ensure(path)
495         for mod_name, mod_path in _bpy.path.module_names(path):
496             is_enabled, is_loaded = addon_check(mod_name)
497
498             # first check if reload is needed before changing state.
499             if reload_scripts:
500                 mod = _sys.modules.get(mod_name)
501                 if mod:
502                     imp.reload(mod)
503
504             if is_enabled == is_loaded:
505                 pass
506             elif is_enabled:
507                 addon_enable(mod_name)
508             elif is_loaded:
509                 print("\taddon_reset_all unloading", mod_name)
510                 addon_disable(mod_name)
511
512
513 def preset_find(name, preset_path, display_name=False):
514     if not name:
515         return None
516     
517     for directory in preset_paths(preset_path):
518         
519         if display_name:
520             filename = ""
521             for fn in _os.listdir(directory):
522                 if fn.endswith(".py") and name == _bpy.path.display_name(fn):
523                     filename = fn
524                     break
525         else:
526             filename = name + ".py"
527
528         if filename:
529             filepath = _os.path.join(directory, filename)
530             if _os.path.exists(filepath):
531                 return filepath
532
533
534 def keyconfig_set(filepath):
535     from os.path import basename, splitext
536
537     print("loading preset:", filepath)
538     keyconfigs = _bpy.context.window_manager.keyconfigs
539     kc_orig = keyconfigs.active
540
541     keyconfigs_old = keyconfigs[:]
542
543     try:
544         exec(compile(open(filepath).read(), filepath, 'exec'), {"__file__": filepath})
545     except:
546         import traceback
547         traceback.print_exc()
548
549     kc_new = [kc for kc in keyconfigs if kc not in keyconfigs_old][0]
550
551     kc_new.name = ""
552
553     # remove duplicates
554     name = splitext(basename(filepath))[0]
555     while True:
556         kc_dupe = keyconfigs.get(name)
557         if kc_dupe:
558             keyconfigs.remove(kc_dupe)
559         else:
560             break
561     
562     kc_new.name = name
563     keyconfigs.active = kc_new
564
565
566 def user_resource(type, path="", create=False):
567     """
568     Return a user resource path (normally from the users home directory).
569
570     :arg type: Resource type in ['DATAFILES', 'CONFIG', 'SCRIPTS', 'AUTOSAVE'].
571     :type type: string
572     :arg subdir: Optional subdirectory.
573     :type subdir: string
574     :arg create: Treat the path as a directory and create it if its not existing.
575     :type create: boolean
576     :return: a path.
577     :rtype: string
578     """
579
580     target_path = _user_resource(type, path)
581
582     if create:
583         # should always be true.
584         if target_path:
585             # create path if not existing.
586             if not _os.path.exists(target_path):
587                 try:
588                     _os.makedirs(target_path)
589                 except:
590                     import traceback
591                     traceback.print_exc()
592                     target_path = ""
593             elif not _os.path.isdir(target_path):
594                 print("Path %r found but isn't a directory!" % target_path)
595                 target_path = ""
596
597     return target_path
598