6c1c669d1f2d845fb30496d18bcbd427c0754230
[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
31 from _bpy import script_paths as _bpy_script_paths
32
33 def _test_import(module_name, loaded_modules):
34     import traceback
35     import time
36     if module_name in loaded_modules:
37         return None
38     if "." in module_name:
39         print("Ignoring '%s', can't import files containing multiple periods." % module_name)
40         return None
41
42     t = time.time()
43     try:
44         mod = __import__(module_name)
45     except:
46         traceback.print_exc()
47         return None
48
49     if _bpy.app.debug:
50         print("time %s %.4f" % (module_name, time.time() - t))
51
52     loaded_modules.add(mod.__name__) # should match mod.__name__ too
53     return mod
54
55
56 def modules_from_path(path, loaded_modules):
57     """
58     Load all modules in a path and return them as a list.
59
60     :arg path: this path is scanned for scripts and packages.
61     :type path: string
62     :arg loaded_modules: already loaded module names, files matching these names will be ignored.
63     :type loaded_modules: set
64     :return: all loaded modules.
65     :rtype: list
66     """
67     import traceback
68     import time
69
70     modules = []
71
72     for f in sorted(_os.listdir(path)):
73         if f.endswith(".py"):
74             # python module
75             mod = _test_import(f[0:-3], loaded_modules)
76         elif ("." not in f) and (_os.path.isfile(_os.path.join(path, f, "__init__.py"))):
77             # python package
78             mod = _test_import(f, loaded_modules)
79         else:
80             mod = None
81
82         if mod:
83             modules.append(mod)
84
85     return modules
86             
87 _global_loaded_modules = [] # store loaded module names for reloading.
88 import bpy_types as _bpy_types # keep for comparisons, never ever reload this.
89
90
91 def load_scripts(reload_scripts=False, refresh_scripts=False):
92     """
93     Load scripts and run each modules register function.
94
95     :arg reload_scripts: Causes all scripts to have their unregister method called before loading.
96     :type reload_scripts: bool
97     :arg refresh_scripts: only load scripts which are not already loaded as modules.
98     :type refresh_scripts: bool
99     """
100     import traceback
101     import time
102
103     # must be set back to True on exits
104     _bpy_types._register_immediate = False
105
106     t_main = time.time()
107
108     loaded_modules = set()
109
110     if refresh_scripts:
111         original_modules = _sys.modules.values()
112     
113     if reload_scripts:
114         _bpy_types.TypeMap.clear()
115         _bpy_types.PropertiesMap.clear()
116
117     def register_module_call(mod):
118         _bpy_types._register_module(mod.__name__)
119         register = getattr(mod, "register", None)
120         if register:
121             try:
122                 register()
123             except:
124                 traceback.print_exc()
125         else:
126             print("\nWarning! '%s' has no register function, this is now a requirement for registerable scripts." % mod.__file__)
127
128     def unregister_module_call(mod):
129         _bpy_types._unregister_module(mod.__name__)
130         unregister = getattr(mod, "unregister", None)
131         if unregister:
132             try:
133                 unregister()
134             except:
135                 traceback.print_exc()
136
137     def sys_path_ensure(path):
138         if path not in _sys.path: # reloading would add twice
139             _sys.path.insert(0, path)
140
141     def test_reload(mod):
142         # reloading this causes internal errors
143         # because the classes from this module are stored internally
144         # possibly to refresh internal references too but for now, best not to.
145         if mod == _bpy_types:
146             return mod
147
148         try:
149             return reload(mod)
150         except:
151             traceback.print_exc()
152
153     def test_register(mod):
154
155         if refresh_scripts and mod in original_modules:
156             return
157
158         if reload_scripts and mod:
159             print("Reloading:", mod)
160             mod = test_reload(mod)
161
162         if mod:
163             register_module_call(mod)
164             _global_loaded_modules.append(mod.__name__)
165
166     if reload_scripts:
167
168         # module names -> modules
169         _global_loaded_modules[:] = [_sys.modules[mod_name] for mod_name in _global_loaded_modules]
170
171         # loop over and unload all scripts
172         _global_loaded_modules.reverse()
173         for mod in _global_loaded_modules:
174             unregister_module_call(mod)
175
176         for mod in _global_loaded_modules:
177             test_reload(mod)
178
179         _global_loaded_modules[:] = []
180
181     user_path = user_script_path()
182
183     for base_path in script_paths():
184         for path_subdir in ("", "ui", "op", "io", "cfg", "keyingsets", "modules"):
185             path = _os.path.join(base_path, path_subdir)
186             if _os.path.isdir(path):
187                 sys_path_ensure(path)
188
189                 # only add this to sys.modules, dont run
190                 if path_subdir == "modules":
191                     continue
192
193                 if user_path != base_path and path_subdir == "":
194                     continue # avoid loading 2.4x scripts
195
196                 for mod in modules_from_path(path, loaded_modules):
197                     test_register(mod)
198
199     # load addons
200     used_ext = {ext.module for ext in _bpy.context.user_preferences.addons}
201     paths = script_paths("addons") + script_paths("addons_contrib")
202     for path in paths:
203         sys_path_ensure(path)
204
205     for module_name in sorted(used_ext):
206         mod = _test_import(module_name, loaded_modules)
207         test_register(mod)
208
209     if reload_scripts:
210         import gc
211         print("gc.collect() -> %d" % gc.collect())
212
213     if _bpy.app.debug:
214         print("Python Script Load Time %.4f" % (time.time() - t_main))
215     
216     _bpy_types._register_immediate = True
217
218
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.python_scripts_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.python_scripts_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
276     return (_os.path.join(_presets, subdir), )
277
278
279 def smpte_from_seconds(time, fps=None):
280     '''
281     Returns an SMPTE formatted string from the time in seconds: "HH:MM:SS:FF".
282
283     If the fps is not given the current scene is used.
284     '''
285     import math
286
287     if fps is None:
288         fps = _bpy.context.scene.render.fps
289
290     hours = minutes = seconds = frames = 0
291
292     if time < 0:
293         time = - time
294         neg = "-"
295     else:
296         neg = ""
297
298     if time >= 3600.0: # hours
299         hours = int(time / 3600.0)
300         time = time % 3600.0
301     if time >= 60.0: # mins
302         minutes = int(time / 60.0)
303         time = time % 60.0
304
305     seconds = int(time)
306     frames = int(round(math.floor(((time - seconds) * fps))))
307
308     return "%s%02d:%02d:%02d:%02d" % (neg, hours, minutes, seconds, frames)
309
310
311 def smpte_from_frame(frame, fps=None, fps_base=None):
312     '''
313     Returns an SMPTE formatted string from the frame: "HH:MM:SS:FF".
314
315     If the fps and fps_base are not given the current scene is used.
316     '''
317
318     if fps is None:
319         fps = _bpy.context.scene.render.fps
320
321     if fps_base is None:
322         fps_base = _bpy.context.scene.render.fps_base
323
324     return smpte_from_seconds((frame * fps_base) / fps, fps)