403eeb8a7592e547db2092ad626b67ef0d870af1
[blender-staging.git] / release / scripts / modules / extensions_framework / util.py
1 # -*- coding: utf8 -*-
2 #
3 # ***** BEGIN GPL LICENSE BLOCK *****
4 #
5 # --------------------------------------------------------------------------
6 # Blender 2.5 Extensions Framework
7 # --------------------------------------------------------------------------
8 #
9 # Authors:
10 # Doug Hammond
11 #
12 # This program is free software; you can redistribute it and/or
13 # modify it under the terms of the GNU General Public License
14 # as published by the Free Software Foundation; either version 2
15 # of the License, or (at your option) any later version.
16 #
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software Foundation,
24 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25 #
26 # ***** END GPL LICENCE BLOCK *****
27 #
28 import configparser
29 import datetime
30 import os
31 import tempfile
32 import threading
33
34 import bpy
35
36 config_paths = bpy.utils.script_paths()
37
38 """This path is set at the start of export, so that calls to
39 path_relative_to_export() can make all exported paths relative to
40 this one.
41 """
42 export_path = '';
43
44 def path_relative_to_export(p):
45         """Return a path that is relative to the export path"""
46         global export_path
47         p = filesystem_path(p)
48         try:
49                 relp = os.path.relpath(p, os.path.dirname(export_path))
50         except ValueError: # path on different drive on windows
51                 relp = p
52         
53         return relp.replace('\\', '/')
54
55 def filesystem_path(p):
56         """Resolve a relative Blender path to a real filesystem path"""
57         if p.startswith('//'):
58                 pout = bpy.path.abspath(p)
59         else:
60                 pout = os.path.realpath(p)
61         
62         return pout.replace('\\', '/')
63
64 # TODO: - somehow specify TYPES to get/set from config
65
66 def find_config_value(module, section, key, default):
67         """Attempt to find the configuration value specified by string key
68         in the specified section of module's configuration file. If it is
69         not found, return default.
70         
71         """
72         global config_paths
73         fc = []
74         for p in config_paths:
75                 if os.path.exists(p) and os.path.isdir(p) and os.access(p, os.W_OK):
76                         fc.append( '/'.join([p, '%s.cfg' % module]))
77         
78         if len(fc) < 1:
79                 print('Cannot find %s config file path' % module)
80                 return default
81                 
82         cp = configparser.SafeConfigParser()
83         
84         cfg_files = cp.read(fc)
85         if len(cfg_files) > 0:
86                 try:
87                         val = cp.get(section, key)
88                         if val == 'true':
89                                 return True
90                         elif val == 'false':
91                                 return False
92                         else:
93                                 return val
94                 except:
95                         return default
96         else:
97                 return default
98
99 def write_config_value(module, section, key, value):
100         """Attempt to write the configuration value specified by string key
101         in the specified section of module's configuration file.
102         
103         """
104         global config_paths
105         fc = []
106         for p in config_paths:
107                 if os.path.exists(p) and os.path.isdir(p) and os.access(p, os.W_OK):
108                         fc.append( '/'.join([p, '%s.cfg' % module]))
109         
110         if len(fc) < 1:
111                 raise Exception('Cannot find a writable path to store %s config file' %
112                         module)
113                 
114         cp = configparser.SafeConfigParser()
115         
116         cfg_files = cp.read(fc)
117         
118         if not cp.has_section(section):
119                 cp.add_section(section)
120                 
121         if value == True:
122                 cp.set(section, key, 'true')
123         elif value == False:
124                 cp.set(section, key, 'false')
125         else:
126                 cp.set(section, key, value)
127         
128         if len(cfg_files) < 1:
129                 cfg_files = fc
130         
131         fh=open(cfg_files[0],'w')
132         cp.write(fh)
133         fh.close()
134         
135         return True
136
137 def scene_filename():
138         """Construct a safe scene filename, using 'untitled' instead of ''"""
139         filename = os.path.splitext(os.path.basename(bpy.data.filepath))[0]
140         if filename == '':
141                 filename = 'untitled'
142         return bpy.path.clean_name(filename)
143
144 def temp_directory():
145         """Return the system temp directory"""
146         return tempfile.gettempdir()
147
148 def temp_file(ext='tmp'):
149         """Get a temporary filename with the given extension. This function
150         will actually attempt to create the file."""
151         tf, fn = tempfile.mkstemp(suffix='.%s'%ext)
152         os.close(tf)
153         return fn
154
155 class TimerThread(threading.Thread):
156         """Periodically call self.kick(). The period of time in seconds
157         between calling is given by self.KICK_PERIOD, and the first call
158         may be delayed by setting self.STARTUP_DELAY, also in seconds.
159         self.kick() will continue to be called at regular intervals until
160         self.stop() is called. Since this is a thread, calling self.join()
161         may be wise after calling self.stop() if self.kick() is performing
162         a task necessary for the continuation of the program.
163         The object that creates this TimerThread may pass into it data
164         needed during self.kick() as a dict LocalStorage in __init__().
165         
166         """
167         STARTUP_DELAY = 0
168         KICK_PERIOD = 8
169         
170         active = True
171         timer = None
172         
173         LocalStorage = None
174         
175         def __init__(self, LocalStorage=dict()):
176                 threading.Thread.__init__(self)
177                 self.LocalStorage = LocalStorage
178         
179         def set_kick_period(self, period):
180                 """Adjust the KICK_PERIOD between __init__() and start()"""
181                 self.KICK_PERIOD = period + self.STARTUP_DELAY
182         
183         def stop(self):
184                 """Stop this timer. This method does not join()"""
185                 self.active = False
186                 if self.timer is not None:
187                         self.timer.cancel()
188                         
189         def run(self):
190                 """Timed Thread loop"""
191                 while self.active:
192                         self.timer = threading.Timer(self.KICK_PERIOD, self.kick_caller)
193                         self.timer.start()
194                         if self.timer.isAlive(): self.timer.join()
195         
196         def kick_caller(self):
197                 """Intermediary between the kick-wait-loop and kick to allow
198                 adjustment of the first KICK_PERIOD by STARTUP_DELAY
199                 
200                 """
201                 if self.STARTUP_DELAY > 0:
202                         self.KICK_PERIOD -= self.STARTUP_DELAY
203                         self.STARTUP_DELAY = 0
204                 
205                 self.kick()
206         
207         def kick(self):
208                 """Sub-classes do their work here"""
209                 pass
210
211 def format_elapsed_time(t):
212         """Format a duration in seconds as an HH:MM:SS format time"""
213         
214         td = datetime.timedelta(seconds=t)
215         min = td.days*1440  + td.seconds/60.0
216         hrs = td.days*24        + td.seconds/3600.0
217         
218         return '%i:%02i:%02i' % (hrs, min%60, td.seconds%60)