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