chiark / gitweb /
Merge upstream, fixed conflicts, showing complete toolbar at bottom of preview
[cura.git] / Cura / gui / printWindow.py
1 from __future__ import absolute_import\r
2 import __init__\r
3 \r
4 import wx, threading, re, subprocess, sys\r
5 \r
6 from gui import machineCom\r
7 from gui import icon\r
8 from util import profile\r
9 from util import gcodeInterpreter\r
10 \r
11 printWindowMonitorHandle = None\r
12 \r
13 def printFile(filename):\r
14         global printWindowMonitorHandle\r
15         if printWindowMonitorHandle == None:\r
16                 printWindowMonitorHandle = printProcessMonitor()\r
17         printWindowMonitorHandle.loadFile(filename)\r
18 \r
19 \r
20 def startPrintInterface(filename):\r
21         #startPrintInterface is called from the main script when we want the printer interface to run in a seperate process.\r
22         # 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).\r
23         app = wx.App(False)\r
24         printWindowHandle = printWindow()\r
25         printWindowHandle.Show(True)\r
26         printWindowHandle.Raise()\r
27         printWindowHandle.OnConnect(None)\r
28         printWindowHandle.LoadGCodeFile(filename)\r
29         app.MainLoop()\r
30 \r
31 class printProcessMonitor():\r
32         def __init__(self):\r
33                 self.handle = None\r
34         \r
35         def loadFile(self, filename):\r
36                 if self.handle == None:\r
37                         self.handle = subprocess.Popen([sys.executable, sys.argv[0], '-r', filename], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)\r
38                         self.thread = threading.Thread(target=self.Monitor)\r
39                         self.thread.start()\r
40                 else:\r
41                         self.handle.stdin.write(filename + '\n')\r
42         \r
43         def Monitor(self):\r
44                 p = self.handle\r
45                 line = p.stdout.readline()\r
46                 while(len(line) > 0):\r
47                         print line.rstrip()\r
48                         line = p.stdout.readline()\r
49                 p.wait()\r
50                 self.handle = None\r
51                 self.thread = None\r
52 \r
53 class printWindow(wx.Frame):\r
54         "Main user interface window"\r
55         def __init__(self):\r
56                 super(printWindow, self).__init__(None, -1, title='Printing')\r
57                 self.machineCom = None\r
58                 self.machineConnected = False\r
59                 self.thread = None\r
60                 self.gcode = None\r
61                 self.gcodeList = None\r
62                 self.sendList = []\r
63                 self.printIdx = None\r
64                 self.temp = None\r
65                 self.bufferLineCount = 4\r
66                 self.sendCnt = 0\r
67 \r
68                 #self.SetIcon(icon.getMainIcon())\r
69                 \r
70                 self.SetSizer(wx.BoxSizer())\r
71                 self.panel = wx.Panel(self)\r
72                 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)\r
73                 self.sizer = wx.GridBagSizer(2, 2)\r
74                 self.panel.SetSizer(self.sizer)\r
75                 \r
76                 sb = wx.StaticBox(self.panel, label="Statistics")\r
77                 boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)\r
78                 self.statsText = wx.StaticText(self.panel, -1, "Filament: ####.##m #.##g\nPrint time: #####:##")\r
79                 boxsizer.Add(self.statsText, flag=wx.LEFT, border=5)\r
80                 \r
81                 self.sizer.Add(boxsizer, pos=(0,0), span=(4,1), flag=wx.EXPAND)\r
82                 \r
83                 self.connectButton = wx.Button(self.panel, -1, 'Connect')\r
84                 #self.loadButton = wx.Button(self.panel, -1, 'Load GCode')\r
85                 self.printButton = wx.Button(self.panel, -1, 'Print GCode')\r
86                 self.cancelButton = wx.Button(self.panel, -1, 'Cancel print')\r
87                 self.progress = wx.Gauge(self.panel, -1)\r
88                 \r
89                 h = self.connectButton.GetSize().GetHeight()\r
90                 self.temperatureSelect = wx.SpinCtrl(self.panel, -1, '0', size=(21*3,21), style=wx.SP_ARROW_KEYS)\r
91                 self.temperatureSelect.SetRange(0, 400)\r
92                 \r
93                 self.sizer.Add(self.connectButton, pos=(0,1))\r
94                 #self.sizer.Add(self.loadButton, pos=(1,1))\r
95                 self.sizer.Add(self.printButton, pos=(2,1))\r
96                 self.sizer.Add(self.cancelButton, pos=(3,1))\r
97                 self.sizer.Add(self.progress, pos=(4,0), span=(1,2), flag=wx.EXPAND)\r
98 \r
99                 self.sizer.Add(wx.StaticText(self.panel, -1, "Temp:"), pos=(0,3))\r
100                 self.sizer.Add(self.temperatureSelect, pos=(0,4))\r
101 \r
102                 self.sizer.AddGrowableRow(3)\r
103                 self.sizer.AddGrowableCol(0)\r
104                 \r
105                 self.Bind(wx.EVT_CLOSE, self.OnClose)\r
106                 self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect)\r
107                 #self.loadButton.Bind(wx.EVT_BUTTON, self.OnLoad)\r
108                 self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint)\r
109                 self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)\r
110                 \r
111                 self.Bind(wx.EVT_SPINCTRL, self.OnTempChange, self.temperatureSelect)\r
112                 \r
113                 self.Layout()\r
114                 self.Fit()\r
115                 self.Centre()\r
116                 \r
117                 self.UpdateButtonStates()\r
118                 self.UpdateProgress()\r
119         \r
120         def UpdateButtonStates(self):\r
121                 self.connectButton.Enable(not self.machineConnected)\r
122                 #self.loadButton.Enable(self.printIdx == None)\r
123                 self.printButton.Enable(self.machineConnected and self.gcodeList != None and self.printIdx == None)\r
124                 self.cancelButton.Enable(self.printIdx != None)\r
125         \r
126         def UpdateProgress(self):\r
127                 status = ""\r
128                 if self.gcode != None:\r
129                         status += "Filament: %.2fm %.2fg\n" % (self.gcode.extrusionAmount / 1000, self.gcode.calculateWeight() * 1000)\r
130                         cost_kg = float(profile.getPreference('filament_cost_kg'))\r
131                         cost_meter = float(profile.getPreference('filament_cost_meter'))\r
132                         if cost_kg > 0.0 and cost_meter > 0.0:\r
133                                 status += "Filament cost: %.2f / %.2f\n" % (self.gcode.calculateWeight() * cost_kg, self.gcode.extrusionAmount / 1000 * cost_meter)\r
134                         elif cost_kg > 0.0:\r
135                                 status += "Filament cost: %.2f\n" % (self.gcode.calculateWeight() * cost_kg)\r
136                         elif cost_meter > 0.0:\r
137                                 status += "Filament cost: %.2f\n" % (self.gcode.extrusionAmount / 1000 * cost_meter)\r
138                         status += "Print time: %02d:%02d\n" % (int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60))\r
139                 if self.printIdx == None:\r
140                         self.progress.SetValue(0)\r
141                         if self.gcodeList != None:\r
142                                 status += 'Line: -/%d\n' % (len(self.gcodeList))\r
143                 else:\r
144                         status += 'Line: %d/%d\n' % (self.printIdx, len(self.gcodeList))\r
145                         self.progress.SetValue(self.printIdx)\r
146                 if self.temp != None:\r
147                         status += 'Temp: %d\n' % (self.temp)\r
148                 self.statsText.SetLabel(status.strip())\r
149                 self.Layout()\r
150         \r
151         def OnConnect(self, e):\r
152                 if self.machineCom != None:\r
153                         self.machineCom.close()\r
154                         self.thread.join()\r
155                 self.machineCom = machineCom.MachineCom()\r
156                 self.thread = threading.Thread(target=self.PrinterMonitor)\r
157                 self.thread.start()\r
158                 self.UpdateButtonStates()\r
159         \r
160         def OnLoad(self, e):\r
161                 pass\r
162         \r
163         def OnPrint(self, e):\r
164                 if not self.machineConnected:\r
165                         return\r
166                 if self.gcodeList == None:\r
167                         return\r
168                 if self.printIdx != None:\r
169                         return\r
170                 self.printIdx = 1\r
171                 self.sendLine(0)\r
172                 self.sendCnt = self.bufferLineCount\r
173                 self.UpdateButtonStates()\r
174         \r
175         def OnCancel(self, e):\r
176                 self.printIdx = None\r
177                 self.UpdateButtonStates()\r
178         \r
179         def OnClose(self, e):\r
180                 global printWindowHandle\r
181                 printWindowHandle = None\r
182                 if self.machineCom != None:\r
183                         self.machineCom.close()\r
184                         self.thread.join()\r
185                 self.Destroy()\r
186 \r
187         def OnTempChange(self, e):\r
188                 self.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))\r
189 \r
190         def LoadGCodeFile(self, filename):\r
191                 if self.printIdx != None:\r
192                         return\r
193                 #Send an initial M110 to reset the line counter to zero.\r
194                 gcodeList = ["M110"]\r
195                 for line in open(filename, 'r'):\r
196                         if ';' in line:\r
197                                 line = line[0:line.find(';')]\r
198                         line = line.strip()\r
199                         if len(line) > 0:\r
200                                 gcodeList.append(line)\r
201                 gcode = gcodeInterpreter.gcode()\r
202                 gcode.loadList(gcodeList)\r
203                 print "Loaded: %s (%d)" % (filename, len(gcodeList))\r
204                 self.progress.SetRange(len(gcodeList))\r
205                 self.gcode = gcode\r
206                 self.gcodeList = gcodeList\r
207                 self.UpdateButtonStates()\r
208                 self.UpdateProgress()\r
209                 \r
210         def sendCommand(self, cmd):\r
211                 if self.machineConnected:\r
212                         if self.printIdx == None:\r
213                                 self.machineCom.sendCommand(cmd)\r
214                         else:\r
215                                 self.sendList.append(cmd)\r
216 \r
217         def sendLine(self, lineNr):\r
218                 if lineNr >= len(self.gcodeList):\r
219                         return False\r
220                 checksum = reduce(lambda x,y:x^y, map(ord, "N%d%s" % (lineNr, self.gcodeList[lineNr])))\r
221                 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, self.gcodeList[lineNr], checksum))\r
222                 return True\r
223 \r
224         def PrinterMonitor(self):\r
225                 while True:\r
226                         line = self.machineCom.readline()\r
227                         if line == None:\r
228                                 self.machineConnected = False\r
229                                 wx.CallAfter(self.UpdateButtonStates)\r
230                                 return\r
231                         if self.machineConnected:\r
232                                 while self.sendCnt > 0:\r
233                                         self.sendLine(self.printIdx)\r
234                                         self.printIdx += 1\r
235                                         self.sendCnt -= 1\r
236                         elif line.startswith("start"):\r
237                                 self.machineConnected = True\r
238                                 wx.CallAfter(self.UpdateButtonStates)\r
239                         if 'T:' in line:\r
240                                 self.temp = float(re.search("[0-9\.]*", line.split('T:')[1]).group(0))\r
241                                 wx.CallAfter(self.UpdateProgress)\r
242                         if self.printIdx == None:\r
243                                 if line == '':  #When we have a communication "timeout" and we're not sending gcode, then read the temperature.\r
244                                         self.machineCom.sendCommand("M105")\r
245                         else:\r
246                                 if line.startswith("ok"):\r
247                                         if len(self.sendList) > 0:\r
248                                                 self.machineCom.sendCommand(self.sendList.pop(0))\r
249                                         else:\r
250                                                 if self.sendLine(self.printIdx):\r
251                                                         self.printIdx += 1\r
252                                                 else:\r
253                                                         self.printIdx = None\r
254                                                         wx.CallAfter(self.UpdateButtonStates)\r
255                                                 wx.CallAfter(self.UpdateProgress)\r
256                                 elif "resend" in line.lower() or "rs" in line:\r
257                                         try:\r
258                                                 lineNr=int(line.replace("N:"," ").replace("N"," ").replace(":"," ").split()[-1])\r
259                                         except:\r
260                                                 if "rs" in line:\r
261                                                         lineNr=int(line.split()[1])\r
262                                         self.printIdx = lineNr\r
263                                         #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.\r
264 \r