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