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