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