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, \ |
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: |
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 |
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 |