Merge pull request #352 from fsantini/experimental

Recovery after pause
master
kliment 2013-04-08 13:40:15 -07:00
commit b4f620a0b8
3 changed files with 329 additions and 7 deletions

213
GCodeAnalyzer.py Normal file
View File

@ -0,0 +1,213 @@
# This file is part of the Printrun suite.
#
# Copyright 2013 Francesco Santini francesco.santini@gmail.com
#
# Printrun is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Printrun is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Printrun. If not, see <http://www.gnu.org/licenses/>.
#
# This code is imported from RepetierHost - Original copyright and license:
# Copyright 2011 repetier repetierdev@gmail.com
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
class GCodeAnalyzer():
def __init__(self):
self.x = 0
self.y = 0
self.z = 0
self.e = 0
self.emax = 0
self.f = 1000
self.lastX = 0
self.lastY = 0
self.lastZ = 0
self.lastE = 0
self.xOffset = 0
self.yOffset = 0
self.zOffset = 0
self.eOffset = 0
self.lastZPrint = 0
self.layerZ = 0
self.relative = False
self.eRelative = False
self.homeX = 0
self.homeY = 0
self.homeZ = 0
self.maxX = 150
self.maxY = 150
self.maxZ = 150
self.minX = 0
self.minY = 0
self.minZ = 0
self.hasHomeX = False
self.hasHomeY = False
self.hasHomeZ = False
# find a code in a gstring line
def findCode(self, gcode, codeStr):
pattern = re.compile(codeStr + "\\s*(-?[\d.]*)",re.I)
m=re.search(pattern, gcode)
if m == None:
return None
else:
return m.group(1)
def Analyze(self, gcode):
gcode = gcode[:gcode.find(";")].lstrip() # remove comments
if gcode.startswith("@"): return # code is a host command
code_g = self.findCode(gcode, "G")
code_m = self.findCode(gcode, "M")
# we have a g_code
if code_g != None:
code_g = int(code_g)
#get movement codes
if code_g == 0 or code_g == 1 or code_g == 2 or code_g == 3:
self.lastX = self.x
self.lastY = self.y
self.lastZ = self.z
self.lastE = self.e
eChanged = False;
code_f = self.findCode(gcode, "F")
if code_f != None:
self.f=float(code_f)
code_x = self.findCode(gcode, "X")
code_y = self.findCode(gcode, "Y")
code_z = self.findCode(gcode, "Z")
code_e = self.findCode(gcode, "E")
if self.relative:
if code_x != None: self.x += float(code_x)
if code_y != None: self.y += float(code_y)
if code_z != None: self.z += float(code_z)
if code_e != None:
e = float(code_e)
if e != 0:
eChanged = True
self.e += e
else:
#absolute coordinates
if code_x != None: self.x = self.xOffset + float(code_x)
if code_y != None: self.y = self.yOffset + float(code_y)
if code_z != None: self.z = self.zOffset + float(code_z)
if code_e != None:
e = float(code_e)
if self.eRelative:
if e != 0:
eChanged = True
self.e += e
else:
# e is absolute. Is it changed?
if self.e != self.eOffset + e:
eChanged = True
self.e = self.eOffset + e
#limit checking
if self.x < self.minX: self.x = self.minX
if self.y < self.minY: self.y = self.minY
if self.z < self.minZ: self.z = self.minZ
if self.x > self.maxX: self.x = self.maxX
if self.y > self.maxY: self.y = self.maxY
if self.z > self.maxZ: self.z = self.maxZ
#Repetier has a bunch of limit-checking code here and time calculations: we are leaving them for now
elif code_g == 28 or code_g == 161:
self.lastX = self.x
self.lastY = self.y
self.lastZ = self.z
self.lastE = self.e
code_x = self.findCode(gcode, "X")
code_y = self.findCode(gcode, "Y")
code_z = self.findCode(gcode, "Z")
code_e = self.findCode(gcode, "E")
homeAll = False
if code_x == None and code_y == None and code_z == None: homeAll = True
if code_x != None or homeAll:
self.hasHomeX = True
self.xOffset = 0
self.x = self.homeX
if code_y != None or homeAll:
self.hasHomeY = True
self.yOffset = 0
self.y = self.homeY
if code_z != None or homeAll:
self.hasHomeZ = True
self.zOffset = 0
self.z = self.homeZ
if code_e != None:
self.eOffset = 0
self.e = 0
elif code_g == 162:
self.lastX = self.x
self.lastY = self.y
self.lastZ = self.z
self.lastE = self.e
code_x = self.findCode(gcode, "X")
code_y = self.findCode(gcode, "Y")
code_z = self.findCode(gcode, "Z")
homeAll = False
if code_x == None and code_y == None and code_z == None: homeAll = True
if code_x != None or homeAll:
self.hasHomeX = True
self.xOffset = 0
self.x = self.maxX
if code_y != None or homeAll:
self.hasHomeY = True
self.yOffset = 0
self.y = self.maxY
if code_z != None or homeAll:
self.hasHomeZ = True
self.zOffset = 0
self.z = self.maxZ
elif code_g == 90: self.relative = False
elif code_g == 91: self.relative = True
elif code_g == 92:
code_x = self.findCode(gcode, "X")
code_y = self.findCode(gcode, "Y")
code_z = self.findCode(gcode, "Z")
code_e = self.findCode(gcode, "E")
if code_x != None:
self.xOffset = self.x - float(code_x)
self.x = self.xOffset
if code_y != None:
self.yOffset = self.y - float(code_y)
self.y = self.yOffset
if code_z != None:
self.zOffset = self.z - float(code_z)
self.z = self.zOffset
if code_e != None:
self.xOffset = self.e - float(code_e)
self.e = self.eOffset
#End code_g != None
if code_m != None:
code_m = int(code_m)
if code_m == 82: self.eRelative = False
elif code_m == 83: self.eRelative = True
def print_status(self):
attrs = vars(self)
print '\n'.join("%s: %s" % item for item in attrs.items())

View File

@ -20,6 +20,7 @@ from threading import Thread
from select import error as SelectError from select import error as SelectError
import time, getopt, sys import time, getopt, sys
import platform, os import platform, os
from GCodeAnalyzer import GCodeAnalyzer
def control_ttyhup(port, disable_hup): def control_ttyhup(port, disable_hup):
"""Controls the HUPCL""" """Controls the HUPCL"""
@ -69,7 +70,11 @@ class printcore():
self.print_thread = None self.print_thread = None
if port is not None and baud is not None: if port is not None and baud is not None:
self.connect(port, baud) self.connect(port, baud)
self.analyzer = GCodeAnalyzer()
self.xy_feedrate = None
self.z_feedrate = None
self.pronterface = None
def disconnect(self): def disconnect(self):
"""Disconnects from printer and pauses the print """Disconnects from printer and pauses the print
""" """
@ -219,19 +224,69 @@ class printcore():
self.print_thread.start() self.print_thread.start()
return True return True
# run a simple script if it exists, no multithreading
def runSmallScript(self, filename):
if filename == None: return
f = None
try:
f = open(filename)
except:
pass
if f != None:
for i in f:
l = i.replace("\n", "")
l = l[:l.find(";")] #remove comment
self.send_now(l)
f.close()
def pause(self): def pause(self):
"""Pauses the print, saving the current position. """Pauses the print, saving the current position.
""" """
if not self.printing: return False if not self.printing: return False
self.paused = True self.paused = True
self.printing = False self.printing = False
self.print_thread.join()
# try joining the print thread: enclose it in try/except because we might be calling it from the thread itself
try:
self.print_thread.join()
except:
pass
self.print_thread = None self.print_thread = None
# saves the status
self.pauseX = self.analyzer.x-self.analyzer.xOffset;
self.pauseY = self.analyzer.y-self.analyzer.yOffset;
self.pauseZ = self.analyzer.z-self.analyzer.zOffset;
self.pauseE = self.analyzer.e-self.analyzer.eOffset;
self.pauseF = self.analyzer.f;
self.pauseRelative = self.analyzer.relative;
def resume(self): def resume(self):
"""Resumes a paused print. """Resumes a paused print.
""" """
if not self.paused: return False if not self.paused: return False
if self.paused:
#restores the status
self.send_now("G90") # go to absolute coordinates
xyFeedString = ""
zFeedString = ""
if self.xy_feedrate != None: xyFeedString = " F" + str(self.xy_feedrate)
if self.z_feedrate != None: zFeedString = " F" + str(self.z_feedrate)
self.send_now("G1 X" + str(self.pauseX) + " Y" + str(self.pauseY) + xyFeedString)
self.send_now("G1 Z" + str(self.pauseZ) + zFeedString)
self.send_now("G92 E" + str(self.pauseE))
if self.pauseRelative: self.send_now("G91") # go back to relative if needed
#reset old feed rate
self.send_now("G1 F" + str(self.pauseF))
self.paused = False self.paused = False
self.printing = True self.printing = True
self.print_thread = Thread(target = self._print) self.print_thread = Thread(target = self._print)
@ -289,13 +344,24 @@ class printcore():
self.sentlines = {} self.sentlines = {}
self.log = [] self.log = []
self.sent = [] self.sent = []
self.print_thread.join() try:
self.print_thread.join()
except: pass
self.print_thread = None self.print_thread = None
if self.endcb: if self.endcb:
#callback for printing done #callback for printing done
try: self.endcb() try: self.endcb()
except: pass except: pass
#now only "pause" is implemented as host command
def processHostCommand(self, command):
command = command.lstrip()
if command.startswith(";@pause"):
if self.pronterface != None:
self.pronterface.pause(None)
else:
self.pause()
def _sendnext(self): def _sendnext(self):
if not self.printer: if not self.printer:
return return
@ -316,6 +382,13 @@ class printcore():
return return
if self.printing and self.queueindex < len(self.mainqueue): if self.printing and self.queueindex < len(self.mainqueue):
tline = self.mainqueue[self.queueindex] tline = self.mainqueue[self.queueindex]
#check for host command
if tline.lstrip().startswith(";@"):
#it is a host command: pop it from the list
self.mainqueue.pop(self.queueindex)
self.processHostCommand(tline)
return
tline = tline.split(";")[0] tline = tline.split(";")[0]
if len(tline) > 0: if len(tline) > 0:
self._send(tline, self.lineno, True) self._send(tline, self.lineno, True)
@ -339,6 +412,7 @@ class printcore():
self.sentlines[lineno] = command self.sentlines[lineno] = command
if self.printer: if self.printer:
self.sent.append(command) self.sent.append(command)
self.analyzer.Analyze(command) # run the command through the analyzer
if self.loud: if self.loud:
print "SENT: ", command print "SENT: ", command
if self.sendcb: if self.sendcb:

View File

@ -85,7 +85,7 @@ class Tee(object):
class PronterWindow(MainWindow, pronsole.pronsole): class PronterWindow(MainWindow, pronsole.pronsole):
def __init__(self, filename = None, size = winsize): def __init__(self, filename = None, size = winsize):
pronsole.pronsole.__init__(self) pronsole.pronsole.__init__(self)
self.settings.build_dimensions = '200x200x100+0+0+0' #default build dimensions are 200x200x100 with 0, 0, 0 in the corner of the bed self.settings.build_dimensions = '200x200x100+0+0+0+0+0+0' #default build dimensions are 200x200x100 with 0, 0, 0 in the corner of the bed
self.settings.last_bed_temperature = 0.0 self.settings.last_bed_temperature = 0.0
self.settings.last_file_path = "" self.settings.last_file_path = ""
self.settings.last_temperature = 0.0 self.settings.last_temperature = 0.0
@ -93,7 +93,11 @@ class PronterWindow(MainWindow, pronsole.pronsole):
self.settings.preview_grid_step1 = 10. self.settings.preview_grid_step1 = 10.
self.settings.preview_grid_step2 = 50. self.settings.preview_grid_step2 = 50.
self.settings.bgcolor = "#FFFFFF" self.settings.bgcolor = "#FFFFFF"
self.helpdict["build_dimensions"] = _("Dimensions of Build Platform\n & optional offset of origin\n\nExamples:\n XXXxYYY\n XXX,YYY,ZZZ\n XXXxYYYxZZZ+OffX+OffY+OffZ")
self.pauseScript = "pause.gcode"
self.endScript = "end.gcode"
self.helpdict["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")
self.helpdict["last_bed_temperature"] = _("Last Set Temperature for the Heated Print Bed") self.helpdict["last_bed_temperature"] = _("Last Set Temperature for the Heated Print Bed")
self.helpdict["last_file_path"] = _("Folder of last opened file") self.helpdict["last_file_path"] = _("Folder of last opened file")
self.helpdict["last_temperature"] = _("Last Temperature of the Hot End") self.helpdict["last_temperature"] = _("Last Temperature of the Hot End")
@ -128,6 +132,30 @@ class PronterWindow(MainWindow, pronsole.pronsole):
self.btndict = {} self.btndict = {}
self.parse_cmdline(sys.argv[1:]) self.parse_cmdline(sys.argv[1:])
self.build_dimensions_list = self.get_build_dimensions(self.settings.build_dimensions) self.build_dimensions_list = self.get_build_dimensions(self.settings.build_dimensions)
#initialize the code analyzer with the correct sizes. There must be a more general way to do so
# minimum = offset
self.p.analyzer.minX = self.build_dimensions_list[3]
self.p.analyzer.minY = self.build_dimensions_list[4]
self.p.analyzer.minZ = self.build_dimensions_list[5]
#max = offset + bedsize
self.p.analyzer.maxX = self.build_dimensions_list[3] + self.build_dimensions_list[0]
self.p.analyzer.maxY = self.build_dimensions_list[4] + self.build_dimensions_list[1]
self.p.analyzer.maxZ = self.build_dimensions_list[5] + self.build_dimensions_list[2]
self.p.analyzer.homeX = self.build_dimensions_list[6]
self.p.analyzer.homeY = self.build_dimensions_list[7]
self.p.analyzer.homeZ = self.build_dimensions_list[8]
#set feedrates in printcore for pause/resume
self.p.xy_feedrate = self.settings.xy_feedrate
self.p.z_feedrate = self.settings.z_feedrate
#make printcore aware of me
self.p.pronterface = self
self.panel.SetBackgroundColour(self.settings.bgcolor) self.panel.SetBackgroundColour(self.settings.bgcolor)
customdict = {} customdict = {}
try: try:
@ -198,6 +226,8 @@ class PronterWindow(MainWindow, pronsole.pronsole):
wx.CallAfter(self.pausebtn.Disable) wx.CallAfter(self.pausebtn.Disable)
wx.CallAfter(self.printbtn.SetLabel, _("Print")) wx.CallAfter(self.printbtn.SetLabel, _("Print"))
self.p.runSmallScript(self.endScript)
param = self.settings.final_command param = self.settings.final_command
if not param: if not param:
return return
@ -1388,7 +1418,9 @@ class PronterWindow(MainWindow, pronsole.pronsole):
#print "Not printing, cannot pause." #print "Not printing, cannot pause."
return return
self.p.pause() self.p.pause()
self.p.runSmallScript(self.pauseScript)
self.paused = True self.paused = True
#self.p.runSmallScript(self.pauseScript)
self.extra_print_time += int(time.time() - self.starttime) self.extra_print_time += int(time.time() - self.starttime)
wx.CallAfter(self.pausebtn.SetLabel, _("Resume")) wx.CallAfter(self.pausebtn.SetLabel, _("Resume"))
else: else:
@ -1527,9 +1559,12 @@ class PronterWindow(MainWindow, pronsole.pronsole):
"[^\d+-]*(\d+)?" + # Z build size "[^\d+-]*(\d+)?" + # Z build size
"[^\d+-]*([+-]\d+)?" + # X corner coordinate "[^\d+-]*([+-]\d+)?" + # X corner coordinate
"[^\d+-]*([+-]\d+)?" + # Y corner coordinate "[^\d+-]*([+-]\d+)?" + # Y corner coordinate
"[^\d+-]*([+-]\d+)?" # Z corner coordinate "[^\d+-]*([+-]\d+)?" + # Z corner coordinate
"[^\d+-]*([+-]\d+)?" + # X endstop
"[^\d+-]*([+-]\d+)?" + # Y endstop
"[^\d+-]*([+-]\d+)?" # Z endstop
,bdim).groups() ,bdim).groups()
defaults = [200, 200, 100, 0, 0, 0] defaults = [200, 200, 100, 0, 0, 0, 0, 0, 0]
bdl_float = [float(value) if value else defaults[i] for i, value in enumerate(bdl)] bdl_float = [float(value) if value else defaults[i] for i, value in enumerate(bdl)]
return bdl_float return bdl_float