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