printrun-src/printrun/gcoder.py

changeset 46
cce0af6351f0
parent 15
0bbb006204fc
equal deleted inserted replaced
45:c82943fb205f 46:cce0af6351f0
1 #!/usr/bin/env python 1 #!/usr/bin/env python3
2 # This file is copied from GCoder.
3 # 2 #
4 # GCoder is free software: you can redistribute it and/or modify 3 # This file is part of the Printrun suite.
4 #
5 # Printrun is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by 6 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or 7 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version. 8 # (at your option) any later version.
8 # 9 #
9 # GCoder is distributed in the hope that it will be useful, 10 # Printrun is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details. 13 # GNU General Public License for more details.
13 # 14 #
14 # You should have received a copy of the GNU General Public License 15 # You should have received a copy of the GNU General Public License
20 import datetime 21 import datetime
21 import logging 22 import logging
22 from array import array 23 from array import array
23 24
24 gcode_parsed_args = ["x", "y", "e", "f", "z", "i", "j"] 25 gcode_parsed_args = ["x", "y", "e", "f", "z", "i", "j"]
25 gcode_parsed_nonargs = ["g", "t", "m", "n"] 26 gcode_parsed_nonargs = 'gtmnd'
26 to_parse = "".join(gcode_parsed_args + gcode_parsed_nonargs) 27 to_parse = "".join(gcode_parsed_args) + gcode_parsed_nonargs
27 gcode_exp = re.compile("\([^\(\)]*\)|;.*|[/\*].*\n|([%s])([-+]?[0-9]*\.?[0-9]*)" % to_parse) 28 gcode_exp = re.compile("\([^\(\)]*\)|;.*|[/\*].*\n|([%s])\s*([-+]?[0-9]*\.?[0-9]*)" % to_parse)
28 gcode_strip_comment_exp = re.compile("\([^\(\)]*\)|;.*|[/\*].*\n") 29 gcode_strip_comment_exp = re.compile("\([^\(\)]*\)|;.*|[/\*].*\n")
29 m114_exp = re.compile("\([^\(\)]*\)|[/\*].*\n|([XYZ]):?([-+]?[0-9]*\.?[0-9]*)") 30 m114_exp = re.compile("\([^\(\)]*\)|[/\*].*\n|([XYZ]):?([-+]?[0-9]*\.?[0-9]*)")
30 specific_exp = "(?:\([^\(\)]*\))|(?:;.*)|(?:[/\*].*\n)|(%s[-+]?[0-9]*\.?[0-9]*)" 31 specific_exp = "(?:\([^\(\)]*\))|(?:;.*)|(?:[/\*].*\n)|(%s[-+]?[0-9]*\.?[0-9]*)"
31 move_gcodes = ["G0", "G1", "G2", "G3"] 32 move_gcodes = ["G0", "G1", "G2", "G3"]
32 33
33 class PyLine(object): 34 class PyLine:
34 35
35 __slots__ = ('x', 'y', 'z', 'e', 'f', 'i', 'j', 36 __slots__ = ('x', 'y', 'z', 'e', 'f', 'i', 'j',
36 'raw', 'command', 'is_move', 37 'raw', 'command', 'is_move',
37 'relative', 'relative_e', 38 'relative', 'relative_e',
38 'current_x', 'current_y', 'current_z', 'extruding', 39 'current_x', 'current_y', 'current_z', 'extruding',
43 self.raw = l 44 self.raw = l
44 45
45 def __getattr__(self, name): 46 def __getattr__(self, name):
46 return None 47 return None
47 48
48 class PyLightLine(object): 49 class PyLightLine:
49 50
50 __slots__ = ('raw', 'command') 51 __slots__ = ('raw', 'command')
51 52
52 def __init__(self, l): 53 def __init__(self, l):
53 self.raw = l 54 self.raw = l
54 55
55 def __getattr__(self, name): 56 def __getattr__(self, name):
56 return None 57 return None
57 58
58 try: 59 try:
59 import gcoder_line 60 from . import gcoder_line
60 Line = gcoder_line.GLine 61 Line = gcoder_line.GLine
61 LightLine = gcoder_line.GLightLine 62 LightLine = gcoder_line.GLightLine
62 except Exception, e: 63 except Exception as e:
63 logging.warning("Memory-efficient GCoder implementation unavailable: %s" % e) 64 logging.warning("Memory-efficient GCoder implementation unavailable: %s" % e)
64 Line = PyLine 65 Line = PyLine
65 LightLine = PyLightLine 66 LightLine = PyLightLine
66 67
67 def find_specific_code(line, code): 68 def find_specific_code(line, code):
105 __slots__ = ("duration", "z") 106 __slots__ = ("duration", "z")
106 107
107 def __init__(self, lines, z = None): 108 def __init__(self, lines, z = None):
108 super(Layer, self).__init__(lines) 109 super(Layer, self).__init__(lines)
109 self.z = z 110 self.z = z
110 111 self.duration = 0
111 class GCode(object): 112
113 class GCode:
112 114
113 line_class = Line 115 line_class = Line
114 116
115 lines = None 117 lines = None
116 layers = None 118 layers = None
119 line_idxs = None 121 line_idxs = None
120 append_layer = None 122 append_layer = None
121 append_layer_id = None 123 append_layer_id = None
122 124
123 imperial = False 125 imperial = False
126 cutting = False
124 relative = False 127 relative = False
125 relative_e = False 128 relative_e = False
126 current_tool = 0 129 current_tool = 0
127 # Home position: current absolute position counted from machine origin 130 # Home position: current absolute position counted from machine origin
128 home_x = 0 131 home_x = 0
215 def _get_layers_count(self): 218 def _get_layers_count(self):
216 return len(self.all_zs) 219 return len(self.all_zs)
217 layers_count = property(_get_layers_count) 220 layers_count = property(_get_layers_count)
218 221
219 def __init__(self, data = None, home_pos = None, 222 def __init__(self, data = None, home_pos = None,
220 layer_callback = None, deferred = False): 223 layer_callback = None, deferred = False,
224 cutting_as_extrusion = False):
225 self.cutting_as_extrusion = cutting_as_extrusion
221 if not deferred: 226 if not deferred:
222 self.prepare(data, home_pos, layer_callback) 227 self.prepare(data, home_pos, layer_callback)
223 228
224 def prepare(self, data = None, home_pos = None, layer_callback = None): 229 def prepare(self, data = None, home_pos = None, layer_callback = None):
225 self.home_pos = home_pos 230 self.home_pos = home_pos
238 self.all_zs = set() 243 self.all_zs = set()
239 self.layers = {} 244 self.layers = {}
240 self.layer_idxs = array('I', []) 245 self.layer_idxs = array('I', [])
241 self.line_idxs = array('I', []) 246 self.line_idxs = array('I', [])
242 247
248 def has_index(self, i):
249 return i < len(self)
243 def __len__(self): 250 def __len__(self):
244 return len(self.line_idxs) 251 return len(self.line_idxs)
245 252
246 def __iter__(self): 253 def __iter__(self):
247 return self.lines.__iter__() 254 return self.lines.__iter__()
312 self._preprocess([gline]) 319 self._preprocess([gline])
313 if store: 320 if store:
314 self.lines.append(gline) 321 self.lines.append(gline)
315 self.append_layer.append(gline) 322 self.append_layer.append(gline)
316 self.layer_idxs.append(self.append_layer_id) 323 self.layer_idxs.append(self.append_layer_id)
317 self.line_idxs.append(len(self.append_layer)) 324 self.line_idxs.append(len(self.append_layer)-1)
318 return gline 325 return gline
319 326
320 def _preprocess(self, lines = None, build_layers = False, 327 def _preprocess(self, lines = None, build_layers = False,
321 layer_callback = None): 328 layer_callback = None):
322 """Checks for imperial/relativeness settings and tool changes""" 329 """Checks for imperial/relativeness settings and tool changes"""
336 # Extrusion computation 343 # Extrusion computation
337 current_e = self.current_e 344 current_e = self.current_e
338 offset_e = self.offset_e 345 offset_e = self.offset_e
339 total_e = self.total_e 346 total_e = self.total_e
340 max_e = self.max_e 347 max_e = self.max_e
348 cutting = self.cutting
341 349
342 current_e_multi = self.current_e_multi[current_tool] 350 current_e_multi = self.current_e_multi[current_tool]
343 offset_e_multi = self.offset_e_multi[current_tool] 351 offset_e_multi = self.offset_e_multi[current_tool]
344 total_e_multi = self.total_e_multi[current_tool] 352 total_e_multi = self.total_e_multi[current_tool]
345 max_e_multi = self.max_e_multi[current_tool] 353 max_e_multi = self.max_e_multi[current_tool]
365 # Duration estimation 373 # Duration estimation
366 # TODO: 374 # TODO:
367 # get device caps from firmware: max speed, acceleration/axis 375 # get device caps from firmware: max speed, acceleration/axis
368 # (including extruder) 376 # (including extruder)
369 # calculate the maximum move duration accounting for above ;) 377 # calculate the maximum move duration accounting for above ;)
370 lastx = lasty = lastz = laste = lastf = 0.0 378 lastx = lasty = lastz = None
379 laste = lastf = 0
371 lastdx = 0 380 lastdx = 0
372 lastdy = 0 381 lastdy = 0
373 x = y = e = f = 0.0 382 x = y = e = f = 0.0
374 currenttravel = 0.0 383 currenttravel = 0.0
375 moveduration = 0.0 384 moveduration = 0.0
381 all_layers = self.all_layers = [] 390 all_layers = self.all_layers = []
382 all_zs = self.all_zs = set() 391 all_zs = self.all_zs = set()
383 layer_idxs = self.layer_idxs = [] 392 layer_idxs = self.layer_idxs = []
384 line_idxs = self.line_idxs = [] 393 line_idxs = self.line_idxs = []
385 394
386 layer_id = 0
387 layer_line = 0
388 395
389 last_layer_z = None 396 last_layer_z = None
390 prev_z = None 397 prev_z = None
391 prev_base_z = (None, None)
392 cur_z = None 398 cur_z = None
393 cur_lines = [] 399 cur_lines = []
400
401 def append_lines(lines, isEnd):
402 if not build_layers:
403 return
404 nonlocal layerbeginduration, last_layer_z
405 if cur_layer_has_extrusion and prev_z != last_layer_z \
406 or not all_layers or isEnd:
407 layer = Layer([], prev_z)
408 last_layer_z = prev_z
409 finished_layer = len(all_layers)-1 if all_layers else None
410 all_layers.append(layer)
411 else:
412 layer = all_layers[-1]
413 finished_layer = None
414 layer_id = len(all_layers)-1
415 layer_line = len(layer)
416 for i, ln in enumerate(lines):
417 layer.append(ln)
418 layer_idxs.append(layer_id)
419 line_idxs.append(layer_line+i)
420 layer.duration += totalduration - layerbeginduration
421 layerbeginduration = totalduration
422 if layer_callback:
423 # we finish a layer when inserting the next
424 if finished_layer is not None:
425 layer_callback(self, finished_layer)
426 # notify about end layer, there will not be next
427 if isEnd:
428 layer_callback(self, layer_id)
394 429
395 if self.line_class != Line: 430 if self.line_class != Line:
396 get_line = lambda l: Line(l.raw) 431 get_line = lambda l: Line(l.raw)
397 else: 432 else:
398 get_line = lambda l: l 433 get_line = lambda l: l
420 elif line.command == "M82": 455 elif line.command == "M82":
421 relative_e = False 456 relative_e = False
422 elif line.command == "M83": 457 elif line.command == "M83":
423 relative_e = True 458 relative_e = True
424 elif line.command[0] == "T": 459 elif line.command[0] == "T":
425 current_tool = int(line.command[1:]) 460 try:
426 while(current_tool+1>len(self.current_e_multi)): 461 current_tool = int(line.command[1:])
462 except:
463 pass #handle T? by treating it as no tool change
464 while current_tool+1 > len(self.current_e_multi):
427 self.current_e_multi+=[0] 465 self.current_e_multi+=[0]
428 self.offset_e_multi+=[0] 466 self.offset_e_multi+=[0]
429 self.total_e_multi+=[0] 467 self.total_e_multi+=[0]
430 self.max_e_multi+=[0] 468 self.max_e_multi+=[0]
469 elif line.command == "M3" or line.command == "M4":
470 cutting = True
471 elif line.command == "M5":
472 cutting = False
473
431 current_e_multi = self.current_e_multi[current_tool] 474 current_e_multi = self.current_e_multi[current_tool]
432 offset_e_multi = self.offset_e_multi[current_tool] 475 offset_e_multi = self.offset_e_multi[current_tool]
433 total_e_multi = self.total_e_multi[current_tool] 476 total_e_multi = self.total_e_multi[current_tool]
434 max_e_multi = self.max_e_multi[current_tool] 477 max_e_multi = self.max_e_multi[current_tool]
435 478
502 max_e_multi=max(max_e_multi, total_e_multi) 545 max_e_multi=max(max_e_multi, total_e_multi)
503 cur_layer_has_extrusion |= line.extruding 546 cur_layer_has_extrusion |= line.extruding
504 elif line.command == "G92": 547 elif line.command == "G92":
505 offset_e = current_e - line.e 548 offset_e = current_e - line.e
506 offset_e_multi = current_e_multi - line.e 549 offset_e_multi = current_e_multi - line.e
550 if cutting and self.cutting_as_extrusion:
551 line.extruding = True
507 552
508 self.current_e_multi[current_tool]=current_e_multi 553 self.current_e_multi[current_tool]=current_e_multi
509 self.offset_e_multi[current_tool]=offset_e_multi 554 self.offset_e_multi[current_tool]=offset_e_multi
510 self.max_e_multi[current_tool]=max_e_multi 555 self.max_e_multi[current_tool]=max_e_multi
511 self.total_e_multi[current_tool]=total_e_multi 556 self.total_e_multi[current_tool]=total_e_multi
514 if build_layers: 559 if build_layers:
515 # Update bounding box 560 # Update bounding box
516 if line.is_move: 561 if line.is_move:
517 if line.extruding: 562 if line.extruding:
518 if line.current_x is not None: 563 if line.current_x is not None:
519 xmin_e = min(xmin_e, line.current_x) 564 # G0 X10 ; G1 X20 E5 results in 10..20 even as G0 is not extruding
520 xmax_e = max(xmax_e, line.current_x) 565 xmin_e = min(xmin_e, line.current_x, xmin_e if lastx is None else lastx)
566 xmax_e = max(xmax_e, line.current_x, xmax_e if lastx is None else lastx)
521 if line.current_y is not None: 567 if line.current_y is not None:
522 ymin_e = min(ymin_e, line.current_y) 568 ymin_e = min(ymin_e, line.current_y, ymin_e if lasty is None else lasty)
523 ymax_e = max(ymax_e, line.current_y) 569 ymax_e = max(ymax_e, line.current_y, ymax_e if lasty is None else lasty)
524 if max_e <= 0: 570 if max_e <= 0:
525 if line.current_x is not None: 571 if line.current_x is not None:
526 xmin = min(xmin, line.current_x) 572 xmin = min(xmin, line.current_x)
527 xmax = max(xmax, line.current_x) 573 xmax = max(xmax, line.current_x)
528 if line.current_y is not None: 574 if line.current_y is not None:
529 ymin = min(ymin, line.current_y) 575 ymin = min(ymin, line.current_y)
530 ymax = max(ymax, line.current_y) 576 ymax = max(ymax, line.current_y)
531 577
532 # Compute duration 578 # Compute duration
533 if line.command == "G0" or line.command == "G1": 579 if line.command == "G0" or line.command == "G1":
534 x = line.x if line.x is not None else lastx 580 x = line.x if line.x is not None else (lastx or 0)
535 y = line.y if line.y is not None else lasty 581 y = line.y if line.y is not None else (lasty or 0)
536 z = line.z if line.z is not None else lastz 582 z = line.z if line.z is not None else (lastz or 0)
537 e = line.e if line.e is not None else laste 583 e = line.e if line.e is not None else laste
538 # mm/s vs mm/m => divide by 60 584 # mm/s vs mm/m => divide by 60
539 f = line.f / 60.0 if line.f is not None else lastf 585 f = line.f / 60.0 if line.f is not None else lastf
540 586
541 # given last feedrate and current feedrate calculate the 587 # given last feedrate and current feedrate calculate the
551 # speed is constant but printer has to fully decellerate 597 # speed is constant but printer has to fully decellerate
552 # and reaccelerate 598 # and reaccelerate
553 # The following code tries to fix it by forcing a full 599 # The following code tries to fix it by forcing a full
554 # reacceleration if this move is in the opposite direction 600 # reacceleration if this move is in the opposite direction
555 # of the previous one 601 # of the previous one
556 dx = x - lastx 602 dx = x - (lastx or 0)
557 dy = y - lasty 603 dy = y - (lasty or 0)
558 if dx * lastdx + dy * lastdy <= 0: 604 if dx * lastdx + dy * lastdy <= 0:
559 lastf = 0 605 lastf = 0
560 606
561 currenttravel = math.hypot(dx, dy) 607 currenttravel = math.hypot(dx, dy)
562 if currenttravel == 0: 608 if currenttravel == 0:
563 if line.z is not None: 609 if line.z is not None:
564 currenttravel = abs(line.z) if line.relative else abs(line.z - lastz) 610 currenttravel = abs(line.z) if line.relative else abs(line.z - (lastz or 0))
565 elif line.e is not None: 611 elif line.e is not None:
566 currenttravel = abs(line.e) if line.relative_e else abs(line.e - laste) 612 currenttravel = abs(line.e) if line.relative_e else abs(line.e - laste)
567 # Feedrate hasn't changed, no acceleration/decceleration planned 613 # Feedrate hasn't changed, no acceleration/decceleration planned
568 if f == lastf: 614 if f == lastf:
569 moveduration = currenttravel / f if f != 0 else 0. 615 moveduration = currenttravel / f if f != 0 else 0.
604 if line.relative and cur_z is not None: 650 if line.relative and cur_z is not None:
605 cur_z += line.z 651 cur_z += line.z
606 else: 652 else:
607 cur_z = line.z 653 cur_z = line.z
608 654
609 # FIXME: the logic behind this code seems to work, but it might be 655 if cur_z != prev_z and cur_layer_has_extrusion:
610 # broken 656 append_lines(cur_lines, False)
611 if cur_z != prev_z: 657 all_zs.add(prev_z)
612 if prev_z is not None and last_layer_z is not None: 658 cur_lines = []
613 offset = self.est_layer_height if self.est_layer_height else 0.01 659 cur_layer_has_extrusion = False
614 if abs(prev_z - last_layer_z) < offset:
615 if self.est_layer_height is None:
616 zs = sorted([l.z for l in all_layers if l.z is not None])
617 heights = [round(zs[i + 1] - zs[i], 3) for i in range(len(zs) - 1)]
618 heights = [height for height in heights if height]
619 if len(heights) >= 2: self.est_layer_height = heights[1]
620 elif heights: self.est_layer_height = heights[0]
621 else: self.est_layer_height = 0.1
622 base_z = round(prev_z - (prev_z % self.est_layer_height), 2)
623 else:
624 base_z = round(prev_z, 2)
625 else:
626 base_z = prev_z
627
628 if base_z != prev_base_z:
629 new_layer = Layer(cur_lines, base_z)
630 new_layer.duration = totalduration - layerbeginduration
631 layerbeginduration = totalduration
632 all_layers.append(new_layer)
633 if cur_layer_has_extrusion and prev_z not in all_zs:
634 all_zs.add(prev_z)
635 cur_lines = []
636 cur_layer_has_extrusion = False
637 layer_id += 1
638 layer_line = 0
639 last_layer_z = base_z
640 if layer_callback is not None:
641 layer_callback(self, len(all_layers) - 1)
642
643 prev_base_z = base_z
644 660
645 if build_layers: 661 if build_layers:
646 cur_lines.append(true_line) 662 cur_lines.append(true_line)
647 layer_idxs.append(layer_id)
648 line_idxs.append(layer_line)
649 layer_line += 1
650 prev_z = cur_z 663 prev_z = cur_z
651 # ## Loop done 664 # ## Loop done
652 665
653 # Store current status 666 # Store current status
654 self.imperial = imperial 667 self.imperial = imperial
667 self.total_e = total_e 680 self.total_e = total_e
668 self.current_e_multi[current_tool]=current_e_multi 681 self.current_e_multi[current_tool]=current_e_multi
669 self.offset_e_multi[current_tool]=offset_e_multi 682 self.offset_e_multi[current_tool]=offset_e_multi
670 self.max_e_multi[current_tool]=max_e_multi 683 self.max_e_multi[current_tool]=max_e_multi
671 self.total_e_multi[current_tool]=total_e_multi 684 self.total_e_multi[current_tool]=total_e_multi
685 self.cutting = cutting
672 686
673 687
674 # Finalize layers 688 # Finalize layers
675 if build_layers: 689 if build_layers:
676 if cur_lines: 690 if cur_lines:
677 new_layer = Layer(cur_lines, prev_z) 691 append_lines(cur_lines, True)
678 new_layer.duration = totalduration - layerbeginduration 692 all_zs.add(prev_z)
679 layerbeginduration = totalduration
680 all_layers.append(new_layer)
681 if cur_layer_has_extrusion and prev_z not in all_zs:
682 all_zs.add(prev_z)
683 693
684 self.append_layer_id = len(all_layers) 694 self.append_layer_id = len(all_layers)
685 self.append_layer = Layer([]) 695 self.append_layer = Layer([])
686 self.append_layer.duration = 0 696 self.append_layer.duration = 0
687 all_layers.append(self.append_layer) 697 all_layers.append(self.append_layer)
688 self.layer_idxs = array('I', layer_idxs) 698 self.layer_idxs = array('I', layer_idxs)
689 self.line_idxs = array('I', line_idxs) 699 self.line_idxs = array('I', line_idxs)
690 700
691 # Compute bounding box 701 # Compute bounding box
692 all_zs = self.all_zs.union(set([zmin])).difference(set([None])) 702 all_zs = self.all_zs.union({zmin}).difference({None})
693 zmin = min(all_zs) 703 zmin = min(all_zs)
694 zmax = max(all_zs) 704 zmax = max(all_zs)
695 705
696 self.filament_length = self.max_e 706 self.filament_length = self.max_e
697 while len(self.filament_length_multi)<len(self.max_e_multi): 707 while len(self.filament_length_multi)<len(self.max_e_multi):
729 class LightGCode(GCode): 739 class LightGCode(GCode):
730 line_class = LightLine 740 line_class = LightLine
731 741
732 def main(): 742 def main():
733 if len(sys.argv) < 2: 743 if len(sys.argv) < 2:
734 print "usage: %s filename.gcode" % sys.argv[0] 744 print("usage: %s filename.gcode" % sys.argv[0])
735 return 745 return
736 746
737 print "Line object size:", sys.getsizeof(Line("G0 X0")) 747 print("Line object size:", sys.getsizeof(Line("G0 X0")))
738 print "Light line object size:", sys.getsizeof(LightLine("G0 X0")) 748 print("Light line object size:", sys.getsizeof(LightLine("G0 X0")))
739 gcode = GCode(open(sys.argv[1], "rU")) 749 gcode = GCode(open(sys.argv[1], "rU"))
740 750
741 print "Dimensions:" 751 print("Dimensions:")
742 xdims = (gcode.xmin, gcode.xmax, gcode.width) 752 xdims = (gcode.xmin, gcode.xmax, gcode.width)
743 print "\tX: %0.02f - %0.02f (%0.02f)" % xdims 753 print("\tX: %0.02f - %0.02f (%0.02f)" % xdims)
744 ydims = (gcode.ymin, gcode.ymax, gcode.depth) 754 ydims = (gcode.ymin, gcode.ymax, gcode.depth)
745 print "\tY: %0.02f - %0.02f (%0.02f)" % ydims 755 print("\tY: %0.02f - %0.02f (%0.02f)" % ydims)
746 zdims = (gcode.zmin, gcode.zmax, gcode.height) 756 zdims = (gcode.zmin, gcode.zmax, gcode.height)
747 print "\tZ: %0.02f - %0.02f (%0.02f)" % zdims 757 print("\tZ: %0.02f - %0.02f (%0.02f)" % zdims)
748 print "Filament used: %0.02fmm" % gcode.filament_length 758 print("Filament used: %0.02fmm" % gcode.filament_length)
749 for i in enumerate(gcode.filament_length_multi): 759 for i in enumerate(gcode.filament_length_multi):
750 print "E%d %0.02fmm" % (i[0],i[1]) 760 print("E%d %0.02fmm" % (i[0],i[1]))
751 print "Number of layers: %d" % gcode.layers_count 761 print("Number of layers: %d" % gcode.layers_count)
752 print "Estimated duration: %s" % gcode.estimate_duration()[1] 762 print("Estimated duration: %s" % gcode.estimate_duration()[1])
753 763
754 if __name__ == '__main__': 764 if __name__ == '__main__':
755 main() 765 main()

mercurial