fix [#27922] using preset_paths() with an absolute path returns twice the same thing
[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 register_class, unregister_class, blend_paths, resource_path
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 import addon_utils as _addon_utils
35
36 _script_module_dirs = "startup", "modules"
37
38
39 def _test_import(module_name, loaded_modules):
40     use_time = _bpy.app.debug
41
42     if module_name in loaded_modules:
43         return None
44     if "." in module_name:
45         print("Ignoring '%s', can't import files containing multiple periods." % module_name)
46         return None
47
48     if use_time:
49         import time
50         t = time.time()
51
52     try:
53         mod = __import__(module_name)
54     except:
55         import traceback
56         traceback.print_exc()
57         return None
58
59     if use_time:
60         print("time %s %.4f" % (module_name, time.time() - t))
61
62     loaded_modules.add(mod.__name__)  # should match mod.__name__ too
63     return mod
64
65
66 def _sys_path_ensure(path):
67     if path not in _sys.path:  # reloading would add twice
68         _sys.path.insert(0, path)
69
70
71 def modules_from_path(path, loaded_modules):
72     """
73     Load all modules in a path and return them as a list.
74
75     :arg path: this path is scanned for scripts and packages.
76     :type path: string
77     :arg loaded_modules: already loaded module names, files matching these names will be ignored.
78     :type loaded_modules: set
79     :return: all loaded modules.
80     :rtype: list
81     """
82     modules = []
83
84     for mod_name, mod_path in _bpy.path.module_names(path):
85         mod = _test_import(mod_name, loaded_modules)
86         if mod:
87             modules.append(mod)
88
89     return modules
90
91
92 _global_loaded_modules = []  # store loaded module names for reloading.
93 import bpy_types as _bpy_types  # keep for comparisons, never ever reload this.
94
95
96 def load_scripts(reload_scripts=False, refresh_scripts=False):
97     """
98     Load scripts and run each modules register function.
99
100     :arg reload_scripts: Causes all scripts to have their unregister method called before loading.
101     :type reload_scripts: bool
102     :arg refresh_scripts: only load scripts which are not already loaded as modules.
103     :type refresh_scripts: bool
104     """
105     use_time = _bpy.app.debug
106
107     if use_time:
108         import time
109         t_main = time.time()
110
111     loaded_modules = set()
112
113     if refresh_scripts:
114         original_modules = _sys.modules.values()
115
116     if reload_scripts:
117         _bpy_types.TypeMap.clear()
118
119         # just unload, dont change user defaults, this means we can sync to reload.
120         # note that they will only actually reload of the modification time changes.
121         # this `wont` work for packages so... its not perfect.
122         for module_name in [ext.module for ext in _bpy.context.user_preferences.addons]:
123             _addon_utils.disable(module_name, default_set=False)
124
125     def register_module_call(mod):
126         register = getattr(mod, "register", None)
127         if register:
128             try:
129                 register()
130             except:
131                 import traceback
132                 traceback.print_exc()
133         else:
134             print("\nWarning! '%s' has no register function, this is now a requirement for registerable scripts." % mod.__file__)
135
136     def unregister_module_call(mod):
137         unregister = getattr(mod, "unregister", None)
138         if unregister:
139             try:
140                 unregister()
141             except:
142                 import traceback
143                 traceback.print_exc()
144
145     def test_reload(mod):
146         import imp
147         # reloading this causes internal errors
148         # because the classes from this module are stored internally
149         # possibly to refresh internal references too but for now, best not to.
150         if mod == _bpy_types:
151             return mod
152
153         try:
154             return imp.reload(mod)
155         except:
156             import traceback
157             traceback.print_exc()
158
159     def test_register(mod):
160
161         if refresh_scripts and mod in original_modules:
162             return
163
164         if reload_scripts and mod:
165             print("Reloading:", mod)
166             mod = test_reload(mod)
167
168         if mod:
169             register_module_call(mod)
170             _global_loaded_modules.append(mod.__name__)
171
172     if reload_scripts:
173
174         # module names -> modules
175         _global_loaded_modules[:] = [_sys.modules[mod_name] for mod_name in _global_loaded_modules]
176
177         # loop over and unload all scripts
178         _global_loaded_modules.reverse()
179         for mod in _global_loaded_modules:
180             unregister_module_call(mod)
181
182         for mod in _global_loaded_modules:
183             test_reload(mod)
184
185         _global_loaded_modules[:] = []
186
187     for base_path in script_paths():
188         for path_subdir in _script_module_dirs:
189             path = _os.path.join(base_path, path_subdir)
190             if _os.path.isdir(path):
191                 _sys_path_ensure(path)
192
193                 # only add this to sys.modules, dont run
194                 if path_subdir == "modules":
195                     continue
196
197                 for mod in modules_from_path(path, loaded_modules):
198                     test_register(mod)
199
200     # deal with addons separately
201     _addon_utils.reset_all(reload_scripts)
202
203     # run the active integration preset
204     filepath = preset_find(_bpy.context.user_preferences.inputs.active_keyconfig, "keyconfig")
205     if filepath:
206         keyconfig_set(filepath)
207
208     if reload_scripts:
209         import gc
210         print("gc.collect() -> %d" % gc.collect())
211
212     if use_time:
213         print("Python Script Load Time %.4f" % (time.time() - t_main))
214
215
216 # base scripts
217 _scripts = _os.path.join(_os.path.dirname(__file__), _os.path.pardir, _os.path.pardir)
218 _scripts = (_os.path.normpath(_scripts), )
219
220
221 def user_script_path():
222     path = _bpy.context.user_preferences.filepaths.script_directory
223
224     if path:
225         path = _os.path.normpath(path)
226         return path
227     else:
228         return None
229
230
231 def script_paths(subdir=None, user_pref=True, all=False):
232     """
233     Returns a list of valid script paths.
234
235     :arg subdir: Optional subdir.
236     :type subdir: string
237     :arg user_pref: Include the user preference script path.
238     :type user_pref: bool
239     :arg all: Include local, user and system paths rather just the paths blender uses.
240     :type all: bool
241     :return: script paths.
242     :rtype: list
243     """
244     scripts = list(_scripts)
245
246     # add user scripts dir
247     if user_pref:
248         user_script_path = _bpy.context.user_preferences.filepaths.script_directory
249     else:
250         user_script_path = None
251
252     if all:
253         # all possible paths
254         base_paths = tuple(_os.path.join(resource_path(res), "scripts") for res in ('LOCAL', 'USER', 'SYSTEM'))
255     else:
256         # only paths blender uses
257         base_paths = _bpy_script_paths()
258
259     for path in base_paths + (user_script_path, ):
260         if path:
261             path = _os.path.normpath(path)
262             if path not in scripts and _os.path.isdir(path):
263                 scripts.append(path)
264
265     if subdir is None:
266         return scripts
267
268     script_paths = []
269     for path in scripts:
270         path_subdir = _os.path.join(path, subdir)
271         if _os.path.isdir(path_subdir):
272             script_paths.append(path_subdir)
273
274     return script_paths
275
276
277 def refresh_script_paths():
278     """
279     Run this after creating new script paths to update sys.path
280     """
281
282     for base_path in script_paths():
283         for path_subdir in _script_module_dirs:
284             path = _os.path.join(base_path, path_subdir)
285             if _os.path.isdir(path):
286                 _sys_path_ensure(path)
287
288     for path in _addon_utils.paths():
289         _sys_path_ensure(path)
290         path = _os.path.join(path, "modules")
291         if _os.path.isdir(path):
292             _sys_path_ensure(path)
293
294
295 _presets = _os.path.join(_scripts[0], "presets")  # FIXME - multiple paths
296
297
298 def preset_paths(subdir):
299     """
300     Returns a list of paths for a specific preset.
301
302     :arg subdir: preset subdirectory (must not be an absolute path).
303     :type subdir: string
304     :return: script paths.
305     :rtype: list
306     """
307     dirs = []
308     for path in script_paths("presets", all=True):
309         directory = _os.path.join(path, subdir)
310         if not directory.startswith(path):
311             raise Exception("invalid subdir given %r" % subdir)
312         elif _os.path.isdir(directory):
313             dirs.append(directory)
314     return dirs
315
316
317 def smpte_from_seconds(time, fps=None):
318     """
319     Returns an SMPTE formatted string from the time in seconds: "HH:MM:SS:FF".
320
321     If the *fps* is not given the current scene is used.
322     """
323     import math
324
325     if fps is None:
326         fps = _bpy.context.scene.render.fps
327
328     hours = minutes = seconds = frames = 0
329
330     if time < 0:
331         time = - time
332         neg = "-"
333     else:
334         neg = ""
335
336     if time >= 3600.0:  # hours
337         hours = int(time / 3600.0)
338         time = time % 3600.0
339     if time >= 60.0:  # mins
340         minutes = int(time / 60.0)
341         time = time % 60.0
342
343     seconds = int(time)
344     frames = int(round(math.floor(((time - seconds) * fps))))
345
346     return "%s%02d:%02d:%02d:%02d" % (neg, hours, minutes, seconds, frames)
347
348
349 def smpte_from_frame(frame, fps=None, fps_base=None):
350     """
351     Returns an SMPTE formatted string from the frame: "HH:MM:SS:FF".
352
353     If *fps* and *fps_base* are not given the current scene is used.
354     """
355
356     if fps is None:
357         fps = _bpy.context.scene.render.fps
358
359     if fps_base is None:
360         fps_base = _bpy.context.scene.render.fps_base
361
362     return smpte_from_seconds((frame * fps_base) / fps, fps)
363
364
365 def preset_find(name, preset_path, display_name=False):
366     if not name:
367         return None
368
369     for directory in preset_paths(preset_path):
370
371         if display_name:
372             filename = ""
373             for fn in _os.listdir(directory):
374                 if fn.endswith(".py") and name == _bpy.path.display_name(fn):
375                     filename = fn
376                     break
377         else:
378             filename = name + ".py"
379
380         if filename:
381             filepath = _os.path.join(directory, filename)
382             if _os.path.exists(filepath):
383                 return filepath
384
385
386 def keyconfig_set(filepath):
387     from os.path import basename, splitext
388
389     if _bpy.app.debug:
390         print("loading preset:", filepath)
391
392     keyconfigs = _bpy.context.window_manager.keyconfigs
393
394     keyconfigs_old = keyconfigs[:]
395
396     try:
397         file = open(filepath)
398         exec(compile(file.read(), filepath, 'exec'), {"__file__": filepath})
399         file.close()
400     except:
401         import traceback
402         traceback.print_exc()
403
404     kc_new = [kc for kc in keyconfigs if kc not in keyconfigs_old][0]
405
406     kc_new.name = ""
407
408     # remove duplicates
409     name = splitext(basename(filepath))[0]
410     while True:
411         kc_dupe = keyconfigs.get(name)
412         if kc_dupe:
413             keyconfigs.remove(kc_dupe)
414         else:
415             break
416
417     kc_new.name = name
418     keyconfigs.active = kc_new
419
420
421 def user_resource(type, path="", create=False):
422     """
423     Return a user resource path (normally from the users home directory).
424
425     :arg type: Resource type in ['DATAFILES', 'CONFIG', 'SCRIPTS', 'AUTOSAVE'].
426     :type type: string
427     :arg subdir: Optional subdirectory.
428     :type subdir: string
429     :arg create: Treat the path as a directory and create it if its not existing.
430     :type create: boolean
431     :return: a path.
432     :rtype: string
433     """
434
435     target_path = _user_resource(type, path)
436
437     if create:
438         # should always be true.
439         if target_path:
440             # create path if not existing.
441             if not _os.path.exists(target_path):
442                 try:
443                     _os.makedirs(target_path)
444                 except:
445                     import traceback
446                     traceback.print_exc()
447                     target_path = ""
448             elif not _os.path.isdir(target_path):
449                 print("Path %r found but isn't a directory!" % target_path)
450                 target_path = ""
451
452     return target_path
453
454
455 def _bpy_module_classes(module, is_registered=False):
456     typemap_list = _bpy_types.TypeMap.get(module, ())
457     i = 0
458     while i < len(typemap_list):
459         cls_weakref = typemap_list[i]
460         cls = cls_weakref()
461
462         if cls is None:
463             del typemap_list[i]
464         else:
465             if is_registered == cls.is_registered:
466                 yield cls
467             i += 1
468
469
470 def register_module(module, verbose=False):
471     if verbose:
472         print("bpy.utils.register_module(%r): ..." % module)
473     cls = None
474     for cls in _bpy_module_classes(module, is_registered=False):
475         if verbose:
476             print("    %r" % cls)
477         try:
478             register_class(cls)
479         except:
480             print("bpy.utils.register_module(): failed to registering class %r" % cls)
481             import traceback
482             traceback.print_exc()
483     if verbose:
484         print("done.\n")
485     if cls is None:
486         raise Exception("register_module(%r): defines no classes" % module)
487
488
489 def unregister_module(module, verbose=False):
490     if verbose:
491         print("bpy.utils.unregister_module(%r): ..." % module)
492     for cls in _bpy_module_classes(module, is_registered=True):
493         if verbose:
494             print("    %r" % cls)
495         try:
496             unregister_class(cls)
497         except:
498             print("bpy.utils.unregister_module(): failed to unregistering class %r" % cls)
499             import traceback
500             traceback.print_exc()
501     if verbose:
502         print("done.\n")