printrun-src/printrun/pronsole.py

changeset 46
cce0af6351f0
parent 39
74801c0f2709
equal deleted inserted replaced
45:c82943fb205f 46:cce0af6351f0
1 #!/usr/bin/env python
2
3 # This file is part of the Printrun suite. 1 # This file is part of the Printrun suite.
4 # 2 #
5 # Printrun is free software: you can redistribute it and/or modify 3 # 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 4 # 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 5 # the Free Software Foundation, either version 3 of the License, or
16 # along with Printrun. If not, see <http://www.gnu.org/licenses/>. 14 # along with Printrun. If not, see <http://www.gnu.org/licenses/>.
17 15
18 import cmd 16 import cmd
19 import glob 17 import glob
20 import os 18 import os
19 import platform
21 import time 20 import time
22 import threading 21 import threading
23 import sys 22 import sys
24 import shutil 23 import shutil
25 import subprocess 24 import subprocess
28 import locale 27 import locale
29 import logging 28 import logging
30 import traceback 29 import traceback
31 import re 30 import re
32 31
32 from appdirs import user_cache_dir, user_config_dir, user_data_dir
33 from serial import SerialException 33 from serial import SerialException
34 34
35 from . import printcore 35 from . import printcore
36 from .utils import install_locale, run_command, get_command_output, \ 36 from .utils import install_locale, run_command, get_command_output, \
37 format_time, format_duration, RemainingTimeEstimator, \ 37 format_time, format_duration, RemainingTimeEstimator, \
40 install_locale('pronterface') 40 install_locale('pronterface')
41 from .settings import Settings, BuildDimensionsSetting 41 from .settings import Settings, BuildDimensionsSetting
42 from .power import powerset_print_start, powerset_print_stop 42 from .power import powerset_print_start, powerset_print_stop
43 from printrun import gcoder 43 from printrun import gcoder
44 from .rpc import ProntRPC 44 from .rpc import ProntRPC
45 from printrun.spoolmanager import spoolmanager
45 46
46 if os.name == "nt": 47 if os.name == "nt":
47 try: 48 try:
48 import _winreg 49 import winreg
49 except: 50 except:
50 pass 51 pass
51 READLINE = True 52 READLINE = True
52 try: 53 try:
53 import readline 54 import readline
62 63
63 REPORT_NONE = 0 64 REPORT_NONE = 0
64 REPORT_POS = 1 65 REPORT_POS = 1
65 REPORT_TEMP = 2 66 REPORT_TEMP = 2
66 REPORT_MANUAL = 4 67 REPORT_MANUAL = 4
67 68 DEG = "\N{DEGREE SIGN}"
68 class Status(object): 69
70 class Status:
69 71
70 def __init__(self): 72 def __init__(self):
71 self.extruder_temp = 0 73 self.extruder_temp = 0
72 self.extruder_temp_target = 0 74 self.extruder_temp_target = 0
73 self.bed_temp = 0 75 self.bed_temp = 0
100 102
101 @property 103 @property
102 def extruder_enabled(self): 104 def extruder_enabled(self):
103 return self.extruder_temp != 0 105 return self.extruder_temp != 0
104 106
107 class RGSGCoder():
108 """Bare alternative to gcoder.LightGCode which does not preload all lines in memory,
109 but still allows run_gcode_script (hence the RGS) to be processed by do_print (checksum,threading,ok waiting)"""
110 def __init__(self, line):
111 self.lines = True
112 self.filament_length = 0.
113 self.filament_length_multi = [0]
114 self.proc = run_command(line, {"$s": 'str(self.filename)'}, stdout = subprocess.PIPE, universal_newlines = True)
115 lr = gcoder.Layer([])
116 lr.duration = 0.
117 self.all_layers = [lr]
118 self.read() #empty layer causes division by zero during progress calculation
119 def read(self):
120 ln = self.proc.stdout.readline()
121 if not ln:
122 self.proc.stdout.close()
123 return None
124 ln = ln.strip()
125 if not ln:
126 return None
127 pyLn = gcoder.PyLightLine(ln)
128 self.all_layers[0].append(pyLn)
129 return pyLn
130 def has_index(self, i):
131 while i >= len(self.all_layers[0]) and not self.proc.stdout.closed:
132 self.read()
133 return i < len(self.all_layers[0])
134 def __len__(self):
135 return len(self.all_layers[0])
136 def idxs(self, i):
137 return 0, i #layer, line
105 138
106 class pronsole(cmd.Cmd): 139 class pronsole(cmd.Cmd):
107 def __init__(self): 140 def __init__(self):
108 cmd.Cmd.__init__(self) 141 cmd.Cmd.__init__(self)
109 if not READLINE: 142 if not READLINE:
141 self.tempreadings = "" 174 self.tempreadings = ""
142 self.userm114 = 0 175 self.userm114 = 0
143 self.userm105 = 0 176 self.userm105 = 0
144 self.m105_waitcycles = 0 177 self.m105_waitcycles = 0
145 self.macros = {} 178 self.macros = {}
146 self.history_file = "~/.pronsole-history"
147 self.rc_loaded = False 179 self.rc_loaded = False
148 self.processing_rc = False 180 self.processing_rc = False
149 self.processing_args = False 181 self.processing_args = False
150 self.settings = Settings(self) 182 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) 183 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 184 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) 185 self.update_build_dimensions(None, self.settings.build_dimensions)
158 self.update_tcp_streaming_mode(None, self.settings.tcp_streaming_mode) 186 self.update_tcp_streaming_mode(None, self.settings.tcp_streaming_mode)
159 self.monitoring = 0 187 self.monitoring = 0
160 self.starttime = 0 188 self.starttime = 0
161 self.extra_print_time = 0 189 self.extra_print_time = 0
162 self.silent = False 190 self.silent = False
163 self.commandprefixes = 'MGT$' 191 self.commandprefixes = 'MGTD$'
164 self.promptstrs = {"offline": "%(bold)soffline>%(normal)s ", 192 self.promptstrs = {"offline": "%(bold)soffline>%(normal)s ",
165 "fallback": "%(bold)sPC>%(normal)s ", 193 "fallback": "%(bold)s%(red)s%(port)s%(white)s PC>%(normal)s ",
166 "macro": "%(bold)s..>%(normal)s ", 194 "macro": "%(bold)s..>%(normal)s ",
167 "online": "%(bold)sT:%(extruder_temp_fancy)s%(progress_fancy)s>%(normal)s "} 195 "online": "%(bold)s%(green)s%(port)s%(white)s %(extruder_temp_fancy)s%(progress_fancy)s>%(normal)s "}
196 self.spool_manager = spoolmanager.SpoolManager(self)
197 self.current_tool = 0 # Keep track of the extruder being used
198 self.cache_dir = os.path.join(user_cache_dir("Printrun"))
199 self.history_file = os.path.join(self.cache_dir,"history")
200 self.config_dir = os.path.join(user_config_dir("Printrun"))
201 self.data_dir = os.path.join(user_data_dir("Printrun"))
202 self.lineignorepattern=re.compile("ok ?\d*$|.*busy: ?processing|.*busy: ?heating|.*Active Extruder: ?\d*$")
168 203
169 # -------------------------------------------------------------- 204 # --------------------------------------------------------------
170 # General console handling 205 # General console handling
171 # -------------------------------------------------------------- 206 # --------------------------------------------------------------
172 207
194 try: 229 try:
195 import readline 230 import readline
196 self.old_completer = readline.get_completer() 231 self.old_completer = readline.get_completer()
197 readline.set_completer(self.complete) 232 readline.set_completer(self.complete)
198 readline.parse_and_bind(self.completekey + ": complete") 233 readline.parse_and_bind(self.completekey + ": complete")
199 history = os.path.expanduser(self.history_file) 234 history = (self.history_file)
235 if not os.path.exists(history):
236 if not os.path.exists(self.cache_dir):
237 os.makedirs(self.cache_dir)
238 history = os.path.join(self.cache_dir, "history")
200 if os.path.exists(history): 239 if os.path.exists(history):
201 readline.read_history_file(history) 240 readline.read_history_file(history)
202 except ImportError: 241 except ImportError:
203 pass 242 pass
204 try: 243 try:
211 if self.cmdqueue: 250 if self.cmdqueue:
212 line = self.cmdqueue.pop(0) 251 line = self.cmdqueue.pop(0)
213 else: 252 else:
214 if self.use_rawinput: 253 if self.use_rawinput:
215 try: 254 try:
216 line = raw_input(self.prompt) 255 line = input(self.prompt)
217 except EOFError: 256 except EOFError:
218 self.log("") 257 self.log("")
219 self.do_exit("") 258 self.do_exit("")
220 except KeyboardInterrupt: 259 except KeyboardInterrupt:
221 self.log("") 260 self.log("")
235 finally: 274 finally:
236 if self.use_rawinput and self.completekey: 275 if self.use_rawinput and self.completekey:
237 try: 276 try:
238 import readline 277 import readline
239 readline.set_completer(self.old_completer) 278 readline.set_completer(self.old_completer)
240 readline.write_history_file(history) 279 readline.write_history_file(self.history_file)
241 except ImportError: 280 except ImportError:
242 pass 281 pass
243 282
244 def confirm(self): 283 def confirm(self):
245 y_or_n = raw_input("y/n: ") 284 y_or_n = input("y/n: ")
246 if y_or_n == "y": 285 if y_or_n == "y":
247 return True 286 return True
248 elif y_or_n != "n": 287 elif y_or_n != "n":
249 return self.confirm() 288 return self.confirm()
250 return False 289 return False
251 290
252 def log(self, *msg): 291 def log(self, *msg):
253 msg = u"".join(unicode(i) for i in msg) 292 msg = "".join(str(i) for i in msg)
254 logging.info(msg) 293 logging.info(msg)
255 294
256 def logError(self, *msg): 295 def logError(self, *msg):
257 msg = u"".join(unicode(i) for i in msg) 296 msg = "".join(str(i) for i in msg)
258 logging.error(msg) 297 logging.error(msg)
259 if not self.settings.error_command: 298 if not self.settings.error_command:
260 return 299 return
261 output = get_command_output(self.settings.error_command, {"$m": msg}) 300 output = get_command_output(self.settings.error_command, {"$m": msg})
262 if output: 301 if output:
277 return promptstr 316 return promptstr
278 else: 317 else:
279 specials = {} 318 specials = {}
280 specials["extruder_temp"] = str(int(self.status.extruder_temp)) 319 specials["extruder_temp"] = str(int(self.status.extruder_temp))
281 specials["extruder_temp_target"] = str(int(self.status.extruder_temp_target)) 320 specials["extruder_temp_target"] = str(int(self.status.extruder_temp_target))
321 # port: /dev/tty* | netaddress:port
322 specials["port"] = self.settings.port.replace('/dev/', '')
282 if self.status.extruder_temp_target == 0: 323 if self.status.extruder_temp_target == 0:
283 specials["extruder_temp_fancy"] = str(int(self.status.extruder_temp)) 324 specials["extruder_temp_fancy"] = str(int(self.status.extruder_temp)) + DEG
284 else: 325 else:
285 specials["extruder_temp_fancy"] = "%s/%s" % (str(int(self.status.extruder_temp)), str(int(self.status.extruder_temp_target))) 326 specials["extruder_temp_fancy"] = "%s%s/%s%s" % (str(int(self.status.extruder_temp)), DEG, str(int(self.status.extruder_temp_target)), DEG)
286 if self.p.printing: 327 if self.p.printing:
287 progress = int(1000 * float(self.p.queueindex) / len(self.p.mainqueue)) / 10 328 progress = int(1000 * float(self.p.queueindex) / len(self.p.mainqueue)) / 10
288 elif self.sdprinting: 329 elif self.sdprinting:
289 progress = self.percentdone 330 progress = self.percentdone
290 else: 331 else:
292 specials["progress"] = str(progress) 333 specials["progress"] = str(progress)
293 if self.p.printing or self.sdprinting: 334 if self.p.printing or self.sdprinting:
294 specials["progress_fancy"] = " " + str(progress) + "%" 335 specials["progress_fancy"] = " " + str(progress) + "%"
295 else: 336 else:
296 specials["progress_fancy"] = "" 337 specials["progress_fancy"] = ""
338 specials["red"] = "\033[31m"
339 specials["green"] = "\033[32m"
340 specials["white"] = "\033[37m"
297 specials["bold"] = "\033[01m" 341 specials["bold"] = "\033[01m"
298 specials["normal"] = "\033[00m" 342 specials["normal"] = "\033[00m"
299 return promptstr % specials 343 return promptstr % specials
300 344
301 def postcmd(self, stop, line): 345 def postcmd(self, stop, line):
374 if self.status.bed_enabled: 418 if self.status.bed_enabled:
375 if self.status.bed_temp_target != 0: 419 if self.status.bed_temp_target != 0:
376 self.log("Setting bed temp to 0") 420 self.log("Setting bed temp to 0")
377 self.p.send_now("M140 S0.0") 421 self.p.send_now("M140 S0.0")
378 self.log("Disconnecting from printer...") 422 self.log("Disconnecting from printer...")
379 if self.p.printing: 423 if self.p.printing and l != "force":
380 self.log(_("Are you sure you want to exit while printing?\n\ 424 self.log(_("Are you sure you want to exit while printing?\n\
381 (this will terminate the print).")) 425 (this will terminate the print)."))
382 if not self.confirm(): 426 if not self.confirm():
383 return 427 return
384 self.log(_("Exiting program. Goodbye!")) 428 self.log(_("Exiting program. Goodbye!"))
451 def compile_macro(self, macro_name, macro_def): 495 def compile_macro(self, macro_name, macro_def):
452 if macro_def.strip() == "": 496 if macro_def.strip() == "":
453 self.logError("Empty macro - cancelled") 497 self.logError("Empty macro - cancelled")
454 return 498 return
455 macro = None 499 macro = None
500 namespace={}
456 pycode = "def macro(self,*arg):\n" 501 pycode = "def macro(self,*arg):\n"
457 if "\n" not in macro_def.strip(): 502 if "\n" not in macro_def.strip():
458 pycode += self.compile_macro_line(" " + macro_def.strip()) 503 pycode += self.compile_macro_line(" " + macro_def.strip())
459 else: 504 else:
460 lines = macro_def.split("\n") 505 lines = macro_def.split("\n")
461 for l in lines: 506 for l in lines:
462 pycode += self.compile_macro_line(l) 507 pycode += self.compile_macro_line(l)
463 exec pycode 508 exec(pycode,namespace)
509 try:
510 macro=namespace['macro']
511 except:
512 pass
464 return macro 513 return macro
465 514
466 def start_macro(self, macro_name, prev_definition = "", suppress_instructions = False): 515 def start_macro(self, macro_name, prev_definition = "", suppress_instructions = False):
467 if not self.processing_rc and not suppress_instructions: 516 if not self.processing_rc and not suppress_instructions:
468 self.logError("Enter macro using indented lines, end with empty line") 517 self.logError("Enter macro using indented lines, end with empty line")
482 else: 531 else:
483 self.logError("Macro '" + macro_name + "' is not defined") 532 self.logError("Macro '" + macro_name + "' is not defined")
484 533
485 def do_macro(self, args): 534 def do_macro(self, args):
486 if args.strip() == "": 535 if args.strip() == "":
487 self.print_topics("User-defined macros", map(str, self.macros.keys()), 15, 80) 536 self.print_topics("User-defined macros", [str(k) for k in self.macros.keys()], 15, 80)
488 return 537 return
489 arglist = args.split(None, 1) 538 arglist = args.split(None, 1)
490 macro_name = arglist[0] 539 macro_name = arglist[0]
491 if macro_name not in self.macros and hasattr(self.__class__, "do_" + macro_name): 540 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") 541 self.logError("Name '" + macro_name + "' is being used by built-in command")
538 value = self.settings._set(var, str) 587 value = self.settings._set(var, str)
539 if not self.processing_rc and not self.processing_args: 588 if not self.processing_rc and not self.processing_args:
540 self.save_in_rc("set " + var, "set %s %s" % (var, value)) 589 self.save_in_rc("set " + var, "set %s %s" % (var, value))
541 except AttributeError: 590 except AttributeError:
542 logging.debug(_("Unknown variable '%s'") % var) 591 logging.debug(_("Unknown variable '%s'") % var)
543 except ValueError, ve: 592 except ValueError as ve:
544 if hasattr(ve, "from_validator"): 593 if hasattr(ve, "from_validator"):
545 self.logError(_("Bad value %s for variable '%s': %s") % (str, var, ve.args[0])) 594 self.logError(_("Bad value %s for variable '%s': %s") % (str, var, ve.args[0]))
546 else: 595 else:
547 self.logError(_("Bad value for variable '%s', expecting %s (%s)") % (var, repr(t)[1:-1], ve.args[0])) 596 self.logError(_("Bad value for variable '%s', expecting %s (%s)") % (var, repr(t)[1:-1], ve.args[0]))
548 597
580 try: 629 try:
581 rc = codecs.open(rc_filename, "r", "utf-8") 630 rc = codecs.open(rc_filename, "r", "utf-8")
582 self.rc_filename = os.path.abspath(rc_filename) 631 self.rc_filename = os.path.abspath(rc_filename)
583 for rc_cmd in rc: 632 for rc_cmd in rc:
584 if not rc_cmd.lstrip().startswith("#"): 633 if not rc_cmd.lstrip().startswith("#"):
634 logging.debug(rc_cmd.rstrip())
585 self.onecmd(rc_cmd) 635 self.onecmd(rc_cmd)
586 rc.close() 636 rc.close()
587 if hasattr(self, "cur_macro_def"): 637 if hasattr(self, "cur_macro_def"):
588 self.end_macro() 638 self.end_macro()
589 self.rc_loaded = True 639 self.rc_loaded = True
590 finally: 640 finally:
591 self.processing_rc = False 641 self.processing_rc = False
592 642
593 def load_default_rc(self, rc_filename = ".pronsolerc"): 643 def load_default_rc(self):
594 if rc_filename == ".pronsolerc" and hasattr(sys, "frozen") and sys.frozen in ["windows_exe", "console_exe"]: 644 # Check if a configuration file exists in an "old" location,
595 rc_filename = "printrunconf.ini" 645 # if not, use the "new" location provided by appdirs
646 for f in '~/.pronsolerc', '~/printrunconf.ini':
647 expanded = os.path.expanduser(f)
648 if os.path.exists(expanded):
649 config = expanded
650 break
651 else:
652 if not os.path.exists(self.config_dir):
653 os.makedirs(self.config_dir)
654
655 config_name = ('printrunconf.ini'
656 if platform.system() == 'Windows'
657 else 'pronsolerc')
658
659 config = os.path.join(self.config_dir, config_name)
660 logging.info('Loading config file ' + config)
661
662 # Load the default configuration file
596 try: 663 try:
597 try: 664 self.load_rc(config)
598 self.load_rc(os.path.join(os.path.expanduser("~"), rc_filename)) 665 except FileNotFoundError:
599 except IOError: 666 # Make sure the filename is initialized,
600 self.load_rc(rc_filename) 667 # and create the file if it doesn't exist
601 except IOError: 668 self.rc_filename = config
602 # make sure the filename is initialized 669 open(self.rc_filename, 'a').close()
603 self.rc_filename = os.path.abspath(os.path.join(os.path.expanduser("~"), rc_filename))
604 670
605 def save_in_rc(self, key, definition): 671 def save_in_rc(self, key, definition):
606 """ 672 """
607 Saves or updates macro or other definitions in .pronsolerc 673 Saves or updates macro or other definitions in .pronsolerc
608 key is prefix that determines what is being defined/updated (e.g. 'macro foo') 674 key is prefix that determines what is being defined/updated (e.g. 'macro foo')
618 if definition != "" and not definition.endswith("\n"): 684 if definition != "" and not definition.endswith("\n"):
619 definition += "\n" 685 definition += "\n"
620 try: 686 try:
621 written = False 687 written = False
622 if os.path.exists(self.rc_filename): 688 if os.path.exists(self.rc_filename):
623 shutil.copy(self.rc_filename, self.rc_filename + "~bak") 689 if not os.path.exists(self.cache_dir):
624 rci = codecs.open(self.rc_filename + "~bak", "r", "utf-8") 690 os.makedirs(self.cache_dir)
625 rco = codecs.open(self.rc_filename + "~new", "w", "utf-8") 691 configcache = os.path.join(self.cache_dir, os.path.basename(self.rc_filename))
692 configcachebak = configcache + "~bak"
693 configcachenew = configcache + "~new"
694 shutil.copy(self.rc_filename, configcachebak)
695 rci = codecs.open(configcachebak, "r", "utf-8")
696 rco = codecs.open(configcachenew, "w", "utf-8")
626 if rci is not None: 697 if rci is not None:
627 overwriting = False 698 overwriting = False
628 for rc_cmd in rci: 699 for rc_cmd in rci:
629 l = rc_cmd.rstrip() 700 l = rc_cmd.rstrip()
630 ls = l.lstrip() 701 ls = l.lstrip()
641 if not written: 712 if not written:
642 rco.write(definition) 713 rco.write(definition)
643 if rci is not None: 714 if rci is not None:
644 rci.close() 715 rci.close()
645 rco.close() 716 rco.close()
646 shutil.move(self.rc_filename + "~new", self.rc_filename) 717 shutil.move(configcachenew, self.rc_filename)
647 # if definition != "": 718 # if definition != "":
648 # self.log("Saved '"+key+"' to '"+self.rc_filename+"'") 719 # self.log("Saved '"+key+"' to '"+self.rc_filename+"'")
649 # else: 720 # else:
650 # self.log("Removed '"+key+"' from '"+self.rc_filename+"'") 721 # self.log("Removed '"+key+"' from '"+self.rc_filename+"'")
651 except Exception, e: 722 except Exception as e:
652 self.logError("Saving failed for ", key + ":", str(e)) 723 self.logError("Saving failed for ", key + ":", str(e))
653 finally: 724 finally:
654 del rci, rco 725 del rci, rco
655 726
656 # -------------------------------------------------------------- 727 # --------------------------------------------------------------
686 def process_cmdline_arguments(self, args): 757 def process_cmdline_arguments(self, args):
687 if args.verbose: 758 if args.verbose:
688 logger = logging.getLogger() 759 logger = logging.getLogger()
689 logger.setLevel(logging.DEBUG) 760 logger.setLevel(logging.DEBUG)
690 for config in args.conf: 761 for config in args.conf:
691 self.load_rc(config) 762 try:
763 self.load_rc(config)
764 except EnvironmentError as err:
765 print(("ERROR: Unable to load configuration file: %s" %
766 str(err)[10:]))
767 sys.exit(1)
692 if not self.rc_loaded: 768 if not self.rc_loaded:
693 self.load_default_rc() 769 self.load_default_rc()
694 self.processing_args = True 770 self.processing_args = True
695 for command in args.execute: 771 for command in args.execute:
696 self.onecmd(command) 772 self.onecmd(command)
697 self.processing_args = False 773 self.processing_args = False
698 self.update_rpc_server(None, self.settings.rpc_server) 774 self.update_rpc_server(None, self.settings.rpc_server)
699 if args.filename: 775 if args.filename:
700 filename = args.filename.decode(locale.getpreferredencoding()) 776 self.cmdline_filename_callback(args.filename)
701 self.cmdline_filename_callback(filename)
702 777
703 def cmdline_filename_callback(self, filename): 778 def cmdline_filename_callback(self, filename):
704 self.do_load(filename) 779 self.do_load(filename)
705 780
706 def parse_cmdline(self, args): 781 def parse_cmdline(self, args):
734 self.logError(_("Error: You are trying to connect to a non-existing port.")) 809 self.logError(_("Error: You are trying to connect to a non-existing port."))
735 else: 810 else:
736 self.logError(traceback.format_exc()) 811 self.logError(traceback.format_exc())
737 return False 812 return False
738 self.statuscheck = True 813 self.statuscheck = True
739 self.status_thread = threading.Thread(target = self.statuschecker) 814 self.status_thread = threading.Thread(target = self.statuschecker,
815 name = 'status thread')
740 self.status_thread.start() 816 self.status_thread.start()
741 return True 817 return True
742 818
743 def do_connect(self, l): 819 def do_connect(self, l):
744 a = l.split() 820 a = l.split()
788 def scanserial(self): 864 def scanserial(self):
789 """scan for available ports. return a list of device names.""" 865 """scan for available ports. return a list of device names."""
790 baselist = [] 866 baselist = []
791 if os.name == "nt": 867 if os.name == "nt":
792 try: 868 try:
793 key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\SERIALCOMM") 869 key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\SERIALCOMM")
794 i = 0 870 i = 0
795 while(1): 871 while(1):
796 baselist += [_winreg.EnumValue(key, i)[1]] 872 baselist += [winreg.EnumValue(key, i)[1]]
797 i += 1 873 i += 1
798 except: 874 except:
799 pass 875 pass
800 876
801 for g in ['/dev/ttyUSB*', '/dev/ttyACM*', "/dev/tty.*", "/dev/cu.*", "/dev/rfcomm*"]: 877 for g in ['/dev/ttyUSB*', '/dev/ttyACM*', "/dev/tty.*", "/dev/cu.*", "/dev/rfcomm*"]:
802 baselist += glob.glob(g) 878 baselist += glob.glob(g)
803 return filter(self._bluetoothSerialFilter, baselist) 879 if(sys.platform!="win32" and self.settings.devicepath):
880 baselist += glob.glob(self.settings.devicepath)
881 return [p for p in baselist if self._bluetoothSerialFilter(p)]
804 882
805 def _bluetoothSerialFilter(self, serial): 883 def _bluetoothSerialFilter(self, serial):
806 return not ("Bluetooth" in serial or "FireFly" in serial) 884 return not ("Bluetooth" in serial or "FireFly" in serial)
807 885
808 def online(self): 886 def online(self):
830 def statuschecker_inner(self, do_monitoring = True): 908 def statuschecker_inner(self, do_monitoring = True):
831 if self.p.online: 909 if self.p.online:
832 if self.p.writefailures >= 4: 910 if self.p.writefailures >= 4:
833 self.logError(_("Disconnecting after 4 failed writes.")) 911 self.logError(_("Disconnecting after 4 failed writes."))
834 self.status_thread = None 912 self.status_thread = None
835 self.disconnect() 913 self.p.disconnect()
836 return 914 return
837 if do_monitoring: 915 if do_monitoring:
838 if self.sdprinting and not self.paused: 916 if self.sdprinting and not self.paused:
839 self.p.send_now("M27") 917 self.p.send_now("M27")
840 if self.m105_waitcycles % 10 == 0: 918 if self.m105_waitcycles % 10 == 0:
881 def load_gcode(self, filename, layer_callback = None, gcode = None): 959 def load_gcode(self, filename, layer_callback = None, gcode = None):
882 if gcode is None: 960 if gcode is None:
883 self.fgcode = gcoder.LightGCode(deferred = True) 961 self.fgcode = gcoder.LightGCode(deferred = True)
884 else: 962 else:
885 self.fgcode = gcode 963 self.fgcode = gcode
886 self.fgcode.prepare(open(filename, "rU"), 964 self.fgcode.prepare(open(filename, "r", encoding="utf-8"),
887 get_home_pos(self.build_dimensions_list), 965 get_home_pos(self.build_dimensions_list),
888 layer_callback = layer_callback) 966 layer_callback = layer_callback)
889 self.fgcode.estimate_duration() 967 self.fgcode.estimate_duration()
890 self.filename = filename 968 self.filename = filename
891 969
915 if not(os.path.exists(l[0])): 993 if not(os.path.exists(l[0])):
916 self.logError(_("File not found!")) 994 self.logError(_("File not found!"))
917 return 995 return
918 try: 996 try:
919 if settings: 997 if settings:
920 command = self.settings.sliceoptscommand 998 command = self.settings.slicecommandpath+self.settings.sliceoptscommand
921 self.log(_("Entering slicer settings: %s") % command) 999 self.log(_("Entering slicer settings: %s") % command)
922 run_command(command, blocking = True) 1000 run_command(command, blocking = True)
923 else: 1001 else:
924 command = self.settings.slicecommand 1002 command = self.settings.slicecommandpath+self.settings.slicecommand
925 stl_name = l[0] 1003 stl_name = l[0]
926 gcode_name = stl_name.replace(".stl", "_export.gcode").replace(".STL", "_export.gcode") 1004 gcode_name = stl_name.replace(".stl", "_export.gcode").replace(".STL", "_export.gcode")
927 run_command(command, 1005 run_command(command,
928 {"$s": stl_name, 1006 {"$s": stl_name,
929 "$o": gcode_name}, 1007 "$o": gcode_name},
930 blocking = True) 1008 blocking = True)
931 self.log(_("Loading sliced file.")) 1009 self.log(_("Loading sliced file."))
932 self.do_load(l[0].replace(".stl", "_export.gcode")) 1010 self.do_load(l[0].replace(".stl", "_export.gcode"))
933 except Exception, e: 1011 except Exception as e:
934 self.logError(_("Slicing failed: %s") % e) 1012 self.logError(_("Slicing failed: %s") % e)
935 1013
936 def complete_slice(self, text, line, begidx, endidx): 1014 def complete_slice(self, text, line, begidx, endidx):
937 s = line.split() 1015 s = line.split()
938 if len(s) > 2: 1016 if len(s) > 2:
1065 self.recvlisteners.remove(self.listfiles) 1143 self.recvlisteners.remove(self.listfiles)
1066 if self.sdlisting_echo: 1144 if self.sdlisting_echo:
1067 self.log(_("Files on SD card:")) 1145 self.log(_("Files on SD card:"))
1068 self.log("\n".join(self.sdfiles)) 1146 self.log("\n".join(self.sdfiles))
1069 elif self.sdlisting: 1147 elif self.sdlisting:
1070 self.sdfiles.append(line.strip().lower()) 1148 self.sdfiles.append(re.sub(" \d+$","",line.strip().lower()))
1071 1149
1072 def _do_ls(self, echo): 1150 def _do_ls(self, echo):
1073 # FIXME: this was 2, but I think it should rather be 0 as in do_upload 1151 # FIXME: this was 2, but I think it should rather be 0 as in do_upload
1074 self.sdlisting = 0 1152 self.sdlisting = 0
1075 self.sdlisting_echo = echo 1153 self.sdlisting_echo = echo
1189 # Update total filament length used 1267 # Update total filament length used
1190 if self.fgcode is not None: 1268 if self.fgcode is not None:
1191 new_total = self.settings.total_filament_used + self.fgcode.filament_length 1269 new_total = self.settings.total_filament_used + self.fgcode.filament_length
1192 self.set("total_filament_used", new_total) 1270 self.set("total_filament_used", new_total)
1193 1271
1272 # Update the length of filament in the spools
1273 self.spool_manager.refresh()
1274 if(len(self.fgcode.filament_length_multi)>1):
1275 for i in enumerate(self.fgcode.filament_length_multi):
1276 if self.spool_manager.getSpoolName(i[0]) != None:
1277 self.spool_manager.editLength(
1278 -i[1], extruder = i[0])
1279 else:
1280 if self.spool_manager.getSpoolName(0) != None:
1281 self.spool_manager.editLength(
1282 -self.fgcode.filament_length, extruder = 0)
1283
1194 if not self.settings.final_command: 1284 if not self.settings.final_command:
1195 return 1285 return
1196 output = get_command_output(self.settings.final_command, 1286 output = get_command_output(self.settings.final_command,
1197 {"$s": str(self.filename), 1287 {"$s": str(self.filename),
1198 "$t": format_duration(print_duration)}) 1288 "$t": format_duration(print_duration)})
1200 self.log("Final command output:") 1290 self.log("Final command output:")
1201 self.log(output.rstrip()) 1291 self.log(output.rstrip())
1202 1292
1203 def recvcb_report(self, l): 1293 def recvcb_report(self, l):
1204 isreport = REPORT_NONE 1294 isreport = REPORT_NONE
1205 if "ok C:" in l or "Count" in l \ 1295 if "ok C:" in l or " Count " in l \
1206 or ("X:" in l and len(gcoder.m114_exp.findall(l)) == 6): 1296 or ("X:" in l and len(gcoder.m114_exp.findall(l)) == 6):
1207 self.posreport = l 1297 self.posreport = l
1208 isreport = REPORT_POS 1298 isreport = REPORT_POS
1209 if self.userm114 > 0: 1299 if self.userm114 > 0:
1210 self.userm114 -= 1 1300 self.userm114 -= 1
1258 listener(l) 1348 listener(l)
1259 if not self.recvcb_actions(l): 1349 if not self.recvcb_actions(l):
1260 report_type = self.recvcb_report(l) 1350 report_type = self.recvcb_report(l)
1261 if report_type & REPORT_TEMP: 1351 if report_type & REPORT_TEMP:
1262 self.status.update_tempreading(l) 1352 self.status.update_tempreading(l)
1263 if l != "ok" and not self.sdlisting \ 1353 if not self.lineignorepattern.match(l) and l[:4] != "wait" and not self.sdlisting \
1264 and not self.monitoring and (report_type == REPORT_NONE or report_type & REPORT_MANUAL): 1354 and not self.monitoring and (report_type == REPORT_NONE or report_type & REPORT_MANUAL):
1265 if l[:5] == "echo:": 1355 if l[:5] == "echo:":
1266 l = l[5:].lstrip() 1356 l = l[5:].lstrip()
1267 if self.silent is False: self.log("\r" + l.ljust(15)) 1357 if self.silent is False: self.log("\r" + l.ljust(15))
1268 sys.stdout.write(self.promptf()) 1358 sys.stdout.write(self.promptf())
1330 self.dynamic_temp = True 1420 self.dynamic_temp = True
1331 if self.p.online: 1421 if self.p.online:
1332 self.p.send_now("M105") 1422 self.p.send_now("M105")
1333 time.sleep(0.75) 1423 time.sleep(0.75)
1334 if not self.status.bed_enabled: 1424 if not self.status.bed_enabled:
1335 self.log(_("Hotend: %s/%s") % (self.status.extruder_temp, self.status.extruder_temp_target)) 1425 self.log(_("Hotend: %s%s/%s%s") % (self.status.extruder_temp, DEG, self.status.extruder_temp_target, DEG))
1336 else: 1426 else:
1337 self.log(_("Hotend: %s/%s") % (self.status.extruder_temp, self.status.extruder_temp_target)) 1427 self.log(_("Hotend: %s%s/%s%s") % (self.status.extruder_temp, DEG, self.status.extruder_temp_target, DEG))
1338 self.log(_("Bed: %s/%s") % (self.status.bed_temp, self.status.bed_temp_target)) 1428 self.log(_("Bed: %s%s/%s%s") % (self.status.bed_temp, DEG, self.status.bed_temp_target, DEG))
1339 1429
1340 def help_gettemp(self): 1430 def help_gettemp(self):
1341 self.log(_("Read the extruder and bed temperature.")) 1431 self.log(_("Read the extruder and bed temperature."))
1342 1432
1343 def do_settemp(self, l): 1433 def do_settemp(self, l):
1364 self.logError(_("You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0.")) 1454 self.logError(_("You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0."))
1365 1455
1366 def help_settemp(self): 1456 def help_settemp(self):
1367 self.log(_("Sets the hotend temperature to the value entered.")) 1457 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")) 1458 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()])) 1459 self.log(', '.join('%s (%s)'%kv for kv in self.temps.items()))
1370 1460
1371 def complete_settemp(self, text, line, begidx, endidx): 1461 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] == " "): 1462 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)] 1463 return [i for i in self.temps.keys() if i.startswith(text)]
1374 1464
1454 self.logError(_("You must specify the tool index as an integer.")) 1544 self.logError(_("You must specify the tool index as an integer."))
1455 if tool is not None and tool >= 0: 1545 if tool is not None and tool >= 0:
1456 if self.p.online: 1546 if self.p.online:
1457 self.p.send_now("T%d" % tool) 1547 self.p.send_now("T%d" % tool)
1458 self.log(_("Using tool %d.") % tool) 1548 self.log(_("Using tool %d.") % tool)
1549 self.current_tool = tool
1459 else: 1550 else:
1460 self.logError(_("Printer is not online.")) 1551 self.logError(_("Printer is not online."))
1461 else: 1552 else:
1462 self.logError(_("You cannot set negative tool numbers.")) 1553 self.logError(_("You cannot set negative tool numbers."))
1463 1554
1558 self.log(_("Length is 0, not doing anything.")) 1649 self.log(_("Length is 0, not doing anything."))
1559 self.p.send_now("G91") 1650 self.p.send_now("G91")
1560 self.p.send_now("G1 E" + str(length) + " F" + str(feed)) 1651 self.p.send_now("G1 E" + str(length) + " F" + str(feed))
1561 self.p.send_now("G90") 1652 self.p.send_now("G90")
1562 1653
1654 # Update the length of filament in the current spool
1655 self.spool_manager.refresh()
1656 if self.spool_manager.getSpoolName(self.current_tool) != None:
1657 self.spool_manager.editLength(-length,
1658 extruder = self.current_tool)
1659
1563 def help_extrude(self): 1660 def help_extrude(self):
1564 self.log(_("Extrudes a length of filament, 5mm by default, or the number of mm given as a parameter")) 1661 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)")) 1662 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)")) 1663 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)")) 1664 self.log(_("extrude -5 - REVERSES 5mm of filament at 300mm/min (5mm/s)"))
1656 command = command[2:] 1753 command = command[2:]
1657 self.log(_("G-Code calling host command \"%s\"") % command) 1754 self.log(_("G-Code calling host command \"%s\"") % command)
1658 self.onecmd(command) 1755 self.onecmd(command)
1659 1756
1660 def do_run_script(self, l): 1757 def do_run_script(self, l):
1661 p = run_command(l, {"$s": str(self.filename)}, stdout = subprocess.PIPE) 1758 p = run_command(l, {"$s": str(self.filename)}, stdout = subprocess.PIPE, universal_newlines = True)
1662 for line in p.stdout.readlines(): 1759 for line in p.stdout.readlines():
1663 self.log("<< " + line.strip()) 1760 self.log("<< " + line.strip())
1664 1761
1665 def help_run_script(self): 1762 def help_run_script(self):
1666 self.log(_("Runs a custom script. Current gcode filename can be given using $s token.")) 1763 self.log(_("Runs a custom script. Current gcode filename can be given using $s token."))
1667 1764
1668 def do_run_gcode_script(self, l): 1765 def do_run_gcode_script(self, l):
1669 p = run_command(l, {"$s": str(self.filename)}, stdout = subprocess.PIPE) 1766 try:
1670 for line in p.stdout.readlines(): 1767 self.fgcode = RGSGCoder(l)
1671 self.onecmd(line.strip()) 1768 self.do_print(None)
1769 except BaseException as e:
1770 self.logError(traceback.format_exc())
1672 1771
1673 def help_run_gcode_script(self): 1772 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.")) 1773 self.log(_("Runs a custom script which output gcode which will in turn be executed. Current gcode filename can be given using $s token."))
1774
1775 def complete_run_gcode_script(self, text, line, begidx, endidx):
1776 words = line.split()
1777 sep = os.path.sep
1778 if len(words) < 2:
1779 return ['.' + sep , sep]
1780 corrected_text = words[-1] # text arg skips leading '/', include it
1781 if corrected_text == '.':
1782 return ['./'] # guide user that in linux, PATH does not include . and relative executed scripts must start with ./
1783 prefix_len = len(corrected_text) - len(text)
1784 res = [((f + sep) if os.path.isdir(f) else f)[prefix_len:] #skip unskipped prefix_len
1785 for f in glob.glob(corrected_text + '*')]
1786 return res

mercurial