|
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.")) |