chiark / gitweb /
Fix resources, imports and indentations.
[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="Connect your computer to AC power\nIf it shuts down during printing, the product will be lost.",
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(5)
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.Layout()
525                 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
526                         self.powerWarningText.Show()
527                         self.Layout()
528
529         def LoadGCodeFile(self, filename):
530                 if self.machineCom != None and self.machineCom.isPrinting():
531                         return
532                 #Send an initial M110 to reset the line counter to zero.
533                 prevLineType = lineType = 'CUSTOM'
534                 gcodeList = ["M110"]
535                 for line in open(filename, 'r'):
536                         if line.startswith(';TYPE:'):
537                                 lineType = line[6:].strip()
538                         if ';' in line:
539                                 line = line[0:line.find(';')]
540                         line = line.strip()
541                         if len(line) > 0:
542                                 if prevLineType != lineType:
543                                         gcodeList.append((line, lineType, ))
544                                 else:
545                                         gcodeList.append(line)
546                                 prevLineType = lineType
547                 gcode = gcodeInterpreter.gcode()
548                 gcode.loadList(gcodeList)
549                 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
550                 self.filename = filename
551                 self.gcode = gcode
552                 self.gcodeList = gcodeList
553
554                 wx.CallAfter(self.progress.SetRange, len(gcodeList))
555                 wx.CallAfter(self.UpdateButtonStates)
556                 wx.CallAfter(self.UpdateProgress)
557                 wx.CallAfter(self.SetTitle, 'Printing: %s' % (filename))
558
559         def sendLine(self, lineNr):
560                 if lineNr >= len(self.gcodeList):
561                         return False
562                 line = self.gcodeList[lineNr]
563                 try:
564                         if ('M104' in line or 'M109' in line) and 'S' in line:
565                                 n = int(re.search('S([0-9]*)', line).group(1))
566                                 wx.CallAfter(self.temperatureSelect.SetValue, n)
567                         if ('M140' in line or 'M190' in line) and 'S' in line:
568                                 n = int(re.search('S([0-9]*)', line).group(1))
569                                 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
570                 except:
571                         print "Unexpected error:", sys.exc_info()
572                 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
573                 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
574                 return True
575
576         def mcLog(self, message):
577                 #print message
578                 pass
579
580         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
581                 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
582                 if self.temperatureSelect.GetValue() != targetTemp:
583                         wx.CallAfter(self.temperatureSelect.SetValue, targetTemp)
584                 if self.bedTemperatureSelect.GetValue() != bedTargetTemp:
585                         wx.CallAfter(self.bedTemperatureSelect.SetValue, bedTargetTemp)
586
587         def mcStateChange(self, state):
588                 if self.machineCom != None:
589                         if state == self.machineCom.STATE_OPERATIONAL and self.cam != None:
590                                 self.cam.endTimelaps()
591                         if state == self.machineCom.STATE_OPERATIONAL:
592                                 taskbar.setBusy(self, False)
593                         if self.machineCom.isClosedOrError():
594                                 taskbar.setBusy(self, False)
595                         if self.machineCom.isPaused():
596                                 taskbar.setPause(self, True)
597                 wx.CallAfter(self.UpdateButtonStates)
598                 wx.CallAfter(self.UpdateProgress)
599
600         def mcMessage(self, message):
601                 wx.CallAfter(self.AddTermLog, message)
602
603         def mcProgress(self, lineNr):
604                 wx.CallAfter(self.UpdateProgress)
605
606         def mcZChange(self, newZ):
607                 self.currentZ = newZ
608                 if self.cam != None:
609                         wx.CallAfter(self.cam.takeNewImage)
610                         wx.CallAfter(self.camPreview.Refresh)
611
612
613 class temperatureGraph(wx.Panel):
614         def __init__(self, parent):
615                 super(temperatureGraph, self).__init__(parent)
616
617                 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
618                 self.Bind(wx.EVT_SIZE, self.OnSize)
619                 self.Bind(wx.EVT_PAINT, self.OnDraw)
620
621                 self.lastDraw = time.time() - 1.0
622                 self.points = []
623                 self.backBuffer = None
624                 self.addPoint(0, 0, 0, 0)
625                 self.SetMinSize((320, 200))
626
627         def OnEraseBackground(self, e):
628                 pass
629
630         def OnSize(self, e):
631                 if self.backBuffer == None or self.GetSize() != self.backBuffer.GetSize():
632                         self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
633                         self.UpdateDrawing(True)
634
635         def OnDraw(self, e):
636                 dc = wx.BufferedPaintDC(self, self.backBuffer)
637
638         def UpdateDrawing(self, force=False):
639                 now = time.time()
640                 if not force and now - self.lastDraw < 1.0:
641                         return
642                 self.lastDraw = now
643                 dc = wx.MemoryDC()
644                 dc.SelectObject(self.backBuffer)
645                 dc.Clear()
646                 w, h = self.GetSizeTuple()
647                 bgLinePen = wx.Pen('#A0A0A0')
648                 tempPen = wx.Pen('#FF4040')
649                 tempSPPen = wx.Pen('#FFA0A0')
650                 tempPenBG = wx.Pen('#FFD0D0')
651                 bedTempPen = wx.Pen('#4040FF')
652                 bedTempSPPen = wx.Pen('#A0A0FF')
653                 bedTempPenBG = wx.Pen('#D0D0FF')
654
655                 #Draw the background up to the current temperatures.
656                 x0 = 0
657                 t0 = 0
658                 bt0 = 0
659                 tSP0 = 0
660                 btSP0 = 0
661                 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
662                         x1 = int(w - (now - t))
663                         for x in xrange(x0, x1 + 1):
664                                 t = float(x - x0) / float(x1 - x0 + 1) * (temp - t0) + t0
665                                 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
666                                 dc.SetPen(tempPenBG)
667                                 dc.DrawLine(x, h, x, h - (t * h / 300))
668                                 dc.SetPen(bedTempPenBG)
669                                 dc.DrawLine(x, h, x, h - (bt * h / 300))
670                         t0 = temp
671                         bt0 = bedTemp
672                         tSP0 = tempSP
673                         btSP0 = bedTempSP
674                         x0 = x1 + 1
675
676                 #Draw the grid
677                 for x in xrange(w, 0, -30):
678                         dc.SetPen(bgLinePen)
679                         dc.DrawLine(x, 0, x, h)
680                 for y in xrange(h - 1, 0, -h * 50 / 300):
681                         dc.SetPen(bgLinePen)
682                         dc.DrawLine(0, y, w, y)
683                 dc.DrawLine(0, 0, w, 0)
684                 dc.DrawLine(0, 0, 0, h)
685
686                 #Draw the main lines
687                 x0 = 0
688                 t0 = 0
689                 bt0 = 0
690                 tSP0 = 0
691                 btSP0 = 0
692                 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
693                         x1 = int(w - (now - t))
694                         for x in xrange(x0, x1 + 1):
695                                 t = float(x - x0) / float(x1 - x0 + 1) * (temp - t0) + t0
696                                 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
697                                 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP - tSP0) + tSP0
698                                 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
699                                 dc.SetPen(tempSPPen)
700                                 dc.DrawPoint(x, h - (tSP * h / 300))
701                                 dc.SetPen(bedTempSPPen)
702                                 dc.DrawPoint(x, h - (btSP * h / 300))
703                                 dc.SetPen(tempPen)
704                                 dc.DrawPoint(x, h - (t * h / 300))
705                                 dc.SetPen(bedTempPen)
706                                 dc.DrawPoint(x, h - (bt * h / 300))
707                         t0 = temp
708                         bt0 = bedTemp
709                         tSP0 = tempSP
710                         btSP0 = bedTempSP
711                         x0 = x1 + 1
712
713                 del dc
714                 self.Refresh(eraseBackground=False)
715                 self.Update()
716
717                 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
718                         self.points.pop(0)
719
720         def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
721                 if bedTemp == None:
722                         bedTemp = 0
723                 if bedTempSP == None:
724                         bedTempSP = 0
725                 self.points.append((temp, tempSP, bedTemp, bedTempSP, time.time()))
726                 wx.CallAfter(self.UpdateDrawing)
727
728
729 class LogWindow(wx.Frame):
730         def __init__(self, logText):
731                 super(LogWindow, self).__init__(None, title="Machine log")
732                 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
733                 self.SetSize((500, 400))
734                 self.Centre()
735                 self.Show(True)