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