spelling correction: alredy --> already
[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
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 modules_from_path(path, loaded_modules):
58     """
59     Load all modules in a path and return them as a list.
60
61     :arg path: this path is scanned for scripts and packages.
62     :type path: string
63     :arg loaded_modules: already loaded module names, files matching these names will be ignored.
64     :type loaded_modules: set
65     :return: all loaded modules.
66     :rtype: list
67     """
68     import traceback
69     import time
70
71     modules = []
72
73     for f in sorted(_os.listdir(path)):
74         if f.endswith(".py"):
75             # python module
76             mod = _test_import(f[0:-3], loaded_modules)
77         elif ("." not in f) and (_os.path.isfile(_os.path.join(path, f, "__init__.py"))):
78             # python package
79             mod = _test_import(f, loaded_modules)
80         else:
81             mod = None
82
83         if mod:
84             modules.append(mod)
85
86     return modules
87
88 _loaded = [] # store loaded modules for reloading.
89 _bpy_types = __import__("bpy_types") # keep for comparisons, never ever reload this.
90
91
92 def load_scripts(reload_scripts=False, refresh_scripts=False):
93     """
94     Load scripts and run each modules register function.
95
96     :arg reload_scripts: Causes all scripts to have their unregister method called before loading.
97     :type reload_scripts: bool
98     :arg refresh_scripts: only load scripts which are not already loaded as modules.
99     :type refresh_scripts: bool
100     """
101     import traceback
102     import time
103
104     t_main = time.time()
105
106     loaded_modules = set()
107
108     if refresh_scripts:
109         original_modules = _sys.modules.values()
110
111     def sys_path_ensure(path):
112         if path not in _sys.path: # reloading would add twice
113             _sys.path.insert(0, path)
114
115     def test_reload(mod):
116         # reloading this causes internal errors
117         # because the classes from this module are stored internally
118         # possibly to refresh internal references too but for now, best not to.
119         if mod == _bpy_types:
120             return mod
121
122         try:
123             return reload(mod)
124         except:
125             traceback.print_exc()
126
127     def test_register(mod):
128
129         if refresh_scripts and mod in original_modules:
130             return
131
132         if reload_scripts and mod:
133             print("Reloading:", mod)
134             mod = test_reload(mod)
135
136         if mod:
137             register = getattr(mod, "register", None)
138             if register:
139                 try:
140                     register()
141                 except:
142                     traceback.print_exc()
143             else:
144                 print("\nWarning! '%s' has no register function, this is now a requirement for registerable scripts." % mod.__file__)
145             _loaded.append(mod)
146
147     if reload_scripts:
148
149         # TODO, this is broken but should work, needs looking into
150         '''
151         # reload modules that may not be directly included
152         for type_class_name in dir(_bpy.types):
153             type_class = getattr(_bpy.types, type_class_name)
154             module_name = getattr(type_class, "__module__", "")
155
156             if module_name and module_name != "bpy.types": # hard coded for C types
157                 loaded_modules.add(module_name)
158
159         # sorting isnt needed but rather it be pradictable
160         for module_name in sorted(loaded_modules):
161             print("Reloading:", module_name)
162             test_reload(_sys.modules[module_name])
163         '''
164
165         # loop over and unload all scripts
166         _loaded.reverse()
167         for mod in _loaded:
168             unregister = getattr(mod, "unregister", None)
169             if unregister:
170                 try:
171                     unregister()
172                 except:
173                     traceback.print_exc()
174
175         for mod in _loaded:
176             reload(mod)
177
178         _loaded[:] = []
179
180     user_path = user_script_path()
181
182     for base_path in script_paths():
183         for path_subdir in ("", "ui", "op", "io", "cfg", "keyingsets", "modules"):
184             path = _os.path.join(base_path, path_subdir)
185             if _os.path.isdir(path):
186                 sys_path_ensure(path)
187
188                 # only add this to sys.modules, dont run
189                 if path_subdir == "modules":
190                     continue
191
192                 if user_path != base_path and path_subdir == "":
193                     continue # avoid loading 2.4x scripts
194
195                 for mod in modules_from_path(path, loaded_modules):
196                     test_register(mod)
197
198     # load addons
199     used_ext = {ext.module for ext in _bpy.context.user_preferences.addons}
200     paths = script_paths("addons")
201     for path in paths:
202         sys_path_ensure(path)
203
204     for module_name in sorted(used_ext):
205         mod = _test_import(module_name, loaded_modules)
206         test_register(mod)
207
208     if reload_scripts:
209         import gc
210         print("gc.collect() -> %d" % gc.collect())
211
212     if _bpy.app.debug:
213         print("Time %.4f" % (time.time() - t_main))
214
215
216 def expandpath(path):
217     """
218     Returns the absolute path relative to the current blend file using the "//" prefix.
219     """
220     if path.startswith("//"):
221         return _os.path.join(_os.path.dirname(_bpy.data.filepath), path[2:])
222
223     return path
224
225
226 def relpath(path, start=None):
227     """
228     Returns the path relative to the current blend file using the "//" prefix.
229
230     :arg start: Relative to this path, when not set the current filename is used.
231     :type start: string
232     """
233     if not path.startswith("//"):
234         if start is None:
235             start = _os.path.dirname(_bpy.data.filepath)
236         return "//" + _os.path.relpath(path, start)
237
238     return path
239
240
241 _unclean_chars = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, \
242     17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, \
243     35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 58, 59, 60, 61, 62, 63, \
244     64, 91, 92, 93, 94, 96, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, \
245     133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, \
246     147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, \
247     161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, \
248     175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, \
249     189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, \
250     203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, \
251     217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, \
252     231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, \
253     245, 246, 247, 248, 249, 250, 251, 252, 253, 254]
254
255 _unclean_chars = ''.join([chr(i) for i in _unclean_chars])
256
257
258 def clean_name(name, replace="_"):
259     """
260     Returns a name with characters replaced that may cause problems under various circumstances, such as writing to a file.
261     All characters besides A-Z/a-z, 0-9 are replaced with "_"
262     or the replace argument if defined.
263     """
264     for ch in _unclean_chars:
265         name = name.replace(ch, replace)
266     return name
267
268
269 def display_name(name):
270     """
271     Creates a display string from name to be used menus and the user interface.
272     Capitalize the first letter in all lowercase names, mixed case names are kept as is.
273     Intended for use with filenames and module names.
274     """
275     name_base = _os.path.splitext(name)[0]
276
277     # string replacements
278     name_base = name_base.replace("_colon_", ":")
279
280     name_base = name_base.replace("_", " ")
281
282     if name_base.islower():
283         return name_base.capitalize()
284     else:
285         return name_base
286
287
288 # base scripts
289 _scripts = _os.path.join(_os.path.dirname(__file__), _os.path.pardir, _os.path.pardir)
290 _scripts = (_os.path.normpath(_scripts), )
291
292
293 def user_script_path():
294     path = _bpy.context.user_preferences.filepaths.python_scripts_directory
295
296     if path:
297         path = _os.path.normpath(path)
298         return path
299     else:
300         return None
301
302
303 def script_paths(subdir=None, user=True):
304     """
305     Returns a list of valid script paths from the home directory and user preferences.
306
307     Accepts any number of string arguments which are joined to make a path.
308     """
309     scripts = list(_scripts)
310
311     # add user scripts dir
312     if user:
313         user_script_path = _bpy.context.user_preferences.filepaths.python_scripts_directory
314     else:
315         user_script_path = None
316
317     for path in _bpy_script_paths() + (user_script_path, ):
318         if path:
319             path = _os.path.normpath(path)
320             if path not in scripts and _os.path.isdir(path):
321                 scripts.append(path)
322
323     if not subdir:
324         return scripts
325
326     script_paths = []
327     for path in scripts:
328         path_subdir = _os.path.join(path, subdir)
329         if _os.path.isdir(path_subdir):
330             script_paths.append(path_subdir)
331
332     return script_paths
333
334
335 _presets = _os.path.join(_scripts[0], "presets") # FIXME - multiple paths
336
337
338 def preset_paths(subdir):
339     '''
340     Returns a list of paths for a spesific preset.
341     '''
342
343     return (_os.path.join(_presets, subdir), )
344
345
346 def smpte_from_seconds(time, fps=None):
347     '''
348     Returns an SMPTE formatted string from the time in seconds: "HH:MM:SS:FF".
349
350     If the fps is not given the current scene is used.
351     '''
352     import math
353
354     if fps is None:
355         fps = _bpy.context.scene.render.fps
356
357     hours = minutes = seconds = frames = 0
358
359     if time < 0:
360         time = - time
361         neg = "-"
362     else:
363         neg = ""
364
365     if time >= 3600.0: # hours
366         hours = int(time / 3600.0)
367         time = time % 3600.0
368     if time >= 60.0: # mins
369         minutes = int(time / 60.0)
370         time = time % 60.0
371
372     seconds = int(time)
373     frames = int(round(math.floor(((time - seconds) * fps))))
374
375     return "%s%02d:%02d:%02d:%02d" % (neg, hours, minutes, seconds, frames)
376
377
378 def smpte_from_frame(frame, fps=None, fps_base=None):
379     '''
380     Returns an SMPTE formatted string from the frame: "HH:MM:SS:FF".
381
382     If the fps and fps_base are not given the current scene is used.
383     '''
384
385     if fps is None:
386         fps = _bpy.context.scene.render.fps
387
388     if fps_base is None:
389         fps_base = _bpy.context.scene.render.fps_base
390
391     return smpte_from_seconds((frame * fps_base) / fps, fps)