from __future__ import absolute_import import __init__ import wx, threading, re, subprocess, sys, os from wx.lib import buttons from gui import machineCom from gui import icon from util import profile from util import gcodeInterpreter printWindowMonitorHandle = None def printFile(filename): global printWindowMonitorHandle if printWindowMonitorHandle == None: printWindowMonitorHandle = printProcessMonitor() printWindowMonitorHandle.loadFile(filename) def startPrintInterface(filename): #startPrintInterface is called from the main script when we want the printer interface to run in a seperate process. # It needs to run in a seperate process, as any running python code blocks the GCode sender pyton code (http://wiki.python.org/moin/GlobalInterpreterLock). app = wx.App(False) printWindowHandle = printWindow() printWindowHandle.Show(True) printWindowHandle.Raise() printWindowHandle.OnConnect(None) printWindowHandle.LoadGCodeFile(filename) app.MainLoop() class printProcessMonitor(): def __init__(self): self.handle = None def loadFile(self, filename): if self.handle == None: self.handle = subprocess.Popen([sys.executable, sys.argv[0], '-r', filename], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) self.thread = threading.Thread(target=self.Monitor) self.thread.start() else: self.handle.stdin.write(filename + '\n') def Monitor(self): p = self.handle line = p.stdout.readline() while(len(line) > 0): print line.rstrip() line = p.stdout.readline() p.wait() self.handle = None self.thread = None class PrintCommandButton(buttons.GenBitmapButton): def __init__(self, parent, command, bitmapFilename, size=(20,20)): self.bitmap = wx.Bitmap(os.path.join(os.path.split(__file__)[0], "../images", bitmapFilename)) super(PrintCommandButton, self).__init__(parent.directControlPanel, -1, self.bitmap, size=size) self.command = command self.parent = parent self.SetBezelWidth(1) self.SetUseFocusIndicator(False) self.Bind(wx.EVT_BUTTON, self.OnClick) def OnClick(self, e): self.parent.sendCommand("G91") self.parent.sendCommand(self.command) e.Skip() class printWindow(wx.Frame): "Main user interface window" def __init__(self): super(printWindow, self).__init__(None, -1, title='Printing') self.machineCom = None self.machineConnected = False self.thread = None self.gcode = None self.gcodeList = None self.sendList = [] self.printIdx = None self.temp = None self.bufferLineCount = 4 self.sendCnt = 0 #self.SetIcon(icon.getMainIcon()) self.SetSizer(wx.BoxSizer()) self.panel = wx.Panel(self) self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND) self.sizer = wx.GridBagSizer(2, 2) self.panel.SetSizer(self.sizer) sb = wx.StaticBox(self.panel, label="Statistics") boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL) self.statsText = wx.StaticText(self.panel, -1, "Filament: ####.##m #.##g\nPrint time: #####:##") boxsizer.Add(self.statsText, flag=wx.LEFT, border=5) self.sizer.Add(boxsizer, pos=(0,0), span=(4,1), flag=wx.EXPAND) self.connectButton = wx.Button(self.panel, -1, 'Connect') #self.loadButton = wx.Button(self.panel, -1, 'Load GCode') self.printButton = wx.Button(self.panel, -1, 'Print GCode') self.cancelButton = wx.Button(self.panel, -1, 'Cancel print') self.progress = wx.Gauge(self.panel, -1) h = self.connectButton.GetSize().GetHeight() self.temperatureSelect = wx.SpinCtrl(self.panel, -1, '0', size=(21*3,21), style=wx.SP_ARROW_KEYS) self.temperatureSelect.SetRange(0, 400) self.sizer.Add(self.connectButton, pos=(0,1)) #self.sizer.Add(self.loadButton, pos=(1,1)) self.sizer.Add(self.printButton, pos=(2,1)) self.sizer.Add(self.cancelButton, pos=(3,1)) self.sizer.Add(self.progress, pos=(4,0), span=(1,2), flag=wx.EXPAND) self.sizer.Add(wx.StaticText(self.panel, -1, "Temp:"), pos=(0,3)) self.sizer.Add(self.temperatureSelect, pos=(0,4)) self.directControlPanel = wx.Panel(self.panel) self.sizer.Add(self.directControlPanel, pos=(1,3), span=(5,4)) sizer = wx.GridBagSizer(2, 2) self.directControlPanel.SetSizer(sizer) sizer.Add(PrintCommandButton(self, 'G1 Y100 F6000', 'print-move-y100.png'), pos=(0,3)) sizer.Add(PrintCommandButton(self, 'G1 Y10 F6000', 'print-move-y10.png'), pos=(1,3)) sizer.Add(PrintCommandButton(self, 'G1 Y1 F6000', 'print-move-y1.png'), pos=(2,3)) sizer.Add(PrintCommandButton(self, 'G1 Y-1 F6000', 'print-move-y-1.png'), pos=(4,3)) sizer.Add(PrintCommandButton(self, 'G1 Y-10 F6000', 'print-move-y-10.png'), pos=(5,3)) sizer.Add(PrintCommandButton(self, 'G1 Y-100 F6000', 'print-move-y-100.png'), pos=(6,3)) sizer.Add(PrintCommandButton(self, 'G1 X-100 F6000', 'print-move-x-100.png'), pos=(3,0)) sizer.Add(PrintCommandButton(self, 'G1 X-10 F6000', 'print-move-x-10.png'), pos=(3,1)) sizer.Add(PrintCommandButton(self, 'G1 X-1 F6000', 'print-move-x-1.png'), pos=(3,2)) sizer.Add(PrintCommandButton(self, 'G28 X0 Y0', 'print-move-home.png'), pos=(3,3)) sizer.Add(PrintCommandButton(self, 'G1 X1 F6000', 'print-move-x1.png'), pos=(3,4)) sizer.Add(PrintCommandButton(self, 'G1 X10 F6000', 'print-move-x10.png'), pos=(3,5)) sizer.Add(PrintCommandButton(self, 'G1 X100 F6000', 'print-move-x100.png'), pos=(3,6)) sizer.Add(PrintCommandButton(self, 'G1 Z10 F200', 'print-move-z10.png'), pos=(0,7)) sizer.Add(PrintCommandButton(self, 'G1 Z1 F200', 'print-move-z1.png'), pos=(1,7)) sizer.Add(PrintCommandButton(self, 'G1 Z0.1 F200', 'print-move-z0.1.png'), pos=(2,7)) sizer.Add(PrintCommandButton(self, 'G28 Z0', 'print-move-home.png'), pos=(3,7)) sizer.Add(PrintCommandButton(self, 'G1 Z-0.1 F200', 'print-move-z-0.1.png'), pos=(4,7)) sizer.Add(PrintCommandButton(self, 'G1 Z-1 F200', 'print-move-z-1.png'), pos=(5,7)) sizer.Add(PrintCommandButton(self, 'G1 Z-10 F200', 'print-move-z-10.png'), pos=(6,7)) self.sizer.AddGrowableRow(3) self.sizer.AddGrowableCol(0) self.Bind(wx.EVT_CLOSE, self.OnClose) self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect) #self.loadButton.Bind(wx.EVT_BUTTON, self.OnLoad) self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint) self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel) self.Bind(wx.EVT_SPINCTRL, self.OnTempChange, self.temperatureSelect) self.Layout() self.Fit() self.Centre() self.UpdateButtonStates() self.UpdateProgress() def UpdateButtonStates(self): self.connectButton.Enable(not self.machineConnected) #self.loadButton.Enable(self.printIdx == None) self.printButton.Enable(self.machineConnected and self.gcodeList != None and self.printIdx == None) self.cancelButton.Enable(self.printIdx != None) self.temperatureSelect.Enable(self.machineConnected) self.directControlPanel.Enable(self.machineConnected) def UpdateProgress(self): status = "" if self.gcode != None: status += "Filament: %.2fm %.2fg\n" % (self.gcode.extrusionAmount / 1000, self.gcode.calculateWeight() * 1000) cost = self.gcode.calculateCost() if cost != False: status += "Filament cost: %s\n" % (cost) status += "Print time: %02d:%02d\n" % (int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60)) if self.printIdx == None: self.progress.SetValue(0) if self.gcodeList != None: status += 'Line: -/%d\n' % (len(self.gcodeList)) else: status += 'Line: %d/%d\n' % (self.printIdx, len(self.gcodeList)) self.progress.SetValue(self.printIdx) if self.temp != None: status += 'Temp: %d\n' % (self.temp) self.statsText.SetLabel(status.strip()) self.Layout() def OnConnect(self, e): if self.machineCom != None: self.machineCom.close() self.thread.join() self.machineCom = machineCom.MachineCom() self.thread = threading.Thread(target=self.PrinterMonitor) self.thread.start() self.UpdateButtonStates() def OnLoad(self, e): pass def OnPrint(self, e): if not self.machineConnected: return if self.gcodeList == None: return if self.printIdx != None: return self.printIdx = 1 self.sendLine(0) self.sendCnt = self.bufferLineCount self.UpdateButtonStates() def OnCancel(self, e): self.printIdx = None self.UpdateButtonStates() def OnClose(self, e): global printWindowHandle printWindowHandle = None if self.machineCom != None: self.machineCom.close() self.thread.join() self.Destroy() def OnTempChange(self, e): self.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue())) def LoadGCodeFile(self, filename): if self.printIdx != None: return #Send an initial M110 to reset the line counter to zero. gcodeList = ["M110"] for line in open(filename, 'r'): if ';' in line: line = line[0:line.find(';')] line = line.strip() if len(line) > 0: gcodeList.append(line) gcode = gcodeInterpreter.gcode() gcode.loadList(gcodeList) print "Loaded: %s (%d)" % (filename, len(gcodeList)) self.progress.SetRange(len(gcodeList)) self.gcode = gcode self.gcodeList = gcodeList self.UpdateButtonStates() self.UpdateProgress() def sendCommand(self, cmd): if self.machineConnected: if self.printIdx == None: self.machineCom.sendCommand(cmd) else: self.sendList.append(cmd) def sendLine(self, lineNr): if lineNr >= len(self.gcodeList): return False checksum = reduce(lambda x,y:x^y, map(ord, "N%d%s" % (lineNr, self.gcodeList[lineNr]))) self.machineCom.sendCommand("N%d%s*%d" % (lineNr, self.gcodeList[lineNr], checksum)) return True def PrinterMonitor(self): while True: line = self.machineCom.readline() if line == None: self.machineConnected = False wx.CallAfter(self.UpdateButtonStates) return if self.machineConnected: while self.sendCnt > 0: self.sendLine(self.printIdx) self.printIdx += 1 self.sendCnt -= 1 elif line.startswith("start"): self.machineConnected = True wx.CallAfter(self.UpdateButtonStates) if 'T:' in line: self.temp = float(re.search("[0-9\.]*", line.split('T:')[1]).group(0)) wx.CallAfter(self.UpdateProgress) if self.printIdx == None: if line == '': #When we have a communication "timeout" and we're not sending gcode, then read the temperature. self.machineCom.sendCommand("M105") else: if line.startswith("ok"): if len(self.sendList) > 0: self.machineCom.sendCommand(self.sendList.pop(0)) else: if self.sendLine(self.printIdx): self.printIdx += 1 else: self.printIdx = None wx.CallAfter(self.UpdateButtonStates) wx.CallAfter(self.UpdateProgress) elif "resend" in line.lower() or "rs" in line: try: lineNr=int(line.replace("N:"," ").replace("N"," ").replace(":"," ").split()[-1]) except: if "rs" in line: lineNr=int(line.split()[1]) self.printIdx = lineNr #we should actually resend the line here, but we also get an "ok" for each error from Marlin. And thus we'll resend on the OK.