chiark / gitweb /
Merge branch 'master' into SteamEngine
[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 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" % (
410                         int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60))
411                 if self.machineCom is None or not self.machineCom.isPrinting():
412                         self.progress.SetValue(0)
413                         if self.gcodeList is not None:
414                                 status += 'Line: -/%d\n' % (len(self.gcodeList))
415                 else:
416                         printTime = self.machineCom.getPrintTime() / 60
417                         printTimeLeft = self.machineCom.getPrintTimeRemainingEstimate()
418                         status += 'Line: %d/%d %d%%\n' % (self.machineCom.getPrintPos(), len(self.gcodeList),
419                                                           self.machineCom.getPrintPos() * 100 / len(self.gcodeList))
420                         if self.currentZ > 0:
421                                 status += 'Height: %0.1f\n' % (self.currentZ)
422                         status += 'Print time: %02d:%02d\n' % (int(printTime / 60), int(printTime % 60))
423                         if printTimeLeft == None:
424                                 status += 'Print time left: Unknown\n'
425                         else:
426                                 status += 'Print time left: %02d:%02d\n' % (int(printTimeLeft / 60), int(printTimeLeft % 60))
427                         self.progress.SetValue(self.machineCom.getPrintPos())
428                         taskbar.setProgress(self, self.machineCom.getPrintPos(), len(self.gcodeList))
429                 if self.machineCom != None:
430                         if self.machineCom.getTemp() > 0:
431                                 status += 'Temp: %d\n' % (self.machineCom.getTemp())
432                         if self.machineCom.getBedTemp() > 0:
433                                 status += 'Bed Temp: %d\n' % (self.machineCom.getBedTemp())
434                                 self.bedTemperatureLabel.Show(True)
435                                 self.bedTemperatureSelect.Show(True)
436                                 self.temperaturePanel.Layout()
437                         status += 'Machine state:%s\n' % (self.machineCom.getStateString())
438
439                 self.statsText.SetLabel(status.strip())
440
441         def OnConnect(self, e):
442                 if self.machineCom is not None:
443                         self.machineCom.close()
444                 self.machineCom = machineCom.MachineCom(callbackObject=self)
445                 self.UpdateButtonStates()
446                 taskbar.setBusy(self, True)
447
448         def OnLoad(self, e):
449                 pass
450
451         def OnPrint(self, e):
452                 if self.machineCom is None or not self.machineCom.isOperational():
453                         return
454                 if self.gcodeList is None:
455                         return
456                 if self.machineCom.isPrinting():
457                         return
458                 self.currentZ = -1
459                 if self.cam is not None and self.timelapsEnable.GetValue():
460                         self.cam.startTimelapse(self.filename[: self.filename.rfind('.')] + ".mpg")
461                 self.machineCom.printGCode(self.gcodeList)
462                 self.UpdateButtonStates()
463
464         def OnCancel(self, e):
465                 self.pauseButton.SetLabel('Pause')
466                 self.machineCom.cancelPrint()
467                 self.machineCom.sendCommand("M84")
468                 self.machineCom.sendCommand("M104 S0")
469                 self.UpdateButtonStates()
470
471         def OnPause(self, e):
472                 if self.machineCom.isPaused():
473                         self.machineCom.setPause(False)
474                 else:
475                         self.machineCom.setPause(True)
476
477         def OnMachineLog(self, e):
478                 LogWindow('\n'.join(self.machineCom.getLog()))
479
480         def OnClose(self, e):
481                 global printWindowHandle
482                 printWindowHandle = None
483                 if self.machineCom is not None:
484                         self.machineCom.close()
485                 self.Destroy()
486
487         def OnTempChange(self, e):
488                 self.machineCom.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))
489
490         def OnBedTempChange(self, e):
491                 self.machineCom.sendCommand("M140 S%d" % (self.bedTemperatureSelect.GetValue()))
492
493         def OnSpeedChange(self, e):
494                 if self.machineCom is None:
495                         return
496                 self.machineCom.setFeedrateModifier('WALL-OUTER', self.outerWallSpeedSelect.GetValue() / 100.0)
497                 self.machineCom.setFeedrateModifier('WALL-INNER', self.innerWallSpeedSelect.GetValue() / 100.0)
498                 self.machineCom.setFeedrateModifier('FILL', self.fillSpeedSelect.GetValue() / 100.0)
499                 self.machineCom.setFeedrateModifier('SUPPORT', self.supportSpeedSelect.GetValue() / 100.0)
500
501         def AddTermLog(self, line):
502                 self.termLog.AppendText(unicode(line, 'utf-8', 'replace'))
503                 l = len(self.termLog.GetValue())
504                 self.termLog.SetCaret(wx.Caret(self.termLog, (l, l)))
505
506         def OnTermEnterLine(self, e):
507                 line = self.termInput.GetValue()
508                 if line == '':
509                         return
510                 self.termLog.AppendText('>%s\n' % (line))
511                 self.machineCom.sendCommand(line)
512                 self.termHistory.append(line)
513                 self.termHistoryIdx = len(self.termHistory)
514                 self.termInput.SetValue('')
515
516         def OnTermKey(self, e):
517                 if len(self.termHistory) > 0:
518                         if e.GetKeyCode() == wx.WXK_UP:
519                                 self.termHistoryIdx -= 1
520                                 if self.termHistoryIdx < 0:
521                                         self.termHistoryIdx = len(self.termHistory) - 1
522                                 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
523                         if e.GetKeyCode() == wx.WXK_DOWN:
524                                 self.termHistoryIdx -= 1
525                                 if self.termHistoryIdx >= len(self.termHistory):
526                                         self.termHistoryIdx = 0
527                                 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
528                 e.Skip()
529
530         def OnPowerWarningChange(self, e):
531                 type = self.powerManagement.get_providing_power_source_type()
532                 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
533                         self.powerWarningText.Hide()
534                         self.panel.Layout()
535                         self.Layout()
536                 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
537                         self.powerWarningText.Show()
538                         self.panel.Layout()
539                         self.Layout()
540
541         def LoadGCodeFile(self, filename):
542                 if self.machineCom is not None and self.machineCom.isPrinting():
543                         return
544                 #Send an initial M110 to reset the line counter to zero.
545                 prevLineType = lineType = 'CUSTOM'
546                 gcodeList = ["M110"]
547                 for line in open(filename, 'r'):
548                         if line.startswith(';TYPE:'):
549                                 lineType = line[6:].strip()
550                         if ';' in line:
551                                 line = line[0:line.find(';')]
552                         line = line.strip()
553                         if len(line) > 0:
554                                 if prevLineType != lineType:
555                                         gcodeList.append((line, lineType, ))
556                                 else:
557                                         gcodeList.append(line)
558                                 prevLineType = lineType
559                 gcode = gcodeInterpreter.gcode()
560                 gcode.loadList(gcodeList)
561                 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
562                 self.filename = filename
563                 self.gcode = gcode
564                 self.gcodeList = gcodeList
565
566                 wx.CallAfter(self.progress.SetRange, len(gcodeList))
567                 wx.CallAfter(self.UpdateButtonStates)
568                 wx.CallAfter(self.UpdateProgress)
569                 wx.CallAfter(self.SetTitle, 'Printing: %s' % (filename))
570
571         def sendLine(self, lineNr):
572                 if lineNr >= len(self.gcodeList):
573                         return False
574                 line = self.gcodeList[lineNr]
575                 try:
576                         if ('M104' in line or 'M109' in line) and 'S' in line:
577                                 n = int(re.search('S([0-9]*)', line).group(1))
578                                 wx.CallAfter(self.temperatureSelect.SetValue, n)
579                         if ('M140' in line or 'M190' in line) and 'S' in line:
580                                 n = int(re.search('S([0-9]*)', line).group(1))
581                                 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
582                 except:
583                         print "Unexpected error:", sys.exc_info()
584                 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
585                 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
586                 return True
587
588         def mcLog(self, message):
589                 #print message
590                 pass
591
592         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
593                 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
594                 wx.CallAfter(self._mcTempUpdate, temp, bedTemp, targetTemp, bedTargetTemp)
595
596         def _mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
597                 if self.temperatureSelect.GetValue() != targetTemp and wx.Window.FindFocus() != self.temperatureSelect:
598                         self.temperatureSelect.SetValue(targetTemp)
599                 if self.bedTemperatureSelect.GetValue() != bedTargetTemp and wx.Window.FindFocus() != self.bedTemperatureSelect:
600                         self.bedTemperatureSelect.SetValue(bedTargetTemp)
601
602         def mcStateChange(self, state):
603                 if self.machineCom is not None:
604                         if state == self.machineCom.STATE_OPERATIONAL and self.cam is not None:
605                                 self.cam.endTimelapse()
606                         if state == self.machineCom.STATE_OPERATIONAL:
607                                 taskbar.setBusy(self, False)
608                         if self.machineCom.isClosedOrError():
609                                 taskbar.setBusy(self, False)
610                         if self.machineCom.isPaused():
611                                 taskbar.setPause(self, True)
612                 wx.CallAfter(self.UpdateButtonStates)
613                 wx.CallAfter(self.UpdateProgress)
614
615         def mcMessage(self, message):
616                 wx.CallAfter(self.AddTermLog, message)
617
618         def mcProgress(self, lineNr):
619                 wx.CallAfter(self.UpdateProgress)
620
621         def mcZChange(self, newZ):
622                 self.currentZ = newZ
623                 if self.cam is not None:
624                         wx.CallAfter(self.cam.takeNewImage)
625                         wx.CallAfter(self.camPreview.Refresh)
626
627
628 class temperatureGraph(wx.Panel):
629         def __init__(self, parent):
630                 super(temperatureGraph, self).__init__(parent)
631
632                 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
633                 self.Bind(wx.EVT_SIZE, self.OnSize)
634                 self.Bind(wx.EVT_PAINT, self.OnDraw)
635
636                 self.lastDraw = time.time() - 1.0
637                 self.points = []
638                 self.backBuffer = None
639                 self.addPoint(0, 0, 0, 0)
640                 self.SetMinSize((320, 200))
641
642         def OnEraseBackground(self, e):
643                 pass
644
645         def OnSize(self, e):
646                 if self.backBuffer == None or self.GetSize() != self.backBuffer.GetSize():
647                         self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
648                         self.UpdateDrawing(True)
649
650         def OnDraw(self, e):
651                 dc = wx.BufferedPaintDC(self, self.backBuffer)
652
653         def UpdateDrawing(self, force=False):
654                 now = time.time()
655                 if not force and now - self.lastDraw < 1.0:
656                         return
657                 self.lastDraw = now
658                 dc = wx.MemoryDC()
659                 dc.SelectObject(self.backBuffer)
660                 dc.Clear()
661                 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
662                 w, h = self.GetSizeTuple()
663                 bgLinePen = wx.Pen('#A0A0A0')
664                 tempPen = wx.Pen('#FF4040')
665                 tempSPPen = wx.Pen('#FFA0A0')
666                 tempPenBG = wx.Pen('#FFD0D0')
667                 bedTempPen = wx.Pen('#4040FF')
668                 bedTempSPPen = wx.Pen('#A0A0FF')
669                 bedTempPenBG = wx.Pen('#D0D0FF')
670
671                 #Draw the background up to the current temperatures.
672                 x0 = 0
673                 t0 = 0
674                 bt0 = 0
675                 tSP0 = 0
676                 btSP0 = 0
677                 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
678                         x1 = int(w - (now - t))
679                         for x in xrange(x0, x1 + 1):
680                                 t = float(x - x0) / float(x1 - x0 + 1) * (temp - t0) + t0
681                                 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
682                                 dc.SetPen(tempPenBG)
683                                 dc.DrawLine(x, h, x, h - (t * h / 300))
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
708                 bt0 = 0
709                 tSP0 = 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                                 t = float(x - x0) / float(x1 - x0 + 1) * (temp - t0) + t0
715                                 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
716                                 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP - tSP0) + tSP0
717                                 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
718                                 dc.SetPen(tempSPPen)
719                                 dc.DrawPoint(x, h - (tSP * h / 300))
720                                 dc.SetPen(bedTempSPPen)
721                                 dc.DrawPoint(x, h - (btSP * h / 300))
722                                 dc.SetPen(tempPen)
723                                 dc.DrawPoint(x, h - (t * h / 300))
724                                 dc.SetPen(bedTempPen)
725                                 dc.DrawPoint(x, h - (bt * h / 300))
726                         t0 = temp
727                         bt0 = bedTemp
728                         tSP0 = tempSP
729                         btSP0 = bedTempSP
730                         x0 = x1 + 1
731
732                 del dc
733                 self.Refresh(eraseBackground=False)
734                 self.Update()
735
736                 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
737                         self.points.pop(0)
738
739         def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
740                 if bedTemp == None:
741                         bedTemp = 0
742                 if bedTempSP == None:
743                         bedTempSP = 0
744                 self.points.append((temp, tempSP, bedTemp, bedTempSP, time.time()))
745                 wx.CallAfter(self.UpdateDrawing)
746
747
748 class LogWindow(wx.Frame):
749         def __init__(self, logText):
750                 super(LogWindow, self).__init__(None, title="Machine log")
751                 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
752                 self.SetSize((500, 400))
753                 self.Centre()
754                 self.Show(True)