chiark / gitweb /
Remove debug messages.
[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                         filename = self.filenameQueue.get()
330                         while self.machineCom is not None and self.machineCom.isPrinting():
331                                 time.sleep(1)
332                         self.LoadGCodeFile(filename)
333
334         def OnCameraTimer(self, e):
335                 if not self.campreviewEnable.GetValue():
336                         return
337                 if self.machineCom is not None and self.machineCom.isPrinting():
338                         return
339                 self.cam.takeNewImage()
340                 self.camPreview.Refresh()
341
342         def OnCameraEraseBackground(self, e):
343                 dc = e.GetDC()
344                 if not dc:
345                         dc = wx.ClientDC(self)
346                         rect = self.GetUpdateRegion().GetBox()
347                         dc.SetClippingRect(rect)
348                 dc.SetBackground(wx.Brush(self.camPreview.GetBackgroundColour(), wx.SOLID))
349                 if self.cam.getLastImage() is not None:
350                         self.camPreview.SetMinSize((self.cam.getLastImage().GetWidth(), self.cam.getLastImage().GetHeight()))
351                         self.camPage.Fit()
352                         dc.DrawBitmap(self.cam.getLastImage(), 0, 0)
353                 else:
354                         dc.Clear()
355
356         def OnPropertyPageButton(self, e):
357                 self.cam.openPropertyPage(e.GetEventObject().index)
358
359         def UpdateButtonStates(self):
360                 self.connectButton.Enable(self.machineCom is None or self.machineCom.isClosedOrError())
361                 #self.loadButton.Enable(self.machineCom == None or not (self.machineCom.isPrinting() or self.machineCom.isPaused()))
362                 self.printButton.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
363                 self.machineCom.isPrinting() or self.machineCom.isPaused()))
364                 self.pauseButton.Enable(
365                         self.machineCom != None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
366                 if self.machineCom != None and self.machineCom.isPaused():
367                         self.pauseButton.SetLabel('Resume')
368                 else:
369                         self.pauseButton.SetLabel('Pause')
370                 self.cancelButton.Enable(
371                         self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
372                 self.temperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
373                 self.bedTemperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
374                 self.directControlPanel.Enable(
375                         self.machineCom is not None and self.machineCom.isOperational() and not self.machineCom.isPrinting())
376                 self.machineLogButton.Show(self.machineCom is not None and self.machineCom.isClosedOrError())
377                 if self.cam != None:
378                         for button in self.cam.buttons:
379                                 button.Enable(self.machineCom is None or not self.machineCom.isPrinting())
380
381         def UpdateProgress(self):
382                 status = ""
383                 if self.gcode == None:
384                         status += "Loading gcode...\n"
385                 else:
386                         status += "Filament: %.2fm %.2fg\n" % (
387                         self.gcode.extrusionAmount / 1000, self.gcode.calculateWeight() * 1000)
388                         cost = self.gcode.calculateCost()
389                         if cost != False:
390                                 status += "Filament cost: %s\n" % (cost)
391                         status += "Estimated print time: %02d:%02d\n" % (
392                         int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60))
393                 if self.machineCom is None or not self.machineCom.isPrinting():
394                         self.progress.SetValue(0)
395                         if self.gcodeList is not None:
396                                 status += 'Line: -/%d\n' % (len(self.gcodeList))
397                 else:
398                         printTime = self.machineCom.getPrintTime() / 60
399                         printTimeLeft = self.machineCom.getPrintTimeRemainingEstimate()
400                         status += 'Line: %d/%d %d%%\n' % (self.machineCom.getPrintPos(), len(self.gcodeList),
401                                                           self.machineCom.getPrintPos() * 100 / len(self.gcodeList))
402                         if self.currentZ > 0:
403                                 status += 'Height: %0.1f\n' % (self.currentZ)
404                         status += 'Print time: %02d:%02d\n' % (int(printTime / 60), int(printTime % 60))
405                         if printTimeLeft == None:
406                                 status += 'Print time left: Unknown\n'
407                         else:
408                                 status += 'Print time left: %02d:%02d\n' % (int(printTimeLeft / 60), int(printTimeLeft % 60))
409                         self.progress.SetValue(self.machineCom.getPrintPos())
410                         taskbar.setProgress(self, self.machineCom.getPrintPos(), len(self.gcodeList))
411                 if self.machineCom != None:
412                         if self.machineCom.getTemp() > 0:
413                                 status += 'Temp: %d\n' % (self.machineCom.getTemp())
414                         if self.machineCom.getBedTemp() > 0:
415                                 status += 'Bed Temp: %d\n' % (self.machineCom.getBedTemp())
416                                 self.bedTemperatureLabel.Show(True)
417                                 self.bedTemperatureSelect.Show(True)
418                                 self.temperaturePanel.Layout()
419                         status += 'Machine state:%s\n' % (self.machineCom.getStateString())
420
421                 self.statsText.SetLabel(status.strip())
422
423         def OnConnect(self, e):
424                 if self.machineCom is not None:
425                         self.machineCom.close()
426                 self.machineCom = machineCom.MachineCom(callbackObject=self)
427                 self.UpdateButtonStates()
428                 taskbar.setBusy(self, True)
429
430         def OnLoad(self, e):
431                 pass
432
433         def OnPrint(self, e):
434                 if self.machineCom is None or not self.machineCom.isOperational():
435                         return
436                 if self.gcodeList is None:
437                         return
438                 if self.machineCom.isPrinting():
439                         return
440                 self.currentZ = -1
441                 if self.cam is not None and self.timelapsEnable.GetValue():
442                         self.cam.startTimelapse(self.filename[: self.filename.rfind('.')] + ".mpg")
443                 self.machineCom.printGCode(self.gcodeList)
444                 self.UpdateButtonStates()
445
446         def OnCancel(self, e):
447                 self.pauseButton.SetLabel('Pause')
448                 self.machineCom.cancelPrint()
449                 self.machineCom.sendCommand("M84")
450                 self.UpdateButtonStates()
451
452         def OnPause(self, e):
453                 if self.machineCom.isPaused():
454                         self.machineCom.setPause(False)
455                 else:
456                         self.machineCom.setPause(True)
457
458         def OnMachineLog(self, e):
459                 LogWindow('\n'.join(self.machineCom.getLog()))
460
461         def OnClose(self, e):
462                 global printWindowHandle
463                 printWindowHandle = None
464                 if self.machineCom != None:
465                         self.machineCom.close()
466                 self.Destroy()
467
468         def OnTempChange(self, e):
469                 self.machineCom.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))
470
471         def OnBedTempChange(self, e):
472                 self.machineCom.sendCommand("M140 S%d" % (self.bedTemperatureSelect.GetValue()))
473
474         def OnSpeedChange(self, e):
475                 if self.machineCom is None:
476                         return
477                 self.machineCom.setFeedrateModifier('WALL-OUTER', self.outerWallSpeedSelect.GetValue() / 100.0)
478                 self.machineCom.setFeedrateModifier('WALL-INNER', self.innerWallSpeedSelect.GetValue() / 100.0)
479                 self.machineCom.setFeedrateModifier('FILL', self.fillSpeedSelect.GetValue() / 100.0)
480                 self.machineCom.setFeedrateModifier('SUPPORT', self.supportSpeedSelect.GetValue() / 100.0)
481
482         def AddTermLog(self, line):
483                 self.termLog.AppendText(unicode(line, 'utf-8', 'replace'))
484                 l = len(self.termLog.GetValue())
485                 self.termLog.SetCaret(wx.Caret(self.termLog, (l, l)))
486
487         def OnTermEnterLine(self, e):
488                 line = self.termInput.GetValue()
489                 if line == '':
490                         return
491                 self.termLog.AppendText('>%s\n' % (line))
492                 self.machineCom.sendCommand(line)
493                 self.termHistory.append(line)
494                 self.termHistoryIdx = len(self.termHistory)
495                 self.termInput.SetValue('')
496
497         def OnTermKey(self, e):
498                 if len(self.termHistory) > 0:
499                         if e.GetKeyCode() == wx.WXK_UP:
500                                 self.termHistoryIdx = self.termHistoryIdx - 1
501                                 if self.termHistoryIdx < 0:
502                                         self.termHistoryIdx = len(self.termHistory) - 1
503                                 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
504                         if e.GetKeyCode() == wx.WXK_DOWN:
505                                 self.termHistoryIdx = self.termHistoryIdx - 1
506                                 if self.termHistoryIdx >= len(self.termHistory):
507                                         self.termHistoryIdx = 0
508                                 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
509                 e.Skip()
510
511         def OnPowerWarningChange(self, e):
512                 type = self.powerManagement.get_providing_power_source_type()
513                 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
514                         self.powerWarningText.Hide()
515                         self.panel.Layout()
516                         self.Layout()
517                 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
518                         self.powerWarningText.Show()
519                         self.panel.Layout()
520                         self.Layout()
521
522         def LoadGCodeFile(self, filename):
523                 if self.machineCom is not None and self.machineCom.isPrinting():
524                         return
525                 #Send an initial M110 to reset the line counter to zero.
526                 prevLineType = lineType = 'CUSTOM'
527                 gcodeList = ["M110"]
528                 for line in open(filename, 'r'):
529                         if line.startswith(';TYPE:'):
530                                 lineType = line[6:].strip()
531                         if ';' in line:
532                                 line = line[0:line.find(';')]
533                         line = line.strip()
534                         if len(line) > 0:
535                                 if prevLineType != lineType:
536                                         gcodeList.append((line, lineType, ))
537                                 else:
538                                         gcodeList.append(line)
539                                 prevLineType = lineType
540                 gcode = gcodeInterpreter.gcode()
541                 gcode.loadList(gcodeList)
542                 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
543                 self.filename = filename
544                 self.gcode = gcode
545                 self.gcodeList = gcodeList
546
547                 wx.CallAfter(self.progress.SetRange, len(gcodeList))
548                 wx.CallAfter(self.UpdateButtonStates)
549                 wx.CallAfter(self.UpdateProgress)
550                 wx.CallAfter(self.SetTitle, 'Printing: %s' % (filename))
551
552         def sendLine(self, lineNr):
553                 if lineNr >= len(self.gcodeList):
554                         return False
555                 line = self.gcodeList[lineNr]
556                 try:
557                         if ('M104' in line or 'M109' in line) and 'S' in line:
558                                 n = int(re.search('S([0-9]*)', line).group(1))
559                                 wx.CallAfter(self.temperatureSelect.SetValue, n)
560                         if ('M140' in line or 'M190' in line) and 'S' in line:
561                                 n = int(re.search('S([0-9]*)', line).group(1))
562                                 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
563                 except:
564                         print "Unexpected error:", sys.exc_info()
565                 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
566                 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
567                 return True
568
569         def mcLog(self, message):
570                 #print message
571                 pass
572
573         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
574                 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
575 #               ToFix, this causes problems with setting the temperature with the keyboard
576 #               if self.temperatureSelect.GetValue() != targetTemp:
577 #                       wx.CallAfter(self.temperatureSelect.SetValue, targetTemp)
578 #               if self.bedTemperatureSelect.GetValue() != bedTargetTemp:
579 #                       wx.CallAfter(self.bedTemperatureSelect.SetValue, bedTargetTemp)
580
581         def mcStateChange(self, state):
582                 if self.machineCom is not None:
583                         if state == self.machineCom.STATE_OPERATIONAL and self.cam is not None:
584                                 self.cam.endTimelapse()
585                         if state == self.machineCom.STATE_OPERATIONAL:
586                                 taskbar.setBusy(self, False)
587                         if self.machineCom.isClosedOrError():
588                                 taskbar.setBusy(self, False)
589                         if self.machineCom.isPaused():
590                                 taskbar.setPause(self, True)
591                 wx.CallAfter(self.UpdateButtonStates)
592                 wx.CallAfter(self.UpdateProgress)
593
594         def mcMessage(self, message):
595                 wx.CallAfter(self.AddTermLog, message)
596
597         def mcProgress(self, lineNr):
598                 wx.CallAfter(self.UpdateProgress)
599
600         def mcZChange(self, newZ):
601                 self.currentZ = newZ
602                 if self.cam is not None:
603                         wx.CallAfter(self.cam.takeNewImage)
604                         wx.CallAfter(self.camPreview.Refresh)
605
606
607 class temperatureGraph(wx.Panel):
608         def __init__(self, parent):
609                 super(temperatureGraph, self).__init__(parent)
610
611                 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
612                 self.Bind(wx.EVT_SIZE, self.OnSize)
613                 self.Bind(wx.EVT_PAINT, self.OnDraw)
614
615                 self.lastDraw = time.time() - 1.0
616                 self.points = []
617                 self.backBuffer = None
618                 self.addPoint(0, 0, 0, 0)
619                 self.SetMinSize((320, 200))
620
621         def OnEraseBackground(self, e):
622                 pass
623
624         def OnSize(self, e):
625                 if self.backBuffer == None or self.GetSize() != self.backBuffer.GetSize():
626                         self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
627                         self.UpdateDrawing(True)
628
629         def OnDraw(self, e):
630                 dc = wx.BufferedPaintDC(self, self.backBuffer)
631
632         def UpdateDrawing(self, force=False):
633                 now = time.time()
634                 if not force and now - self.lastDraw < 1.0:
635                         return
636                 self.lastDraw = now
637                 dc = wx.MemoryDC()
638                 dc.SelectObject(self.backBuffer)
639                 dc.Clear()
640                 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
641                 w, h = self.GetSizeTuple()
642                 bgLinePen = wx.Pen('#A0A0A0')
643                 tempPen = wx.Pen('#FF4040')
644                 tempSPPen = wx.Pen('#FFA0A0')
645                 tempPenBG = wx.Pen('#FFD0D0')
646                 bedTempPen = wx.Pen('#4040FF')
647                 bedTempSPPen = wx.Pen('#A0A0FF')
648                 bedTempPenBG = wx.Pen('#D0D0FF')
649
650                 #Draw the background up to the current temperatures.
651                 x0 = 0
652                 t0 = 0
653                 bt0 = 0
654                 tSP0 = 0
655                 btSP0 = 0
656                 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
657                         x1 = int(w - (now - t))
658                         for x in xrange(x0, x1 + 1):
659                                 t = float(x - x0) / float(x1 - x0 + 1) * (temp - t0) + t0
660                                 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
661                                 dc.SetPen(tempPenBG)
662                                 dc.DrawLine(x, h, x, h - (t * h / 300))
663                                 dc.SetPen(bedTempPenBG)
664                                 dc.DrawLine(x, h, x, h - (bt * h / 300))
665                         t0 = temp
666                         bt0 = bedTemp
667                         tSP0 = tempSP
668                         btSP0 = bedTempSP
669                         x0 = x1 + 1
670
671                 #Draw the grid
672                 for x in xrange(w, 0, -30):
673                         dc.SetPen(bgLinePen)
674                         dc.DrawLine(x, 0, x, h)
675                 tmpNr = 0
676                 for y in xrange(h - 1, 0, -h * 50 / 300):
677                         dc.SetPen(bgLinePen)
678                         dc.DrawLine(0, y, w, y)
679                         dc.DrawText(str(tmpNr), 0, y - dc.GetFont().GetPixelSize().GetHeight())
680                         tmpNr += 50
681                 dc.DrawLine(0, 0, w, 0)
682                 dc.DrawLine(0, 0, 0, h)
683
684                 #Draw the main lines
685                 x0 = 0
686                 t0 = 0
687                 bt0 = 0
688                 tSP0 = 0
689                 btSP0 = 0
690                 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
691                         x1 = int(w - (now - t))
692                         for x in xrange(x0, x1 + 1):
693                                 t = float(x - x0) / float(x1 - x0 + 1) * (temp - t0) + t0
694                                 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
695                                 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP - tSP0) + tSP0
696                                 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
697                                 dc.SetPen(tempSPPen)
698                                 dc.DrawPoint(x, h - (tSP * h / 300))
699                                 dc.SetPen(bedTempSPPen)
700                                 dc.DrawPoint(x, h - (btSP * h / 300))
701                                 dc.SetPen(tempPen)
702                                 dc.DrawPoint(x, h - (t * h / 300))
703                                 dc.SetPen(bedTempPen)
704                                 dc.DrawPoint(x, h - (bt * h / 300))
705                         t0 = temp
706                         bt0 = bedTemp
707                         tSP0 = tempSP
708                         btSP0 = bedTempSP
709                         x0 = x1 + 1
710
711                 del dc
712                 self.Refresh(eraseBackground=False)
713                 self.Update()
714
715                 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
716                         self.points.pop(0)
717
718         def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
719                 if bedTemp == None:
720                         bedTemp = 0
721                 if bedTempSP == None:
722                         bedTempSP = 0
723                 self.points.append((temp, tempSP, bedTemp, bedTempSP, time.time()))
724                 wx.CallAfter(self.UpdateDrawing)
725
726
727 class LogWindow(wx.Frame):
728         def __init__(self, logText):
729                 super(LogWindow, self).__init__(None, title="Machine log")
730                 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
731                 self.SetSize((500, 400))
732                 self.Centre()
733                 self.Show(True)