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