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