[#21141] boring segfault on F8/"reload scripts", 26878, backtrace
[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
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     try:
43         t = time.time()
44         ret = __import__(module_name)
45         if _bpy.app.debug:
46             print("time %s %.4f" % (module_name, time.time() - t))
47         return ret
48     except:
49         traceback.print_exc()
50         return None
51
52
53 def modules_from_path(path, loaded_modules):
54     """
55     Load all modules in a path and return them as a list.
56     """
57     import traceback
58     import time
59     
60     modules = []
61     
62     for f in sorted(_os.listdir(path)):
63         if f.endswith(".py"):
64             # python module
65             mod = _test_import(f[0:-3], loaded_modules)
66         elif ("." not in f) and (_os.path.isfile(_os.path.join(path, f, "__init__.py"))):
67             # python package
68             mod = _test_import(f, loaded_modules)
69         else:
70             mod = None
71         
72         if mod:
73             modules.append(mod)
74     
75     return modules
76
77 _loaded = [] # store loaded modules for reloading.
78 _bpy_types = __import__("bpy_types") # keep for comparisons, never ever reload this.
79
80
81 def load_scripts(reload_scripts=False, refresh_scripts=False):
82     import traceback
83     import time
84
85     t_main = time.time()
86
87     loaded_modules = set()
88     
89     if refresh_scripts:
90         original_modules = _sys.modules.values()
91
92     def sys_path_ensure(path):
93         if path not in _sys.path: # reloading would add twice
94             _sys.path.insert(0, path)
95
96     def test_reload(mod):
97         # reloading this causes internal errors
98         # because the classes from this module are stored internally
99         # possibly to refresh internal references too but for now, best not to.
100         if mod == _bpy_types:
101             return mod
102
103         try:
104             return reload(mod)
105         except:
106             traceback.print_exc()
107     
108     def test_register(mod):
109
110         if refresh_scripts and mod in original_modules:
111             return
112
113         if reload_scripts and mod:
114             print("Reloading:", mod)
115             mod = test_reload(mod)
116
117         if mod:
118             register = getattr(mod, "register", None)
119             if register:
120                 register()
121             else:
122                 print("\nWarning! '%s' has no register function, this is now a requirement for registerable scripts." % mod.__file__)
123             _loaded.append(mod)
124     
125     if reload_scripts:
126         # reload modules that may not be directly included
127         for type_class_name in dir(_bpy.types):
128             type_class = getattr(_bpy.types, type_class_name)
129             module_name = getattr(type_class, "__module__", "")
130
131             if module_name and module_name != "bpy.types": # hard coded for C types
132                 loaded_modules.add(module_name)
133
134         for module_name in loaded_modules:
135             print("Reloading:", module_name)
136             test_reload(_sys.modules[module_name])
137             
138         # loop over and unload all scripts
139         _loaded.reverse()
140         for mod in _loaded:
141             func = getattr(mod, "unregister", None)
142             if func:
143                 func()
144         _loaded[:] = []
145
146
147     for base_path in script_paths():
148         for path_subdir in ("", "ui", "op", "io", "cfg"):
149             path = _os.path.join(base_path, path_subdir)
150             if _os.path.isdir(path):
151                 sys_path_ensure(path)
152
153                 for mod in modules_from_path(path, loaded_modules):
154                     test_register(mod)
155
156
157     # load extensions
158     used_ext = {ext.module for ext in _bpy.context.user_preferences.extensions}    
159     paths = script_paths("extensions")
160     for path in paths:
161         sys_path_ensure(path)
162     
163     for module_name in sorted(used_ext):
164         mod = _test_import(module_name, loaded_modules)
165         test_register(mod)
166
167
168     if _bpy.app.debug:
169         print("Time %.4f" % (time.time() - t_main))
170
171
172 def expandpath(path):
173     if path.startswith("//"):
174         return _os.path.join(_os.path.dirname(_bpy.data.filename), path[2:])
175
176     return path
177
178
179 _unclean_chars = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, \
180     17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, \
181     35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 58, 59, 60, 61, 62, 63, \
182     64, 91, 92, 93, 94, 96, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, \
183     133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, \
184     147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, \
185     161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, \
186     175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, \
187     189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, \
188     203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, \
189     217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, \
190     231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, \
191     245, 246, 247, 248, 249, 250, 251, 252, 253, 254]
192
193 _unclean_chars = ''.join([chr(i) for i in _unclean_chars])
194
195
196 def clean_name(name, replace="_"):
197     """
198     Returns a name with characters replaced that may cause problems under various circumstances, such as writing to a file.
199     All characters besides A-Z/a-z, 0-9 are replaced with "_"
200     or the replace argumet if defined.
201     """
202     for ch in _unclean_chars:
203         name = name.replace(ch, replace)
204     return name
205
206
207 def display_name(name):
208     """
209     Creates a display string from name to be used menus and the user interface.
210     Capitalize the first letter in all lowercase names, mixed case names are kept as is.
211     Intended for use with filenames and module names.
212     """
213     name_base = _os.path.splitext(name)[0]
214
215     # string replacements
216     name_base = name_base.replace("_colon_", ":")
217
218     name_base = name_base.replace("_", " ")
219
220     if name_base.islower():
221         return name_base.capitalize()
222     else:
223         return name_base
224
225
226 # base scripts
227 _scripts = _os.path.join(_os.path.dirname(__file__), _os.path.pardir, _os.path.pardir)
228 _scripts = (_os.path.normpath(_scripts), )
229
230
231 def script_paths(*args):
232     """
233     Returns a list of valid script paths from the home directory and user preferences.
234
235     Accepts any number of string arguments which are joined to make a path.
236     """
237     scripts = list(_scripts)
238
239     # add user scripts dir
240     user_script_path = _bpy.context.user_preferences.filepaths.python_scripts_directory
241     
242     for path in home_paths("scripts") + (user_script_path, ):
243         if path:
244             path = _os.path.normpath(path)
245             if path not in scripts and _os.path.isdir(path):
246                 scripts.append(path)
247
248     if not args:
249         return scripts
250
251     subdir = _os.path.join(*args)
252     script_paths = []
253     for path in scripts:
254         path_subdir = _os.path.join(path, subdir)
255         if _os.path.isdir(path_subdir):
256             script_paths.append(path_subdir)
257
258     return script_paths
259
260
261 _presets = _os.path.join(_scripts[0], "presets") # FIXME - multiple paths
262
263
264 def preset_paths(subdir):
265     '''
266     Returns a list of paths for a spesific preset.
267     '''
268
269     return (_os.path.join(_presets, subdir), )