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