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