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