chiark / gitweb /
Merge pull request #557 from GreatFruitOmsk/i18n
[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.AppendText(unicode(line, 'utf-8', 'replace'))
532                 l = len(self.termLog.GetValue())
533                 self.termLog.SetCaret(wx.Caret(self.termLog, (l, l)))
534
535         def OnTermEnterLine(self, e):
536                 line = self.termInput.GetValue()
537                 if line == '':
538                         return
539                 self.termLog.AppendText('>%s\n' % (line))
540                 self.machineCom.sendCommand(line)
541                 self.termHistory.append(line)
542                 self.termHistoryIdx = len(self.termHistory)
543                 self.termInput.SetValue('')
544
545         def OnTermKey(self, e):
546                 if len(self.termHistory) > 0:
547                         if e.GetKeyCode() == wx.WXK_UP:
548                                 self.termHistoryIdx -= 1
549                                 if self.termHistoryIdx < 0:
550                                         self.termHistoryIdx = len(self.termHistory) - 1
551                                 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
552                         if e.GetKeyCode() == wx.WXK_DOWN:
553                                 self.termHistoryIdx -= 1
554                                 if self.termHistoryIdx >= len(self.termHistory):
555                                         self.termHistoryIdx = 0
556                                 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
557                 e.Skip()
558
559         def OnPowerWarningChange(self, e):
560                 type = self.powerManagement.get_providing_power_source_type()
561                 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
562                         self.powerWarningText.Hide()
563                         self.panel.Layout()
564                         self.Layout()
565                 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
566                         self.powerWarningText.Show()
567                         self.panel.Layout()
568                         self.Layout()
569
570         def LoadGCodeFile(self, filename):
571                 if self.machineCom is not None and self.machineCom.isPrinting():
572                         return False
573                 #Send an initial M110 to reset the line counter to zero.
574                 prevLineType = lineType = 'CUSTOM'
575                 gcodeList = ["M110"]
576                 for line in open(filename, 'r'):
577                         if line.startswith(';TYPE:'):
578                                 lineType = line[6:].strip()
579                         if ';' in line:
580                                 line = line[0:line.find(';')]
581                         line = line.strip()
582                         if len(line) > 0:
583                                 if prevLineType != lineType:
584                                         gcodeList.append((line, lineType, ))
585                                 else:
586                                         gcodeList.append(line)
587                                 prevLineType = lineType
588                 gcode = gcodeInterpreter.gcode()
589                 gcode.loadList(gcodeList)
590                 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
591                 self.filename = filename
592                 self.gcode = gcode
593                 self.gcodeList = gcodeList
594
595                 wx.CallAfter(self.progress.SetRange, len(gcodeList))
596                 wx.CallAfter(self.UpdateButtonStates)
597                 wx.CallAfter(self.UpdateProgress)
598                 return True
599
600         def sendLine(self, lineNr):
601                 if lineNr >= len(self.gcodeList):
602                         return False
603                 line = self.gcodeList[lineNr]
604                 try:
605                         if ('M104' in line or 'M109' in line) and 'S' in line:
606                                 n = int(re.search('S([0-9]*)', line).group(1))
607                                 wx.CallAfter(self.temperatureSelect.SetValue, n)
608                         if ('M140' in line or 'M190' in line) and 'S' in line:
609                                 n = int(re.search('S([0-9]*)', line).group(1))
610                                 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
611                 except:
612                         print "Unexpected error:", sys.exc_info()
613                 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
614                 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
615                 return True
616
617         def mcLog(self, message):
618                 #print message
619                 pass
620
621         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
622                 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
623                 wx.CallAfter(self._mcTempUpdate, temp, bedTemp, targetTemp, bedTargetTemp)
624
625         def _mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
626                 if self.temperatureSelect.GetValue() != targetTemp[0] and wx.Window.FindFocus() != self.temperatureSelect:
627                         self.temperatureSelect.SetValue(targetTemp[0])
628                 if self.bedTemperatureSelect.GetValue() != bedTargetTemp and wx.Window.FindFocus() != self.bedTemperatureSelect:
629                         self.bedTemperatureSelect.SetValue(bedTargetTemp)
630
631         def mcStateChange(self, state):
632                 if self.machineCom is not None:
633                         if state == self.machineCom.STATE_OPERATIONAL and self.cam is not None:
634                                 self.cam.endTimelapse()
635                         if state == self.machineCom.STATE_OPERATIONAL:
636                                 taskbar.setBusy(self, False)
637                         if self.machineCom.isClosedOrError():
638                                 taskbar.setBusy(self, False)
639                         if self.machineCom.isPaused():
640                                 taskbar.setPause(self, True)
641                         if self.machineCom.isClosedOrError():
642                                 print 'STATE:CLOSED'
643                         elif self.machineCom.isPrinting():
644                                 print 'STATE:PRINTING'
645                         else:
646                                 print 'STATE:IDLE'
647                 wx.CallAfter(self.UpdateButtonStates)
648                 wx.CallAfter(self.UpdateProgress)
649
650         def mcMessage(self, message):
651                 wx.CallAfter(self.AddTermLog, message)
652
653         def mcProgress(self, lineNr):
654                 wx.CallAfter(self.UpdateProgress)
655
656         def mcZChange(self, newZ):
657                 self.currentZ = newZ
658                 print 'Z:%f' % newZ
659                 if self.cam is not None:
660                         wx.CallAfter(self.cam.takeNewImage)
661                         wx.CallAfter(self.camPreview.Refresh)
662
663
664 class temperatureGraph(wx.Panel):
665         def __init__(self, parent):
666                 super(temperatureGraph, self).__init__(parent)
667
668                 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
669                 self.Bind(wx.EVT_SIZE, self.OnSize)
670                 self.Bind(wx.EVT_PAINT, self.OnDraw)
671
672                 self.lastDraw = time.time() - 1.0
673                 self.points = []
674                 self.backBuffer = None
675                 self.addPoint([0]*16, [0]*16, 0, 0)
676                 self.SetMinSize((320, 200))
677
678         def OnEraseBackground(self, e):
679                 pass
680
681         def OnSize(self, e):
682                 if self.backBuffer is None or self.GetSize() != self.backBuffer.GetSize():
683                         self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
684                         self.UpdateDrawing(True)
685
686         def OnDraw(self, e):
687                 dc = wx.BufferedPaintDC(self, self.backBuffer)
688
689         def UpdateDrawing(self, force=False):
690                 now = time.time()
691                 if not force and now - self.lastDraw < 1.0:
692                         return
693                 self.lastDraw = now
694                 dc = wx.MemoryDC()
695                 dc.SelectObject(self.backBuffer)
696                 dc.Clear()
697                 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
698                 w, h = self.GetSizeTuple()
699                 bgLinePen = wx.Pen('#A0A0A0')
700                 tempPen = wx.Pen('#FF4040')
701                 tempSPPen = wx.Pen('#FFA0A0')
702                 tempPenBG = wx.Pen('#FFD0D0')
703                 bedTempPen = wx.Pen('#4040FF')
704                 bedTempSPPen = wx.Pen('#A0A0FF')
705                 bedTempPenBG = wx.Pen('#D0D0FF')
706
707                 #Draw the background up to the current temperatures.
708                 x0 = 0
709                 t0 = [0] * len(self.points[0][0])
710                 bt0 = 0
711                 tSP0 = 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                                         dc.SetPen(tempPenBG)
719                                         dc.DrawLine(x, h, x, h - (t * h / 300))
720                                 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
721                                 dc.SetPen(bedTempPenBG)
722                                 dc.DrawLine(x, h, x, h - (bt * h / 300))
723                         t0 = temp
724                         bt0 = bedTemp
725                         tSP0 = tempSP
726                         btSP0 = bedTempSP
727                         x0 = x1 + 1
728
729                 #Draw the grid
730                 for x in xrange(w, 0, -30):
731                         dc.SetPen(bgLinePen)
732                         dc.DrawLine(x, 0, x, h)
733                 tmpNr = 0
734                 for y in xrange(h - 1, 0, -h * 50 / 300):
735                         dc.SetPen(bgLinePen)
736                         dc.DrawLine(0, y, w, y)
737                         dc.DrawText(str(tmpNr), 0, y - dc.GetFont().GetPixelSize().GetHeight())
738                         tmpNr += 50
739                 dc.DrawLine(0, 0, w, 0)
740                 dc.DrawLine(0, 0, 0, h)
741
742                 #Draw the main lines
743                 x0 = 0
744                 t0 = [0] * len(self.points[0][0])
745                 bt0 = 0
746                 tSP0 = [0] * len(self.points[0][0])
747                 btSP0 = 0
748                 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
749                         x1 = int(w - (now - t))
750                         for x in xrange(x0, x1 + 1):
751                                 for n in xrange(0, len(temp)):
752                                         t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
753                                         tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP[n] - tSP0[n]) + tSP0[n]
754                                         dc.SetPen(tempSPPen)
755                                         dc.DrawPoint(x, h - (tSP * h / 300))
756                                         dc.SetPen(tempPen)
757                                         dc.DrawPoint(x, h - (t * h / 300))
758                                 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
759                                 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
760                                 dc.SetPen(bedTempSPPen)
761                                 dc.DrawPoint(x, h - (btSP * h / 300))
762                                 dc.SetPen(bedTempPen)
763                                 dc.DrawPoint(x, h - (bt * h / 300))
764                         t0 = temp
765                         bt0 = bedTemp
766                         tSP0 = tempSP
767                         btSP0 = bedTempSP
768                         x0 = x1 + 1
769
770                 del dc
771                 self.Refresh(eraseBackground=False)
772                 self.Update()
773
774                 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
775                         self.points.pop(0)
776
777         def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
778                 if bedTemp is None:
779                         bedTemp = 0
780                 if bedTempSP is None:
781                         bedTempSP = 0
782                 self.points.append((temp[:], tempSP[:], bedTemp, bedTempSP, time.time()))
783                 wx.CallAfter(self.UpdateDrawing)
784
785
786 class LogWindow(wx.Frame):
787         def __init__(self, logText):
788                 super(LogWindow, self).__init__(None, title="Machine log")
789                 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
790                 self.SetSize((500, 400))
791                 self.Centre()
792                 self.Show(True)