chiark / gitweb /
Update print window to use multiprocessing instead of starting our own subprocess.
[cura.git] / Cura / gui / printWindow.py
1 # coding=utf-8
2 from __future__ import absolute_import
3
4 import threading
5 import re
6 import subprocess
7 import multiprocessing
8 import sys
9 import time
10 import platform
11 import os
12 import power
13
14 import wx
15 from wx.lib import buttons
16
17 from Cura.gui.util import webcam
18 from Cura.gui.util import taskbar
19 from Cura.util import machineCom
20 from Cura.util import gcodeInterpreter
21 from Cura.util.resources import getPathForImage
22
23 printWindowMonitorHandle = None
24
25 def printFile(filename):
26         global printWindowMonitorHandle
27         if printWindowMonitorHandle is None:
28                 printWindowMonitorHandle = printProcessMonitor()
29         printWindowMonitorHandle.loadFile(filename)
30
31
32 def startPrintInterface(filename, queue):
33         #startPrintInterface is called from the main script when we want the printer interface to run in a separate process.
34         # It needs to run in a separate process, as any running python code blocks the GCode sender python code (http://wiki.python.org/moin/GlobalInterpreterLock).
35         app = wx.App(False)
36         printWindowHandle = printWindow(queue)
37         printWindowHandle.Show(True)
38         printWindowHandle.Raise()
39         printWindowHandle.OnConnect(None)
40         t = threading.Thread(target=printWindowHandle.LoadGCodeFile, args=(filename,))
41         t.daemon = True
42         t.start()
43         app.MainLoop()
44
45 class printProcessMonitor():
46         def __init__(self):
47                 self.handle = None
48                 self.queue = multiprocessing.Queue()
49
50         def loadFile(self, filename):
51                 if self.handle is None or not self.handle.is_alive():
52                         if self.handle is not None:
53                                 self.handle.join()
54                         self.handle = multiprocessing.Process(target=startPrintInterface, args=(filename, self.queue))
55                         self.handle.start()
56                 else:
57                         self.queue.put(filename)
58
59 class PrintCommandButton(buttons.GenBitmapButton):
60         def __init__(self, parent, commandList, bitmapFilename, size=(20, 20)):
61                 self.bitmap = wx.Bitmap(getPathForImage(bitmapFilename))
62                 super(PrintCommandButton, self).__init__(parent.directControlPanel, -1, self.bitmap, size=size)
63
64                 self.commandList = commandList
65                 self.parent = parent
66
67                 self.SetBezelWidth(1)
68                 self.SetUseFocusIndicator(False)
69
70                 self.Bind(wx.EVT_BUTTON, self.OnClick)
71
72         def OnClick(self, e):
73                 if self.parent.machineCom is None or self.parent.machineCom.isPrinting():
74                         return;
75                 for cmd in self.commandList:
76                         self.parent.machineCom.sendCommand(cmd)
77                 e.Skip()
78
79
80 class printWindow(wx.Frame):
81         "Main user interface window"
82
83         def __init__(self, queue):
84                 super(printWindow, self).__init__(None, -1, title='Printing')
85                 self.machineCom = None
86                 self.gcode = None
87                 self.gcodeList = None
88                 self.sendList = []
89                 self.temp = None
90                 self.bedTemp = None
91                 self.bufferLineCount = 4
92                 self.sendCnt = 0
93                 self.feedrateRatioOuterWall = 1.0
94                 self.feedrateRatioInnerWall = 1.0
95                 self.feedrateRatioFill = 1.0
96                 self.feedrateRatioSupport = 1.0
97                 self.pause = False
98                 self.termHistory = []
99                 self.termHistoryIdx = 0
100                 self.filenameQueue = queue
101
102                 self.cam = None
103                 if webcam.hasWebcamSupport():
104                         self.cam = webcam.webcam()
105                         if not self.cam.hasCamera():
106                                 self.cam = None
107
108                 self.SetSizer(wx.BoxSizer())
109                 self.panel = wx.Panel(self)
110                 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
111                 self.sizer = wx.GridBagSizer(2, 2)
112                 self.panel.SetSizer(self.sizer)
113
114                 sb = wx.StaticBox(self.panel, label="Statistics")
115                 boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)
116
117                 self.powerWarningText = wx.StaticText(parent=self.panel,
118                         id=-1,
119                         label="Your computer is running on battery power.\nConnect your computer to AC power or your print might not finish.",
120                         style=wx.ALIGN_CENTER)
121                 self.powerWarningText.SetBackgroundColour('red')
122                 self.powerWarningText.SetForegroundColour('white')
123                 boxsizer.AddF(self.powerWarningText, flags=wx.SizerFlags().Expand().Border(wx.BOTTOM, 10))
124                 self.powerManagement = power.PowerManagement()
125                 self.powerWarningTimer = wx.Timer(self)
126                 self.Bind(wx.EVT_TIMER, self.OnPowerWarningChange, self.powerWarningTimer)
127                 self.OnPowerWarningChange(None)
128                 self.powerWarningTimer.Start(10000)
129
130                 self.statsText = wx.StaticText(self.panel, -1,
131                         "Filament: ####.##m #.##g\nEstimated print time: #####:##\nMachine state:\nDetecting baudrateXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
132                 boxsizer.Add(self.statsText, flag=wx.LEFT, border=5)
133
134                 self.sizer.Add(boxsizer, pos=(0, 0), span=(7, 1), flag=wx.EXPAND)
135
136                 self.connectButton = wx.Button(self.panel, -1, 'Connect')
137                 #self.loadButton = wx.Button(self.panel, -1, 'Load')
138                 self.printButton = wx.Button(self.panel, -1, 'Print')
139                 self.pauseButton = wx.Button(self.panel, -1, 'Pause')
140                 self.cancelButton = wx.Button(self.panel, -1, 'Cancel print')
141                 self.machineLogButton = wx.Button(self.panel, -1, 'Error log')
142                 self.progress = wx.Gauge(self.panel, -1)
143
144                 self.sizer.Add(self.connectButton, pos=(1, 1), flag=wx.EXPAND)
145                 #self.sizer.Add(self.loadButton, pos=(1,1), flag=wx.EXPAND)
146                 self.sizer.Add(self.printButton, pos=(2, 1), flag=wx.EXPAND)
147                 self.sizer.Add(self.pauseButton, pos=(3, 1), flag=wx.EXPAND)
148                 self.sizer.Add(self.cancelButton, pos=(4, 1), flag=wx.EXPAND)
149                 self.sizer.Add(self.machineLogButton, pos=(5, 1), flag=wx.EXPAND)
150                 self.sizer.Add(self.progress, pos=(7, 0), span=(1, 7), flag=wx.EXPAND)
151
152                 nb = wx.Notebook(self.panel)
153                 self.sizer.Add(nb, pos=(0, 2), span=(7, 4), flag=wx.EXPAND)
154
155                 self.temperaturePanel = wx.Panel(nb)
156                 sizer = wx.GridBagSizer(2, 2)
157                 self.temperaturePanel.SetSizer(sizer)
158
159                 self.temperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
160                 self.temperatureSelect.SetRange(0, 400)
161                 self.bedTemperatureLabel = wx.StaticText(self.temperaturePanel, -1, "BedTemp:")
162                 self.bedTemperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21),
163                         style=wx.SP_ARROW_KEYS)
164                 self.bedTemperatureSelect.SetRange(0, 400)
165                 self.bedTemperatureLabel.Show(False)
166                 self.bedTemperatureSelect.Show(False)
167
168                 self.temperatureGraph = temperatureGraph(self.temperaturePanel)
169
170                 sizer.Add(wx.StaticText(self.temperaturePanel, -1, "Temp:"), pos=(0, 0))
171                 sizer.Add(self.temperatureSelect, pos=(0, 1))
172                 sizer.Add(self.bedTemperatureLabel, pos=(1, 0))
173                 sizer.Add(self.bedTemperatureSelect, pos=(1, 1))
174                 sizer.Add(self.temperatureGraph, pos=(2, 0), span=(1, 2), flag=wx.EXPAND)
175                 sizer.AddGrowableRow(2)
176                 sizer.AddGrowableCol(1)
177
178                 nb.AddPage(self.temperaturePanel, 'Temp')
179
180                 self.directControlPanel = wx.Panel(nb)
181
182                 sizer = wx.GridBagSizer(2, 2)
183                 self.directControlPanel.SetSizer(sizer)
184                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y100 F6000', 'G90'], 'print-move-y100.png'), pos=(0, 3))
185                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y10 F6000', 'G90'], 'print-move-y10.png'), pos=(1, 3))
186                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y1 F6000', 'G90'], 'print-move-y1.png'), pos=(2, 3))
187
188                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-1 F6000', 'G90'], 'print-move-y-1.png'), pos=(4, 3))
189                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-10 F6000', 'G90'], 'print-move-y-10.png'), pos=(5, 3))
190                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-100 F6000', 'G90'], 'print-move-y-100.png'), pos=(6, 3))
191
192                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-100 F6000', 'G90'], 'print-move-x-100.png'), pos=(3, 0))
193                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-10 F6000', 'G90'], 'print-move-x-10.png'), pos=(3, 1))
194                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-1 F6000', 'G90'], 'print-move-x-1.png'), pos=(3, 2))
195
196                 sizer.Add(PrintCommandButton(self, ['G28 X0 Y0'], 'print-move-home.png'), pos=(3, 3))
197
198                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X1 F6000', 'G90'], 'print-move-x1.png'), pos=(3, 4))
199                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X10 F6000', 'G90'], 'print-move-x10.png'), pos=(3, 5))
200                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X100 F6000', 'G90'], 'print-move-x100.png'), pos=(3, 6))
201
202                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z10 F200', 'G90'], 'print-move-z10.png'), pos=(0, 8))
203                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z1 F200', 'G90'], 'print-move-z1.png'), pos=(1, 8))
204                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z0.1 F200', 'G90'], 'print-move-z0.1.png'), pos=(2, 8))
205
206                 sizer.Add(PrintCommandButton(self, ['G28 Z0'], 'print-move-home.png'), pos=(3, 8))
207
208                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-0.1 F200', 'G90'], 'print-move-z-0.1.png'), pos=(4, 8))
209                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-1 F200', 'G90'], 'print-move-z-1.png'), pos=(5, 8))
210                 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-10 F200', 'G90'], 'print-move-z-10.png'), pos=(6, 8))
211
212                 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E2 F120'], 'extrude.png', size=(60, 20)), pos=(1, 10),
213                         span=(1, 3), flag=wx.EXPAND)
214                 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E-2 F120'], 'retract.png', size=(60, 20)), pos=(2, 10),
215                         span=(1, 3), flag=wx.EXPAND)
216
217                 nb.AddPage(self.directControlPanel, 'Jog')
218
219                 self.speedPanel = wx.Panel(nb)
220                 sizer = wx.GridBagSizer(2, 2)
221                 self.speedPanel.SetSizer(sizer)
222
223                 self.outerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
224                 self.outerWallSpeedSelect.SetRange(5, 1000)
225                 self.innerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
226                 self.innerWallSpeedSelect.SetRange(5, 1000)
227                 self.fillSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
228                 self.fillSpeedSelect.SetRange(5, 1000)
229                 self.supportSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
230                 self.supportSpeedSelect.SetRange(5, 1000)
231
232                 sizer.Add(wx.StaticText(self.speedPanel, -1, "Outer wall:"), pos=(0, 0))
233                 sizer.Add(self.outerWallSpeedSelect, pos=(0, 1))
234                 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(0, 2))
235                 sizer.Add(wx.StaticText(self.speedPanel, -1, "Inner wall:"), pos=(1, 0))
236                 sizer.Add(self.innerWallSpeedSelect, pos=(1, 1))
237                 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(1, 2))
238                 sizer.Add(wx.StaticText(self.speedPanel, -1, "Fill:"), pos=(2, 0))
239                 sizer.Add(self.fillSpeedSelect, pos=(2, 1))
240                 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(2, 2))
241                 sizer.Add(wx.StaticText(self.speedPanel, -1, "Support:"), pos=(3, 0))
242                 sizer.Add(self.supportSpeedSelect, pos=(3, 1))
243                 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(3, 2))
244
245                 nb.AddPage(self.speedPanel, 'Speed')
246
247                 self.termPanel = wx.Panel(nb)
248                 sizer = wx.GridBagSizer(2, 2)
249                 self.termPanel.SetSizer(sizer)
250
251                 f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
252                 self.termLog = wx.TextCtrl(self.termPanel, style=wx.TE_MULTILINE | wx.TE_DONTWRAP)
253                 self.termLog.SetFont(f)
254                 self.termLog.SetEditable(0)
255                 self.termInput = wx.TextCtrl(self.termPanel, style=wx.TE_PROCESS_ENTER)
256                 self.termInput.SetFont(f)
257
258                 sizer.Add(self.termLog, pos=(0, 0), flag=wx.EXPAND)
259                 sizer.Add(self.termInput, pos=(1, 0), flag=wx.EXPAND)
260                 sizer.AddGrowableCol(0)
261                 sizer.AddGrowableRow(0)
262
263                 nb.AddPage(self.termPanel, 'Term')
264
265                 if self.cam is not None:
266                         self.camPage = wx.Panel(nb)
267                         sizer = wx.GridBagSizer(2, 2)
268                         self.camPage.SetSizer(sizer)
269
270                         self.timelapsEnable = wx.CheckBox(self.camPage, -1, 'Enable timelapse movie recording')
271                         sizer.Add(self.timelapsEnable, pos=(0, 0), span=(1, 2), flag=wx.EXPAND)
272
273                         pages = self.cam.propertyPages()
274                         self.cam.buttons = [self.timelapsEnable]
275                         for page in pages:
276                                 button = wx.Button(self.camPage, -1, page)
277                                 button.index = pages.index(page)
278                                 sizer.Add(button, pos=(1, pages.index(page)))
279                                 button.Bind(wx.EVT_BUTTON, self.OnPropertyPageButton)
280                                 self.cam.buttons.append(button)
281
282                         self.campreviewEnable = wx.CheckBox(self.camPage, -1, 'Show preview')
283                         sizer.Add(self.campreviewEnable, pos=(2, 0), span=(1, 2), flag=wx.EXPAND)
284
285                         self.camPreview = wx.Panel(self.camPage)
286                         sizer.Add(self.camPreview, pos=(3, 0), span=(1, 2), flag=wx.EXPAND)
287
288                         nb.AddPage(self.camPage, 'Camera')
289                         self.camPreview.timer = wx.Timer(self)
290                         self.Bind(wx.EVT_TIMER, self.OnCameraTimer, self.camPreview.timer)
291                         self.camPreview.timer.Start(500)
292                         self.camPreview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
293
294                 self.sizer.AddGrowableRow(6)
295                 self.sizer.AddGrowableCol(3)
296
297                 self.Bind(wx.EVT_CLOSE, self.OnClose)
298                 self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect)
299                 #self.loadButton.Bind(wx.EVT_BUTTON, self.OnLoad)
300                 self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint)
301                 self.pauseButton.Bind(wx.EVT_BUTTON, self.OnPause)
302                 self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)
303                 self.machineLogButton.Bind(wx.EVT_BUTTON, self.OnMachineLog)
304
305                 self.Bind(wx.EVT_SPINCTRL, self.OnTempChange, self.temperatureSelect)
306                 self.Bind(wx.EVT_SPINCTRL, self.OnBedTempChange, self.bedTemperatureSelect)
307
308                 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.outerWallSpeedSelect)
309                 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.innerWallSpeedSelect)
310                 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.fillSpeedSelect)
311                 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.supportSpeedSelect)
312                 self.Bind(wx.EVT_TEXT_ENTER, self.OnTermEnterLine, self.termInput)
313                 self.termInput.Bind(wx.EVT_CHAR, self.OnTermKey)
314
315                 self.Layout()
316                 self.Fit()
317                 self.Centre()
318
319                 self.statsText.SetMinSize(self.statsText.GetSize())
320
321                 self.UpdateButtonStates()
322
323                 t = threading.Thread(target=self._readQueue)
324                 t.daemon = True
325                 t.start()
326
327         def _readQueue(self):
328                 while True:
329                         print 'get'
330                         filename = self.filenameQueue.get()
331                         print filename
332                         while self.machineCom is not None and self.machineCom.isPrinting():
333                                 time.sleep(1)
334                         self.LoadGCodeFile(filename)
335
336         def OnCameraTimer(self, e):
337                 if not self.campreviewEnable.GetValue():
338                         return
339                 if self.machineCom is not None and self.machineCom.isPrinting():
340                         return
341                 self.cam.takeNewImage()
342                 self.camPreview.Refresh()
343
344         def OnCameraEraseBackground(self, e):
345                 dc = e.GetDC()
346                 if not dc:
347                         dc = wx.ClientDC(self)
348                         rect = self.GetUpdateRegion().GetBox()
349                         dc.SetClippingRect(rect)
350                 dc.SetBackground(wx.Brush(self.camPreview.GetBackgroundColour(), wx.SOLID))
351                 if self.cam.getLastImage() is not None:
352                         self.camPreview.SetMinSize((self.cam.getLastImage().GetWidth(), self.cam.getLastImage().GetHeight()))
353                         self.camPage.Fit()
354                         dc.DrawBitmap(self.cam.getLastImage(), 0, 0)
355                 else:
356                         dc.Clear()
357
358         def OnPropertyPageButton(self, e):
359                 self.cam.openPropertyPage(e.GetEventObject().index)
360
361         def UpdateButtonStates(self):
362                 self.connectButton.Enable(self.machineCom is None or self.machineCom.isClosedOrError())
363                 #self.loadButton.Enable(self.machineCom == None or not (self.machineCom.isPrinting() or self.machineCom.isPaused()))
364                 self.printButton.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
365                 self.machineCom.isPrinting() or self.machineCom.isPaused()))
366                 self.pauseButton.Enable(
367                         self.machineCom != None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
368                 if self.machineCom != None and self.machineCom.isPaused():
369                         self.pauseButton.SetLabel('Resume')
370                 else:
371                         self.pauseButton.SetLabel('Pause')
372                 self.cancelButton.Enable(
373                         self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
374                 self.temperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
375                 self.bedTemperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
376                 self.directControlPanel.Enable(
377                         self.machineCom is not None and self.machineCom.isOperational() and not self.machineCom.isPrinting())
378                 self.machineLogButton.Show(self.machineCom is not None and self.machineCom.isClosedOrError())
379                 if self.cam != None:
380                         for button in self.cam.buttons:
381                                 button.Enable(self.machineCom is None or not self.machineCom.isPrinting())
382
383         def UpdateProgress(self):
384                 status = ""
385                 if self.gcode == None:
386                         status += "Loading gcode...\n"
387                 else:
388                         status += "Filament: %.2fm %.2fg\n" % (
389                         self.gcode.extrusionAmount / 1000, self.gcode.calculateWeight() * 1000)
390                         cost = self.gcode.calculateCost()
391                         if cost != False:
392                                 status += "Filament cost: %s\n" % (cost)
393                         status += "Estimated print time: %02d:%02d\n" % (
394                         int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60))
395                 if self.machineCom is None or not self.machineCom.isPrinting():
396                         self.progress.SetValue(0)
397                         if self.gcodeList is not None:
398                                 status += 'Line: -/%d\n' % (len(self.gcodeList))
399                 else:
400                         printTime = self.machineCom.getPrintTime() / 60
401                         printTimeLeft = self.machineCom.getPrintTimeRemainingEstimate()
402                         status += 'Line: %d/%d %d%%\n' % (self.machineCom.getPrintPos(), len(self.gcodeList),
403                                                           self.machineCom.getPrintPos() * 100 / len(self.gcodeList))
404                         if self.currentZ > 0:
405                                 status += 'Height: %0.1f\n' % (self.currentZ)
406                         status += 'Print time: %02d:%02d\n' % (int(printTime / 60), int(printTime % 60))
407                         if printTimeLeft == None:
408                                 status += 'Print time left: Unknown\n'
409                         else:
410                                 status += 'Print time left: %02d:%02d\n' % (int(printTimeLeft / 60), int(printTimeLeft % 60))
411                         self.progress.SetValue(self.machineCom.getPrintPos())
412                         taskbar.setProgress(self, self.machineCom.getPrintPos(), len(self.gcodeList))
413                 if self.machineCom != None:
414                         if self.machineCom.getTemp() > 0:
415                                 status += 'Temp: %d\n' % (self.machineCom.getTemp())
416                         if self.machineCom.getBedTemp() > 0:
417                                 status += 'Bed Temp: %d\n' % (self.machineCom.getBedTemp())
418                                 self.bedTemperatureLabel.Show(True)
419                                 self.bedTemperatureSelect.Show(True)
420                                 self.temperaturePanel.Layout()
421                         status += 'Machine state:%s\n' % (self.machineCom.getStateString())
422
423                 self.statsText.SetLabel(status.strip())
424
425         def OnConnect(self, e):
426                 if self.machineCom is not None:
427                         self.machineCom.close()
428                 self.machineCom = machineCom.MachineCom(callbackObject=self)
429                 self.UpdateButtonStates()
430                 taskbar.setBusy(self, True)
431
432         def OnLoad(self, e):
433                 pass
434
435         def OnPrint(self, e):
436                 if self.machineCom is None or not self.machineCom.isOperational():
437                         return
438                 if self.gcodeList is None:
439                         return
440                 if self.machineCom.isPrinting():
441                         return
442                 self.currentZ = -1
443                 if self.cam is not None and self.timelapsEnable.GetValue():
444                         self.cam.startTimelapse(self.filename[: self.filename.rfind('.')] + ".mpg")
445                 self.machineCom.printGCode(self.gcodeList)
446                 self.UpdateButtonStates()
447
448         def OnCancel(self, e):
449                 self.pauseButton.SetLabel('Pause')
450                 self.machineCom.cancelPrint()
451                 self.machineCom.sendCommand("M84")
452                 self.UpdateButtonStates()
453
454         def OnPause(self, e):
455                 if self.machineCom.isPaused():
456                         self.machineCom.setPause(False)
457                 else:
458                         self.machineCom.setPause(True)
459
460         def OnMachineLog(self, e):
461                 LogWindow('\n'.join(self.machineCom.getLog()))
462
463         def OnClose(self, e):
464                 global printWindowHandle
465                 printWindowHandle = None
466                 if self.machineCom != None:
467                         self.machineCom.close()
468                 self.Destroy()
469
470         def OnTempChange(self, e):
471                 self.machineCom.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))
472
473         def OnBedTempChange(self, e):
474                 self.machineCom.sendCommand("M140 S%d" % (self.bedTemperatureSelect.GetValue()))
475
476         def OnSpeedChange(self, e):
477                 if self.machineCom is None:
478                         return
479                 self.machineCom.setFeedrateModifier('WALL-OUTER', self.outerWallSpeedSelect.GetValue() / 100.0)
480                 self.machineCom.setFeedrateModifier('WALL-INNER', self.innerWallSpeedSelect.GetValue() / 100.0)
481                 self.machineCom.setFeedrateModifier('FILL', self.fillSpeedSelect.GetValue() / 100.0)
482                 self.machineCom.setFeedrateModifier('SUPPORT', self.supportSpeedSelect.GetValue() / 100.0)
483
484         def AddTermLog(self, line):
485                 self.termLog.AppendText(unicode(line, 'utf-8', 'replace'))
486                 l = len(self.termLog.GetValue())
487                 self.termLog.SetCaret(wx.Caret(self.termLog, (l, l)))
488
489         def OnTermEnterLine(self, e):
490                 line = self.termInput.GetValue()
491                 if line == '':
492                         return
493                 self.termLog.AppendText('>%s\n' % (line))
494                 self.machineCom.sendCommand(line)
495                 self.termHistory.append(line)
496                 self.termHistoryIdx = len(self.termHistory)
497                 self.termInput.SetValue('')
498
499         def OnTermKey(self, e):
500                 if len(self.termHistory) > 0:
501                         if e.GetKeyCode() == wx.WXK_UP:
502                                 self.termHistoryIdx = self.termHistoryIdx - 1
503                                 if self.termHistoryIdx < 0:
504                                         self.termHistoryIdx = len(self.termHistory) - 1
505                                 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
506                         if e.GetKeyCode() == wx.WXK_DOWN:
507                                 self.termHistoryIdx = self.termHistoryIdx - 1
508                                 if self.termHistoryIdx >= len(self.termHistory):
509                                         self.termHistoryIdx = 0
510                                 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
511                 e.Skip()
512
513         def OnPowerWarningChange(self, e):
514                 type = self.powerManagement.get_providing_power_source_type()
515                 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
516                         self.powerWarningText.Hide()
517                         self.panel.Layout()
518                         self.Layout()
519                 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
520                         self.powerWarningText.Show()
521                         self.panel.Layout()
522                         self.Layout()
523
524         def LoadGCodeFile(self, filename):
525                 if self.machineCom is not None and self.machineCom.isPrinting():
526                         return
527                 #Send an initial M110 to reset the line counter to zero.
528                 prevLineType = lineType = 'CUSTOM'
529                 gcodeList = ["M110"]
530                 for line in open(filename, 'r'):
531                         if line.startswith(';TYPE:'):
532                                 lineType = line[6:].strip()
533                         if ';' in line:
534                                 line = line[0:line.find(';')]
535                         line = line.strip()
536                         if len(line) > 0:
537                                 if prevLineType != lineType:
538                                         gcodeList.append((line, lineType, ))
539                                 else:
540                                         gcodeList.append(line)
541                                 prevLineType = lineType
542                 gcode = gcodeInterpreter.gcode()
543                 gcode.loadList(gcodeList)
544                 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
545                 self.filename = filename
546                 self.gcode = gcode
547                 self.gcodeList = gcodeList
548
549                 wx.CallAfter(self.progress.SetRange, len(gcodeList))
550                 wx.CallAfter(self.UpdateButtonStates)
551                 wx.CallAfter(self.UpdateProgress)
552                 wx.CallAfter(self.SetTitle, 'Printing: %s' % (filename))
553
554         def sendLine(self, lineNr):
555                 if lineNr >= len(self.gcodeList):
556                         return False
557                 line = self.gcodeList[lineNr]
558                 try:
559                         if ('M104' in line or 'M109' in line) and 'S' in line:
560                                 n = int(re.search('S([0-9]*)', line).group(1))
561                                 wx.CallAfter(self.temperatureSelect.SetValue, n)
562                         if ('M140' in line or 'M190' in line) and 'S' in line:
563                                 n = int(re.search('S([0-9]*)', line).group(1))
564                                 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
565                 except:
566                         print "Unexpected error:", sys.exc_info()
567                 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
568                 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
569                 return True
570
571         def mcLog(self, message):
572                 #print message
573                 pass
574
575         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
576                 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
577 #               ToFix, this causes problems with setting the temperature with the keyboard
578 #               if self.temperatureSelect.GetValue() != targetTemp:
579 #                       wx.CallAfter(self.temperatureSelect.SetValue, targetTemp)
580 #               if self.bedTemperatureSelect.GetValue() != bedTargetTemp:
581 #                       wx.CallAfter(self.bedTemperatureSelect.SetValue, bedTargetTemp)
582
583         def mcStateChange(self, state):
584                 if self.machineCom is not None:
585                         if state == self.machineCom.STATE_OPERATIONAL and self.cam is not None:
586                                 self.cam.endTimelapse()
587                         if state == self.machineCom.STATE_OPERATIONAL:
588                                 taskbar.setBusy(self, False)
589                         if self.machineCom.isClosedOrError():
590                                 taskbar.setBusy(self, False)
591                         if self.machineCom.isPaused():
592                                 taskbar.setPause(self, True)
593                 wx.CallAfter(self.UpdateButtonStates)
594                 wx.CallAfter(self.UpdateProgress)
595
596         def mcMessage(self, message):
597                 wx.CallAfter(self.AddTermLog, message)
598
599         def mcProgress(self, lineNr):
600                 wx.CallAfter(self.UpdateProgress)
601
602         def mcZChange(self, newZ):
603                 self.currentZ = newZ
604                 if self.cam is not None:
605                         wx.CallAfter(self.cam.takeNewImage)
606                         wx.CallAfter(self.camPreview.Refresh)
607
608
609 class temperatureGraph(wx.Panel):
610         def __init__(self, parent):
611                 super(temperatureGraph, self).__init__(parent)
612
613                 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
614                 self.Bind(wx.EVT_SIZE, self.OnSize)
615                 self.Bind(wx.EVT_PAINT, self.OnDraw)
616
617                 self.lastDraw = time.time() - 1.0
618                 self.points = []
619                 self.backBuffer = None
620                 self.addPoint(0, 0, 0, 0)
621                 self.SetMinSize((320, 200))
622
623         def OnEraseBackground(self, e):
624                 pass
625
626         def OnSize(self, e):
627                 if self.backBuffer == None or self.GetSize() != self.backBuffer.GetSize():
628                         self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
629                         self.UpdateDrawing(True)
630
631         def OnDraw(self, e):
632                 dc = wx.BufferedPaintDC(self, self.backBuffer)
633
634         def UpdateDrawing(self, force=False):
635                 now = time.time()
636                 if not force and now - self.lastDraw < 1.0:
637                         return
638                 self.lastDraw = now
639                 dc = wx.MemoryDC()
640                 dc.SelectObject(self.backBuffer)
641                 dc.Clear()
642                 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
643                 w, h = self.GetSizeTuple()
644                 bgLinePen = wx.Pen('#A0A0A0')
645                 tempPen = wx.Pen('#FF4040')
646                 tempSPPen = wx.Pen('#FFA0A0')
647                 tempPenBG = wx.Pen('#FFD0D0')
648                 bedTempPen = wx.Pen('#4040FF')
649                 bedTempSPPen = wx.Pen('#A0A0FF')
650                 bedTempPenBG = wx.Pen('#D0D0FF')
651
652                 #Draw the background up to the current temperatures.
653                 x0 = 0
654                 t0 = 0
655                 bt0 = 0
656                 tSP0 = 0
657                 btSP0 = 0
658                 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
659                         x1 = int(w - (now - t))
660                         for x in xrange(x0, x1 + 1):
661                                 t = float(x - x0) / float(x1 - x0 + 1) * (temp - t0) + t0
662                                 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
663                                 dc.SetPen(tempPenBG)
664                                 dc.DrawLine(x, h, x, h - (t * h / 300))
665                                 dc.SetPen(bedTempPenBG)
666                                 dc.DrawLine(x, h, x, h - (bt * h / 300))
667                         t0 = temp
668                         bt0 = bedTemp
669                         tSP0 = tempSP
670                         btSP0 = bedTempSP
671                         x0 = x1 + 1
672
673                 #Draw the grid
674                 for x in xrange(w, 0, -30):
675                         dc.SetPen(bgLinePen)
676                         dc.DrawLine(x, 0, x, h)
677                 tmpNr = 0
678                 for y in xrange(h - 1, 0, -h * 50 / 300):
679                         dc.SetPen(bgLinePen)
680                         dc.DrawLine(0, y, w, y)
681                         dc.DrawText(str(tmpNr), 0, y - dc.GetFont().GetPixelSize().GetHeight())
682                         tmpNr += 50
683                 dc.DrawLine(0, 0, w, 0)
684                 dc.DrawLine(0, 0, 0, h)
685
686                 #Draw the main lines
687                 x0 = 0
688                 t0 = 0
689                 bt0 = 0
690                 tSP0 = 0
691                 btSP0 = 0
692                 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
693                         x1 = int(w - (now - t))
694                         for x in xrange(x0, x1 + 1):
695                                 t = float(x - x0) / float(x1 - x0 + 1) * (temp - t0) + t0
696                                 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
697                                 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP - tSP0) + tSP0
698                                 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
699                                 dc.SetPen(tempSPPen)
700                                 dc.DrawPoint(x, h - (tSP * h / 300))
701                                 dc.SetPen(bedTempSPPen)
702                                 dc.DrawPoint(x, h - (btSP * h / 300))
703                                 dc.SetPen(tempPen)
704                                 dc.DrawPoint(x, h - (t * h / 300))
705                                 dc.SetPen(bedTempPen)
706                                 dc.DrawPoint(x, h - (bt * h / 300))
707                         t0 = temp
708                         bt0 = bedTemp
709                         tSP0 = tempSP
710                         btSP0 = bedTempSP
711                         x0 = x1 + 1
712
713                 del dc
714                 self.Refresh(eraseBackground=False)
715                 self.Update()
716
717                 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
718                         self.points.pop(0)
719
720         def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
721                 if bedTemp == None:
722                         bedTemp = 0
723                 if bedTempSP == None:
724                         bedTempSP = 0
725                 self.points.append((temp, tempSP, bedTemp, bedTempSP, time.time()))
726                 wx.CallAfter(self.UpdateDrawing)
727
728
729 class LogWindow(wx.Frame):
730         def __init__(self, logText):
731                 super(LogWindow, self).__init__(None, title="Machine log")
732                 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
733                 self.SetSize((500, 400))
734                 self.Centre()
735                 self.Show(True)