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 import resources
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 resources.setupLocalization()
94 printWindowHandle = printWindow()
95 printWindowHandle.Show(True)
96 printWindowHandle.Raise()
97 printWindowHandle.OnConnect(None)
98 t = threading.Thread(target=printWindowHandle.LoadGCodeFile, args=(filename,))
103 class PrintCommandButton(buttons.GenBitmapButton):
104 def __init__(self, parent, commandList, bitmapFilename, size=(20, 20)):
105 self.bitmap = wx.Bitmap(resources.getPathForImage(bitmapFilename))
106 super(PrintCommandButton, self).__init__(parent.directControlPanel, -1, self.bitmap, size=size)
108 self.commandList = commandList
111 self.SetBezelWidth(1)
112 self.SetUseFocusIndicator(False)
114 self.Bind(wx.EVT_BUTTON, self.OnClick)
116 def OnClick(self, e):
117 if self.parent.machineCom is None or self.parent.machineCom.isPrinting():
119 for cmd in self.commandList:
120 self.parent.machineCom.sendCommand(cmd)
124 class printWindow(wx.Frame):
125 "Main user interface window"
128 super(printWindow, self).__init__(None, -1, title=_("Printing"))
129 self.machineCom = None
131 self.gcodeList = None
135 self.bufferLineCount = 4
137 self.feedrateRatioOuterWall = 1.0
138 self.feedrateRatioInnerWall = 1.0
139 self.feedrateRatioFill = 1.0
140 self.feedrateRatioSupport = 1.0
142 self.termHistory = []
143 self.termHistoryIdx = 0
146 if webcam.hasWebcamSupport():
147 self.cam = webcam.webcam()
148 if not self.cam.hasCamera():
151 self.SetSizer(wx.BoxSizer())
152 self.panel = wx.Panel(self)
153 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
154 self.sizer = wx.GridBagSizer(2, 2)
155 self.panel.SetSizer(self.sizer)
157 sb = wx.StaticBox(self.panel, label=_("Statistics"))
158 boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)
160 self.powerWarningText = wx.StaticText(parent=self.panel,
162 label=_("Your computer is running on battery power.\nConnect your computer to AC power or your print might not finish."),
163 style=wx.ALIGN_CENTER)
164 self.powerWarningText.SetBackgroundColour('red')
165 self.powerWarningText.SetForegroundColour('white')
166 boxsizer.AddF(self.powerWarningText, flags=wx.SizerFlags().Expand().Border(wx.BOTTOM, 10))
167 self.powerManagement = power.PowerManagement()
168 self.powerWarningTimer = wx.Timer(self)
169 self.Bind(wx.EVT_TIMER, self.OnPowerWarningChange, self.powerWarningTimer)
170 self.OnPowerWarningChange(None)
171 self.powerWarningTimer.Start(10000)
173 self.statsText = wx.StaticText(self.panel, -1, _("Filament: ####.##m #.##g\nEstimated print time: #####:##\nMachine state:\nDetecting baudrateXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"))
174 boxsizer.Add(self.statsText, flag=wx.LEFT, border=5)
176 self.sizer.Add(boxsizer, pos=(0, 0), span=(7, 1), flag=wx.EXPAND)
178 self.connectButton = wx.Button(self.panel, -1, _("Connect"))
179 #self.loadButton = wx.Button(self.panel, -1, 'Load')
180 self.printButton = wx.Button(self.panel, -1, _("Print"))
181 self.pauseButton = wx.Button(self.panel, -1, _("Pause"))
182 self.cancelButton = wx.Button(self.panel, -1, _("Cancel print"))
183 self.machineLogButton = wx.Button(self.panel, -1, _("Error log"))
184 self.progress = wx.Gauge(self.panel, -1)
186 self.sizer.Add(self.connectButton, pos=(1, 1), flag=wx.EXPAND)
187 #self.sizer.Add(self.loadButton, pos=(1,1), flag=wx.EXPAND)
188 self.sizer.Add(self.printButton, pos=(2, 1), flag=wx.EXPAND)
189 self.sizer.Add(self.pauseButton, pos=(3, 1), flag=wx.EXPAND)
190 self.sizer.Add(self.cancelButton, pos=(4, 1), flag=wx.EXPAND)
191 self.sizer.Add(self.machineLogButton, pos=(5, 1), flag=wx.EXPAND)
192 self.sizer.Add(self.progress, pos=(7, 0), span=(1, 7), flag=wx.EXPAND)
194 nb = wx.Notebook(self.panel)
195 self.sizer.Add(nb, pos=(0, 2), span=(7, 4), flag=wx.EXPAND)
197 self.temperaturePanel = wx.Panel(nb)
198 sizer = wx.GridBagSizer(2, 2)
199 self.temperaturePanel.SetSizer(sizer)
201 self.temperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
202 self.temperatureSelect.SetRange(0, 400)
203 self.temperatureHeatUpPLA = wx.Button(self.temperaturePanel, -1, '210C')
204 self.bedTemperatureLabel = wx.StaticText(self.temperaturePanel, -1, _("BedTemp:"))
205 self.bedTemperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
206 self.bedTemperatureSelect.SetRange(0, 400)
207 self.bedTemperatureLabel.Show(False)
208 self.bedTemperatureSelect.Show(False)
210 self.temperatureGraph = temperatureGraph(self.temperaturePanel)
212 sizer.Add(wx.StaticText(self.temperaturePanel, -1, _("Temp:")), pos=(0, 0))
213 sizer.Add(self.temperatureSelect, pos=(0, 1))
214 sizer.Add(self.temperatureHeatUpPLA, pos=(0, 2))
215 sizer.Add(self.bedTemperatureLabel, pos=(1, 0))
216 sizer.Add(self.bedTemperatureSelect, pos=(1, 1))
217 sizer.Add(self.temperatureGraph, pos=(2, 0), span=(1, 3), flag=wx.EXPAND)
218 sizer.AddGrowableRow(2)
219 sizer.AddGrowableCol(2)
221 nb.AddPage(self.temperaturePanel, 'Temp')
223 self.directControlPanel = wx.Panel(nb)
225 sizer = wx.GridBagSizer(2, 2)
226 self.directControlPanel.SetSizer(sizer)
227 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y100 F6000', 'G90'], 'print-move-y100.png'), pos=(0, 3))
228 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y10 F6000', 'G90'], 'print-move-y10.png'), pos=(1, 3))
229 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y1 F6000', 'G90'], 'print-move-y1.png'), pos=(2, 3))
231 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-1 F6000', 'G90'], 'print-move-y-1.png'), pos=(4, 3))
232 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-10 F6000', 'G90'], 'print-move-y-10.png'), pos=(5, 3))
233 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-100 F6000', 'G90'], 'print-move-y-100.png'), pos=(6, 3))
235 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-100 F6000', 'G90'], 'print-move-x-100.png'), pos=(3, 0))
236 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-10 F6000', 'G90'], 'print-move-x-10.png'), pos=(3, 1))
237 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-1 F6000', 'G90'], 'print-move-x-1.png'), pos=(3, 2))
239 sizer.Add(PrintCommandButton(self, ['G28 X0 Y0'], 'print-move-home.png'), pos=(3, 3))
241 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X1 F6000', 'G90'], 'print-move-x1.png'), pos=(3, 4))
242 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X10 F6000', 'G90'], 'print-move-x10.png'), pos=(3, 5))
243 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X100 F6000', 'G90'], 'print-move-x100.png'), pos=(3, 6))
245 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z10 F200', 'G90'], 'print-move-z10.png'), pos=(0, 8))
246 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z1 F200', 'G90'], 'print-move-z1.png'), pos=(1, 8))
247 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z0.1 F200', 'G90'], 'print-move-z0.1.png'), pos=(2, 8))
249 sizer.Add(PrintCommandButton(self, ['G28 Z0'], 'print-move-home.png'), pos=(3, 8))
251 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-0.1 F200', 'G90'], 'print-move-z-0.1.png'), pos=(4, 8))
252 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-1 F200', 'G90'], 'print-move-z-1.png'), pos=(5, 8))
253 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-10 F200', 'G90'], 'print-move-z-10.png'), pos=(6, 8))
255 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E2 F120'], 'extrude.png', size=(60, 20)), pos=(1, 10),
256 span=(1, 3), flag=wx.EXPAND)
257 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E-2 F120'], 'retract.png', size=(60, 20)), pos=(2, 10),
258 span=(1, 3), flag=wx.EXPAND)
260 nb.AddPage(self.directControlPanel, _("Jog"))
262 self.speedPanel = wx.Panel(nb)
263 sizer = wx.GridBagSizer(2, 2)
264 self.speedPanel.SetSizer(sizer)
266 self.outerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
267 self.outerWallSpeedSelect.SetRange(5, 1000)
268 self.innerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
269 self.innerWallSpeedSelect.SetRange(5, 1000)
270 self.fillSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
271 self.fillSpeedSelect.SetRange(5, 1000)
272 self.supportSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
273 self.supportSpeedSelect.SetRange(5, 1000)
275 sizer.Add(wx.StaticText(self.speedPanel, -1, _("Outer wall:")), pos=(0, 0))
276 sizer.Add(self.outerWallSpeedSelect, pos=(0, 1))
277 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(0, 2))
278 sizer.Add(wx.StaticText(self.speedPanel, -1, _("Inner wall:")), pos=(1, 0))
279 sizer.Add(self.innerWallSpeedSelect, pos=(1, 1))
280 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(1, 2))
281 sizer.Add(wx.StaticText(self.speedPanel, -1, _("Fill:")), pos=(2, 0))
282 sizer.Add(self.fillSpeedSelect, pos=(2, 1))
283 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(2, 2))
284 sizer.Add(wx.StaticText(self.speedPanel, -1, _("Support:")), pos=(3, 0))
285 sizer.Add(self.supportSpeedSelect, pos=(3, 1))
286 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(3, 2))
288 nb.AddPage(self.speedPanel, _("Speed"))
290 self.termPanel = wx.Panel(nb)
291 sizer = wx.GridBagSizer(2, 2)
292 self.termPanel.SetSizer(sizer)
294 f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
295 self.termLog = wx.TextCtrl(self.termPanel, style=wx.TE_MULTILINE | wx.TE_DONTWRAP)
296 self.termLog.SetFont(f)
297 self.termLog.SetEditable(0)
298 self.termInput = wx.TextCtrl(self.termPanel, style=wx.TE_PROCESS_ENTER)
299 self.termInput.SetFont(f)
301 sizer.Add(self.termLog, pos=(0, 0), flag=wx.EXPAND)
302 sizer.Add(self.termInput, pos=(1, 0), flag=wx.EXPAND)
303 sizer.AddGrowableCol(0)
304 sizer.AddGrowableRow(0)
306 nb.AddPage(self.termPanel, _("Term"))
308 if self.cam is not None:
309 self.camPage = wx.Panel(nb)
310 sizer = wx.GridBagSizer(2, 2)
311 self.camPage.SetSizer(sizer)
313 self.timelapsEnable = wx.CheckBox(self.camPage, -1, _("Enable timelapse movie recording"))
314 self.timelapsSavePath = wx.TextCtrl(self.camPage, -1, os.path.expanduser('~/timelaps_' + datetime.datetime.now().strftime('%Y-%m-%d_%H:%M') + '.mpg'))
315 sizer.Add(self.timelapsEnable, pos=(0, 0), span=(1, 2), flag=wx.EXPAND)
316 sizer.Add(self.timelapsSavePath, pos=(1, 0), span=(1, 2), flag=wx.EXPAND)
318 pages = self.cam.propertyPages()
319 self.cam.buttons = [self.timelapsEnable, self.timelapsSavePath]
321 button = wx.Button(self.camPage, -1, page)
322 button.index = pages.index(page)
323 sizer.Add(button, pos=(2, pages.index(page)))
324 button.Bind(wx.EVT_BUTTON, self.OnPropertyPageButton)
325 self.cam.buttons.append(button)
327 self.campreviewEnable = wx.CheckBox(self.camPage, -1, _("Show preview"))
328 sizer.Add(self.campreviewEnable, pos=(3, 0), span=(1, 2), flag=wx.EXPAND)
330 self.camPreview = wx.Panel(self.camPage)
331 sizer.Add(self.camPreview, pos=(4, 0), span=(1, 2), flag=wx.EXPAND)
333 nb.AddPage(self.camPage, _("Camera"))
334 self.camPreview.timer = wx.Timer(self)
335 self.Bind(wx.EVT_TIMER, self.OnCameraTimer, self.camPreview.timer)
336 self.camPreview.timer.Start(500)
337 self.camPreview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
339 self.sizer.AddGrowableRow(6)
340 self.sizer.AddGrowableCol(3)
342 self.Bind(wx.EVT_CLOSE, self.OnClose)
343 self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect)
344 #self.loadButton.Bind(wx.EVT_BUTTON, self.OnLoad)
345 self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint)
346 self.pauseButton.Bind(wx.EVT_BUTTON, self.OnPause)
347 self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)
348 self.machineLogButton.Bind(wx.EVT_BUTTON, self.OnMachineLog)
350 self.Bind(wx.EVT_BUTTON, lambda e: (self.temperatureSelect.SetValue(210), self.machineCom.sendCommand("M104 S210")), self.temperatureHeatUpPLA)
351 self.Bind(wx.EVT_SPINCTRL, self.OnTempChange, self.temperatureSelect)
352 self.Bind(wx.EVT_SPINCTRL, self.OnBedTempChange, self.bedTemperatureSelect)
354 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.outerWallSpeedSelect)
355 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.innerWallSpeedSelect)
356 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.fillSpeedSelect)
357 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.supportSpeedSelect)
358 self.Bind(wx.EVT_TEXT_ENTER, self.OnTermEnterLine, self.termInput)
359 self.termInput.Bind(wx.EVT_CHAR, self.OnTermKey)
365 self.statsText.SetMinSize(self.statsText.GetSize())
367 self.UpdateButtonStates()
369 #self.UpdateProgress()
370 self._thread = threading.Thread(target=self._stdinMonitor)
371 self._thread.daemon = True
374 def _stdinMonitor(self):
376 line = sys.stdin.readline().rstrip()
377 if line.startswith('LOAD:'):
378 if not self.LoadGCodeFile(line[5:]):
381 def OnCameraTimer(self, e):
382 if not self.campreviewEnable.GetValue():
384 if self.machineCom is not None and self.machineCom.isPrinting():
386 self.cam.takeNewImage()
387 self.camPreview.Refresh()
389 def OnCameraEraseBackground(self, e):
392 dc = wx.ClientDC(self)
393 rect = self.GetUpdateRegion().GetBox()
394 dc.SetClippingRect(rect)
395 dc.SetBackground(wx.Brush(self.camPreview.GetBackgroundColour(), wx.SOLID))
396 if self.cam.getLastImage() is not None:
397 self.camPreview.SetMinSize((self.cam.getLastImage().GetWidth(), self.cam.getLastImage().GetHeight()))
399 dc.DrawBitmap(self.cam.getLastImage(), 0, 0)
403 def OnPropertyPageButton(self, e):
404 self.cam.openPropertyPage(e.GetEventObject().index)
406 def UpdateButtonStates(self):
407 self.connectButton.Enable(self.machineCom is None or self.machineCom.isClosedOrError())
408 #self.loadButton.Enable(self.machineCom == None or not (self.machineCom.isPrinting() or self.machineCom.isPaused()))
409 self.printButton.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
410 self.machineCom.isPrinting() or self.machineCom.isPaused()))
411 self.temperatureHeatUpPLA.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
412 self.machineCom.isPrinting() or self.machineCom.isPaused()))
413 self.pauseButton.Enable(
414 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
415 if self.machineCom is not None and self.machineCom.isPaused():
416 self.pauseButton.SetLabel(_("Resume"))
418 self.pauseButton.SetLabel(_("Pause"))
419 self.cancelButton.Enable(
420 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
421 self.temperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
422 self.bedTemperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
423 self.directControlPanel.Enable(
424 self.machineCom is not None and self.machineCom.isOperational() and not self.machineCom.isPrinting())
425 self.machineLogButton.Show(self.machineCom is not None and self.machineCom.isClosedOrError())
427 for button in self.cam.buttons:
428 button.Enable(self.machineCom is None or not self.machineCom.isPrinting())
430 def UpdateProgress(self):
432 if self.gcode is None:
433 status += _("Loading gcode...\n")
435 status += _("Filament: %(amount).2fm %(weight).2fg\n") % {'amount': self.gcode.extrusionAmount / 1000, 'weight': 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.SetInsertionPointEnd()
534 self.termLog.AppendText(unicode(line, 'utf-8', 'replace'))
535 #l = self.termLog.GetLastPosition() # if needed (windows? mac?)
536 #self.termLog.ShowPosition(l)
538 def OnTermEnterLine(self, e):
539 line = self.termInput.GetValue()
542 self.termLog.AppendText('>%s\n' % (line))
543 self.machineCom.sendCommand(line)
544 self.termHistory.append(line)
545 self.termHistoryIdx = len(self.termHistory)
546 self.termInput.SetValue('')
548 def OnTermKey(self, e):
549 if len(self.termHistory) > 0:
550 if e.GetKeyCode() == wx.WXK_UP:
551 self.termHistoryIdx -= 1
552 if self.termHistoryIdx < 0:
553 self.termHistoryIdx = len(self.termHistory) - 1
554 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
555 if e.GetKeyCode() == wx.WXK_DOWN:
556 self.termHistoryIdx -= 1
557 if self.termHistoryIdx >= len(self.termHistory):
558 self.termHistoryIdx = 0
559 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
562 def OnPowerWarningChange(self, e):
563 type = self.powerManagement.get_providing_power_source_type()
564 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
565 self.powerWarningText.Hide()
568 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
569 self.powerWarningText.Show()
573 def LoadGCodeFile(self, filename):
574 if self.machineCom is not None and self.machineCom.isPrinting():
576 #Send an initial M110 to reset the line counter to zero.
577 prevLineType = lineType = 'CUSTOM'
579 for line in open(filename, 'r'):
580 if line.startswith(';TYPE:'):
581 lineType = line[6:].strip()
583 line = line[0:line.find(';')]
586 if prevLineType != lineType:
587 gcodeList.append((line, lineType, ))
589 gcodeList.append(line)
590 prevLineType = lineType
591 gcode = gcodeInterpreter.gcode()
592 gcode.loadList(gcodeList)
593 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
594 self.filename = filename
596 self.gcodeList = gcodeList
598 wx.CallAfter(self.progress.SetRange, len(gcodeList))
599 wx.CallAfter(self.UpdateButtonStates)
600 wx.CallAfter(self.UpdateProgress)
603 def sendLine(self, lineNr):
604 if lineNr >= len(self.gcodeList):
606 line = self.gcodeList[lineNr]
608 if ('M104' in line or 'M109' in line) and 'S' in line:
609 n = int(re.search('S([0-9]*)', line).group(1))
610 wx.CallAfter(self.temperatureSelect.SetValue, n)
611 if ('M140' in line or 'M190' in line) and 'S' in line:
612 n = int(re.search('S([0-9]*)', line).group(1))
613 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
615 print "Unexpected error:", sys.exc_info()
616 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
617 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
620 def mcLog(self, message):
624 def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
625 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
626 wx.CallAfter(self._mcTempUpdate, temp, bedTemp, targetTemp, bedTargetTemp)
628 def _mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
629 if self.temperatureSelect.GetValue() != targetTemp[0] and wx.Window.FindFocus() != self.temperatureSelect:
630 self.temperatureSelect.SetValue(targetTemp[0])
631 if self.bedTemperatureSelect.GetValue() != bedTargetTemp and wx.Window.FindFocus() != self.bedTemperatureSelect:
632 self.bedTemperatureSelect.SetValue(bedTargetTemp)
634 def mcStateChange(self, state):
635 if self.machineCom is not None:
636 if state == self.machineCom.STATE_OPERATIONAL and self.cam is not None:
637 self.cam.endTimelapse()
638 if state == self.machineCom.STATE_OPERATIONAL:
639 taskbar.setBusy(self, False)
640 if self.machineCom.isClosedOrError():
641 taskbar.setBusy(self, False)
642 if self.machineCom.isPaused():
643 taskbar.setPause(self, True)
644 if self.machineCom.isClosedOrError():
646 elif self.machineCom.isPrinting():
647 print 'STATE:PRINTING'
650 wx.CallAfter(self.UpdateButtonStates)
651 wx.CallAfter(self.UpdateProgress)
653 def mcMessage(self, message):
654 wx.CallAfter(self.AddTermLog, message)
656 def mcProgress(self, lineNr):
657 wx.CallAfter(self.UpdateProgress)
659 def mcZChange(self, newZ):
662 if self.cam is not None:
663 wx.CallAfter(self.cam.takeNewImage)
664 wx.CallAfter(self.camPreview.Refresh)
667 class temperatureGraph(wx.Panel):
668 def __init__(self, parent):
669 super(temperatureGraph, self).__init__(parent)
671 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
672 self.Bind(wx.EVT_SIZE, self.OnSize)
673 self.Bind(wx.EVT_PAINT, self.OnDraw)
675 self.lastDraw = time.time() - 1.0
677 self.backBuffer = None
678 self.addPoint([0]*16, [0]*16, 0, 0)
679 self.SetMinSize((320, 200))
681 def OnEraseBackground(self, e):
685 if self.backBuffer is None or self.GetSize() != self.backBuffer.GetSize():
686 self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
687 self.UpdateDrawing(True)
690 dc = wx.BufferedPaintDC(self, self.backBuffer)
692 def UpdateDrawing(self, force=False):
694 if not force and now - self.lastDraw < 1.0:
698 dc.SelectObject(self.backBuffer)
700 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
701 w, h = self.GetSizeTuple()
702 bgLinePen = wx.Pen('#A0A0A0')
703 tempPen = wx.Pen('#FF4040')
704 tempSPPen = wx.Pen('#FFA0A0')
705 tempPenBG = wx.Pen('#FFD0D0')
706 bedTempPen = wx.Pen('#4040FF')
707 bedTempSPPen = wx.Pen('#A0A0FF')
708 bedTempPenBG = wx.Pen('#D0D0FF')
710 #Draw the background up to the current temperatures.
712 t0 = [0] * len(self.points[0][0])
716 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
717 x1 = int(w - (now - t))
718 for x in xrange(x0, x1 + 1):
719 for n in xrange(0, len(temp)):
720 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
722 dc.DrawLine(x, h, x, h - (t * h / 300))
723 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
724 dc.SetPen(bedTempPenBG)
725 dc.DrawLine(x, h, x, h - (bt * h / 300))
733 for x in xrange(w, 0, -30):
735 dc.DrawLine(x, 0, x, h)
737 for y in xrange(h - 1, 0, -h * 50 / 300):
739 dc.DrawLine(0, y, w, y)
740 dc.DrawText(str(tmpNr), 0, y - dc.GetFont().GetPixelSize().GetHeight())
742 dc.DrawLine(0, 0, w, 0)
743 dc.DrawLine(0, 0, 0, h)
747 t0 = [0] * len(self.points[0][0])
749 tSP0 = [0] * len(self.points[0][0])
751 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
752 x1 = int(w - (now - t))
753 for x in xrange(x0, x1 + 1):
754 for n in xrange(0, len(temp)):
755 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
756 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP[n] - tSP0[n]) + tSP0[n]
758 dc.DrawPoint(x, h - (tSP * h / 300))
760 dc.DrawPoint(x, h - (t * h / 300))
761 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
762 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
763 dc.SetPen(bedTempSPPen)
764 dc.DrawPoint(x, h - (btSP * h / 300))
765 dc.SetPen(bedTempPen)
766 dc.DrawPoint(x, h - (bt * h / 300))
774 self.Refresh(eraseBackground=False)
777 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
780 def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
783 if bedTempSP is None:
785 self.points.append((temp[:], tempSP[:], bedTemp, bedTempSP, time.time()))
786 wx.CallAfter(self.UpdateDrawing)
789 class LogWindow(wx.Frame):
790 def __init__(self, logText):
791 super(LogWindow, self).__init__(None, title="Machine log")
792 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
793 self.SetSize((500, 400))