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 self.termLog.AppendText(unicode(line, 'utf-8', 'replace'))
532 l = len(self.termLog.GetValue())
533 self.termLog.SetCaret(wx.Caret(self.termLog, (l, l)))
535 def OnTermEnterLine(self, e):
536 line = self.termInput.GetValue()
539 self.termLog.AppendText('>%s\n' % (line))
540 self.machineCom.sendCommand(line)
541 self.termHistory.append(line)
542 self.termHistoryIdx = len(self.termHistory)
543 self.termInput.SetValue('')
545 def OnTermKey(self, e):
546 if len(self.termHistory) > 0:
547 if e.GetKeyCode() == wx.WXK_UP:
548 self.termHistoryIdx -= 1
549 if self.termHistoryIdx < 0:
550 self.termHistoryIdx = len(self.termHistory) - 1
551 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
552 if e.GetKeyCode() == wx.WXK_DOWN:
553 self.termHistoryIdx -= 1
554 if self.termHistoryIdx >= len(self.termHistory):
555 self.termHistoryIdx = 0
556 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
559 def OnPowerWarningChange(self, e):
560 type = self.powerManagement.get_providing_power_source_type()
561 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
562 self.powerWarningText.Hide()
565 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
566 self.powerWarningText.Show()
570 def LoadGCodeFile(self, filename):
571 if self.machineCom is not None and self.machineCom.isPrinting():
573 #Send an initial M110 to reset the line counter to zero.
574 prevLineType = lineType = 'CUSTOM'
576 for line in open(filename, 'r'):
577 if line.startswith(';TYPE:'):
578 lineType = line[6:].strip()
580 line = line[0:line.find(';')]
583 if prevLineType != lineType:
584 gcodeList.append((line, lineType, ))
586 gcodeList.append(line)
587 prevLineType = lineType
588 gcode = gcodeInterpreter.gcode()
589 gcode.loadList(gcodeList)
590 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
591 self.filename = filename
593 self.gcodeList = gcodeList
595 wx.CallAfter(self.progress.SetRange, len(gcodeList))
596 wx.CallAfter(self.UpdateButtonStates)
597 wx.CallAfter(self.UpdateProgress)
600 def sendLine(self, lineNr):
601 if lineNr >= len(self.gcodeList):
603 line = self.gcodeList[lineNr]
605 if ('M104' in line or 'M109' in line) and 'S' in line:
606 n = int(re.search('S([0-9]*)', line).group(1))
607 wx.CallAfter(self.temperatureSelect.SetValue, n)
608 if ('M140' in line or 'M190' in line) and 'S' in line:
609 n = int(re.search('S([0-9]*)', line).group(1))
610 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
612 print "Unexpected error:", sys.exc_info()
613 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
614 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
617 def mcLog(self, message):
621 def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
622 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
623 wx.CallAfter(self._mcTempUpdate, temp, bedTemp, targetTemp, bedTargetTemp)
625 def _mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
626 if self.temperatureSelect.GetValue() != targetTemp[0] and wx.Window.FindFocus() != self.temperatureSelect:
627 self.temperatureSelect.SetValue(targetTemp[0])
628 if self.bedTemperatureSelect.GetValue() != bedTargetTemp and wx.Window.FindFocus() != self.bedTemperatureSelect:
629 self.bedTemperatureSelect.SetValue(bedTargetTemp)
631 def mcStateChange(self, state):
632 if self.machineCom is not None:
633 if state == self.machineCom.STATE_OPERATIONAL and self.cam is not None:
634 self.cam.endTimelapse()
635 if state == self.machineCom.STATE_OPERATIONAL:
636 taskbar.setBusy(self, False)
637 if self.machineCom.isClosedOrError():
638 taskbar.setBusy(self, False)
639 if self.machineCom.isPaused():
640 taskbar.setPause(self, True)
641 if self.machineCom.isClosedOrError():
643 elif self.machineCom.isPrinting():
644 print 'STATE:PRINTING'
647 wx.CallAfter(self.UpdateButtonStates)
648 wx.CallAfter(self.UpdateProgress)
650 def mcMessage(self, message):
651 wx.CallAfter(self.AddTermLog, message)
653 def mcProgress(self, lineNr):
654 wx.CallAfter(self.UpdateProgress)
656 def mcZChange(self, newZ):
659 if self.cam is not None:
660 wx.CallAfter(self.cam.takeNewImage)
661 wx.CallAfter(self.camPreview.Refresh)
664 class temperatureGraph(wx.Panel):
665 def __init__(self, parent):
666 super(temperatureGraph, self).__init__(parent)
668 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
669 self.Bind(wx.EVT_SIZE, self.OnSize)
670 self.Bind(wx.EVT_PAINT, self.OnDraw)
672 self.lastDraw = time.time() - 1.0
674 self.backBuffer = None
675 self.addPoint([0]*16, [0]*16, 0, 0)
676 self.SetMinSize((320, 200))
678 def OnEraseBackground(self, e):
682 if self.backBuffer is None or self.GetSize() != self.backBuffer.GetSize():
683 self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
684 self.UpdateDrawing(True)
687 dc = wx.BufferedPaintDC(self, self.backBuffer)
689 def UpdateDrawing(self, force=False):
691 if not force and now - self.lastDraw < 1.0:
695 dc.SelectObject(self.backBuffer)
697 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
698 w, h = self.GetSizeTuple()
699 bgLinePen = wx.Pen('#A0A0A0')
700 tempPen = wx.Pen('#FF4040')
701 tempSPPen = wx.Pen('#FFA0A0')
702 tempPenBG = wx.Pen('#FFD0D0')
703 bedTempPen = wx.Pen('#4040FF')
704 bedTempSPPen = wx.Pen('#A0A0FF')
705 bedTempPenBG = wx.Pen('#D0D0FF')
707 #Draw the background up to the current temperatures.
709 t0 = [0] * len(self.points[0][0])
713 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
714 x1 = int(w - (now - t))
715 for x in xrange(x0, x1 + 1):
716 for n in xrange(0, len(temp)):
717 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
719 dc.DrawLine(x, h, x, h - (t * h / 300))
720 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
721 dc.SetPen(bedTempPenBG)
722 dc.DrawLine(x, h, x, h - (bt * h / 300))
730 for x in xrange(w, 0, -30):
732 dc.DrawLine(x, 0, x, h)
734 for y in xrange(h - 1, 0, -h * 50 / 300):
736 dc.DrawLine(0, y, w, y)
737 dc.DrawText(str(tmpNr), 0, y - dc.GetFont().GetPixelSize().GetHeight())
739 dc.DrawLine(0, 0, w, 0)
740 dc.DrawLine(0, 0, 0, h)
744 t0 = [0] * len(self.points[0][0])
746 tSP0 = [0] * len(self.points[0][0])
748 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
749 x1 = int(w - (now - t))
750 for x in xrange(x0, x1 + 1):
751 for n in xrange(0, len(temp)):
752 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
753 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP[n] - tSP0[n]) + tSP0[n]
755 dc.DrawPoint(x, h - (tSP * h / 300))
757 dc.DrawPoint(x, h - (t * h / 300))
758 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
759 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
760 dc.SetPen(bedTempSPPen)
761 dc.DrawPoint(x, h - (btSP * h / 300))
762 dc.SetPen(bedTempPen)
763 dc.DrawPoint(x, h - (bt * h / 300))
771 self.Refresh(eraseBackground=False)
774 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
777 def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
780 if bedTempSP is None:
782 self.points.append((temp[:], tempSP[:], bedTemp, bedTempSP, time.time()))
783 wx.CallAfter(self.UpdateDrawing)
786 class LogWindow(wx.Frame):
787 def __init__(self, logText):
788 super(LogWindow, self).__init__(None, title="Machine log")
789 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
790 self.SetSize((500, 400))