Fri, 28 Jul 2017 15:57:22 +0200
Changed min/max setting values
15 | 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) |