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