1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
15 from wx.lib import buttons
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.resources import getPathForImage
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):
28 self._state = 'CLOSED'
30 self._callback = callback
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')]
38 cmdList = [sys.executable, '-m', 'Cura.cura']
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)
49 self.handle.stdin.write('LOAD:%s\n' % filename)
54 line = p.stdout.readline()
58 if line.startswith('Z:'):
59 self._z = float(line[2:])
61 elif line.startswith('STATE:'):
62 self._state = line[6:]
66 #print '>' + line.rstrip()
67 line = p.stdout.readline()
68 line = p.stderr.readline()
70 print '>>' + line.rstrip()
71 line = p.stderr.readline()
85 def _callCallback(self):
86 if self._callback is not None:
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).
93 printWindowHandle = printWindow()
94 printWindowHandle.Show(True)
95 printWindowHandle.Raise()
96 printWindowHandle.OnConnect(None)
97 t = threading.Thread(target=printWindowHandle.LoadGCodeFile, args=(filename,))
102 class PrintCommandButton(buttons.GenBitmapButton):
103 def __init__(self, parent, commandList, bitmapFilename, size=(20, 20)):
104 self.bitmap = wx.Bitmap(getPathForImage(bitmapFilename))
105 super(PrintCommandButton, self).__init__(parent.directControlPanel, -1, self.bitmap, size=size)
107 self.commandList = commandList
110 self.SetBezelWidth(1)
111 self.SetUseFocusIndicator(False)
113 self.Bind(wx.EVT_BUTTON, self.OnClick)
115 def OnClick(self, e):
116 if self.parent.machineCom is None or self.parent.machineCom.isPrinting():
118 for cmd in self.commandList:
119 self.parent.machineCom.sendCommand(cmd)
123 class printWindow(wx.Frame):
124 "Main user interface window"
127 super(printWindow, self).__init__(None, -1, title='Printing')
128 self.machineCom = None
130 self.gcodeList = None
134 self.bufferLineCount = 4
136 self.feedrateRatioOuterWall = 1.0
137 self.feedrateRatioInnerWall = 1.0
138 self.feedrateRatioFill = 1.0
139 self.feedrateRatioSupport = 1.0
141 self.termHistory = []
142 self.termHistoryIdx = 0
145 if webcam.hasWebcamSupport():
146 self.cam = webcam.webcam()
147 if not self.cam.hasCamera():
150 self.SetSizer(wx.BoxSizer())
151 self.panel = wx.Panel(self)
152 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
153 self.sizer = wx.GridBagSizer(2, 2)
154 self.panel.SetSizer(self.sizer)
156 sb = wx.StaticBox(self.panel, label="Statistics")
157 boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)
159 self.powerWarningText = wx.StaticText(parent=self.panel,
161 label="Your computer is running on battery power.\nConnect your computer to AC power or your print might not finish.",
162 style=wx.ALIGN_CENTER)
163 self.powerWarningText.SetBackgroundColour('red')
164 self.powerWarningText.SetForegroundColour('white')
165 boxsizer.AddF(self.powerWarningText, flags=wx.SizerFlags().Expand().Border(wx.BOTTOM, 10))
166 self.powerManagement = power.PowerManagement()
167 self.powerWarningTimer = wx.Timer(self)
168 self.Bind(wx.EVT_TIMER, self.OnPowerWarningChange, self.powerWarningTimer)
169 self.OnPowerWarningChange(None)
170 self.powerWarningTimer.Start(10000)
172 self.statsText = wx.StaticText(self.panel, -1, "Filament: ####.##m #.##g\nEstimated print time: #####:##\nMachine state:\nDetecting baudrateXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
173 boxsizer.Add(self.statsText, flag=wx.LEFT, border=5)
175 self.sizer.Add(boxsizer, pos=(0, 0), span=(7, 1), flag=wx.EXPAND)
177 self.connectButton = wx.Button(self.panel, -1, 'Connect')
178 #self.loadButton = wx.Button(self.panel, -1, 'Load')
179 self.printButton = wx.Button(self.panel, -1, 'Print')
180 self.pauseButton = wx.Button(self.panel, -1, 'Pause')
181 self.cancelButton = wx.Button(self.panel, -1, 'Cancel print')
182 self.machineLogButton = wx.Button(self.panel, -1, 'Error log')
183 self.progress = wx.Gauge(self.panel, -1)
185 self.sizer.Add(self.connectButton, pos=(1, 1), flag=wx.EXPAND)
186 #self.sizer.Add(self.loadButton, pos=(1,1), flag=wx.EXPAND)
187 self.sizer.Add(self.printButton, pos=(2, 1), flag=wx.EXPAND)
188 self.sizer.Add(self.pauseButton, pos=(3, 1), flag=wx.EXPAND)
189 self.sizer.Add(self.cancelButton, pos=(4, 1), flag=wx.EXPAND)
190 self.sizer.Add(self.machineLogButton, pos=(5, 1), flag=wx.EXPAND)
191 self.sizer.Add(self.progress, pos=(7, 0), span=(1, 7), flag=wx.EXPAND)
193 nb = wx.Notebook(self.panel)
194 self.sizer.Add(nb, pos=(0, 2), span=(7, 4), flag=wx.EXPAND)
196 self.temperaturePanel = wx.Panel(nb)
197 sizer = wx.GridBagSizer(2, 2)
198 self.temperaturePanel.SetSizer(sizer)
200 self.temperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
201 self.temperatureSelect.SetRange(0, 400)
202 self.temperatureHeatUpPLA = wx.Button(self.temperaturePanel, -1, '210C')
203 self.bedTemperatureLabel = wx.StaticText(self.temperaturePanel, -1, "BedTemp:")
204 self.bedTemperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
205 self.bedTemperatureSelect.SetRange(0, 400)
206 self.bedTemperatureLabel.Show(False)
207 self.bedTemperatureSelect.Show(False)
209 self.temperatureGraph = temperatureGraph(self.temperaturePanel)
211 sizer.Add(wx.StaticText(self.temperaturePanel, -1, "Temp:"), pos=(0, 0))
212 sizer.Add(self.temperatureSelect, pos=(0, 1))
213 sizer.Add(self.temperatureHeatUpPLA, pos=(0, 2))
214 sizer.Add(self.bedTemperatureLabel, pos=(1, 0))
215 sizer.Add(self.bedTemperatureSelect, pos=(1, 1))
216 sizer.Add(self.temperatureGraph, pos=(2, 0), span=(1, 3), flag=wx.EXPAND)
217 sizer.AddGrowableRow(2)
218 sizer.AddGrowableCol(2)
220 nb.AddPage(self.temperaturePanel, 'Temp')
222 self.directControlPanel = wx.Panel(nb)
224 sizer = wx.GridBagSizer(2, 2)
225 self.directControlPanel.SetSizer(sizer)
226 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y100 F6000', 'G90'], 'print-move-y100.png'), pos=(0, 3))
227 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y10 F6000', 'G90'], 'print-move-y10.png'), pos=(1, 3))
228 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y1 F6000', 'G90'], 'print-move-y1.png'), pos=(2, 3))
230 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-1 F6000', 'G90'], 'print-move-y-1.png'), pos=(4, 3))
231 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-10 F6000', 'G90'], 'print-move-y-10.png'), pos=(5, 3))
232 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-100 F6000', 'G90'], 'print-move-y-100.png'), pos=(6, 3))
234 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-100 F6000', 'G90'], 'print-move-x-100.png'), pos=(3, 0))
235 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-10 F6000', 'G90'], 'print-move-x-10.png'), pos=(3, 1))
236 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-1 F6000', 'G90'], 'print-move-x-1.png'), pos=(3, 2))
238 sizer.Add(PrintCommandButton(self, ['G28 X0 Y0'], 'print-move-home.png'), pos=(3, 3))
240 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X1 F6000', 'G90'], 'print-move-x1.png'), pos=(3, 4))
241 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X10 F6000', 'G90'], 'print-move-x10.png'), pos=(3, 5))
242 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X100 F6000', 'G90'], 'print-move-x100.png'), pos=(3, 6))
244 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z10 F200', 'G90'], 'print-move-z10.png'), pos=(0, 8))
245 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z1 F200', 'G90'], 'print-move-z1.png'), pos=(1, 8))
246 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z0.1 F200', 'G90'], 'print-move-z0.1.png'), pos=(2, 8))
248 sizer.Add(PrintCommandButton(self, ['G28 Z0'], 'print-move-home.png'), pos=(3, 8))
250 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-0.1 F200', 'G90'], 'print-move-z-0.1.png'), pos=(4, 8))
251 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-1 F200', 'G90'], 'print-move-z-1.png'), pos=(5, 8))
252 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-10 F200', 'G90'], 'print-move-z-10.png'), pos=(6, 8))
254 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E2 F120'], 'extrude.png', size=(60, 20)), pos=(1, 10),
255 span=(1, 3), flag=wx.EXPAND)
256 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E-2 F120'], 'retract.png', size=(60, 20)), pos=(2, 10),
257 span=(1, 3), flag=wx.EXPAND)
259 nb.AddPage(self.directControlPanel, 'Jog')
261 self.speedPanel = wx.Panel(nb)
262 sizer = wx.GridBagSizer(2, 2)
263 self.speedPanel.SetSizer(sizer)
265 self.outerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
266 self.outerWallSpeedSelect.SetRange(5, 1000)
267 self.innerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
268 self.innerWallSpeedSelect.SetRange(5, 1000)
269 self.fillSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
270 self.fillSpeedSelect.SetRange(5, 1000)
271 self.supportSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
272 self.supportSpeedSelect.SetRange(5, 1000)
274 sizer.Add(wx.StaticText(self.speedPanel, -1, "Outer wall:"), pos=(0, 0))
275 sizer.Add(self.outerWallSpeedSelect, pos=(0, 1))
276 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(0, 2))
277 sizer.Add(wx.StaticText(self.speedPanel, -1, "Inner wall:"), pos=(1, 0))
278 sizer.Add(self.innerWallSpeedSelect, pos=(1, 1))
279 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(1, 2))
280 sizer.Add(wx.StaticText(self.speedPanel, -1, "Fill:"), pos=(2, 0))
281 sizer.Add(self.fillSpeedSelect, pos=(2, 1))
282 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(2, 2))
283 sizer.Add(wx.StaticText(self.speedPanel, -1, "Support:"), pos=(3, 0))
284 sizer.Add(self.supportSpeedSelect, pos=(3, 1))
285 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(3, 2))
287 nb.AddPage(self.speedPanel, 'Speed')
289 self.termPanel = wx.Panel(nb)
290 sizer = wx.GridBagSizer(2, 2)
291 self.termPanel.SetSizer(sizer)
293 f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
294 self.termLog = wx.TextCtrl(self.termPanel, style=wx.TE_MULTILINE | wx.TE_DONTWRAP)
295 self.termLog.SetFont(f)
296 self.termLog.SetEditable(0)
297 self.termInput = wx.TextCtrl(self.termPanel, style=wx.TE_PROCESS_ENTER)
298 self.termInput.SetFont(f)
300 sizer.Add(self.termLog, pos=(0, 0), flag=wx.EXPAND)
301 sizer.Add(self.termInput, pos=(1, 0), flag=wx.EXPAND)
302 sizer.AddGrowableCol(0)
303 sizer.AddGrowableRow(0)
305 nb.AddPage(self.termPanel, 'Term')
307 if self.cam is not None:
308 self.camPage = wx.Panel(nb)
309 sizer = wx.GridBagSizer(2, 2)
310 self.camPage.SetSizer(sizer)
312 self.timelapsEnable = wx.CheckBox(self.camPage, -1, 'Enable timelapse movie recording')
313 self.timelapsSavePath = wx.TextCtrl(self.camPage, -1, os.path.expanduser('~/timelaps_' + datetime.datetime.now().strftime('%Y-%m-%d_%H:%M') + '.mpg'))
314 sizer.Add(self.timelapsEnable, pos=(0, 0), span=(1, 2), flag=wx.EXPAND)
315 sizer.Add(self.timelapsSavePath, pos=(1, 0), span=(1, 2), flag=wx.EXPAND)
317 pages = self.cam.propertyPages()
318 self.cam.buttons = [self.timelapsEnable, self.timelapsSavePath]
320 button = wx.Button(self.camPage, -1, page)
321 button.index = pages.index(page)
322 sizer.Add(button, pos=(2, pages.index(page)))
323 button.Bind(wx.EVT_BUTTON, self.OnPropertyPageButton)
324 self.cam.buttons.append(button)
326 self.campreviewEnable = wx.CheckBox(self.camPage, -1, 'Show preview')
327 sizer.Add(self.campreviewEnable, pos=(3, 0), span=(1, 2), flag=wx.EXPAND)
329 self.camPreview = wx.Panel(self.camPage)
330 sizer.Add(self.camPreview, pos=(4, 0), span=(1, 2), flag=wx.EXPAND)
332 nb.AddPage(self.camPage, 'Camera')
333 self.camPreview.timer = wx.Timer(self)
334 self.Bind(wx.EVT_TIMER, self.OnCameraTimer, self.camPreview.timer)
335 self.camPreview.timer.Start(500)
336 self.camPreview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
338 self.sizer.AddGrowableRow(6)
339 self.sizer.AddGrowableCol(3)
341 self.Bind(wx.EVT_CLOSE, self.OnClose)
342 self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect)
343 #self.loadButton.Bind(wx.EVT_BUTTON, self.OnLoad)
344 self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint)
345 self.pauseButton.Bind(wx.EVT_BUTTON, self.OnPause)
346 self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)
347 self.machineLogButton.Bind(wx.EVT_BUTTON, self.OnMachineLog)
349 self.Bind(wx.EVT_BUTTON, lambda e: (self.temperatureSelect.SetValue(210), self.machineCom.sendCommand("M104 S210")), self.temperatureHeatUpPLA)
350 self.Bind(wx.EVT_SPINCTRL, self.OnTempChange, self.temperatureSelect)
351 self.Bind(wx.EVT_SPINCTRL, self.OnBedTempChange, self.bedTemperatureSelect)
353 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.outerWallSpeedSelect)
354 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.innerWallSpeedSelect)
355 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.fillSpeedSelect)
356 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.supportSpeedSelect)
357 self.Bind(wx.EVT_TEXT_ENTER, self.OnTermEnterLine, self.termInput)
358 self.termInput.Bind(wx.EVT_CHAR, self.OnTermKey)
364 self.statsText.SetMinSize(self.statsText.GetSize())
366 self.UpdateButtonStates()
368 #self.UpdateProgress()
369 self._thread = threading.Thread(target=self._stdinMonitor)
370 self._thread.daemon = True
373 def _stdinMonitor(self):
375 line = sys.stdin.readline().rstrip()
376 if line.startswith('LOAD:'):
377 if not self.LoadGCodeFile(line[5:]):
380 def OnCameraTimer(self, e):
381 if not self.campreviewEnable.GetValue():
383 if self.machineCom is not None and self.machineCom.isPrinting():
385 self.cam.takeNewImage()
386 self.camPreview.Refresh()
388 def OnCameraEraseBackground(self, e):
391 dc = wx.ClientDC(self)
392 rect = self.GetUpdateRegion().GetBox()
393 dc.SetClippingRect(rect)
394 dc.SetBackground(wx.Brush(self.camPreview.GetBackgroundColour(), wx.SOLID))
395 if self.cam.getLastImage() is not None:
396 self.camPreview.SetMinSize((self.cam.getLastImage().GetWidth(), self.cam.getLastImage().GetHeight()))
398 dc.DrawBitmap(self.cam.getLastImage(), 0, 0)
402 def OnPropertyPageButton(self, e):
403 self.cam.openPropertyPage(e.GetEventObject().index)
405 def UpdateButtonStates(self):
406 self.connectButton.Enable(self.machineCom is None or self.machineCom.isClosedOrError())
407 #self.loadButton.Enable(self.machineCom == None or not (self.machineCom.isPrinting() or self.machineCom.isPaused()))
408 self.printButton.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
409 self.machineCom.isPrinting() or self.machineCom.isPaused()))
410 self.temperatureHeatUpPLA.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
411 self.machineCom.isPrinting() or self.machineCom.isPaused()))
412 self.pauseButton.Enable(
413 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
414 if self.machineCom is not None and self.machineCom.isPaused():
415 self.pauseButton.SetLabel('Resume')
417 self.pauseButton.SetLabel('Pause')
418 self.cancelButton.Enable(
419 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
420 self.temperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
421 self.bedTemperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
422 self.directControlPanel.Enable(
423 self.machineCom is not None and self.machineCom.isOperational() and not self.machineCom.isPrinting())
424 self.machineLogButton.Show(self.machineCom is not None and self.machineCom.isClosedOrError())
426 for button in self.cam.buttons:
427 button.Enable(self.machineCom is None or not self.machineCom.isPrinting())
429 def UpdateProgress(self):
431 if self.gcode is None:
432 status += "Loading gcode...\n"
434 status += "Filament: %.2fm %.2fg\n" % (
435 self.gcode.extrusionAmount / 1000, self.gcode.calculateWeight() * 1000)
436 cost = self.gcode.calculateCost()
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))
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'
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())
468 self.statsText.SetLabel(status.strip())
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)
480 def OnPrint(self, e):
481 if self.machineCom is None or not self.machineCom.isOperational():
483 if self.gcodeList is None:
485 if self.machineCom.isPrinting():
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()
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()
500 def OnPause(self, e):
501 if self.machineCom.isPaused():
502 self.machineCom.setPause(False)
504 self.machineCom.setPause(True)
506 def OnMachineLog(self, e):
507 LogWindow('\n'.join(self.machineCom.getLog()))
509 def OnClose(self, e):
510 global printWindowHandle
511 printWindowHandle = None
512 if self.machineCom is not None:
513 self.machineCom.close()
516 def OnTempChange(self, e):
517 self.machineCom.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))
519 def OnBedTempChange(self, e):
520 self.machineCom.sendCommand("M140 S%d" % (self.bedTemperatureSelect.GetValue()))
522 def OnSpeedChange(self, e):
523 if self.machineCom is None:
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)
530 def AddTermLog(self, line):
531 if len(self.termLog.GetValue()) > 10000:
532 self.termLog.SetValue(self.termLog.GetValue()[-10000:])
533 self.termLog.AppendText(unicode(line, 'utf-8', 'replace'))
534 l = len(self.termLog.GetValue())
535 self.termLog.SetCaret(wx.Caret(self.termLog, (l, l)))
537 def OnTermEnterLine(self, e):
538 line = self.termInput.GetValue()
541 self.termLog.AppendText('>%s\n' % (line))
542 self.machineCom.sendCommand(line)
543 self.termHistory.append(line)
544 self.termHistoryIdx = len(self.termHistory)
545 self.termInput.SetValue('')
547 def OnTermKey(self, e):
548 if len(self.termHistory) > 0:
549 if e.GetKeyCode() == wx.WXK_UP:
550 self.termHistoryIdx -= 1
551 if self.termHistoryIdx < 0:
552 self.termHistoryIdx = len(self.termHistory) - 1
553 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
554 if e.GetKeyCode() == wx.WXK_DOWN:
555 self.termHistoryIdx -= 1
556 if self.termHistoryIdx >= len(self.termHistory):
557 self.termHistoryIdx = 0
558 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
561 def OnPowerWarningChange(self, e):
562 type = self.powerManagement.get_providing_power_source_type()
563 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
564 self.powerWarningText.Hide()
567 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
568 self.powerWarningText.Show()
572 def LoadGCodeFile(self, filename):
573 if self.machineCom is not None and self.machineCom.isPrinting():
575 #Send an initial M110 to reset the line counter to zero.
576 prevLineType = lineType = 'CUSTOM'
578 for line in open(filename, 'r'):
579 if line.startswith(';TYPE:'):
580 lineType = line[6:].strip()
582 line = line[0:line.find(';')]
585 if prevLineType != lineType:
586 gcodeList.append((line, lineType, ))
588 gcodeList.append(line)
589 prevLineType = lineType
590 gcode = gcodeInterpreter.gcode()
591 gcode.loadList(gcodeList)
592 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
593 self.filename = filename
595 self.gcodeList = gcodeList
597 wx.CallAfter(self.progress.SetRange, len(gcodeList))
598 wx.CallAfter(self.UpdateButtonStates)
599 wx.CallAfter(self.UpdateProgress)
602 def sendLine(self, lineNr):
603 if lineNr >= len(self.gcodeList):
605 line = self.gcodeList[lineNr]
607 if ('M104' in line or 'M109' in line) and 'S' in line:
608 n = int(re.search('S([0-9]*)', line).group(1))
609 wx.CallAfter(self.temperatureSelect.SetValue, n)
610 if ('M140' in line or 'M190' in line) and 'S' in line:
611 n = int(re.search('S([0-9]*)', line).group(1))
612 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
614 print "Unexpected error:", sys.exc_info()
615 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
616 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
619 def mcLog(self, message):
623 def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
624 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
625 wx.CallAfter(self._mcTempUpdate, temp, bedTemp, targetTemp, bedTargetTemp)
627 def _mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
628 if self.temperatureSelect.GetValue() != targetTemp[0] and wx.Window.FindFocus() != self.temperatureSelect:
629 self.temperatureSelect.SetValue(targetTemp[0])
630 if self.bedTemperatureSelect.GetValue() != bedTargetTemp and wx.Window.FindFocus() != self.bedTemperatureSelect:
631 self.bedTemperatureSelect.SetValue(bedTargetTemp)
633 def mcStateChange(self, state):
634 if self.machineCom is not None:
635 if state == self.machineCom.STATE_OPERATIONAL and self.cam is not None:
636 self.cam.endTimelapse()
637 if state == self.machineCom.STATE_OPERATIONAL:
638 taskbar.setBusy(self, False)
639 if self.machineCom.isClosedOrError():
640 taskbar.setBusy(self, False)
641 if self.machineCom.isPaused():
642 taskbar.setPause(self, True)
643 if self.machineCom.isClosedOrError():
645 elif self.machineCom.isPrinting():
646 print 'STATE:PRINTING'
649 wx.CallAfter(self.UpdateButtonStates)
650 wx.CallAfter(self.UpdateProgress)
652 def mcMessage(self, message):
653 wx.CallAfter(self.AddTermLog, message)
655 def mcProgress(self, lineNr):
656 wx.CallAfter(self.UpdateProgress)
658 def mcZChange(self, newZ):
661 if self.cam is not None:
662 wx.CallAfter(self.cam.takeNewImage)
663 wx.CallAfter(self.camPreview.Refresh)
666 class temperatureGraph(wx.Panel):
667 def __init__(self, parent):
668 super(temperatureGraph, self).__init__(parent)
670 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
671 self.Bind(wx.EVT_SIZE, self.OnSize)
672 self.Bind(wx.EVT_PAINT, self.OnDraw)
674 self.lastDraw = time.time() - 1.0
676 self.backBuffer = None
677 self.addPoint([0]*16, [0]*16, 0, 0)
678 self.SetMinSize((320, 200))
680 def OnEraseBackground(self, e):
684 if self.backBuffer is None or self.GetSize() != self.backBuffer.GetSize():
685 self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
686 self.UpdateDrawing(True)
689 dc = wx.BufferedPaintDC(self, self.backBuffer)
691 def UpdateDrawing(self, force=False):
693 if not force and now - self.lastDraw < 1.0:
697 dc.SelectObject(self.backBuffer)
699 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
700 w, h = self.GetSizeTuple()
701 bgLinePen = wx.Pen('#A0A0A0')
702 tempPen = wx.Pen('#FF4040')
703 tempSPPen = wx.Pen('#FFA0A0')
704 tempPenBG = wx.Pen('#FFD0D0')
705 bedTempPen = wx.Pen('#4040FF')
706 bedTempSPPen = wx.Pen('#A0A0FF')
707 bedTempPenBG = wx.Pen('#D0D0FF')
709 #Draw the background up to the current temperatures.
711 t0 = [0] * len(self.points[0][0])
715 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
716 x1 = int(w - (now - t))
717 for x in xrange(x0, x1 + 1):
718 for n in xrange(0, len(temp)):
719 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
721 dc.DrawLine(x, h, x, h - (t * h / 300))
722 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
723 dc.SetPen(bedTempPenBG)
724 dc.DrawLine(x, h, x, h - (bt * h / 300))
732 for x in xrange(w, 0, -30):
734 dc.DrawLine(x, 0, x, h)
736 for y in xrange(h - 1, 0, -h * 50 / 300):
738 dc.DrawLine(0, y, w, y)
739 dc.DrawText(str(tmpNr), 0, y - dc.GetFont().GetPixelSize().GetHeight())
741 dc.DrawLine(0, 0, w, 0)
742 dc.DrawLine(0, 0, 0, h)
746 t0 = [0] * len(self.points[0][0])
748 tSP0 = [0] * len(self.points[0][0])
750 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
751 x1 = int(w - (now - t))
752 for x in xrange(x0, x1 + 1):
753 for n in xrange(0, len(temp)):
754 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
755 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP[n] - tSP0[n]) + tSP0[n]
757 dc.DrawPoint(x, h - (tSP * h / 300))
759 dc.DrawPoint(x, h - (t * h / 300))
760 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
761 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
762 dc.SetPen(bedTempSPPen)
763 dc.DrawPoint(x, h - (btSP * h / 300))
764 dc.SetPen(bedTempPen)
765 dc.DrawPoint(x, h - (bt * h / 300))
773 self.Refresh(eraseBackground=False)
776 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
779 def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
782 if bedTempSP is None:
784 self.points.append((temp[:], tempSP[:], bedTemp, bedTempSP, time.time()))
785 wx.CallAfter(self.UpdateDrawing)
788 class LogWindow(wx.Frame):
789 def __init__(self, logText):
790 super(LogWindow, self).__init__(None, title="Machine log")
791 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
792 self.SetSize((500, 400))