Fri, 28 Jul 2017 15:56:52 +0200
Bugfix SVG offset correction
Changed settings min/max values
15 | 1 | #!/usr/bin/env python |
2 | ||
3 | # This file is part of the Printrun suite. | |
4 | # | |
5 | # Printrun is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU General Public License as published by | |
7 | # the Free Software Foundation, either version 3 of the License, or | |
8 | # (at your option) any later version. | |
9 | # | |
10 | # Printrun is distributed in the hope that it will be useful, | |
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | # GNU General Public License for more details. | |
14 | # | |
15 | # You should have received a copy of the GNU General Public License | |
16 | # along with Printrun. If not, see <http://www.gnu.org/licenses/>. | |
17 | ||
18 | import cmd | |
19 | import glob | |
20 | import os | |
21 | import time | |
22 | import threading | |
23 | import sys | |
24 | import shutil | |
25 | import subprocess | |
26 | import codecs | |
27 | import argparse | |
28 | import locale | |
29 | import logging | |
30 | import traceback | |
31 | import re | |
32 | ||
33 | from serial import SerialException | |
34 | ||
35 | from . import printcore | |
36 | from .utils import install_locale, run_command, get_command_output, \ | |
37 | format_time, format_duration, RemainingTimeEstimator, \ | |
38 | get_home_pos, parse_build_dimensions, parse_temperature_report, \ | |
39 | setup_logging | |
40 | install_locale('pronterface') | |
41 | from .settings import Settings, BuildDimensionsSetting | |
42 | from .power import powerset_print_start, powerset_print_stop | |
43 | from printrun import gcoder | |
44 | from .rpc import ProntRPC | |
45 | ||
46 | if os.name == "nt": | |
47 | try: | |
48 | import _winreg | |
49 | except: | |
50 | pass | |
51 | READLINE = True | |
52 | try: | |
53 | import readline | |
54 | try: | |
55 | readline.rl.mode.show_all_if_ambiguous = "on" # config pyreadline on windows | |
56 | except: | |
57 | pass | |
58 | except: | |
59 | READLINE = False # neither readline module is available | |
60 | ||
61 | tempreading_exp = re.compile("(^T:| T:)") | |
62 | ||
63 | REPORT_NONE = 0 | |
64 | REPORT_POS = 1 | |
65 | REPORT_TEMP = 2 | |
66 | REPORT_MANUAL = 4 | |
67 | ||
68 | class Status(object): | |
69 | ||
70 | def __init__(self): | |
71 | self.extruder_temp = 0 | |
72 | self.extruder_temp_target = 0 | |
73 | self.bed_temp = 0 | |
74 | self.bed_temp_target = 0 | |
75 | self.print_job = None | |
76 | self.print_job_progress = 1.0 | |
77 | ||
78 | def update_tempreading(self, tempstr): | |
79 | temps = parse_temperature_report(tempstr) | |
80 | if "T0" in temps and temps["T0"][0]: hotend_temp = float(temps["T0"][0]) | |
81 | elif "T" in temps and temps["T"][0]: hotend_temp = float(temps["T"][0]) | |
82 | else: hotend_temp = None | |
83 | if "T0" in temps and temps["T0"][1]: hotend_setpoint = float(temps["T0"][1]) | |
84 | elif "T" in temps and temps["T"][1]: hotend_setpoint = float(temps["T"][1]) | |
85 | else: hotend_setpoint = None | |
86 | if hotend_temp is not None: | |
87 | self.extruder_temp = hotend_temp | |
88 | if hotend_setpoint is not None: | |
89 | self.extruder_temp_target = hotend_setpoint | |
90 | bed_temp = float(temps["B"][0]) if "B" in temps and temps["B"][0] else None | |
91 | if bed_temp is not None: | |
92 | self.bed_temp = bed_temp | |
93 | setpoint = temps["B"][1] | |
94 | if setpoint: | |
95 | self.bed_temp_target = float(setpoint) | |
96 | ||
97 | @property | |
98 | def bed_enabled(self): | |
99 | return self.bed_temp != 0 | |
100 | ||
101 | @property | |
102 | def extruder_enabled(self): | |
103 | return self.extruder_temp != 0 | |
104 | ||
105 | ||
106 | class pronsole(cmd.Cmd): | |
107 | def __init__(self): | |
108 | cmd.Cmd.__init__(self) | |
109 | if not READLINE: | |
110 | self.completekey = None | |
111 | self.status = Status() | |
112 | self.dynamic_temp = False | |
113 | self.compute_eta = None | |
114 | self.statuscheck = False | |
115 | self.status_thread = None | |
116 | self.monitor_interval = 3 | |
117 | self.p = printcore.printcore() | |
118 | self.p.recvcb = self.recvcb | |
119 | self.p.startcb = self.startcb | |
120 | self.p.endcb = self.endcb | |
121 | self.p.layerchangecb = self.layer_change_cb | |
122 | self.p.process_host_command = self.process_host_command | |
123 | self.recvlisteners = [] | |
124 | self.in_macro = False | |
125 | self.p.onlinecb = self.online | |
126 | self.p.errorcb = self.logError | |
127 | self.fgcode = None | |
128 | self.filename = None | |
129 | self.rpc_server = None | |
130 | self.curlayer = 0 | |
131 | self.sdlisting = 0 | |
132 | self.sdlisting_echo = 0 | |
133 | self.sdfiles = [] | |
134 | self.paused = False | |
135 | self.sdprinting = 0 | |
136 | self.uploading = 0 # Unused, just for pronterface generalization | |
137 | self.temps = {"pla": "185", "abs": "230", "off": "0"} | |
138 | self.bedtemps = {"pla": "60", "abs": "110", "off": "0"} | |
139 | self.percentdone = 0 | |
140 | self.posreport = "" | |
141 | self.tempreadings = "" | |
142 | self.userm114 = 0 | |
143 | self.userm105 = 0 | |
144 | self.m105_waitcycles = 0 | |
145 | self.macros = {} | |
146 | self.history_file = "~/.pronsole-history" | |
147 | self.rc_loaded = False | |
148 | self.processing_rc = False | |
149 | self.processing_args = False | |
150 | self.settings = Settings(self) | |
151 | self.settings._add(BuildDimensionsSetting("build_dimensions", "200x200x100+0+0+0+0+0+0", _("Build dimensions"), _("Dimensions of Build Platform\n & optional offset of origin\n & optional switch position\n\nExamples:\n XXXxYYY\n XXX,YYY,ZZZ\n XXXxYYYxZZZ+OffX+OffY+OffZ\nXXXxYYYxZZZ+OffX+OffY+OffZ+HomeX+HomeY+HomeZ"), "Printer"), self.update_build_dimensions) | |
152 | self.settings._port_list = self.scanserial | |
153 | self.settings._temperature_abs_cb = self.set_temp_preset | |
154 | self.settings._temperature_pla_cb = self.set_temp_preset | |
155 | self.settings._bedtemp_abs_cb = self.set_temp_preset | |
156 | self.settings._bedtemp_pla_cb = self.set_temp_preset | |
157 | self.update_build_dimensions(None, self.settings.build_dimensions) | |
158 | self.update_tcp_streaming_mode(None, self.settings.tcp_streaming_mode) | |
159 | self.monitoring = 0 | |
160 | self.starttime = 0 | |
161 | self.extra_print_time = 0 | |
162 | self.silent = False | |
163 | self.commandprefixes = 'MGT$' | |
164 | self.promptstrs = {"offline": "%(bold)soffline>%(normal)s ", | |
165 | "fallback": "%(bold)sPC>%(normal)s ", | |
166 | "macro": "%(bold)s..>%(normal)s ", | |
167 | "online": "%(bold)sT:%(extruder_temp_fancy)s%(progress_fancy)s>%(normal)s "} | |
168 | ||
169 | # -------------------------------------------------------------- | |
170 | # General console handling | |
171 | # -------------------------------------------------------------- | |
172 | ||
173 | def postloop(self): | |
174 | self.p.disconnect() | |
175 | cmd.Cmd.postloop(self) | |
176 | ||
177 | def preloop(self): | |
178 | self.log(_("Welcome to the printer console! Type \"help\" for a list of available commands.")) | |
179 | self.prompt = self.promptf() | |
180 | cmd.Cmd.preloop(self) | |
181 | ||
182 | # We replace this function, defined in cmd.py . | |
183 | # It's default behavior with regards to Ctr-C | |
184 | # and Ctr-D doesn't make much sense... | |
185 | def cmdloop(self, intro=None): | |
186 | """Repeatedly issue a prompt, accept input, parse an initial prefix | |
187 | off the received input, and dispatch to action methods, passing them | |
188 | the remainder of the line as argument. | |
189 | ||
190 | """ | |
191 | ||
192 | self.preloop() | |
193 | if self.use_rawinput and self.completekey: | |
194 | try: | |
195 | import readline | |
196 | self.old_completer = readline.get_completer() | |
197 | readline.set_completer(self.complete) | |
198 | readline.parse_and_bind(self.completekey + ": complete") | |
199 | history = os.path.expanduser(self.history_file) | |
200 | if os.path.exists(history): | |
201 | readline.read_history_file(history) | |
202 | except ImportError: | |
203 | pass | |
204 | try: | |
205 | if intro is not None: | |
206 | self.intro = intro | |
207 | if self.intro: | |
208 | self.stdout.write(str(self.intro) + "\n") | |
209 | stop = None | |
210 | while not stop: | |
211 | if self.cmdqueue: | |
212 | line = self.cmdqueue.pop(0) | |
213 | else: | |
214 | if self.use_rawinput: | |
215 | try: | |
216 | line = raw_input(self.prompt) | |
217 | except EOFError: | |
218 | self.log("") | |
219 | self.do_exit("") | |
220 | except KeyboardInterrupt: | |
221 | self.log("") | |
222 | line = "" | |
223 | else: | |
224 | self.stdout.write(self.prompt) | |
225 | self.stdout.flush() | |
226 | line = self.stdin.readline() | |
227 | if not len(line): | |
228 | line = "" | |
229 | else: | |
230 | line = line.rstrip('\r\n') | |
231 | line = self.precmd(line) | |
232 | stop = self.onecmd(line) | |
233 | stop = self.postcmd(stop, line) | |
234 | self.postloop() | |
235 | finally: | |
236 | if self.use_rawinput and self.completekey: | |
237 | try: | |
238 | import readline | |
239 | readline.set_completer(self.old_completer) | |
240 | readline.write_history_file(history) | |
241 | except ImportError: | |
242 | pass | |
243 | ||
244 | def confirm(self): | |
245 | y_or_n = raw_input("y/n: ") | |
246 | if y_or_n == "y": | |
247 | return True | |
248 | elif y_or_n != "n": | |
249 | return self.confirm() | |
250 | return False | |
251 | ||
252 | def log(self, *msg): | |
253 | msg = u"".join(unicode(i) for i in msg) | |
254 | logging.info(msg) | |
255 | ||
256 | def logError(self, *msg): | |
257 | msg = u"".join(unicode(i) for i in msg) | |
258 | logging.error(msg) | |
259 | if not self.settings.error_command: | |
260 | return | |
261 | output = get_command_output(self.settings.error_command, {"$m": msg}) | |
262 | if output: | |
263 | self.log("Error command output:") | |
264 | self.log(output.rstrip()) | |
265 | ||
266 | def promptf(self): | |
267 | """A function to generate prompts so that we can do dynamic prompts. """ | |
268 | if self.in_macro: | |
269 | promptstr = self.promptstrs["macro"] | |
270 | elif not self.p.online: | |
271 | promptstr = self.promptstrs["offline"] | |
272 | elif self.status.extruder_enabled: | |
273 | promptstr = self.promptstrs["online"] | |
274 | else: | |
275 | promptstr = self.promptstrs["fallback"] | |
276 | if "%" not in promptstr: | |
277 | return promptstr | |
278 | else: | |
279 | specials = {} | |
280 | specials["extruder_temp"] = str(int(self.status.extruder_temp)) | |
281 | specials["extruder_temp_target"] = str(int(self.status.extruder_temp_target)) | |
282 | if self.status.extruder_temp_target == 0: | |
283 | specials["extruder_temp_fancy"] = str(int(self.status.extruder_temp)) | |
284 | else: | |
285 | specials["extruder_temp_fancy"] = "%s/%s" % (str(int(self.status.extruder_temp)), str(int(self.status.extruder_temp_target))) | |
286 | if self.p.printing: | |
287 | progress = int(1000 * float(self.p.queueindex) / len(self.p.mainqueue)) / 10 | |
288 | elif self.sdprinting: | |
289 | progress = self.percentdone | |
290 | else: | |
291 | progress = 0.0 | |
292 | specials["progress"] = str(progress) | |
293 | if self.p.printing or self.sdprinting: | |
294 | specials["progress_fancy"] = " " + str(progress) + "%" | |
295 | else: | |
296 | specials["progress_fancy"] = "" | |
297 | specials["bold"] = "\033[01m" | |
298 | specials["normal"] = "\033[00m" | |
299 | return promptstr % specials | |
300 | ||
301 | def postcmd(self, stop, line): | |
302 | """ A hook we override to generate prompts after | |
303 | each command is executed, for the next prompt. | |
304 | We also use it to send M105 commands so that | |
305 | temp info gets updated for the prompt.""" | |
306 | if self.p.online and self.dynamic_temp: | |
307 | self.p.send_now("M105") | |
308 | self.prompt = self.promptf() | |
309 | return stop | |
310 | ||
311 | def kill(self): | |
312 | self.statuscheck = False | |
313 | if self.status_thread: | |
314 | self.status_thread.join() | |
315 | self.status_thread = None | |
316 | if self.rpc_server is not None: | |
317 | self.rpc_server.shutdown() | |
318 | ||
319 | def write_prompt(self): | |
320 | sys.stdout.write(self.promptf()) | |
321 | sys.stdout.flush() | |
322 | ||
323 | def help_help(self, l = ""): | |
324 | self.do_help("") | |
325 | ||
326 | def do_gcodes(self, l = ""): | |
327 | self.help_gcodes() | |
328 | ||
329 | def help_gcodes(self): | |
330 | self.log("Gcodes are passed through to the printer as they are") | |
331 | ||
332 | def precmd(self, line): | |
333 | if line.upper().startswith("M114"): | |
334 | self.userm114 += 1 | |
335 | elif line.upper().startswith("M105"): | |
336 | self.userm105 += 1 | |
337 | return line | |
338 | ||
339 | def help_shell(self): | |
340 | self.log("Executes a python command. Example:") | |
341 | self.log("! os.listdir('.')") | |
342 | ||
343 | def do_shell(self, l): | |
344 | exec(l) | |
345 | ||
346 | def emptyline(self): | |
347 | """Called when an empty line is entered - do not remove""" | |
348 | pass | |
349 | ||
350 | def default(self, l): | |
351 | if l[0].upper() in self.commandprefixes.upper(): | |
352 | if self.p and self.p.online: | |
353 | if not self.p.loud: | |
354 | self.log("SENDING:" + l.upper()) | |
355 | self.p.send_now(l.upper()) | |
356 | else: | |
357 | self.logError(_("Printer is not online.")) | |
358 | return | |
359 | elif l[0] == "@": | |
360 | if self.p and self.p.online: | |
361 | if not self.p.loud: | |
362 | self.log("SENDING:" + l[1:]) | |
363 | self.p.send_now(l[1:]) | |
364 | else: | |
365 | self.logError(_("Printer is not online.")) | |
366 | return | |
367 | else: | |
368 | cmd.Cmd.default(self, l) | |
369 | ||
370 | def do_exit(self, l): | |
371 | if self.status.extruder_temp_target != 0: | |
372 | self.log("Setting extruder temp to 0") | |
373 | self.p.send_now("M104 S0.0") | |
374 | if self.status.bed_enabled: | |
375 | if self.status.bed_temp_target != 0: | |
376 | self.log("Setting bed temp to 0") | |
377 | self.p.send_now("M140 S0.0") | |
378 | self.log("Disconnecting from printer...") | |
379 | if self.p.printing: | |
380 | self.log(_("Are you sure you want to exit while printing?\n\ | |
381 | (this will terminate the print).")) | |
382 | if not self.confirm(): | |
383 | return | |
384 | self.log(_("Exiting program. Goodbye!")) | |
385 | self.p.disconnect() | |
386 | self.kill() | |
387 | sys.exit() | |
388 | ||
389 | def help_exit(self): | |
390 | self.log(_("Disconnects from the printer and exits the program.")) | |
391 | ||
392 | # -------------------------------------------------------------- | |
393 | # Macro handling | |
394 | # -------------------------------------------------------------- | |
395 | ||
396 | def complete_macro(self, text, line, begidx, endidx): | |
397 | if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1] == " "): | |
398 | return [i for i in self.macros.keys() if i.startswith(text)] | |
399 | elif len(line.split()) == 3 or (len(line.split()) == 2 and line[-1] == " "): | |
400 | return [i for i in ["/D", "/S"] + self.completenames(text) if i.startswith(text)] | |
401 | else: | |
402 | return [] | |
403 | ||
404 | def hook_macro(self, l): | |
405 | l = l.rstrip() | |
406 | ls = l.lstrip() | |
407 | ws = l[:len(l) - len(ls)] # just leading whitespace | |
408 | if len(ws) == 0: | |
409 | self.end_macro() | |
410 | # pass the unprocessed line to regular command processor to not require empty line in .pronsolerc | |
411 | return self.onecmd(l) | |
412 | self.cur_macro_def += l + "\n" | |
413 | ||
414 | def end_macro(self): | |
415 | if "onecmd" in self.__dict__: del self.onecmd # remove override | |
416 | self.in_macro = False | |
417 | self.prompt = self.promptf() | |
418 | if self.cur_macro_def != "": | |
419 | self.macros[self.cur_macro_name] = self.cur_macro_def | |
420 | macro = self.compile_macro(self.cur_macro_name, self.cur_macro_def) | |
421 | setattr(self.__class__, "do_" + self.cur_macro_name, lambda self, largs, macro = macro: macro(self, *largs.split())) | |
422 | setattr(self.__class__, "help_" + self.cur_macro_name, lambda self, macro_name = self.cur_macro_name: self.subhelp_macro(macro_name)) | |
423 | if not self.processing_rc: | |
424 | self.log("Macro '" + self.cur_macro_name + "' defined") | |
425 | # save it | |
426 | if not self.processing_args: | |
427 | macro_key = "macro " + self.cur_macro_name | |
428 | macro_def = macro_key | |
429 | if "\n" in self.cur_macro_def: | |
430 | macro_def += "\n" | |
431 | else: | |
432 | macro_def += " " | |
433 | macro_def += self.cur_macro_def | |
434 | self.save_in_rc(macro_key, macro_def) | |
435 | else: | |
436 | self.logError("Empty macro - cancelled") | |
437 | del self.cur_macro_name, self.cur_macro_def | |
438 | ||
439 | def compile_macro_line(self, line): | |
440 | line = line.rstrip() | |
441 | ls = line.lstrip() | |
442 | ws = line[:len(line) - len(ls)] # just leading whitespace | |
443 | if ls == "" or ls.startswith('#'): return "" # no code | |
444 | if ls.startswith('!'): | |
445 | return ws + ls[1:] + "\n" # python mode | |
446 | else: | |
447 | ls = ls.replace('"', '\\"') # need to escape double quotes | |
448 | ret = ws + 'self.precmd("' + ls + '".format(*arg))\n' # parametric command mode | |
449 | return ret + ws + 'self.onecmd("' + ls + '".format(*arg))\n' | |
450 | ||
451 | def compile_macro(self, macro_name, macro_def): | |
452 | if macro_def.strip() == "": | |
453 | self.logError("Empty macro - cancelled") | |
454 | return | |
455 | macro = None | |
456 | pycode = "def macro(self,*arg):\n" | |
457 | if "\n" not in macro_def.strip(): | |
458 | pycode += self.compile_macro_line(" " + macro_def.strip()) | |
459 | else: | |
460 | lines = macro_def.split("\n") | |
461 | for l in lines: | |
462 | pycode += self.compile_macro_line(l) | |
463 | exec pycode | |
464 | return macro | |
465 | ||
466 | def start_macro(self, macro_name, prev_definition = "", suppress_instructions = False): | |
467 | if not self.processing_rc and not suppress_instructions: | |
468 | self.logError("Enter macro using indented lines, end with empty line") | |
469 | self.cur_macro_name = macro_name | |
470 | self.cur_macro_def = "" | |
471 | self.onecmd = self.hook_macro # override onecmd temporarily | |
472 | self.in_macro = False | |
473 | self.prompt = self.promptf() | |
474 | ||
475 | def delete_macro(self, macro_name): | |
476 | if macro_name in self.macros.keys(): | |
477 | delattr(self.__class__, "do_" + macro_name) | |
478 | del self.macros[macro_name] | |
479 | self.log("Macro '" + macro_name + "' removed") | |
480 | if not self.processing_rc and not self.processing_args: | |
481 | self.save_in_rc("macro " + macro_name, "") | |
482 | else: | |
483 | self.logError("Macro '" + macro_name + "' is not defined") | |
484 | ||
485 | def do_macro(self, args): | |
486 | if args.strip() == "": | |
487 | self.print_topics("User-defined macros", map(str, self.macros.keys()), 15, 80) | |
488 | return | |
489 | arglist = args.split(None, 1) | |
490 | macro_name = arglist[0] | |
491 | if macro_name not in self.macros and hasattr(self.__class__, "do_" + macro_name): | |
492 | self.logError("Name '" + macro_name + "' is being used by built-in command") | |
493 | return | |
494 | if len(arglist) == 2: | |
495 | macro_def = arglist[1] | |
496 | if macro_def.lower() == "/d": | |
497 | self.delete_macro(macro_name) | |
498 | return | |
499 | if macro_def.lower() == "/s": | |
500 | self.subhelp_macro(macro_name) | |
501 | return | |
502 | self.cur_macro_def = macro_def | |
503 | self.cur_macro_name = macro_name | |
504 | self.end_macro() | |
505 | return | |
506 | if macro_name in self.macros: | |
507 | self.start_macro(macro_name, self.macros[macro_name]) | |
508 | else: | |
509 | self.start_macro(macro_name) | |
510 | ||
511 | def help_macro(self): | |
512 | self.log("Define single-line macro: macro <name> <definition>") | |
513 | self.log("Define multi-line macro: macro <name>") | |
514 | self.log("Enter macro definition in indented lines. Use {0} .. {N} to substitute macro arguments") | |
515 | self.log("Enter python code, prefixed with ! Use arg[0] .. arg[N] to substitute macro arguments") | |
516 | self.log("Delete macro: macro <name> /d") | |
517 | self.log("Show macro definition: macro <name> /s") | |
518 | self.log("'macro' without arguments displays list of defined macros") | |
519 | ||
520 | def subhelp_macro(self, macro_name): | |
521 | if macro_name in self.macros.keys(): | |
522 | macro_def = self.macros[macro_name] | |
523 | if "\n" in macro_def: | |
524 | self.log("Macro '" + macro_name + "' defined as:") | |
525 | self.log(self.macros[macro_name] + "----------------") | |
526 | else: | |
527 | self.log("Macro '" + macro_name + "' defined as: '" + macro_def + "'") | |
528 | else: | |
529 | self.logError("Macro '" + macro_name + "' is not defined") | |
530 | ||
531 | # -------------------------------------------------------------- | |
532 | # Configuration handling | |
533 | # -------------------------------------------------------------- | |
534 | ||
535 | def set(self, var, str): | |
536 | try: | |
537 | t = type(getattr(self.settings, var)) | |
538 | value = self.settings._set(var, str) | |
539 | if not self.processing_rc and not self.processing_args: | |
540 | self.save_in_rc("set " + var, "set %s %s" % (var, value)) | |
541 | except AttributeError: | |
542 | logging.debug(_("Unknown variable '%s'") % var) | |
543 | except ValueError, ve: | |
544 | if hasattr(ve, "from_validator"): | |
545 | self.logError(_("Bad value %s for variable '%s': %s") % (str, var, ve.args[0])) | |
546 | else: | |
547 | self.logError(_("Bad value for variable '%s', expecting %s (%s)") % (var, repr(t)[1:-1], ve.args[0])) | |
548 | ||
549 | def do_set(self, argl): | |
550 | args = argl.split(None, 1) | |
551 | if len(args) < 1: | |
552 | for k in [kk for kk in dir(self.settings) if not kk.startswith("_")]: | |
553 | self.log("%s = %s" % (k, str(getattr(self.settings, k)))) | |
554 | return | |
555 | if len(args) < 2: | |
556 | # Try getting the default value of the setting to check whether it | |
557 | # actually exists | |
558 | try: | |
559 | getattr(self.settings, args[0]) | |
560 | except AttributeError: | |
561 | logging.warning("Unknown variable '%s'" % args[0]) | |
562 | return | |
563 | self.set(args[0], args[1]) | |
564 | ||
565 | def help_set(self): | |
566 | self.log("Set variable: set <variable> <value>") | |
567 | self.log("Show variable: set <variable>") | |
568 | self.log("'set' without arguments displays all variables") | |
569 | ||
570 | def complete_set(self, text, line, begidx, endidx): | |
571 | if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1] == " "): | |
572 | return [i for i in dir(self.settings) if not i.startswith("_") and i.startswith(text)] | |
573 | elif len(line.split()) == 3 or (len(line.split()) == 2 and line[-1] == " "): | |
574 | return [i for i in self.settings._tabcomplete(line.split()[1]) if i.startswith(text)] | |
575 | else: | |
576 | return [] | |
577 | ||
578 | def load_rc(self, rc_filename): | |
579 | self.processing_rc = True | |
580 | try: | |
581 | rc = codecs.open(rc_filename, "r", "utf-8") | |
582 | self.rc_filename = os.path.abspath(rc_filename) | |
583 | for rc_cmd in rc: | |
584 | if not rc_cmd.lstrip().startswith("#"): | |
585 | self.onecmd(rc_cmd) | |
586 | rc.close() | |
587 | if hasattr(self, "cur_macro_def"): | |
588 | self.end_macro() | |
589 | self.rc_loaded = True | |
590 | finally: | |
591 | self.processing_rc = False | |
592 | ||
593 | def load_default_rc(self, rc_filename = ".pronsolerc"): | |
594 | if rc_filename == ".pronsolerc" and hasattr(sys, "frozen") and sys.frozen in ["windows_exe", "console_exe"]: | |
595 | rc_filename = "printrunconf.ini" | |
596 | try: | |
597 | try: | |
598 | self.load_rc(os.path.join(os.path.expanduser("~"), rc_filename)) | |
599 | except IOError: | |
600 | self.load_rc(rc_filename) | |
601 | except IOError: | |
602 | # make sure the filename is initialized | |
603 | self.rc_filename = os.path.abspath(os.path.join(os.path.expanduser("~"), rc_filename)) | |
604 | ||
605 | def save_in_rc(self, key, definition): | |
606 | """ | |
607 | Saves or updates macro or other definitions in .pronsolerc | |
608 | key is prefix that determines what is being defined/updated (e.g. 'macro foo') | |
609 | definition is the full definition (that is written to file). (e.g. 'macro foo move x 10') | |
610 | Set key as empty string to just add (and not overwrite) | |
611 | Set definition as empty string to remove it from .pronsolerc | |
612 | To delete line from .pronsolerc, set key as the line contents, and definition as empty string | |
613 | Only first definition with given key is overwritten. | |
614 | Updates are made in the same file position. | |
615 | Additions are made to the end of the file. | |
616 | """ | |
617 | rci, rco = None, None | |
618 | if definition != "" and not definition.endswith("\n"): | |
619 | definition += "\n" | |
620 | try: | |
621 | written = False | |
622 | if os.path.exists(self.rc_filename): | |
623 | shutil.copy(self.rc_filename, self.rc_filename + "~bak") | |
624 | rci = codecs.open(self.rc_filename + "~bak", "r", "utf-8") | |
625 | rco = codecs.open(self.rc_filename + "~new", "w", "utf-8") | |
626 | if rci is not None: | |
627 | overwriting = False | |
628 | for rc_cmd in rci: | |
629 | l = rc_cmd.rstrip() | |
630 | ls = l.lstrip() | |
631 | ws = l[:len(l) - len(ls)] # just leading whitespace | |
632 | if overwriting and len(ws) == 0: | |
633 | overwriting = False | |
634 | if not written and key != "" and rc_cmd.startswith(key) and (rc_cmd + "\n")[len(key)].isspace(): | |
635 | overwriting = True | |
636 | written = True | |
637 | rco.write(definition) | |
638 | if not overwriting: | |
639 | rco.write(rc_cmd) | |
640 | if not rc_cmd.endswith("\n"): rco.write("\n") | |
641 | if not written: | |
642 | rco.write(definition) | |
643 | if rci is not None: | |
644 | rci.close() | |
645 | rco.close() | |
646 | shutil.move(self.rc_filename + "~new", self.rc_filename) | |
647 | # if definition != "": | |
648 | # self.log("Saved '"+key+"' to '"+self.rc_filename+"'") | |
649 | # else: | |
650 | # self.log("Removed '"+key+"' from '"+self.rc_filename+"'") | |
651 | except Exception, e: | |
652 | self.logError("Saving failed for ", key + ":", str(e)) | |
653 | finally: | |
654 | del rci, rco | |
655 | ||
656 | # -------------------------------------------------------------- | |
657 | # Configuration update callbacks | |
658 | # -------------------------------------------------------------- | |
659 | ||
660 | def update_build_dimensions(self, param, value): | |
661 | self.build_dimensions_list = parse_build_dimensions(value) | |
662 | self.p.analyzer.home_pos = get_home_pos(self.build_dimensions_list) | |
663 | ||
664 | def update_tcp_streaming_mode(self, param, value): | |
665 | self.p.tcp_streaming_mode = self.settings.tcp_streaming_mode | |
666 | ||
667 | def update_rpc_server(self, param, value): | |
668 | if value: | |
669 | if self.rpc_server is None: | |
670 | self.rpc_server = ProntRPC(self) | |
671 | else: | |
672 | if self.rpc_server is not None: | |
673 | self.rpc_server.shutdown() | |
674 | self.rpc_server = None | |
675 | ||
676 | # -------------------------------------------------------------- | |
677 | # Command line options handling | |
678 | # -------------------------------------------------------------- | |
679 | ||
680 | def add_cmdline_arguments(self, parser): | |
681 | parser.add_argument('-v', '--verbose', help = _("increase verbosity"), action = "store_true") | |
682 | parser.add_argument('-c', '--conf', '--config', help = _("load this file on startup instead of .pronsolerc ; you may chain config files, if so settings auto-save will use the last specified file"), action = "append", default = []) | |
683 | parser.add_argument('-e', '--execute', help = _("executes command after configuration/.pronsolerc is loaded ; macros/settings from these commands are not autosaved"), action = "append", default = []) | |
684 | parser.add_argument('filename', nargs='?', help = _("file to load")) | |
685 | ||
686 | def process_cmdline_arguments(self, args): | |
687 | if args.verbose: | |
688 | logger = logging.getLogger() | |
689 | logger.setLevel(logging.DEBUG) | |
690 | for config in args.conf: | |
691 | self.load_rc(config) | |
692 | if not self.rc_loaded: | |
693 | self.load_default_rc() | |
694 | self.processing_args = True | |
695 | for command in args.execute: | |
696 | self.onecmd(command) | |
697 | self.processing_args = False | |
698 | self.update_rpc_server(None, self.settings.rpc_server) | |
699 | if args.filename: | |
700 | filename = args.filename.decode(locale.getpreferredencoding()) | |
701 | self.cmdline_filename_callback(filename) | |
702 | ||
703 | def cmdline_filename_callback(self, filename): | |
704 | self.do_load(filename) | |
705 | ||
706 | def parse_cmdline(self, args): | |
707 | parser = argparse.ArgumentParser(description = 'Printrun 3D printer interface') | |
708 | self.add_cmdline_arguments(parser) | |
709 | args = [arg for arg in args if not arg.startswith("-psn")] | |
710 | args = parser.parse_args(args = args) | |
711 | self.process_cmdline_arguments(args) | |
712 | setup_logging(sys.stdout, self.settings.log_path, True) | |
713 | ||
714 | # -------------------------------------------------------------- | |
715 | # Printer connection handling | |
716 | # -------------------------------------------------------------- | |
717 | ||
718 | def connect_to_printer(self, port, baud, dtr): | |
719 | try: | |
720 | self.p.connect(port, baud, dtr) | |
721 | except SerialException as e: | |
722 | # Currently, there is no errno, but it should be there in the future | |
723 | if e.errno == 2: | |
724 | self.logError(_("Error: You are trying to connect to a non-existing port.")) | |
725 | elif e.errno == 8: | |
726 | self.logError(_("Error: You don't have permission to open %s.") % port) | |
727 | self.logError(_("You might need to add yourself to the dialout group.")) | |
728 | else: | |
729 | self.logError(traceback.format_exc()) | |
730 | # Kill the scope anyway | |
731 | return False | |
732 | except OSError as e: | |
733 | if e.errno == 2: | |
734 | self.logError(_("Error: You are trying to connect to a non-existing port.")) | |
735 | else: | |
736 | self.logError(traceback.format_exc()) | |
737 | return False | |
738 | self.statuscheck = True | |
739 | self.status_thread = threading.Thread(target = self.statuschecker) | |
740 | self.status_thread.start() | |
741 | return True | |
742 | ||
743 | def do_connect(self, l): | |
744 | a = l.split() | |
745 | p = self.scanserial() | |
746 | port = self.settings.port | |
747 | if (port == "" or port not in p) and len(p) > 0: | |
748 | port = p[0] | |
749 | baud = self.settings.baudrate or 115200 | |
750 | if len(a) > 0: | |
751 | port = a[0] | |
752 | if len(a) > 1: | |
753 | try: | |
754 | baud = int(a[1]) | |
755 | except: | |
756 | self.log("Bad baud value '" + a[1] + "' ignored") | |
757 | if len(p) == 0 and not port: | |
758 | self.log("No serial ports detected - please specify a port") | |
759 | return | |
760 | if len(a) == 0: | |
761 | self.log("No port specified - connecting to %s at %dbps" % (port, baud)) | |
762 | if port != self.settings.port: | |
763 | self.settings.port = port | |
764 | self.save_in_rc("set port", "set port %s" % port) | |
765 | if baud != self.settings.baudrate: | |
766 | self.settings.baudrate = baud | |
767 | self.save_in_rc("set baudrate", "set baudrate %d" % baud) | |
768 | self.connect_to_printer(port, baud, self.settings.dtr) | |
769 | ||
770 | def help_connect(self): | |
771 | self.log("Connect to printer") | |
772 | self.log("connect <port> <baudrate>") | |
773 | self.log("If port and baudrate are not specified, connects to first detected port at 115200bps") | |
774 | ports = self.scanserial() | |
775 | if ports: | |
776 | self.log("Available ports: ", " ".join(ports)) | |
777 | else: | |
778 | self.log("No serial ports were automatically found.") | |
779 | ||
780 | def complete_connect(self, text, line, begidx, endidx): | |
781 | if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1] == " "): | |
782 | return [i for i in self.scanserial() if i.startswith(text)] | |
783 | elif len(line.split()) == 3 or (len(line.split()) == 2 and line[-1] == " "): | |
784 | return [i for i in ["2400", "9600", "19200", "38400", "57600", "115200"] if i.startswith(text)] | |
785 | else: | |
786 | return [] | |
787 | ||
788 | def scanserial(self): | |
789 | """scan for available ports. return a list of device names.""" | |
790 | baselist = [] | |
791 | if os.name == "nt": | |
792 | try: | |
793 | key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\SERIALCOMM") | |
794 | i = 0 | |
795 | while(1): | |
796 | baselist += [_winreg.EnumValue(key, i)[1]] | |
797 | i += 1 | |
798 | except: | |
799 | pass | |
800 | ||
801 | for g in ['/dev/ttyUSB*', '/dev/ttyACM*', "/dev/tty.*", "/dev/cu.*", "/dev/rfcomm*"]: | |
802 | baselist += glob.glob(g) | |
803 | return filter(self._bluetoothSerialFilter, baselist) | |
804 | ||
805 | def _bluetoothSerialFilter(self, serial): | |
806 | return not ("Bluetooth" in serial or "FireFly" in serial) | |
807 | ||
808 | def online(self): | |
809 | self.log("\rPrinter is now online") | |
810 | self.write_prompt() | |
811 | ||
812 | def do_disconnect(self, l): | |
813 | self.p.disconnect() | |
814 | ||
815 | def help_disconnect(self): | |
816 | self.log("Disconnects from the printer") | |
817 | ||
818 | def do_block_until_online(self, l): | |
819 | while not self.p.online: | |
820 | time.sleep(0.1) | |
821 | ||
822 | def help_block_until_online(self, l): | |
823 | self.log("Blocks until printer is online") | |
824 | self.log("Warning: if something goes wrong, this can block pronsole forever") | |
825 | ||
826 | # -------------------------------------------------------------- | |
827 | # Printer status monitoring | |
828 | # -------------------------------------------------------------- | |
829 | ||
830 | def statuschecker_inner(self, do_monitoring = True): | |
831 | if self.p.online: | |
832 | if self.p.writefailures >= 4: | |
833 | self.logError(_("Disconnecting after 4 failed writes.")) | |
834 | self.status_thread = None | |
835 | self.disconnect() | |
836 | return | |
837 | if do_monitoring: | |
838 | if self.sdprinting and not self.paused: | |
839 | self.p.send_now("M27") | |
840 | if self.m105_waitcycles % 10 == 0: | |
841 | self.p.send_now("M105") | |
842 | self.m105_waitcycles += 1 | |
843 | cur_time = time.time() | |
844 | wait_time = 0 | |
845 | while time.time() < cur_time + self.monitor_interval - 0.25: | |
846 | if not self.statuscheck: | |
847 | break | |
848 | time.sleep(0.25) | |
849 | # Safeguard: if system time changes and goes back in the past, | |
850 | # we could get stuck almost forever | |
851 | wait_time += 0.25 | |
852 | if wait_time > self.monitor_interval - 0.25: | |
853 | break | |
854 | # Always sleep at least a bit, if something goes wrong with the | |
855 | # system time we'll avoid freezing the whole app this way | |
856 | time.sleep(0.25) | |
857 | ||
858 | def statuschecker(self): | |
859 | while self.statuscheck: | |
860 | self.statuschecker_inner() | |
861 | ||
862 | # -------------------------------------------------------------- | |
863 | # File loading handling | |
864 | # -------------------------------------------------------------- | |
865 | ||
866 | def do_load(self, filename): | |
867 | self._do_load(filename) | |
868 | ||
869 | def _do_load(self, filename): | |
870 | if not filename: | |
871 | self.logError("No file name given.") | |
872 | return | |
873 | self.log(_("Loading file: %s") % filename) | |
874 | if not os.path.exists(filename): | |
875 | self.logError("File not found!") | |
876 | return | |
877 | self.load_gcode(filename) | |
878 | self.log(_("Loaded %s, %d lines.") % (filename, len(self.fgcode))) | |
879 | self.log(_("Estimated duration: %d layers, %s") % self.fgcode.estimate_duration()) | |
880 | ||
881 | def load_gcode(self, filename, layer_callback = None, gcode = None): | |
882 | if gcode is None: | |
883 | self.fgcode = gcoder.LightGCode(deferred = True) | |
884 | else: | |
885 | self.fgcode = gcode | |
886 | self.fgcode.prepare(open(filename, "rU"), | |
887 | get_home_pos(self.build_dimensions_list), | |
888 | layer_callback = layer_callback) | |
889 | self.fgcode.estimate_duration() | |
890 | self.filename = filename | |
891 | ||
892 | def complete_load(self, text, line, begidx, endidx): | |
893 | s = line.split() | |
894 | if len(s) > 2: | |
895 | return [] | |
896 | if (len(s) == 1 and line[-1] == " ") or (len(s) == 2 and line[-1] != " "): | |
897 | if len(s) > 1: | |
898 | return [i[len(s[1]) - len(text):] for i in glob.glob(s[1] + "*/") + glob.glob(s[1] + "*.g*")] | |
899 | else: | |
900 | return glob.glob("*/") + glob.glob("*.g*") | |
901 | ||
902 | def help_load(self): | |
903 | self.log("Loads a gcode file (with tab-completion)") | |
904 | ||
905 | def do_slice(self, l): | |
906 | l = l.split() | |
907 | if len(l) == 0: | |
908 | self.logError(_("No file name given.")) | |
909 | return | |
910 | settings = 0 | |
911 | if l[0] == "set": | |
912 | settings = 1 | |
913 | else: | |
914 | self.log(_("Slicing file: %s") % l[0]) | |
915 | if not(os.path.exists(l[0])): | |
916 | self.logError(_("File not found!")) | |
917 | return | |
918 | try: | |
919 | if settings: | |
920 | command = self.settings.sliceoptscommand | |
921 | self.log(_("Entering slicer settings: %s") % command) | |
922 | run_command(command, blocking = True) | |
923 | else: | |
924 | command = self.settings.slicecommand | |
925 | stl_name = l[0] | |
926 | gcode_name = stl_name.replace(".stl", "_export.gcode").replace(".STL", "_export.gcode") | |
927 | run_command(command, | |
928 | {"$s": stl_name, | |
929 | "$o": gcode_name}, | |
930 | blocking = True) | |
931 | self.log(_("Loading sliced file.")) | |
932 | self.do_load(l[0].replace(".stl", "_export.gcode")) | |
933 | except Exception, e: | |
934 | self.logError(_("Slicing failed: %s") % e) | |
935 | ||
936 | def complete_slice(self, text, line, begidx, endidx): | |
937 | s = line.split() | |
938 | if len(s) > 2: | |
939 | return [] | |
940 | if (len(s) == 1 and line[-1] == " ") or (len(s) == 2 and line[-1] != " "): | |
941 | if len(s) > 1: | |
942 | return [i[len(s[1]) - len(text):] for i in glob.glob(s[1] + "*/") + glob.glob(s[1] + "*.stl")] | |
943 | else: | |
944 | return glob.glob("*/") + glob.glob("*.stl") | |
945 | ||
946 | def help_slice(self): | |
947 | self.log(_("Creates a gcode file from an stl model using the slicer (with tab-completion)")) | |
948 | self.log(_("slice filename.stl - create gcode file")) | |
949 | self.log(_("slice filename.stl view - create gcode file and view using skeiniso (if using skeinforge)")) | |
950 | self.log(_("slice set - adjust slicer settings")) | |
951 | ||
952 | # -------------------------------------------------------------- | |
953 | # Print/upload handling | |
954 | # -------------------------------------------------------------- | |
955 | ||
956 | def do_upload(self, l): | |
957 | names = l.split() | |
958 | if len(names) == 2: | |
959 | filename = names[0] | |
960 | targetname = names[1] | |
961 | else: | |
962 | self.logError(_("Please enter target name in 8.3 format.")) | |
963 | return | |
964 | if not self.p.online: | |
965 | self.logError(_("Not connected to printer.")) | |
966 | return | |
967 | self._do_load(filename) | |
968 | self.log(_("Uploading as %s") % targetname) | |
969 | self.log(_("Uploading %s") % self.filename) | |
970 | self.p.send_now("M28 " + targetname) | |
971 | self.log(_("Press Ctrl-C to interrupt upload.")) | |
972 | self.p.startprint(self.fgcode) | |
973 | try: | |
974 | sys.stdout.write(_("Progress: ") + "00.0%") | |
975 | sys.stdout.flush() | |
976 | while self.p.printing: | |
977 | time.sleep(0.5) | |
978 | sys.stdout.write("\b\b\b\b\b%04.1f%%" % (100 * float(self.p.queueindex) / len(self.p.mainqueue),)) | |
979 | sys.stdout.flush() | |
980 | self.p.send_now("M29 " + targetname) | |
981 | time.sleep(0.2) | |
982 | self.p.clear = True | |
983 | self._do_ls(False) | |
984 | self.log("\b\b\b\b\b100%.") | |
985 | self.log(_("Upload completed. %s should now be on the card.") % targetname) | |
986 | return | |
987 | except (KeyboardInterrupt, Exception) as e: | |
988 | if isinstance(e, KeyboardInterrupt): | |
989 | self.logError(_("...interrupted!")) | |
990 | else: | |
991 | self.logError(_("Something wrong happened while uploading:") | |
992 | + "\n" + traceback.format_exc()) | |
993 | self.p.pause() | |
994 | self.p.send_now("M29 " + targetname) | |
995 | time.sleep(0.2) | |
996 | self.p.cancelprint() | |
997 | self.logError(_("A partial file named %s may have been written to the sd card.") % targetname) | |
998 | ||
999 | def complete_upload(self, text, line, begidx, endidx): | |
1000 | s = line.split() | |
1001 | if len(s) > 2: | |
1002 | return [] | |
1003 | if (len(s) == 1 and line[-1] == " ") or (len(s) == 2 and line[-1] != " "): | |
1004 | if len(s) > 1: | |
1005 | return [i[len(s[1]) - len(text):] for i in glob.glob(s[1] + "*/") + glob.glob(s[1] + "*.g*")] | |
1006 | else: | |
1007 | return glob.glob("*/") + glob.glob("*.g*") | |
1008 | ||
1009 | def help_upload(self): | |
1010 | self.log("Uploads a gcode file to the sd card") | |
1011 | ||
1012 | def help_print(self): | |
1013 | if not self.fgcode: | |
1014 | self.log(_("Send a loaded gcode file to the printer. Load a file with the load command first.")) | |
1015 | else: | |
1016 | self.log(_("Send a loaded gcode file to the printer. You have %s loaded right now.") % self.filename) | |
1017 | ||
1018 | def do_print(self, l): | |
1019 | if not self.fgcode: | |
1020 | self.logError(_("No file loaded. Please use load first.")) | |
1021 | return | |
1022 | if not self.p.online: | |
1023 | self.logError(_("Not connected to printer.")) | |
1024 | return | |
1025 | self.log(_("Printing %s") % self.filename) | |
1026 | self.log(_("You can monitor the print with the monitor command.")) | |
1027 | self.sdprinting = False | |
1028 | self.p.startprint(self.fgcode) | |
1029 | ||
1030 | def do_pause(self, l): | |
1031 | if self.sdprinting: | |
1032 | self.p.send_now("M25") | |
1033 | else: | |
1034 | if not self.p.printing: | |
1035 | self.logError(_("Not printing, cannot pause.")) | |
1036 | return | |
1037 | self.p.pause() | |
1038 | self.paused = True | |
1039 | ||
1040 | def help_pause(self): | |
1041 | self.log(_("Pauses a running print")) | |
1042 | ||
1043 | def pause(self, event = None): | |
1044 | return self.do_pause(None) | |
1045 | ||
1046 | def do_resume(self, l): | |
1047 | if not self.paused: | |
1048 | self.logError(_("Not paused, unable to resume. Start a print first.")) | |
1049 | return | |
1050 | self.paused = False | |
1051 | if self.sdprinting: | |
1052 | self.p.send_now("M24") | |
1053 | return | |
1054 | else: | |
1055 | self.p.resume() | |
1056 | ||
1057 | def help_resume(self): | |
1058 | self.log(_("Resumes a paused print.")) | |
1059 | ||
1060 | def listfiles(self, line): | |
1061 | if "Begin file list" in line: | |
1062 | self.sdlisting = 1 | |
1063 | elif "End file list" in line: | |
1064 | self.sdlisting = 0 | |
1065 | self.recvlisteners.remove(self.listfiles) | |
1066 | if self.sdlisting_echo: | |
1067 | self.log(_("Files on SD card:")) | |
1068 | self.log("\n".join(self.sdfiles)) | |
1069 | elif self.sdlisting: | |
1070 | self.sdfiles.append(line.strip().lower()) | |
1071 | ||
1072 | def _do_ls(self, echo): | |
1073 | # FIXME: this was 2, but I think it should rather be 0 as in do_upload | |
1074 | self.sdlisting = 0 | |
1075 | self.sdlisting_echo = echo | |
1076 | self.sdfiles = [] | |
1077 | self.recvlisteners.append(self.listfiles) | |
1078 | self.p.send_now("M20") | |
1079 | ||
1080 | def do_ls(self, l): | |
1081 | if not self.p.online: | |
1082 | self.logError(_("Printer is not online. Please connect to it first.")) | |
1083 | return | |
1084 | self._do_ls(True) | |
1085 | ||
1086 | def help_ls(self): | |
1087 | self.log(_("Lists files on the SD card")) | |
1088 | ||
1089 | def waitforsdresponse(self, l): | |
1090 | if "file.open failed" in l: | |
1091 | self.logError(_("Opening file failed.")) | |
1092 | self.recvlisteners.remove(self.waitforsdresponse) | |
1093 | return | |
1094 | if "File opened" in l: | |
1095 | self.log(l) | |
1096 | if "File selected" in l: | |
1097 | self.log(_("Starting print")) | |
1098 | self.p.send_now("M24") | |
1099 | self.sdprinting = True | |
1100 | # self.recvlisteners.remove(self.waitforsdresponse) | |
1101 | return | |
1102 | if "Done printing file" in l: | |
1103 | self.log(l) | |
1104 | self.sdprinting = False | |
1105 | self.recvlisteners.remove(self.waitforsdresponse) | |
1106 | return | |
1107 | if "SD printing byte" in l: | |
1108 | # M27 handler | |
1109 | try: | |
1110 | resp = l.split() | |
1111 | vals = resp[-1].split("/") | |
1112 | self.percentdone = 100.0 * int(vals[0]) / int(vals[1]) | |
1113 | except: | |
1114 | pass | |
1115 | ||
1116 | def do_reset(self, l): | |
1117 | self.p.reset() | |
1118 | ||
1119 | def help_reset(self): | |
1120 | self.log(_("Resets the printer.")) | |
1121 | ||
1122 | def do_sdprint(self, l): | |
1123 | if not self.p.online: | |
1124 | self.log(_("Printer is not online. Please connect to it first.")) | |
1125 | return | |
1126 | self._do_ls(False) | |
1127 | while self.listfiles in self.recvlisteners: | |
1128 | time.sleep(0.1) | |
1129 | if l.lower() not in self.sdfiles: | |
1130 | self.log(_("File is not present on card. Please upload it first.")) | |
1131 | return | |
1132 | self.recvlisteners.append(self.waitforsdresponse) | |
1133 | self.p.send_now("M23 " + l.lower()) | |
1134 | self.log(_("Printing file: %s from SD card.") % l.lower()) | |
1135 | self.log(_("Requesting SD print...")) | |
1136 | time.sleep(1) | |
1137 | ||
1138 | def help_sdprint(self): | |
1139 | self.log(_("Print a file from the SD card. Tab completes with available file names.")) | |
1140 | self.log(_("sdprint filename.g")) | |
1141 | ||
1142 | def complete_sdprint(self, text, line, begidx, endidx): | |
1143 | if not self.sdfiles and self.p.online: | |
1144 | self._do_ls(False) | |
1145 | while self.listfiles in self.recvlisteners: | |
1146 | time.sleep(0.1) | |
1147 | if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1] == " "): | |
1148 | return [i for i in self.sdfiles if i.startswith(text)] | |
1149 | ||
1150 | # -------------------------------------------------------------- | |
1151 | # Printcore callbacks | |
1152 | # -------------------------------------------------------------- | |
1153 | ||
1154 | def startcb(self, resuming = False): | |
1155 | self.starttime = time.time() | |
1156 | if resuming: | |
1157 | self.log(_("Print resumed at: %s") % format_time(self.starttime)) | |
1158 | else: | |
1159 | self.log(_("Print started at: %s") % format_time(self.starttime)) | |
1160 | if not self.sdprinting: | |
1161 | self.compute_eta = RemainingTimeEstimator(self.fgcode) | |
1162 | else: | |
1163 | self.compute_eta = None | |
1164 | ||
1165 | if self.settings.start_command: | |
1166 | output = get_command_output(self.settings.start_command, | |
1167 | {"$s": str(self.filename), | |
1168 | "$t": format_time(time.time())}) | |
1169 | if output: | |
1170 | self.log("Start command output:") | |
1171 | self.log(output.rstrip()) | |
1172 | try: | |
1173 | powerset_print_start(reason = "Preventing sleep during print") | |
1174 | except: | |
1175 | self.logError(_("Failed to set power settings:") | |
1176 | + "\n" + traceback.format_exc()) | |
1177 | ||
1178 | def endcb(self): | |
1179 | try: | |
1180 | powerset_print_stop() | |
1181 | except: | |
1182 | self.logError(_("Failed to set power settings:") | |
1183 | + "\n" + traceback.format_exc()) | |
1184 | if self.p.queueindex == 0: | |
1185 | print_duration = int(time.time() - self.starttime + self.extra_print_time) | |
1186 | self.log(_("Print ended at: %(end_time)s and took %(duration)s") % {"end_time": format_time(time.time()), | |
1187 | "duration": format_duration(print_duration)}) | |
1188 | ||
1189 | # Update total filament length used | |
1190 | if self.fgcode is not None: | |
1191 | new_total = self.settings.total_filament_used + self.fgcode.filament_length | |
1192 | self.set("total_filament_used", new_total) | |
1193 | ||
1194 | if not self.settings.final_command: | |
1195 | return | |
1196 | output = get_command_output(self.settings.final_command, | |
1197 | {"$s": str(self.filename), | |
1198 | "$t": format_duration(print_duration)}) | |
1199 | if output: | |
1200 | self.log("Final command output:") | |
1201 | self.log(output.rstrip()) | |
1202 | ||
1203 | def recvcb_report(self, l): | |
1204 | isreport = REPORT_NONE | |
1205 | if "ok C:" in l or "Count" in l \ | |
1206 | or ("X:" in l and len(gcoder.m114_exp.findall(l)) == 6): | |
1207 | self.posreport = l | |
1208 | isreport = REPORT_POS | |
1209 | if self.userm114 > 0: | |
1210 | self.userm114 -= 1 | |
1211 | isreport |= REPORT_MANUAL | |
1212 | if "ok T:" in l or tempreading_exp.findall(l): | |
1213 | self.tempreadings = l | |
1214 | isreport = REPORT_TEMP | |
1215 | if self.userm105 > 0: | |
1216 | self.userm105 -= 1 | |
1217 | isreport |= REPORT_MANUAL | |
1218 | else: | |
1219 | self.m105_waitcycles = 0 | |
1220 | return isreport | |
1221 | ||
1222 | def recvcb_actions(self, l): | |
1223 | if l.startswith("!!"): | |
1224 | self.do_pause(None) | |
1225 | msg = l.split(" ", 1) | |
1226 | if len(msg) > 1 and self.silent is False: self.logError(msg[1].ljust(15)) | |
1227 | sys.stdout.write(self.promptf()) | |
1228 | sys.stdout.flush() | |
1229 | return True | |
1230 | elif l.startswith("//"): | |
1231 | command = l.split(" ", 1) | |
1232 | if len(command) > 1: | |
1233 | command = command[1] | |
1234 | self.log(_("Received command %s") % command) | |
1235 | command = command.split(":") | |
1236 | if len(command) == 2 and command[0] == "action": | |
1237 | command = command[1] | |
1238 | if command == "pause": | |
1239 | self.do_pause(None) | |
1240 | sys.stdout.write(self.promptf()) | |
1241 | sys.stdout.flush() | |
1242 | return True | |
1243 | elif command == "resume": | |
1244 | self.do_resume(None) | |
1245 | sys.stdout.write(self.promptf()) | |
1246 | sys.stdout.flush() | |
1247 | return True | |
1248 | elif command == "disconnect": | |
1249 | self.do_disconnect(None) | |
1250 | sys.stdout.write(self.promptf()) | |
1251 | sys.stdout.flush() | |
1252 | return True | |
1253 | return False | |
1254 | ||
1255 | def recvcb(self, l): | |
1256 | l = l.rstrip() | |
1257 | for listener in self.recvlisteners: | |
1258 | listener(l) | |
1259 | if not self.recvcb_actions(l): | |
1260 | report_type = self.recvcb_report(l) | |
1261 | if report_type & REPORT_TEMP: | |
1262 | self.status.update_tempreading(l) | |
1263 | if l != "ok" and not self.sdlisting \ | |
1264 | and not self.monitoring and (report_type == REPORT_NONE or report_type & REPORT_MANUAL): | |
1265 | if l[:5] == "echo:": | |
1266 | l = l[5:].lstrip() | |
1267 | if self.silent is False: self.log("\r" + l.ljust(15)) | |
1268 | sys.stdout.write(self.promptf()) | |
1269 | sys.stdout.flush() | |
1270 | ||
1271 | def layer_change_cb(self, newlayer): | |
1272 | layerz = self.fgcode.all_layers[newlayer].z | |
1273 | if layerz is not None: | |
1274 | self.curlayer = layerz | |
1275 | if self.compute_eta: | |
1276 | secondselapsed = int(time.time() - self.starttime + self.extra_print_time) | |
1277 | self.compute_eta.update_layer(newlayer, secondselapsed) | |
1278 | ||
1279 | def get_eta(self): | |
1280 | if self.sdprinting or self.uploading: | |
1281 | if self.uploading: | |
1282 | fractioncomplete = float(self.p.queueindex) / len(self.p.mainqueue) | |
1283 | else: | |
1284 | fractioncomplete = float(self.percentdone / 100.0) | |
1285 | secondselapsed = int(time.time() - self.starttime + self.extra_print_time) | |
1286 | # Prevent division by zero | |
1287 | secondsestimate = secondselapsed / max(fractioncomplete, 0.000001) | |
1288 | secondsremain = secondsestimate - secondselapsed | |
1289 | progress = fractioncomplete | |
1290 | elif self.compute_eta is not None: | |
1291 | secondselapsed = int(time.time() - self.starttime + self.extra_print_time) | |
1292 | secondsremain, secondsestimate = self.compute_eta(self.p.queueindex, secondselapsed) | |
1293 | progress = self.p.queueindex | |
1294 | else: | |
1295 | secondsremain, secondsestimate, progress = 1, 1, 0 | |
1296 | return secondsremain, secondsestimate, progress | |
1297 | ||
1298 | def do_eta(self, l): | |
1299 | if not self.p.printing: | |
1300 | self.logError(_("Printer is not currently printing. No ETA available.")) | |
1301 | else: | |
1302 | secondsremain, secondsestimate, progress = self.get_eta() | |
1303 | eta = _("Est: %s of %s remaining") % (format_duration(secondsremain), | |
1304 | format_duration(secondsestimate)) | |
1305 | self.log(eta.strip()) | |
1306 | ||
1307 | def help_eta(self): | |
1308 | self.log(_("Displays estimated remaining print time.")) | |
1309 | ||
1310 | # -------------------------------------------------------------- | |
1311 | # Temperature handling | |
1312 | # -------------------------------------------------------------- | |
1313 | ||
1314 | def set_temp_preset(self, key, value): | |
1315 | if not key.startswith("bed"): | |
1316 | self.temps["pla"] = str(self.settings.temperature_pla) | |
1317 | self.temps["abs"] = str(self.settings.temperature_abs) | |
1318 | self.log("Hotend temperature presets updated, pla:%s, abs:%s" % (self.temps["pla"], self.temps["abs"])) | |
1319 | else: | |
1320 | self.bedtemps["pla"] = str(self.settings.bedtemp_pla) | |
1321 | self.bedtemps["abs"] = str(self.settings.bedtemp_abs) | |
1322 | self.log("Bed temperature presets updated, pla:%s, abs:%s" % (self.bedtemps["pla"], self.bedtemps["abs"])) | |
1323 | ||
1324 | def tempcb(self, l): | |
1325 | if "T:" in l: | |
1326 | self.log(l.strip().replace("T", "Hotend").replace("B", "Bed").replace("ok ", "")) | |
1327 | ||
1328 | def do_gettemp(self, l): | |
1329 | if "dynamic" in l: | |
1330 | self.dynamic_temp = True | |
1331 | if self.p.online: | |
1332 | self.p.send_now("M105") | |
1333 | time.sleep(0.75) | |
1334 | if not self.status.bed_enabled: | |
1335 | self.log(_("Hotend: %s/%s") % (self.status.extruder_temp, self.status.extruder_temp_target)) | |
1336 | else: | |
1337 | self.log(_("Hotend: %s/%s") % (self.status.extruder_temp, self.status.extruder_temp_target)) | |
1338 | self.log(_("Bed: %s/%s") % (self.status.bed_temp, self.status.bed_temp_target)) | |
1339 | ||
1340 | def help_gettemp(self): | |
1341 | self.log(_("Read the extruder and bed temperature.")) | |
1342 | ||
1343 | def do_settemp(self, l): | |
1344 | l = l.lower().replace(", ", ".") | |
1345 | for i in self.temps.keys(): | |
1346 | l = l.replace(i, self.temps[i]) | |
1347 | try: | |
1348 | f = float(l) | |
1349 | except: | |
1350 | self.logError(_("You must enter a temperature.")) | |
1351 | return | |
1352 | ||
1353 | if f >= 0: | |
1354 | if f > 250: | |
1355 | self.log(_("%s is a high temperature to set your extruder to. Are you sure you want to do that?") % f) | |
1356 | if not self.confirm(): | |
1357 | return | |
1358 | if self.p.online: | |
1359 | self.p.send_now("M104 S" + l) | |
1360 | self.log(_("Setting hotend temperature to %s degrees Celsius.") % f) | |
1361 | else: | |
1362 | self.logError(_("Printer is not online.")) | |
1363 | else: | |
1364 | self.logError(_("You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0.")) | |
1365 | ||
1366 | def help_settemp(self): | |
1367 | self.log(_("Sets the hotend temperature to the value entered.")) | |
1368 | self.log(_("Enter either a temperature in celsius or one of the following keywords")) | |
1369 | self.log(", ".join([i + "(" + self.temps[i] + ")" for i in self.temps.keys()])) | |
1370 | ||
1371 | def complete_settemp(self, text, line, begidx, endidx): | |
1372 | if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1] == " "): | |
1373 | return [i for i in self.temps.keys() if i.startswith(text)] | |
1374 | ||
1375 | def do_bedtemp(self, l): | |
1376 | f = None | |
1377 | try: | |
1378 | l = l.lower().replace(", ", ".") | |
1379 | for i in self.bedtemps.keys(): | |
1380 | l = l.replace(i, self.bedtemps[i]) | |
1381 | f = float(l) | |
1382 | except: | |
1383 | self.logError(_("You must enter a temperature.")) | |
1384 | if f is not None and f >= 0: | |
1385 | if self.p.online: | |
1386 | self.p.send_now("M140 S" + l) | |
1387 | self.log(_("Setting bed temperature to %s degrees Celsius.") % f) | |
1388 | else: | |
1389 | self.logError(_("Printer is not online.")) | |
1390 | else: | |
1391 | self.logError(_("You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0.")) | |
1392 | ||
1393 | def help_bedtemp(self): | |
1394 | self.log(_("Sets the bed temperature to the value entered.")) | |
1395 | self.log(_("Enter either a temperature in celsius or one of the following keywords")) | |
1396 | self.log(", ".join([i + "(" + self.bedtemps[i] + ")" for i in self.bedtemps.keys()])) | |
1397 | ||
1398 | def complete_bedtemp(self, text, line, begidx, endidx): | |
1399 | if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1] == " "): | |
1400 | return [i for i in self.bedtemps.keys() if i.startswith(text)] | |
1401 | ||
1402 | def do_monitor(self, l): | |
1403 | interval = 5 | |
1404 | if not self.p.online: | |
1405 | self.logError(_("Printer is not online. Please connect to it first.")) | |
1406 | return | |
1407 | if not (self.p.printing or self.sdprinting): | |
1408 | self.logError(_("Printer is not printing. Please print something before monitoring.")) | |
1409 | return | |
1410 | self.log(_("Monitoring printer, use ^C to interrupt.")) | |
1411 | if len(l): | |
1412 | try: | |
1413 | interval = float(l) | |
1414 | except: | |
1415 | self.logError(_("Invalid period given.")) | |
1416 | self.log(_("Updating values every %f seconds.") % (interval,)) | |
1417 | self.monitoring = 1 | |
1418 | prev_msg_len = 0 | |
1419 | try: | |
1420 | while True: | |
1421 | self.p.send_now("M105") | |
1422 | if self.sdprinting: | |
1423 | self.p.send_now("M27") | |
1424 | time.sleep(interval) | |
1425 | if self.p.printing: | |
1426 | preface = _("Print progress: ") | |
1427 | progress = 100 * float(self.p.queueindex) / len(self.p.mainqueue) | |
1428 | elif self.sdprinting: | |
1429 | preface = _("SD print progress: ") | |
1430 | progress = self.percentdone | |
1431 | prev_msg = preface + "%.1f%%" % progress | |
1432 | if self.silent is False: | |
1433 | sys.stdout.write("\r" + prev_msg.ljust(prev_msg_len)) | |
1434 | sys.stdout.flush() | |
1435 | prev_msg_len = len(prev_msg) | |
1436 | except KeyboardInterrupt: | |
1437 | if self.silent is False: self.log(_("Done monitoring.")) | |
1438 | self.monitoring = 0 | |
1439 | ||
1440 | def help_monitor(self): | |
1441 | self.log(_("Monitor a machine's temperatures and an SD print's status.")) | |
1442 | self.log(_("monitor - Reports temperature and SD print status (if SD printing) every 5 seconds")) | |
1443 | self.log(_("monitor 2 - Reports temperature and SD print status (if SD printing) every 2 seconds")) | |
1444 | ||
1445 | # -------------------------------------------------------------- | |
1446 | # Manual printer controls | |
1447 | # -------------------------------------------------------------- | |
1448 | ||
1449 | def do_tool(self, l): | |
1450 | tool = None | |
1451 | try: | |
1452 | tool = int(l.lower().strip()) | |
1453 | except: | |
1454 | self.logError(_("You must specify the tool index as an integer.")) | |
1455 | if tool is not None and tool >= 0: | |
1456 | if self.p.online: | |
1457 | self.p.send_now("T%d" % tool) | |
1458 | self.log(_("Using tool %d.") % tool) | |
1459 | else: | |
1460 | self.logError(_("Printer is not online.")) | |
1461 | else: | |
1462 | self.logError(_("You cannot set negative tool numbers.")) | |
1463 | ||
1464 | def help_tool(self): | |
1465 | self.log(_("Switches to the specified tool (e.g. doing tool 1 will emit a T1 G-Code).")) | |
1466 | ||
1467 | def do_move(self, l): | |
1468 | if len(l.split()) < 2: | |
1469 | self.logError(_("No move specified.")) | |
1470 | return | |
1471 | if self.p.printing: | |
1472 | self.logError(_("Printer is currently printing. Please pause the print before you issue manual commands.")) | |
1473 | return | |
1474 | if not self.p.online: | |
1475 | self.logError(_("Printer is not online. Unable to move.")) | |
1476 | return | |
1477 | l = l.split() | |
1478 | if l[0].lower() == "x": | |
1479 | feed = self.settings.xy_feedrate | |
1480 | axis = "X" | |
1481 | elif l[0].lower() == "y": | |
1482 | feed = self.settings.xy_feedrate | |
1483 | axis = "Y" | |
1484 | elif l[0].lower() == "z": | |
1485 | feed = self.settings.z_feedrate | |
1486 | axis = "Z" | |
1487 | elif l[0].lower() == "e": | |
1488 | feed = self.settings.e_feedrate | |
1489 | axis = "E" | |
1490 | else: | |
1491 | self.logError(_("Unknown axis.")) | |
1492 | return | |
1493 | try: | |
1494 | float(l[1]) # check if distance can be a float | |
1495 | except: | |
1496 | self.logError(_("Invalid distance")) | |
1497 | return | |
1498 | try: | |
1499 | feed = int(l[2]) | |
1500 | except: | |
1501 | pass | |
1502 | self.p.send_now("G91") | |
1503 | self.p.send_now("G0 " + axis + str(l[1]) + " F" + str(feed)) | |
1504 | self.p.send_now("G90") | |
1505 | ||
1506 | def help_move(self): | |
1507 | self.log(_("Move an axis. Specify the name of the axis and the amount. ")) | |
1508 | self.log(_("move X 10 will move the X axis forward by 10mm at %s mm/min (default XY speed)") % self.settings.xy_feedrate) | |
1509 | self.log(_("move Y 10 5000 will move the Y axis forward by 10mm at 5000mm/min")) | |
1510 | self.log(_("move Z -1 will move the Z axis down by 1mm at %s mm/min (default Z speed)") % self.settings.z_feedrate) | |
1511 | self.log(_("Common amounts are in the tabcomplete list.")) | |
1512 | ||
1513 | def complete_move(self, text, line, begidx, endidx): | |
1514 | if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1] == " "): | |
1515 | return [i for i in ["X ", "Y ", "Z ", "E "] if i.lower().startswith(text)] | |
1516 | elif len(line.split()) == 3 or (len(line.split()) == 2 and line[-1] == " "): | |
1517 | base = line.split()[-1] | |
1518 | rlen = 0 | |
1519 | if base.startswith("-"): | |
1520 | rlen = 1 | |
1521 | if line[-1] == " ": | |
1522 | base = "" | |
1523 | return [i[rlen:] for i in ["-100", "-10", "-1", "-0.1", "100", "10", "1", "0.1", "-50", "-5", "-0.5", "50", "5", "0.5", "-200", "-20", "-2", "-0.2", "200", "20", "2", "0.2"] if i.startswith(base)] | |
1524 | else: | |
1525 | return [] | |
1526 | ||
1527 | def do_extrude(self, l, override = None, overridefeed = 300): | |
1528 | length = self.settings.default_extrusion # default extrusion length | |
1529 | feed = self.settings.e_feedrate # default speed | |
1530 | if not self.p.online: | |
1531 | self.logError("Printer is not online. Unable to extrude.") | |
1532 | return | |
1533 | if self.p.printing: | |
1534 | self.logError(_("Printer is currently printing. Please pause the print before you issue manual commands.")) | |
1535 | return | |
1536 | ls = l.split() | |
1537 | if len(ls): | |
1538 | try: | |
1539 | length = float(ls[0]) | |
1540 | except: | |
1541 | self.logError(_("Invalid length given.")) | |
1542 | if len(ls) > 1: | |
1543 | try: | |
1544 | feed = int(ls[1]) | |
1545 | except: | |
1546 | self.logError(_("Invalid speed given.")) | |
1547 | if override is not None: | |
1548 | length = override | |
1549 | feed = overridefeed | |
1550 | self.do_extrude_final(length, feed) | |
1551 | ||
1552 | def do_extrude_final(self, length, feed): | |
1553 | if length > 0: | |
1554 | self.log(_("Extruding %fmm of filament.") % (length,)) | |
1555 | elif length < 0: | |
1556 | self.log(_("Reversing %fmm of filament.") % (-length,)) | |
1557 | else: | |
1558 | self.log(_("Length is 0, not doing anything.")) | |
1559 | self.p.send_now("G91") | |
1560 | self.p.send_now("G1 E" + str(length) + " F" + str(feed)) | |
1561 | self.p.send_now("G90") | |
1562 | ||
1563 | def help_extrude(self): | |
1564 | self.log(_("Extrudes a length of filament, 5mm by default, or the number of mm given as a parameter")) | |
1565 | self.log(_("extrude - extrudes 5mm of filament at 300mm/min (5mm/s)")) | |
1566 | self.log(_("extrude 20 - extrudes 20mm of filament at 300mm/min (5mm/s)")) | |
1567 | self.log(_("extrude -5 - REVERSES 5mm of filament at 300mm/min (5mm/s)")) | |
1568 | self.log(_("extrude 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)")) | |
1569 | ||
1570 | def do_reverse(self, l): | |
1571 | length = self.settings.default_extrusion # default extrusion length | |
1572 | feed = self.settings.e_feedrate # default speed | |
1573 | if not self.p.online: | |
1574 | self.logError(_("Printer is not online. Unable to reverse.")) | |
1575 | return | |
1576 | if self.p.printing: | |
1577 | self.logError(_("Printer is currently printing. Please pause the print before you issue manual commands.")) | |
1578 | return | |
1579 | ls = l.split() | |
1580 | if len(ls): | |
1581 | try: | |
1582 | length = float(ls[0]) | |
1583 | except: | |
1584 | self.logError(_("Invalid length given.")) | |
1585 | if len(ls) > 1: | |
1586 | try: | |
1587 | feed = int(ls[1]) | |
1588 | except: | |
1589 | self.logError(_("Invalid speed given.")) | |
1590 | self.do_extrude("", -length, feed) | |
1591 | ||
1592 | def help_reverse(self): | |
1593 | self.log(_("Reverses the extruder, 5mm by default, or the number of mm given as a parameter")) | |
1594 | self.log(_("reverse - reverses 5mm of filament at 300mm/min (5mm/s)")) | |
1595 | self.log(_("reverse 20 - reverses 20mm of filament at 300mm/min (5mm/s)")) | |
1596 | self.log(_("reverse 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)")) | |
1597 | self.log(_("reverse -5 - EXTRUDES 5mm of filament at 300mm/min (5mm/s)")) | |
1598 | ||
1599 | def do_home(self, l): | |
1600 | if not self.p.online: | |
1601 | self.logError(_("Printer is not online. Unable to move.")) | |
1602 | return | |
1603 | if self.p.printing: | |
1604 | self.logError(_("Printer is currently printing. Please pause the print before you issue manual commands.")) | |
1605 | return | |
1606 | if "x" in l.lower(): | |
1607 | self.p.send_now("G28 X0") | |
1608 | if "y" in l.lower(): | |
1609 | self.p.send_now("G28 Y0") | |
1610 | if "z" in l.lower(): | |
1611 | self.p.send_now("G28 Z0") | |
1612 | if "e" in l.lower(): | |
1613 | self.p.send_now("G92 E0") | |
1614 | if not len(l): | |
1615 | self.p.send_now("G28") | |
1616 | self.p.send_now("G92 E0") | |
1617 | ||
1618 | def help_home(self): | |
1619 | self.log(_("Homes the printer")) | |
1620 | self.log(_("home - homes all axes and zeroes the extruder(Using G28 and G92)")) | |
1621 | self.log(_("home xy - homes x and y axes (Using G28)")) | |
1622 | self.log(_("home z - homes z axis only (Using G28)")) | |
1623 | self.log(_("home e - set extruder position to zero (Using G92)")) | |
1624 | self.log(_("home xyze - homes all axes and zeroes the extruder (Using G28 and G92)")) | |
1625 | ||
1626 | def do_off(self, l): | |
1627 | self.off() | |
1628 | ||
1629 | def off(self, ignore = None): | |
1630 | if self.p.online: | |
1631 | if self.p.printing: self.pause(None) | |
1632 | self.log(_("; Motors off")) | |
1633 | self.onecmd("M84") | |
1634 | self.log(_("; Extruder off")) | |
1635 | self.onecmd("M104 S0") | |
1636 | self.log(_("; Heatbed off")) | |
1637 | self.onecmd("M140 S0") | |
1638 | self.log(_("; Fan off")) | |
1639 | self.onecmd("M107") | |
1640 | self.log(_("; Power supply off")) | |
1641 | self.onecmd("M81") | |
1642 | else: | |
1643 | self.logError(_("Printer is not online. Unable to turn it off.")) | |
1644 | ||
1645 | def help_off(self): | |
1646 | self.log(_("Turns off everything on the printer")) | |
1647 | ||
1648 | # -------------------------------------------------------------- | |
1649 | # Host commands handling | |
1650 | # -------------------------------------------------------------- | |
1651 | ||
1652 | def process_host_command(self, command): | |
1653 | """Override host command handling""" | |
1654 | command = command.lstrip() | |
1655 | if command.startswith(";@"): | |
1656 | command = command[2:] | |
1657 | self.log(_("G-Code calling host command \"%s\"") % command) | |
1658 | self.onecmd(command) | |
1659 | ||
1660 | def do_run_script(self, l): | |
1661 | p = run_command(l, {"$s": str(self.filename)}, stdout = subprocess.PIPE) | |
1662 | for line in p.stdout.readlines(): | |
1663 | self.log("<< " + line.strip()) | |
1664 | ||
1665 | def help_run_script(self): | |
1666 | self.log(_("Runs a custom script. Current gcode filename can be given using $s token.")) | |
1667 | ||
1668 | def do_run_gcode_script(self, l): | |
1669 | p = run_command(l, {"$s": str(self.filename)}, stdout = subprocess.PIPE) | |
1670 | for line in p.stdout.readlines(): | |
1671 | self.onecmd(line.strip()) | |
1672 | ||
1673 | def help_run_gcode_script(self): | |
1674 | self.log(_("Runs a custom script which output gcode which will in turn be executed. Current gcode filename can be given using $s token.")) |