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 printWindowMonitorHandle = None
25 def printFile(filename):
26 global printWindowMonitorHandle
27 if printWindowMonitorHandle is None:
28 printWindowMonitorHandle = printProcessMonitor()
29 printWindowMonitorHandle.loadFile(filename)
32 def startPrintInterface(filename):
33 #startPrintInterface is called from the main script when we want the printer interface to run in a seperate process.
34 # It needs to run in a seperate process, as any running python code blocks the GCode sender pyton code (http://wiki.python.org/moin/GlobalInterpreterLock).
36 printWindowHandle = printWindow()
37 printWindowHandle.Show(True)
38 printWindowHandle.Raise()
39 printWindowHandle.OnConnect(None)
40 t = threading.Thread(target=printWindowHandle.LoadGCodeFile, args=(filename,))
45 class printProcessMonitor():
49 def loadFile(self, filename):
50 if self.handle is None:
51 if platform.system() == "Darwin" and hasattr(sys, 'frozen'):
52 cmdList = [os.path.join(os.path.dirname(sys.executable), 'Cura')]
54 cmdList = [sys.executable, '-m', 'Cura.cura']
56 cmdList.append(filename)
57 if platform.system() == "Darwin":
58 if platform.machine() == 'i386':
59 cmdList.insert(0, 'arch')
60 cmdList.insert(1, '-i386')
61 self.handle = subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 stderr=subprocess.PIPE)
63 self.thread = threading.Thread(target=self.Monitor)
66 self.handle.stdin.write(filename + '\n')
70 line = p.stdout.readline()
73 line = p.stdout.readline()
74 line = p.stderr.readline()
77 line = p.stderr.readline()
83 class PrintCommandButton(buttons.GenBitmapButton):
84 def __init__(self, parent, commandList, bitmapFilename, size=(20, 20)):
85 self.bitmap = wx.Bitmap(getPathForImage(bitmapFilename))
86 super(PrintCommandButton, self).__init__(parent.directControlPanel, -1, self.bitmap, size=size)
88 self.commandList = commandList
92 self.SetUseFocusIndicator(False)
94 self.Bind(wx.EVT_BUTTON, self.OnClick)
97 if self.parent.machineCom is None or self.parent.machineCom.isPrinting():
99 for cmd in self.commandList:
100 self.parent.machineCom.sendCommand(cmd)
104 class printWindow(wx.Frame):
105 "Main user interface window"
108 super(printWindow, self).__init__(None, -1, title='Printing')
109 self.machineCom = None
111 self.gcodeList = None
115 self.bufferLineCount = 4
117 self.feedrateRatioOuterWall = 1.0
118 self.feedrateRatioInnerWall = 1.0
119 self.feedrateRatioFill = 1.0
120 self.feedrateRatioSupport = 1.0
122 self.termHistory = []
123 self.termHistoryIdx = 0
126 if webcam.hasWebcamSupport():
127 self.cam = webcam.webcam()
128 if not self.cam.hasCamera():
131 self.SetSizer(wx.BoxSizer())
132 self.panel = wx.Panel(self)
133 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
134 self.sizer = wx.GridBagSizer(2, 2)
135 self.panel.SetSizer(self.sizer)
137 sb = wx.StaticBox(self.panel, label="Statistics")
138 boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)
140 self.powerWarningText = wx.StaticText(parent=self.panel,
142 label="Your computer is running on battery power.\nConnect your computer to AC power or your print might not finish.",
143 style=wx.ALIGN_CENTER)
144 self.powerWarningText.SetBackgroundColour('red')
145 self.powerWarningText.SetForegroundColour('white')
146 boxsizer.AddF(self.powerWarningText, flags=wx.SizerFlags().Expand().Border(wx.BOTTOM, 10))
147 self.powerManagement = power.PowerManagement()
148 self.powerWarningTimer = wx.Timer(self)
149 self.Bind(wx.EVT_TIMER, self.OnPowerWarningChange, self.powerWarningTimer)
150 self.OnPowerWarningChange(None)
151 self.powerWarningTimer.Start(10000)
153 self.statsText = wx.StaticText(self.panel, -1,
154 "Filament: ####.##m #.##g\nEstimated print time: #####:##\nMachine state:\nDetecting baudrateXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
155 boxsizer.Add(self.statsText, flag=wx.LEFT, border=5)
157 self.sizer.Add(boxsizer, pos=(0, 0), span=(7, 1), flag=wx.EXPAND)
159 self.connectButton = wx.Button(self.panel, -1, 'Connect')
160 #self.loadButton = wx.Button(self.panel, -1, 'Load')
161 self.printButton = wx.Button(self.panel, -1, 'Print')
162 self.pauseButton = wx.Button(self.panel, -1, 'Pause')
163 self.cancelButton = wx.Button(self.panel, -1, 'Cancel print')
164 self.machineLogButton = wx.Button(self.panel, -1, 'Error log')
165 self.progress = wx.Gauge(self.panel, -1)
167 self.sizer.Add(self.connectButton, pos=(1, 1), flag=wx.EXPAND)
168 #self.sizer.Add(self.loadButton, pos=(1,1), flag=wx.EXPAND)
169 self.sizer.Add(self.printButton, pos=(2, 1), flag=wx.EXPAND)
170 self.sizer.Add(self.pauseButton, pos=(3, 1), flag=wx.EXPAND)
171 self.sizer.Add(self.cancelButton, pos=(4, 1), flag=wx.EXPAND)
172 self.sizer.Add(self.machineLogButton, pos=(5, 1), flag=wx.EXPAND)
173 self.sizer.Add(self.progress, pos=(7, 0), span=(1, 7), flag=wx.EXPAND)
175 nb = wx.Notebook(self.panel)
176 self.sizer.Add(nb, pos=(0, 2), span=(7, 4), flag=wx.EXPAND)
178 self.temperaturePanel = wx.Panel(nb)
179 sizer = wx.GridBagSizer(2, 2)
180 self.temperaturePanel.SetSizer(sizer)
182 self.temperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
183 self.temperatureSelect.SetRange(0, 400)
184 self.temperatureHeatUpPLA = wx.Button(self.temperaturePanel, -1, '210C')
185 self.bedTemperatureLabel = wx.StaticText(self.temperaturePanel, -1, "BedTemp:")
186 self.bedTemperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21),
187 style=wx.SP_ARROW_KEYS)
188 self.bedTemperatureSelect.SetRange(0, 400)
189 self.bedTemperatureLabel.Show(False)
190 self.bedTemperatureSelect.Show(False)
192 self.temperatureGraph = temperatureGraph(self.temperaturePanel)
194 sizer.Add(wx.StaticText(self.temperaturePanel, -1, "Temp:"), pos=(0, 0))
195 sizer.Add(self.temperatureSelect, pos=(0, 1))
196 sizer.Add(self.temperatureHeatUpPLA, pos=(0, 2))
197 sizer.Add(self.bedTemperatureLabel, pos=(1, 0))
198 sizer.Add(self.bedTemperatureSelect, pos=(1, 1))
199 sizer.Add(self.temperatureGraph, pos=(2, 0), span=(1, 3), flag=wx.EXPAND)
200 sizer.AddGrowableRow(2)
201 sizer.AddGrowableCol(2)
203 nb.AddPage(self.temperaturePanel, 'Temp')
205 self.directControlPanel = wx.Panel(nb)
207 sizer = wx.GridBagSizer(2, 2)
208 self.directControlPanel.SetSizer(sizer)
209 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y100 F6000', 'G90'], 'print-move-y100.png'), pos=(0, 3))
210 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y10 F6000', 'G90'], 'print-move-y10.png'), pos=(1, 3))
211 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y1 F6000', 'G90'], 'print-move-y1.png'), pos=(2, 3))
213 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-1 F6000', 'G90'], 'print-move-y-1.png'), pos=(4, 3))
214 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-10 F6000', 'G90'], 'print-move-y-10.png'), pos=(5, 3))
215 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-100 F6000', 'G90'], 'print-move-y-100.png'), pos=(6, 3))
217 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-100 F6000', 'G90'], 'print-move-x-100.png'), pos=(3, 0))
218 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-10 F6000', 'G90'], 'print-move-x-10.png'), pos=(3, 1))
219 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-1 F6000', 'G90'], 'print-move-x-1.png'), pos=(3, 2))
221 sizer.Add(PrintCommandButton(self, ['G28 X0 Y0'], 'print-move-home.png'), pos=(3, 3))
223 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X1 F6000', 'G90'], 'print-move-x1.png'), pos=(3, 4))
224 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X10 F6000', 'G90'], 'print-move-x10.png'), pos=(3, 5))
225 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X100 F6000', 'G90'], 'print-move-x100.png'), pos=(3, 6))
227 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z10 F200', 'G90'], 'print-move-z10.png'), pos=(0, 8))
228 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z1 F200', 'G90'], 'print-move-z1.png'), pos=(1, 8))
229 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z0.1 F200', 'G90'], 'print-move-z0.1.png'), pos=(2, 8))
231 sizer.Add(PrintCommandButton(self, ['G28 Z0'], 'print-move-home.png'), pos=(3, 8))
233 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-0.1 F200', 'G90'], 'print-move-z-0.1.png'), pos=(4, 8))
234 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-1 F200', 'G90'], 'print-move-z-1.png'), pos=(5, 8))
235 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-10 F200', 'G90'], 'print-move-z-10.png'), pos=(6, 8))
237 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E2 F120'], 'extrude.png', size=(60, 20)), pos=(1, 10),
238 span=(1, 3), flag=wx.EXPAND)
239 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E-2 F120'], 'retract.png', size=(60, 20)), pos=(2, 10),
240 span=(1, 3), flag=wx.EXPAND)
242 nb.AddPage(self.directControlPanel, 'Jog')
244 self.speedPanel = wx.Panel(nb)
245 sizer = wx.GridBagSizer(2, 2)
246 self.speedPanel.SetSizer(sizer)
248 self.outerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
249 self.outerWallSpeedSelect.SetRange(5, 1000)
250 self.innerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
251 self.innerWallSpeedSelect.SetRange(5, 1000)
252 self.fillSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
253 self.fillSpeedSelect.SetRange(5, 1000)
254 self.supportSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
255 self.supportSpeedSelect.SetRange(5, 1000)
257 sizer.Add(wx.StaticText(self.speedPanel, -1, "Outer wall:"), pos=(0, 0))
258 sizer.Add(self.outerWallSpeedSelect, pos=(0, 1))
259 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(0, 2))
260 sizer.Add(wx.StaticText(self.speedPanel, -1, "Inner wall:"), pos=(1, 0))
261 sizer.Add(self.innerWallSpeedSelect, pos=(1, 1))
262 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(1, 2))
263 sizer.Add(wx.StaticText(self.speedPanel, -1, "Fill:"), pos=(2, 0))
264 sizer.Add(self.fillSpeedSelect, pos=(2, 1))
265 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(2, 2))
266 sizer.Add(wx.StaticText(self.speedPanel, -1, "Support:"), pos=(3, 0))
267 sizer.Add(self.supportSpeedSelect, pos=(3, 1))
268 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(3, 2))
270 nb.AddPage(self.speedPanel, 'Speed')
272 self.termPanel = wx.Panel(nb)
273 sizer = wx.GridBagSizer(2, 2)
274 self.termPanel.SetSizer(sizer)
276 f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
277 self.termLog = wx.TextCtrl(self.termPanel, style=wx.TE_MULTILINE | wx.TE_DONTWRAP)
278 self.termLog.SetFont(f)
279 self.termLog.SetEditable(0)
280 self.termInput = wx.TextCtrl(self.termPanel, style=wx.TE_PROCESS_ENTER)
281 self.termInput.SetFont(f)
283 sizer.Add(self.termLog, pos=(0, 0), flag=wx.EXPAND)
284 sizer.Add(self.termInput, pos=(1, 0), flag=wx.EXPAND)
285 sizer.AddGrowableCol(0)
286 sizer.AddGrowableRow(0)
288 nb.AddPage(self.termPanel, 'Term')
290 if self.cam is not None:
291 self.camPage = wx.Panel(nb)
292 sizer = wx.GridBagSizer(2, 2)
293 self.camPage.SetSizer(sizer)
295 self.timelapsEnable = wx.CheckBox(self.camPage, -1, 'Enable timelapse movie recording')
296 self.timelapsSavePath = wx.TextCtrl(self.camPage, -1, os.path.expanduser('~/timelaps_' + datetime.datetime.now().strftime('%Y-%m-%d_%H:%M') + '.mpg'))
297 sizer.Add(self.timelapsEnable, pos=(0, 0), span=(1, 2), flag=wx.EXPAND)
298 sizer.Add(self.timelapsSavePath, pos=(1, 0), span=(1, 2), flag=wx.EXPAND)
300 pages = self.cam.propertyPages()
301 self.cam.buttons = [self.timelapsEnable, self.timelapsSavePath]
303 button = wx.Button(self.camPage, -1, page)
304 button.index = pages.index(page)
305 sizer.Add(button, pos=(2, pages.index(page)))
306 button.Bind(wx.EVT_BUTTON, self.OnPropertyPageButton)
307 self.cam.buttons.append(button)
309 self.campreviewEnable = wx.CheckBox(self.camPage, -1, 'Show preview')
310 sizer.Add(self.campreviewEnable, pos=(3, 0), span=(1, 2), flag=wx.EXPAND)
312 self.camPreview = wx.Panel(self.camPage)
313 sizer.Add(self.camPreview, pos=(4, 0), span=(1, 2), flag=wx.EXPAND)
315 nb.AddPage(self.camPage, 'Camera')
316 self.camPreview.timer = wx.Timer(self)
317 self.Bind(wx.EVT_TIMER, self.OnCameraTimer, self.camPreview.timer)
318 self.camPreview.timer.Start(500)
319 self.camPreview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
321 self.sizer.AddGrowableRow(6)
322 self.sizer.AddGrowableCol(3)
324 self.Bind(wx.EVT_CLOSE, self.OnClose)
325 self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect)
326 #self.loadButton.Bind(wx.EVT_BUTTON, self.OnLoad)
327 self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint)
328 self.pauseButton.Bind(wx.EVT_BUTTON, self.OnPause)
329 self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)
330 self.machineLogButton.Bind(wx.EVT_BUTTON, self.OnMachineLog)
332 self.Bind(wx.EVT_BUTTON, lambda e: (self.temperatureSelect.SetValue(210), self.machineCom.sendCommand("M104 S210")), self.temperatureHeatUpPLA)
333 self.Bind(wx.EVT_SPINCTRL, self.OnTempChange, self.temperatureSelect)
334 self.Bind(wx.EVT_SPINCTRL, self.OnBedTempChange, self.bedTemperatureSelect)
336 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.outerWallSpeedSelect)
337 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.innerWallSpeedSelect)
338 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.fillSpeedSelect)
339 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.supportSpeedSelect)
340 self.Bind(wx.EVT_TEXT_ENTER, self.OnTermEnterLine, self.termInput)
341 self.termInput.Bind(wx.EVT_CHAR, self.OnTermKey)
347 self.statsText.SetMinSize(self.statsText.GetSize())
349 self.UpdateButtonStates()
351 #self.UpdateProgress()
353 def OnCameraTimer(self, e):
354 if not self.campreviewEnable.GetValue():
356 if self.machineCom is not None and self.machineCom.isPrinting():
358 self.cam.takeNewImage()
359 self.camPreview.Refresh()
361 def OnCameraEraseBackground(self, e):
364 dc = wx.ClientDC(self)
365 rect = self.GetUpdateRegion().GetBox()
366 dc.SetClippingRect(rect)
367 dc.SetBackground(wx.Brush(self.camPreview.GetBackgroundColour(), wx.SOLID))
368 if self.cam.getLastImage() is not None:
369 self.camPreview.SetMinSize((self.cam.getLastImage().GetWidth(), self.cam.getLastImage().GetHeight()))
371 dc.DrawBitmap(self.cam.getLastImage(), 0, 0)
375 def OnPropertyPageButton(self, e):
376 self.cam.openPropertyPage(e.GetEventObject().index)
378 def UpdateButtonStates(self):
379 self.connectButton.Enable(self.machineCom is None or self.machineCom.isClosedOrError())
380 #self.loadButton.Enable(self.machineCom == None or not (self.machineCom.isPrinting() or self.machineCom.isPaused()))
381 self.printButton.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
382 self.machineCom.isPrinting() or self.machineCom.isPaused()))
383 self.temperatureHeatUpPLA.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
384 self.machineCom.isPrinting() or self.machineCom.isPaused()))
385 self.pauseButton.Enable(
386 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
387 if self.machineCom is not None and self.machineCom.isPaused():
388 self.pauseButton.SetLabel('Resume')
390 self.pauseButton.SetLabel('Pause')
391 self.cancelButton.Enable(
392 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
393 self.temperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
394 self.bedTemperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
395 self.directControlPanel.Enable(
396 self.machineCom is not None and self.machineCom.isOperational() and not self.machineCom.isPrinting())
397 self.machineLogButton.Show(self.machineCom is not None and self.machineCom.isClosedOrError())
399 for button in self.cam.buttons:
400 button.Enable(self.machineCom is None or not self.machineCom.isPrinting())
402 def UpdateProgress(self):
404 if self.gcode is None:
405 status += "Loading gcode...\n"
407 status += "Filament: %.2fm %.2fg\n" % (
408 self.gcode.extrusionAmount / 1000, self.gcode.calculateWeight() * 1000)
409 cost = self.gcode.calculateCost()
411 status += "Filament cost: %s\n" % (cost)
412 #status += "Estimated print time: %02d:%02d\n" % (int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60))
413 if self.machineCom is None or not self.machineCom.isPrinting():
414 self.progress.SetValue(0)
415 if self.gcodeList is not None:
416 status += 'Line: -/%d\n' % (len(self.gcodeList))
418 printTime = self.machineCom.getPrintTime() / 60
419 printTimeLeft = self.machineCom.getPrintTimeRemainingEstimate()
420 status += 'Line: %d/%d %d%%\n' % (self.machineCom.getPrintPos(), len(self.gcodeList),
421 self.machineCom.getPrintPos() * 100 / len(self.gcodeList))
422 if self.currentZ > 0:
423 status += 'Height: %0.1f\n' % (self.currentZ)
424 status += 'Print time: %02d:%02d\n' % (int(printTime / 60), int(printTime % 60))
425 if printTimeLeft is None:
426 status += 'Print time left: Unknown\n'
428 status += 'Print time left: %02d:%02d\n' % (int(printTimeLeft / 60), int(printTimeLeft % 60))
429 self.progress.SetValue(self.machineCom.getPrintPos())
430 taskbar.setProgress(self, self.machineCom.getPrintPos(), len(self.gcodeList))
431 if self.machineCom != None:
432 if self.machineCom.getTemp() > 0:
433 status += 'Temp: %s\n' % (' ,'.join(map(str, self.machineCom.getTemp())))
434 if self.machineCom.getBedTemp() > 0:
435 status += 'Bed Temp: %d\n' % (self.machineCom.getBedTemp())
436 self.bedTemperatureLabel.Show(True)
437 self.bedTemperatureSelect.Show(True)
438 self.temperaturePanel.Layout()
439 status += 'Machine state:%s\n' % (self.machineCom.getStateString())
441 self.statsText.SetLabel(status.strip())
443 def OnConnect(self, e):
444 if self.machineCom is not None:
445 self.machineCom.close()
446 self.machineCom = machineCom.MachineCom(callbackObject=self)
447 self.UpdateButtonStates()
448 taskbar.setBusy(self, True)
453 def OnPrint(self, e):
454 if self.machineCom is None or not self.machineCom.isOperational():
456 if self.gcodeList is None:
458 if self.machineCom.isPrinting():
461 if self.cam is not None and self.timelapsEnable.GetValue():
462 self.cam.startTimelapse(self.timelapsSavePath)
463 self.machineCom.printGCode(self.gcodeList)
464 self.UpdateButtonStates()
466 def OnCancel(self, e):
467 self.pauseButton.SetLabel('Pause')
468 self.machineCom.cancelPrint()
469 self.machineCom.sendCommand("M84")
470 self.machineCom.sendCommand("M104 S0")
471 self.UpdateButtonStates()
473 def OnPause(self, e):
474 if self.machineCom.isPaused():
475 self.machineCom.setPause(False)
477 self.machineCom.setPause(True)
479 def OnMachineLog(self, e):
480 LogWindow('\n'.join(self.machineCom.getLog()))
482 def OnClose(self, e):
483 global printWindowHandle
484 printWindowHandle = None
485 if self.machineCom is not None:
486 self.machineCom.close()
489 def OnTempChange(self, e):
490 self.machineCom.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))
492 def OnBedTempChange(self, e):
493 self.machineCom.sendCommand("M140 S%d" % (self.bedTemperatureSelect.GetValue()))
495 def OnSpeedChange(self, e):
496 if self.machineCom is None:
498 self.machineCom.setFeedrateModifier('WALL-OUTER', self.outerWallSpeedSelect.GetValue() / 100.0)
499 self.machineCom.setFeedrateModifier('WALL-INNER', self.innerWallSpeedSelect.GetValue() / 100.0)
500 self.machineCom.setFeedrateModifier('FILL', self.fillSpeedSelect.GetValue() / 100.0)
501 self.machineCom.setFeedrateModifier('SUPPORT', self.supportSpeedSelect.GetValue() / 100.0)
503 def AddTermLog(self, line):
504 self.termLog.AppendText(unicode(line, 'utf-8', 'replace'))
505 l = len(self.termLog.GetValue())
506 self.termLog.SetCaret(wx.Caret(self.termLog, (l, l)))
508 def OnTermEnterLine(self, e):
509 line = self.termInput.GetValue()
512 self.termLog.AppendText('>%s\n' % (line))
513 self.machineCom.sendCommand(line)
514 self.termHistory.append(line)
515 self.termHistoryIdx = len(self.termHistory)
516 self.termInput.SetValue('')
518 def OnTermKey(self, e):
519 if len(self.termHistory) > 0:
520 if e.GetKeyCode() == wx.WXK_UP:
521 self.termHistoryIdx -= 1
522 if self.termHistoryIdx < 0:
523 self.termHistoryIdx = len(self.termHistory) - 1
524 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
525 if e.GetKeyCode() == wx.WXK_DOWN:
526 self.termHistoryIdx -= 1
527 if self.termHistoryIdx >= len(self.termHistory):
528 self.termHistoryIdx = 0
529 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
532 def OnPowerWarningChange(self, e):
533 type = self.powerManagement.get_providing_power_source_type()
534 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
535 self.powerWarningText.Hide()
538 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
539 self.powerWarningText.Show()
543 def LoadGCodeFile(self, filename):
544 if self.machineCom is not None and self.machineCom.isPrinting():
546 #Send an initial M110 to reset the line counter to zero.
547 prevLineType = lineType = 'CUSTOM'
549 for line in open(filename, 'r'):
550 if line.startswith(';TYPE:'):
551 lineType = line[6:].strip()
553 line = line[0:line.find(';')]
556 if prevLineType != lineType:
557 gcodeList.append((line, lineType, ))
559 gcodeList.append(line)
560 prevLineType = lineType
561 gcode = gcodeInterpreter.gcode()
562 gcode.loadList(gcodeList)
563 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
564 self.filename = filename
566 self.gcodeList = gcodeList
568 wx.CallAfter(self.progress.SetRange, len(gcodeList))
569 wx.CallAfter(self.UpdateButtonStates)
570 wx.CallAfter(self.UpdateProgress)
572 def sendLine(self, lineNr):
573 if lineNr >= len(self.gcodeList):
575 line = self.gcodeList[lineNr]
577 if ('M104' in line or 'M109' in line) and 'S' in line:
578 n = int(re.search('S([0-9]*)', line).group(1))
579 wx.CallAfter(self.temperatureSelect.SetValue, n)
580 if ('M140' in line or 'M190' in line) and 'S' in line:
581 n = int(re.search('S([0-9]*)', line).group(1))
582 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
584 print "Unexpected error:", sys.exc_info()
585 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
586 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
589 def mcLog(self, message):
593 def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
594 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
595 wx.CallAfter(self._mcTempUpdate, temp, bedTemp, targetTemp, bedTargetTemp)
597 def _mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
598 if self.temperatureSelect.GetValue() != targetTemp[0] and wx.Window.FindFocus() != self.temperatureSelect:
599 self.temperatureSelect.SetValue(targetTemp[0])
600 if self.bedTemperatureSelect.GetValue() != bedTargetTemp and wx.Window.FindFocus() != self.bedTemperatureSelect:
601 self.bedTemperatureSelect.SetValue(bedTargetTemp)
603 def mcStateChange(self, state):
604 if self.machineCom is not None:
605 if state == self.machineCom.STATE_OPERATIONAL and self.cam is not None:
606 self.cam.endTimelapse()
607 if state == self.machineCom.STATE_OPERATIONAL:
608 taskbar.setBusy(self, False)
609 if self.machineCom.isClosedOrError():
610 taskbar.setBusy(self, False)
611 if self.machineCom.isPaused():
612 taskbar.setPause(self, True)
613 wx.CallAfter(self.UpdateButtonStates)
614 wx.CallAfter(self.UpdateProgress)
616 def mcMessage(self, message):
617 wx.CallAfter(self.AddTermLog, message)
619 def mcProgress(self, lineNr):
620 wx.CallAfter(self.UpdateProgress)
622 def mcZChange(self, newZ):
624 if self.cam is not None:
625 wx.CallAfter(self.cam.takeNewImage)
626 wx.CallAfter(self.camPreview.Refresh)
629 class temperatureGraph(wx.Panel):
630 def __init__(self, parent):
631 super(temperatureGraph, self).__init__(parent)
633 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
634 self.Bind(wx.EVT_SIZE, self.OnSize)
635 self.Bind(wx.EVT_PAINT, self.OnDraw)
637 self.lastDraw = time.time() - 1.0
639 self.backBuffer = None
640 self.addPoint([0]*16, [0]*16, 0, 0)
641 self.SetMinSize((320, 200))
643 def OnEraseBackground(self, e):
647 if self.backBuffer is None or self.GetSize() != self.backBuffer.GetSize():
648 self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
649 self.UpdateDrawing(True)
652 dc = wx.BufferedPaintDC(self, self.backBuffer)
654 def UpdateDrawing(self, force=False):
656 if not force and now - self.lastDraw < 1.0:
660 dc.SelectObject(self.backBuffer)
662 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
663 w, h = self.GetSizeTuple()
664 bgLinePen = wx.Pen('#A0A0A0')
665 tempPen = wx.Pen('#FF4040')
666 tempSPPen = wx.Pen('#FFA0A0')
667 tempPenBG = wx.Pen('#FFD0D0')
668 bedTempPen = wx.Pen('#4040FF')
669 bedTempSPPen = wx.Pen('#A0A0FF')
670 bedTempPenBG = wx.Pen('#D0D0FF')
672 #Draw the background up to the current temperatures.
674 t0 = [0] * len(self.points[0][0])
678 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
679 x1 = int(w - (now - t))
680 for x in xrange(x0, x1 + 1):
681 for n in xrange(0, len(temp)):
682 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
684 dc.DrawLine(x, h, x, h - (t * h / 300))
685 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
686 dc.SetPen(bedTempPenBG)
687 dc.DrawLine(x, h, x, h - (bt * h / 300))
695 for x in xrange(w, 0, -30):
697 dc.DrawLine(x, 0, x, h)
699 for y in xrange(h - 1, 0, -h * 50 / 300):
701 dc.DrawLine(0, y, w, y)
702 dc.DrawText(str(tmpNr), 0, y - dc.GetFont().GetPixelSize().GetHeight())
704 dc.DrawLine(0, 0, w, 0)
705 dc.DrawLine(0, 0, 0, h)
709 t0 = [0] * len(self.points[0][0])
711 tSP0 = [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]
718 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP[n] - tSP0[n]) + tSP0[n]
720 dc.DrawPoint(x, h - (tSP * h / 300))
722 dc.DrawPoint(x, h - (t * h / 300))
723 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
724 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
725 dc.SetPen(bedTempSPPen)
726 dc.DrawPoint(x, h - (btSP * h / 300))
727 dc.SetPen(bedTempPen)
728 dc.DrawPoint(x, h - (bt * h / 300))
736 self.Refresh(eraseBackground=False)
739 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
742 def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
745 if bedTempSP is None:
747 self.points.append((temp[:], tempSP[:], bedTemp, bedTempSP, time.time()))
748 wx.CallAfter(self.UpdateDrawing)
751 class LogWindow(wx.Frame):
752 def __init__(self, logText):
753 super(LogWindow, self).__init__(None, title="Machine log")
754 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
755 self.SetSize((500, 400))