Experemental XML UI, define panels/menus/headers which load at startup like python...
[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 _TEST_XML = _bpy.app.debug
34
35 def _test_import(module_name, loaded_modules):
36     import traceback
37     import time
38     if module_name in loaded_modules:
39         return None
40     if "." in module_name:
41         print("Ignoring '%s', can't import files containing multiple periods." % module_name)
42         return None
43
44     t = time.time()
45     try:
46         mod = __import__(module_name)
47     except:
48         traceback.print_exc()
49         return None
50
51     if _bpy.app.debug:
52         print("time %s %.4f" % (module_name, time.time() - t))
53
54     loaded_modules.add(mod.__name__) # should match mod.__name__ too
55     return mod
56
57 if _TEST_XML:
58     # TEST CODE
59     def _test_import_xml(path, f, loaded_modules):
60         import bpy_xml_ui
61         import traceback
62
63         f_full = _os.path.join(path, f)
64         _bpy_types._register_immediate = True
65         try:
66             classes = bpy_xml_ui.load_xml(f_full)
67         except:
68             traceback.print_exc()
69             classes = []
70         _bpy_types._register_immediate = False
71
72         if classes:
73             mod_name = f.split(".")[0]
74
75             # fake module
76             mod = type(traceback)(mod_name)
77             mod.__file__ = f_full
78             for cls in classes:
79                 setattr(mod, cls.__name__, cls)
80             
81             loaded_modules.add(mod_name)
82             _sys.modules[mod_name] = mod
83             mod.register = lambda: None # quiet errors
84             return mod
85
86
87 def modules_from_path(path, loaded_modules):
88     """
89     Load all modules in a path and return them as a list.
90
91     :arg path: this path is scanned for scripts and packages.
92     :type path: string
93     :arg loaded_modules: already loaded module names, files matching these names will be ignored.
94     :type loaded_modules: set
95     :return: all loaded modules.
96     :rtype: list
97     """
98     import traceback
99     import time
100
101     modules = []
102
103     for f in sorted(_os.listdir(path)):
104         if f.endswith(".py"):
105             # python module
106             mod = _test_import(f[0:-3], loaded_modules)
107         elif ("." not in f) and (_os.path.isfile(_os.path.join(path, f, "__init__.py"))):
108             # python package
109             mod = _test_import(f, loaded_modules)
110         else:
111             mod = None
112
113         if _TEST_XML:
114             if mod is None and f.endswith(".xml"):
115                 mod = _test_import_xml(path, f, loaded_modules)
116
117         if mod:
118             modules.append(mod)
119
120     return modules
121             
122 _global_loaded_modules = [] # store loaded module names for reloading.
123 import bpy_types as _bpy_types # keep for comparisons, never ever reload this.
124
125
126 def load_scripts(reload_scripts=False, refresh_scripts=False):
127     """
128     Load scripts and run each modules register function.
129
130     :arg reload_scripts: Causes all scripts to have their unregister method called before loading.
131     :type reload_scripts: bool
132     :arg refresh_scripts: only load scripts which are not already loaded as modules.
133     :type refresh_scripts: bool
134     """
135     import traceback
136     import time
137
138     # must be set back to True on exits
139     _bpy_types._register_immediate = False
140
141     t_main = time.time()
142
143     loaded_modules = set()
144
145     if refresh_scripts:
146         original_modules = _sys.modules.values()
147     
148     if reload_scripts:
149         _bpy_types.TypeMap.clear()
150         _bpy_types.PropertiesMap.clear()
151
152     def register_module_call(mod):
153         _bpy_types._register_module(mod.__name__)
154         register = getattr(mod, "register", None)
155         if register:
156             try:
157                 register()
158             except:
159                 traceback.print_exc()
160         else:
161             print("\nWarning! '%s' has no register function, this is now a requirement for registerable scripts." % mod.__file__)
162
163     def unregister_module_call(mod):
164         _bpy_types._unregister_module(mod.__name__)
165         unregister = getattr(mod, "unregister", None)
166         if unregister:
167             try:
168                 unregister()
169             except:
170                 traceback.print_exc()
171
172     def sys_path_ensure(path):
173         if path not in _sys.path: # reloading would add twice
174             _sys.path.insert(0, path)
175
176     def test_reload(mod):
177         # reloading this causes internal errors
178         # because the classes from this module are stored internally
179         # possibly to refresh internal references too but for now, best not to.
180         if mod == _bpy_types:
181             return mod
182
183         try:
184             return reload(mod)
185         except:
186             traceback.print_exc()
187
188     def test_register(mod):
189
190         if refresh_scripts and mod in original_modules:
191             return
192
193         if reload_scripts and mod:
194             print("Reloading:", mod)
195             mod = test_reload(mod)
196
197         if mod:
198             register_module_call(mod)
199             _global_loaded_modules.append(mod.__name__)
200
201     if reload_scripts:
202
203         # module names -> modules
204         _global_loaded_modules[:] = [_sys.modules[mod_name] for mod_name in _global_loaded_modules]
205
206         # loop over and unload all scripts
207         _global_loaded_modules.reverse()
208         for mod in _global_loaded_modules:
209             unregister_module_call(mod)
210
211         for mod in _global_loaded_modules:
212             test_reload(mod)
213
214         _global_loaded_modules[:] = []
215
216     user_path = user_script_path()
217
218     for base_path in script_paths():
219         for path_subdir in ("", "ui", "op", "io", "cfg", "keyingsets", "modules"):
220             path = _os.path.join(base_path, path_subdir)
221             if _os.path.isdir(path):
222                 sys_path_ensure(path)
223
224                 # only add this to sys.modules, dont run
225                 if path_subdir == "modules":
226                     continue
227
228                 if user_path != base_path and path_subdir == "":
229                     continue # avoid loading 2.4x scripts
230
231                 for mod in modules_from_path(path, loaded_modules):
232                     test_register(mod)
233
234     # load addons
235     used_ext = {ext.module for ext in _bpy.context.user_preferences.addons}
236     paths = script_paths("addons") + script_paths("addons_contrib")
237     for path in paths:
238         sys_path_ensure(path)
239
240     for module_name in sorted(used_ext):
241         mod = _test_import(module_name, loaded_modules)
242         test_register(mod)
243
244     if reload_scripts:
245         import gc
246         print("gc.collect() -> %d" % gc.collect())
247
248     if _bpy.app.debug:
249         print("Python Script Load Time %.4f" % (time.time() - t_main))
250     
251     _bpy_types._register_immediate = True
252
253
254
255
256 # base scripts
257 _scripts = _os.path.join(_os.path.dirname(__file__), _os.path.pardir, _os.path.pardir)
258 _scripts = (_os.path.normpath(_scripts), )
259
260
261 def user_script_path():
262     path = _bpy.context.user_preferences.filepaths.python_scripts_directory
263
264     if path:
265         path = _os.path.normpath(path)
266         return path
267     else:
268         return None
269
270
271 def script_paths(subdir=None, user=True):
272     """
273     Returns a list of valid script paths from the home directory and user preferences.
274
275     Accepts any number of string arguments which are joined to make a path.
276     """
277     scripts = list(_scripts)
278
279     # add user scripts dir
280     if user:
281         user_script_path = _bpy.context.user_preferences.filepaths.python_scripts_directory
282     else:
283         user_script_path = None
284
285     for path in _bpy_script_paths() + (user_script_path, ):
286         if path:
287             path = _os.path.normpath(path)
288             if path not in scripts and _os.path.isdir(path):
289                 scripts.append(path)
290
291     if not subdir:
292         return scripts
293
294     script_paths = []
295     for path in scripts:
296         path_subdir = _os.path.join(path, subdir)
297         if _os.path.isdir(path_subdir):
298             script_paths.append(path_subdir)
299
300     return script_paths
301
302
303 _presets = _os.path.join(_scripts[0], "presets") # FIXME - multiple paths
304
305
306 def preset_paths(subdir):
307     '''
308     Returns a list of paths for a specific preset.
309     '''
310
311     return (_os.path.join(_presets, subdir), )
312
313
314 def smpte_from_seconds(time, fps=None):
315     '''
316     Returns an SMPTE formatted string from the time in seconds: "HH:MM:SS:FF".
317
318     If the fps is not given the current scene is used.
319     '''
320     import math
321
322     if fps is None:
323         fps = _bpy.context.scene.render.fps
324
325     hours = minutes = seconds = frames = 0
326
327     if time < 0:
328         time = - time
329         neg = "-"
330     else:
331         neg = ""
332
333     if time >= 3600.0: # hours
334         hours = int(time / 3600.0)
335         time = time % 3600.0
336     if time >= 60.0: # mins
337         minutes = int(time / 60.0)
338         time = time % 60.0
339
340     seconds = int(time)
341     frames = int(round(math.floor(((time - seconds) * fps))))
342
343     return "%s%02d:%02d:%02d:%02d" % (neg, hours, minutes, seconds, frames)
344
345
346 def smpte_from_frame(frame, fps=None, fps_base=None):
347     '''
348     Returns an SMPTE formatted string from the frame: "HH:MM:SS:FF".
349
350     If the fps and fps_base are not given the current scene is used.
351     '''
352
353     if fps is None:
354         fps = _bpy.context.scene.render.fps
355
356     if fps_base is None:
357         fps_base = _bpy.context.scene.render.fps_base
358
359     return smpte_from_seconds((frame * fps_base) / fps, fps)