printrun-src/printrun/utils.py

changeset 15
0bbb006204fc
child 46
cce0af6351f0
equal deleted inserted replaced
14:51bf56ba3c10 15:0bbb006204fc
1 # This file is part of the Printrun suite.
2 #
3 # Printrun is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
7 #
8 # Printrun 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 Printrun. If not, see <http://www.gnu.org/licenses/>.
15
16 import os
17 import sys
18 import re
19 import gettext
20 import datetime
21 import subprocess
22 import shlex
23 import logging
24
25 # Set up Internationalization using gettext
26 # searching for installed locales on /usr/share; uses relative folder if not
27 # found (windows)
28 def install_locale(domain):
29 if os.path.exists('/usr/share/pronterface/locale'):
30 gettext.install(domain, '/usr/share/pronterface/locale', unicode = 1)
31 elif os.path.exists('/usr/local/share/pronterface/locale'):
32 gettext.install(domain, '/usr/local/share/pronterface/locale',
33 unicode = 1)
34 else:
35 gettext.install(domain, './locale', unicode = 1)
36
37 class LogFormatter(logging.Formatter):
38 def __init__(self, format_default, format_info):
39 super(LogFormatter, self).__init__(format_info)
40 self.format_default = format_default
41 self.format_info = format_info
42
43 def format(self, record):
44 if record.levelno == logging.INFO:
45 self._fmt = self.format_info
46 else:
47 self._fmt = self.format_default
48 return super(LogFormatter, self).format(record)
49
50 def setup_logging(out, filepath = None, reset_handlers = False):
51 logger = logging.getLogger()
52 logger.setLevel(logging.INFO)
53 if reset_handlers:
54 logger.handlers = []
55 formatter = LogFormatter("[%(levelname)s] %(message)s", "%(message)s")
56 logging_handler = logging.StreamHandler(out)
57 logging_handler.setFormatter(formatter)
58 logger.addHandler(logging_handler)
59 if filepath:
60 if os.path.isdir(filepath):
61 filepath = os.path.join(filepath, "printrun.log")
62 formatter = LogFormatter("%(asctime)s - [%(levelname)s] %(message)s", "%(asctime)s - %(message)s")
63 logging_handler = logging.FileHandler(filepath)
64 logging_handler.setFormatter(formatter)
65 logger.addHandler(logging_handler)
66
67 def iconfile(filename):
68 if hasattr(sys, "frozen") and sys.frozen == "windows_exe":
69 return sys.executable
70 else:
71 return pixmapfile(filename)
72
73 def imagefile(filename):
74 for prefix in ['/usr/local/share/pronterface/images',
75 '/usr/share/pronterface/images']:
76 candidate = os.path.join(prefix, filename)
77 if os.path.exists(candidate):
78 return candidate
79 local_candidate = os.path.join(os.path.dirname(sys.argv[0]),
80 "images", filename)
81 if os.path.exists(local_candidate):
82 return local_candidate
83 else:
84 return os.path.join("images", filename)
85
86 def lookup_file(filename, prefixes):
87 local_candidate = os.path.join(os.path.dirname(sys.argv[0]), filename)
88 if os.path.exists(local_candidate):
89 return local_candidate
90 for prefix in prefixes:
91 candidate = os.path.join(prefix, filename)
92 if os.path.exists(candidate):
93 return candidate
94 return filename
95
96 def pixmapfile(filename):
97 return lookup_file(filename, ['/usr/local/share/pixmaps',
98 '/usr/share/pixmaps'])
99
100 def sharedfile(filename):
101 return lookup_file(filename, ['/usr/local/share/pronterface',
102 '/usr/share/pronterface'])
103
104 def configfile(filename):
105 return lookup_file(filename, [os.path.expanduser("~/.printrun/"), ])
106
107 def decode_utf8(s):
108 try:
109 s = s.decode("utf-8")
110 except:
111 pass
112 return s
113
114 def format_time(timestamp):
115 return datetime.datetime.fromtimestamp(timestamp).strftime("%H:%M:%S")
116
117 def format_duration(delta):
118 return str(datetime.timedelta(seconds = int(delta)))
119
120 def prepare_command(command, replaces = None):
121 command = shlex.split(command.replace("\\", "\\\\").encode())
122 if replaces:
123 replaces["$python"] = sys.executable
124 for pattern, rep in replaces.items():
125 command = [bit.replace(pattern, rep) for bit in command]
126 command = [bit.encode() for bit in command]
127 return command
128
129 def run_command(command, replaces = None, stdout = subprocess.STDOUT, stderr = subprocess.STDOUT, blocking = False):
130 command = prepare_command(command, replaces)
131 if blocking:
132 return subprocess.call(command)
133 else:
134 return subprocess.Popen(command, stderr = stderr, stdout = stdout)
135
136 def get_command_output(command, replaces):
137 p = run_command(command, replaces,
138 stdout = subprocess.PIPE, stderr = subprocess.STDOUT,
139 blocking = False)
140 return p.stdout.read()
141
142 def dosify(name):
143 return os.path.split(name)[1].split(".")[0][:8] + ".g"
144
145 class RemainingTimeEstimator(object):
146
147 drift = None
148 gcode = None
149
150 def __init__(self, gcode):
151 self.drift = 1
152 self.previous_layers_estimate = 0
153 self.current_layer_estimate = 0
154 self.current_layer_lines = 0
155 self.gcode = gcode
156 self.remaining_layers_estimate = sum(layer.duration for layer in gcode.all_layers)
157 if len(gcode) > 0:
158 self.update_layer(0, 0)
159
160 def update_layer(self, layer, printtime):
161 self.previous_layers_estimate += self.current_layer_estimate
162 if self.previous_layers_estimate > 1. and printtime > 1.:
163 self.drift = printtime / self.previous_layers_estimate
164 self.current_layer_estimate = self.gcode.all_layers[layer].duration
165 self.current_layer_lines = len(self.gcode.all_layers[layer])
166 self.remaining_layers_estimate -= self.current_layer_estimate
167 self.last_idx = -1
168 self.last_estimate = None
169
170 def __call__(self, idx, printtime):
171 if not self.current_layer_lines:
172 return (0, 0)
173 if idx == self.last_idx:
174 return self.last_estimate
175 layer, line = self.gcode.idxs(idx)
176 layer_progress = (1 - (float(line + 1) / self.current_layer_lines))
177 remaining = layer_progress * self.current_layer_estimate + self.remaining_layers_estimate
178 estimate = self.drift * remaining
179 total = estimate + printtime
180 self.last_idx = idx
181 self.last_estimate = (estimate, total)
182 return self.last_estimate
183
184 def parse_build_dimensions(bdim):
185 # a string containing up to six numbers delimited by almost anything
186 # first 0-3 numbers specify the build volume, no sign, always positive
187 # remaining 0-3 numbers specify the coordinates of the "southwest" corner of the build platform
188 # "XXX,YYY"
189 # "XXXxYYY+xxx-yyy"
190 # "XXX,YYY,ZZZ+xxx+yyy-zzz"
191 # etc
192 bdl = re.findall("([-+]?[0-9]*\.?[0-9]*)", bdim)
193 defaults = [200, 200, 100, 0, 0, 0, 0, 0, 0]
194 bdl = filter(None, bdl)
195 bdl_float = [float(value) if value else defaults[i] for i, value in enumerate(bdl)]
196 if len(bdl_float) < len(defaults):
197 bdl_float += [defaults[i] for i in range(len(bdl_float), len(defaults))]
198 for i in range(3): # Check for nonpositive dimensions for build volume
199 if bdl_float[i] <= 0: bdl_float[i] = 1
200 return bdl_float
201
202 def get_home_pos(build_dimensions):
203 return build_dimensions[6:9] if len(build_dimensions) >= 9 else None
204
205 def hexcolor_to_float(color, components):
206 color = color[1:]
207 numel = len(color)
208 ndigits = numel / components
209 div = 16 ** ndigits - 1
210 return tuple(round(float(int(color[i:i + ndigits], 16)) / div, 2)
211 for i in range(0, numel, ndigits))
212
213 def check_rgb_color(color):
214 if len(color[1:]) % 3 != 0:
215 ex = ValueError(_("Color must be specified as #RGB"))
216 ex.from_validator = True
217 raise ex
218
219 def check_rgba_color(color):
220 if len(color[1:]) % 4 != 0:
221 ex = ValueError(_("Color must be specified as #RGBA"))
222 ex.from_validator = True
223 raise ex
224
225 tempreport_exp = re.compile("([TB]\d*):([-+]?\d*\.?\d*)(?: ?\/)?([-+]?\d*\.?\d*)")
226 def parse_temperature_report(report):
227 matches = tempreport_exp.findall(report)
228 return dict((m[0], (m[1], m[2])) for m in matches)

mercurial