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