1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
2 from __future__ import absolute_import
14 from wx.lib import buttons
16 from Cura.gui.util import webcam
17 from Cura.gui.util import taskbar
18 from Cura.util import machineCom
19 from Cura.util import gcodeInterpreter
20 from Cura.util.resources import getPathForImage
22 printWindowMonitorHandle = None
24 def printFile(filename):
25 global printWindowMonitorHandle
26 if printWindowMonitorHandle is None:
27 printWindowMonitorHandle = printProcessMonitor()
28 printWindowMonitorHandle.loadFile(filename)
31 def startPrintInterface(filename):
32 #startPrintInterface is called from the main script when we want the printer interface to run in a seperate process.
33 # 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).
35 printWindowHandle = printWindow()
36 printWindowHandle.Show(True)
37 printWindowHandle.Raise()
38 printWindowHandle.OnConnect(None)
39 t = threading.Thread(target=printWindowHandle.LoadGCodeFile, args=(filename,))
44 class printProcessMonitor():
48 def loadFile(self, filename):
49 if self.handle is None:
50 if platform.system() == "Darwin" and hasattr(sys, 'frozen'):
51 cmdList = [os.path.join(os.path.dirname(sys.executable), 'Cura')]
53 cmdList = [sys.executable, '-m', 'Cura.cura']
55 cmdList.append(filename)
56 if platform.system() == "Darwin":
57 if platform.machine() == 'i386':
58 cmdList.insert(0, 'arch')
59 cmdList.insert(1, '-i386')
60 self.handle = subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
61 stderr=subprocess.PIPE)
62 self.thread = threading.Thread(target=self.Monitor)
65 self.handle.stdin.write(filename + '\n')
69 line = p.stdout.readline()
72 line = p.stdout.readline()
73 line = p.stderr.readline()
76 line = p.stderr.readline()
82 class PrintCommandButton(buttons.GenBitmapButton):
83 def __init__(self, parent, commandList, bitmapFilename, size=(20, 20)):
84 self.bitmap = wx.Bitmap(getPathForImage(bitmapFilename))
85 super(PrintCommandButton, self).__init__(parent.directControlPanel, -1, self.bitmap, size=size)
87 self.commandList = commandList
91 self.SetUseFocusIndicator(False)
93 self.Bind(wx.EVT_BUTTON, self.OnClick)
96 if self.parent.machineCom is None or self.parent.machineCom.isPrinting():
98 for cmd in self.commandList:
99 self.parent.machineCom.sendCommand(cmd)
103 class printWindow(wx.Frame):
104 "Main user interface window"
107 super(printWindow, self).__init__(None, -1, title='Printing')
108 self.machineCom = None
110 self.gcodeList = None
114 self.bufferLineCount = 4
116 self.feedrateRatioOuterWall = 1.0
117 self.feedrateRatioInnerWall = 1.0
118 self.feedrateRatioFill = 1.0
119 self.feedrateRatioSupport = 1.0
121 self.termHistory = []
122 self.termHistoryIdx = 0
125 if webcam.hasWebcamSupport():
126 self.cam = webcam.webcam()
127 if not self.cam.hasCamera():
130 self.SetSizer(wx.BoxSizer())
131 self.panel = wx.Panel(self)
132 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
133 self.sizer = wx.GridBagSizer(2, 2)
134 self.panel.SetSizer(self.sizer)
136 sb = wx.StaticBox(self.panel, label="Statistics")
137 boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)
139 self.powerWarningText = wx.StaticText(parent=self.panel,
141 label="Your computer is running on battery power.\nConnect your computer to AC power or your print might not finish.",
142 style=wx.ALIGN_CENTER)
143 self.powerWarningText.SetBackgroundColour('red')
144 self.powerWarningText.SetForegroundColour('white')
145 boxsizer.AddF(self.powerWarningText, flags=wx.SizerFlags().Expand().Border(wx.BOTTOM, 10))
146 self.powerManagement = power.PowerManagement()
147 self.powerWarningTimer = wx.Timer(self)
148 self.Bind(wx.EVT_TIMER, self.OnPowerWarningChange, self.powerWarningTimer)
149 self.OnPowerWarningChange(None)
150 self.powerWarningTimer.Start(10000)
152 self.statsText = wx.StaticText(self.panel, -1,
153 "Filament: ####.##m #.##g\nEstimated print time: #####:##\nMachine state:\nDetecting baudrateXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
154 boxsizer.Add(self.statsText, flag=wx.LEFT, border=5)
156 self.sizer.Add(boxsizer, pos=(0, 0), span=(7, 1), flag=wx.EXPAND)
158 self.connectButton = wx.Button(self.panel, -1, 'Connect')
159 #self.loadButton = wx.Button(self.panel, -1, 'Load')
160 self.printButton = wx.Button(self.panel, -1, 'Print')
161 self.pauseButton = wx.Button(self.panel, -1, 'Pause')
162 self.cancelButton = wx.Button(self.panel, -1, 'Cancel print')
163 self.machineLogButton = wx.Button(self.panel, -1, 'Error log')
164 self.progress = wx.Gauge(self.panel, -1)
166 self.sizer.Add(self.connectButton, pos=(1, 1), flag=wx.EXPAND)
167 #self.sizer.Add(self.loadButton, pos=(1,1), flag=wx.EXPAND)
168 self.sizer.Add(self.printButton, pos=(2, 1), flag=wx.EXPAND)
169 self.sizer.Add(self.pauseButton, pos=(3, 1), flag=wx.EXPAND)
170 self.sizer.Add(self.cancelButton, pos=(4, 1), flag=wx.EXPAND)
171 self.sizer.Add(self.machineLogButton, pos=(5, 1), flag=wx.EXPAND)
172 self.sizer.Add(self.progress, pos=(7, 0), span=(1, 7), flag=wx.EXPAND)
174 nb = wx.Notebook(self.panel)
175 self.sizer.Add(nb, pos=(0, 2), span=(7, 4), flag=wx.EXPAND)
177 self.temperaturePanel = wx.Panel(nb)
178 sizer = wx.GridBagSizer(2, 2)
179 self.temperaturePanel.SetSizer(sizer)
181 self.temperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
182 self.temperatureSelect.SetRange(0, 400)
183 self.temperatureHeatUpPLA = wx.Button(self.temperaturePanel, -1, '210C')
184 self.bedTemperatureLabel = wx.StaticText(self.temperaturePanel, -1, "BedTemp:")
185 self.bedTemperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21),
186 style=wx.SP_ARROW_KEYS)
187 self.bedTemperatureSelect.SetRange(0, 400)
188 self.bedTemperatureLabel.Show(False)
189 self.bedTemperatureSelect.Show(False)
191 self.temperatureGraph = temperatureGraph(self.temperaturePanel)
193 sizer.Add(wx.StaticText(self.temperaturePanel, -1, "Temp:"), pos=(0, 0))
194 sizer.Add(self.temperatureSelect, pos=(0, 1))
195 sizer.Add(self.temperatureHeatUpPLA, pos=(0, 2))
196 sizer.Add(self.bedTemperatureLabel, pos=(1, 0))
197 sizer.Add(self.bedTemperatureSelect, pos=(1, 1))
198 sizer.Add(self.temperatureGraph, pos=(2, 0), span=(1, 3), flag=wx.EXPAND)
199 sizer.AddGrowableRow(2)
200 sizer.AddGrowableCol(2)
202 nb.AddPage(self.temperaturePanel, 'Temp')
204 self.directControlPanel = wx.Panel(nb)
206 sizer = wx.GridBagSizer(2, 2)
207 self.directControlPanel.SetSizer(sizer)
208 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y100 F6000', 'G90'], 'print-move-y100.png'), pos=(0, 3))
209 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y10 F6000', 'G90'], 'print-move-y10.png'), pos=(1, 3))
210 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y1 F6000', 'G90'], 'print-move-y1.png'), pos=(2, 3))
212 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-1 F6000', 'G90'], 'print-move-y-1.png'), pos=(4, 3))
213 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-10 F6000', 'G90'], 'print-move-y-10.png'), pos=(5, 3))
214 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-100 F6000', 'G90'], 'print-move-y-100.png'), pos=(6, 3))
216 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-100 F6000', 'G90'], 'print-move-x-100.png'), pos=(3, 0))
217 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-10 F6000', 'G90'], 'print-move-x-10.png'), pos=(3, 1))
218 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-1 F6000', 'G90'], 'print-move-x-1.png'), pos=(3, 2))
220 sizer.Add(PrintCommandButton(self, ['G28 X0 Y0'], 'print-move-home.png'), pos=(3, 3))
222 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X1 F6000', 'G90'], 'print-move-x1.png'), pos=(3, 4))
223 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X10 F6000', 'G90'], 'print-move-x10.png'), pos=(3, 5))
224 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X100 F6000', 'G90'], 'print-move-x100.png'), pos=(3, 6))
226 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z10 F200', 'G90'], 'print-move-z10.png'), pos=(0, 8))
227 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z1 F200', 'G90'], 'print-move-z1.png'), pos=(1, 8))
228 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z0.1 F200', 'G90'], 'print-move-z0.1.png'), pos=(2, 8))
230 sizer.Add(PrintCommandButton(self, ['G28 Z0'], 'print-move-home.png'), pos=(3, 8))
232 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-0.1 F200', 'G90'], 'print-move-z-0.1.png'), pos=(4, 8))
233 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-1 F200', 'G90'], 'print-move-z-1.png'), pos=(5, 8))
234 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-10 F200', 'G90'], 'print-move-z-10.png'), pos=(6, 8))
236 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E2 F120'], 'extrude.png', size=(60, 20)), pos=(1, 10),
237 span=(1, 3), flag=wx.EXPAND)
238 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E-2 F120'], 'retract.png', size=(60, 20)), pos=(2, 10),
239 span=(1, 3), flag=wx.EXPAND)
241 nb.AddPage(self.directControlPanel, 'Jog')
243 self.speedPanel = wx.Panel(nb)
244 sizer = wx.GridBagSizer(2, 2)
245 self.speedPanel.SetSizer(sizer)
247 self.outerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
248 self.outerWallSpeedSelect.SetRange(5, 1000)
249 self.innerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
250 self.innerWallSpeedSelect.SetRange(5, 1000)
251 self.fillSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
252 self.fillSpeedSelect.SetRange(5, 1000)
253 self.supportSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
254 self.supportSpeedSelect.SetRange(5, 1000)
256 sizer.Add(wx.StaticText(self.speedPanel, -1, "Outer wall:"), pos=(0, 0))
257 sizer.Add(self.outerWallSpeedSelect, pos=(0, 1))
258 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(0, 2))
259 sizer.Add(wx.StaticText(self.speedPanel, -1, "Inner wall:"), pos=(1, 0))
260 sizer.Add(self.innerWallSpeedSelect, pos=(1, 1))
261 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(1, 2))
262 sizer.Add(wx.StaticText(self.speedPanel, -1, "Fill:"), pos=(2, 0))
263 sizer.Add(self.fillSpeedSelect, pos=(2, 1))
264 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(2, 2))
265 sizer.Add(wx.StaticText(self.speedPanel, -1, "Support:"), pos=(3, 0))
266 sizer.Add(self.supportSpeedSelect, pos=(3, 1))
267 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(3, 2))
269 nb.AddPage(self.speedPanel, 'Speed')
271 self.termPanel = wx.Panel(nb)
272 sizer = wx.GridBagSizer(2, 2)
273 self.termPanel.SetSizer(sizer)
275 f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
276 self.termLog = wx.TextCtrl(self.termPanel, style=wx.TE_MULTILINE | wx.TE_DONTWRAP)
277 self.termLog.SetFont(f)
278 self.termLog.SetEditable(0)
279 self.termInput = wx.TextCtrl(self.termPanel, style=wx.TE_PROCESS_ENTER)
280 self.termInput.SetFont(f)
282 sizer.Add(self.termLog, pos=(0, 0), flag=wx.EXPAND)
283 sizer.Add(self.termInput, pos=(1, 0), flag=wx.EXPAND)
284 sizer.AddGrowableCol(0)
285 sizer.AddGrowableRow(0)
287 nb.AddPage(self.termPanel, 'Term')
289 if self.cam is not None:
290 self.camPage = wx.Panel(nb)
291 sizer = wx.GridBagSizer(2, 2)
292 self.camPage.SetSizer(sizer)
294 self.timelapsEnable = wx.CheckBox(self.camPage, -1, 'Enable timelapse movie recording')
295 sizer.Add(self.timelapsEnable, pos=(0, 0), span=(1, 2), flag=wx.EXPAND)
297 pages = self.cam.propertyPages()
298 self.cam.buttons = [self.timelapsEnable]
300 button = wx.Button(self.camPage, -1, page)
301 button.index = pages.index(page)
302 sizer.Add(button, pos=(1, pages.index(page)))
303 button.Bind(wx.EVT_BUTTON, self.OnPropertyPageButton)
304 self.cam.buttons.append(button)
306 self.campreviewEnable = wx.CheckBox(self.camPage, -1, 'Show preview')
307 sizer.Add(self.campreviewEnable, pos=(2, 0), span=(1, 2), flag=wx.EXPAND)
309 self.camPreview = wx.Panel(self.camPage)
310 sizer.Add(self.camPreview, pos=(3, 0), span=(1, 2), flag=wx.EXPAND)
312 nb.AddPage(self.camPage, 'Camera')
313 self.camPreview.timer = wx.Timer(self)
314 self.Bind(wx.EVT_TIMER, self.OnCameraTimer, self.camPreview.timer)
315 self.camPreview.timer.Start(500)
316 self.camPreview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
318 self.sizer.AddGrowableRow(6)
319 self.sizer.AddGrowableCol(3)
321 self.Bind(wx.EVT_CLOSE, self.OnClose)
322 self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect)
323 #self.loadButton.Bind(wx.EVT_BUTTON, self.OnLoad)
324 self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint)
325 self.pauseButton.Bind(wx.EVT_BUTTON, self.OnPause)
326 self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)
327 self.machineLogButton.Bind(wx.EVT_BUTTON, self.OnMachineLog)
329 self.Bind(wx.EVT_BUTTON, lambda e: (self.temperatureSelect.SetValue(210), self.machineCom.sendCommand("M104 S210")), self.temperatureHeatUpPLA)
330 self.Bind(wx.EVT_SPINCTRL, self.OnTempChange, self.temperatureSelect)
331 self.Bind(wx.EVT_SPINCTRL, self.OnBedTempChange, self.bedTemperatureSelect)
333 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.outerWallSpeedSelect)
334 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.innerWallSpeedSelect)
335 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.fillSpeedSelect)
336 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.supportSpeedSelect)
337 self.Bind(wx.EVT_TEXT_ENTER, self.OnTermEnterLine, self.termInput)
338 self.termInput.Bind(wx.EVT_CHAR, self.OnTermKey)
344 self.statsText.SetMinSize(self.statsText.GetSize())
346 self.UpdateButtonStates()
348 #self.UpdateProgress()
350 def OnCameraTimer(self, e):
351 if not self.campreviewEnable.GetValue():
353 if self.machineCom is not None and self.machineCom.isPrinting():
355 self.cam.takeNewImage()
356 self.camPreview.Refresh()
358 def OnCameraEraseBackground(self, e):
361 dc = wx.ClientDC(self)
362 rect = self.GetUpdateRegion().GetBox()
363 dc.SetClippingRect(rect)
364 dc.SetBackground(wx.Brush(self.camPreview.GetBackgroundColour(), wx.SOLID))
365 if self.cam.getLastImage() is not None:
366 self.camPreview.SetMinSize((self.cam.getLastImage().GetWidth(), self.cam.getLastImage().GetHeight()))
368 dc.DrawBitmap(self.cam.getLastImage(), 0, 0)
372 def OnPropertyPageButton(self, e):
373 self.cam.openPropertyPage(e.GetEventObject().index)
375 def UpdateButtonStates(self):
376 self.connectButton.Enable(self.machineCom is None or self.machineCom.isClosedOrError())
377 #self.loadButton.Enable(self.machineCom == None or not (self.machineCom.isPrinting() or self.machineCom.isPaused()))
378 self.printButton.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
379 self.machineCom.isPrinting() or self.machineCom.isPaused()))
380 self.temperatureHeatUpPLA.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
381 self.machineCom.isPrinting() or self.machineCom.isPaused()))
382 self.pauseButton.Enable(
383 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
384 if self.machineCom is not None and self.machineCom.isPaused():
385 self.pauseButton.SetLabel('Resume')
387 self.pauseButton.SetLabel('Pause')
388 self.cancelButton.Enable(
389 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
390 self.temperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
391 self.bedTemperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
392 self.directControlPanel.Enable(
393 self.machineCom is not None and self.machineCom.isOperational() and not self.machineCom.isPrinting())
394 self.machineLogButton.Show(self.machineCom is not None and self.machineCom.isClosedOrError())
396 for button in self.cam.buttons:
397 button.Enable(self.machineCom is None or not self.machineCom.isPrinting())
399 def UpdateProgress(self):
401 if self.gcode == None:
402 status += "Loading gcode...\n"
404 status += "Filament: %.2fm %.2fg\n" % (
405 self.gcode.extrusionAmount / 1000, self.gcode.calculateWeight() * 1000)
406 cost = self.gcode.calculateCost()
408 status += "Filament cost: %s\n" % (cost)
409 #status += "Estimated print time: %02d:%02d\n" % (int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60))
410 if self.machineCom is None or not self.machineCom.isPrinting():
411 self.progress.SetValue(0)
412 if self.gcodeList is not None:
413 status += 'Line: -/%d\n' % (len(self.gcodeList))
415 printTime = self.machineCom.getPrintTime() / 60
416 printTimeLeft = self.machineCom.getPrintTimeRemainingEstimate()
417 status += 'Line: %d/%d %d%%\n' % (self.machineCom.getPrintPos(), len(self.gcodeList),
418 self.machineCom.getPrintPos() * 100 / len(self.gcodeList))
419 if self.currentZ > 0:
420 status += 'Height: %0.1f\n' % (self.currentZ)
421 status += 'Print time: %02d:%02d\n' % (int(printTime / 60), int(printTime % 60))
422 if printTimeLeft is None:
423 status += 'Print time left: Unknown\n'
425 status += 'Print time left: %02d:%02d\n' % (int(printTimeLeft / 60), int(printTimeLeft % 60))
426 self.progress.SetValue(self.machineCom.getPrintPos())
427 taskbar.setProgress(self, self.machineCom.getPrintPos(), len(self.gcodeList))
428 if self.machineCom != None:
429 if self.machineCom.getTemp() > 0:
430 status += 'Temp: %s\n' % (' ,'.join(map(str, self.machineCom.getTemp())))
431 if self.machineCom.getBedTemp() > 0:
432 status += 'Bed Temp: %d\n' % (self.machineCom.getBedTemp())
433 self.bedTemperatureLabel.Show(True)
434 self.bedTemperatureSelect.Show(True)
435 self.temperaturePanel.Layout()
436 status += 'Machine state:%s\n' % (self.machineCom.getStateString())
438 self.statsText.SetLabel(status.strip())
440 def OnConnect(self, e):
441 if self.machineCom is not None:
442 self.machineCom.close()
443 self.machineCom = machineCom.MachineCom(callbackObject=self)
444 self.UpdateButtonStates()
445 taskbar.setBusy(self, True)
450 def OnPrint(self, e):
451 if self.machineCom is None or not self.machineCom.isOperational():
453 if self.gcodeList is None:
455 if self.machineCom.isPrinting():
458 if self.cam is not None and self.timelapsEnable.GetValue():
459 self.cam.startTimelapse(self.filename[: self.filename.rfind('.')] + ".mpg")
460 self.machineCom.printGCode(self.gcodeList)
461 self.UpdateButtonStates()
463 def OnCancel(self, e):
464 self.pauseButton.SetLabel('Pause')
465 self.machineCom.cancelPrint()
466 self.machineCom.sendCommand("M84")
467 self.machineCom.sendCommand("M104 S0")
468 self.UpdateButtonStates()
470 def OnPause(self, e):
471 if self.machineCom.isPaused():
472 self.machineCom.setPause(False)
474 self.machineCom.setPause(True)
476 def OnMachineLog(self, e):
477 LogWindow('\n'.join(self.machineCom.getLog()))
479 def OnClose(self, e):
480 global printWindowHandle
481 printWindowHandle = None
482 if self.machineCom is not None:
483 self.machineCom.close()
486 def OnTempChange(self, e):
487 self.machineCom.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))
489 def OnBedTempChange(self, e):
490 self.machineCom.sendCommand("M140 S%d" % (self.bedTemperatureSelect.GetValue()))
492 def OnSpeedChange(self, e):
493 if self.machineCom is None:
495 self.machineCom.setFeedrateModifier('WALL-OUTER', self.outerWallSpeedSelect.GetValue() / 100.0)
496 self.machineCom.setFeedrateModifier('WALL-INNER', self.innerWallSpeedSelect.GetValue() / 100.0)
497 self.machineCom.setFeedrateModifier('FILL', self.fillSpeedSelect.GetValue() / 100.0)
498 self.machineCom.setFeedrateModifier('SUPPORT', self.supportSpeedSelect.GetValue() / 100.0)
500 def AddTermLog(self, line):
501 self.termLog.AppendText(unicode(line, 'utf-8', 'replace'))
502 l = len(self.termLog.GetValue())
503 self.termLog.SetCaret(wx.Caret(self.termLog, (l, l)))
505 def OnTermEnterLine(self, e):
506 line = self.termInput.GetValue()
509 self.termLog.AppendText('>%s\n' % (line))
510 self.machineCom.sendCommand(line)
511 self.termHistory.append(line)
512 self.termHistoryIdx = len(self.termHistory)
513 self.termInput.SetValue('')
515 def OnTermKey(self, e):
516 if len(self.termHistory) > 0:
517 if e.GetKeyCode() == wx.WXK_UP:
518 self.termHistoryIdx -= 1
519 if self.termHistoryIdx < 0:
520 self.termHistoryIdx = len(self.termHistory) - 1
521 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
522 if e.GetKeyCode() == wx.WXK_DOWN:
523 self.termHistoryIdx -= 1
524 if self.termHistoryIdx >= len(self.termHistory):
525 self.termHistoryIdx = 0
526 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
529 def OnPowerWarningChange(self, e):
530 type = self.powerManagement.get_providing_power_source_type()
531 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
532 self.powerWarningText.Hide()
535 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
536 self.powerWarningText.Show()
540 def LoadGCodeFile(self, filename):
541 if self.machineCom is not None and self.machineCom.isPrinting():
543 #Send an initial M110 to reset the line counter to zero.
544 prevLineType = lineType = 'CUSTOM'
546 for line in open(filename, 'r'):
547 if line.startswith(';TYPE:'):
548 lineType = line[6:].strip()
550 line = line[0:line.find(';')]
553 if prevLineType != lineType:
554 gcodeList.append((line, lineType, ))
556 gcodeList.append(line)
557 prevLineType = lineType
558 gcode = gcodeInterpreter.gcode()
559 gcode.loadList(gcodeList)
560 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
561 self.filename = filename
563 self.gcodeList = gcodeList
565 wx.CallAfter(self.progress.SetRange, len(gcodeList))
566 wx.CallAfter(self.UpdateButtonStates)
567 wx.CallAfter(self.UpdateProgress)
568 wx.CallAfter(self.SetTitle, 'Printing: %s' % (filename))
570 def sendLine(self, lineNr):
571 if lineNr >= len(self.gcodeList):
573 line = self.gcodeList[lineNr]
575 if ('M104' in line or 'M109' in line) and 'S' in line:
576 n = int(re.search('S([0-9]*)', line).group(1))
577 wx.CallAfter(self.temperatureSelect.SetValue, n)
578 if ('M140' in line or 'M190' in line) and 'S' in line:
579 n = int(re.search('S([0-9]*)', line).group(1))
580 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
582 print "Unexpected error:", sys.exc_info()
583 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
584 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
587 def mcLog(self, message):
591 def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
592 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
593 wx.CallAfter(self._mcTempUpdate, temp, bedTemp, targetTemp, bedTargetTemp)
595 def _mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
596 if self.temperatureSelect.GetValue() != targetTemp[0] and wx.Window.FindFocus() != self.temperatureSelect:
597 self.temperatureSelect.SetValue(targetTemp[0])
598 if self.bedTemperatureSelect.GetValue() != bedTargetTemp and wx.Window.FindFocus() != self.bedTemperatureSelect:
599 self.bedTemperatureSelect.SetValue(bedTargetTemp)
601 def mcStateChange(self, state):
602 if self.machineCom is not None:
603 if state == self.machineCom.STATE_OPERATIONAL and self.cam is not None:
604 self.cam.endTimelapse()
605 if state == self.machineCom.STATE_OPERATIONAL:
606 taskbar.setBusy(self, False)
607 if self.machineCom.isClosedOrError():
608 taskbar.setBusy(self, False)
609 if self.machineCom.isPaused():
610 taskbar.setPause(self, True)
611 wx.CallAfter(self.UpdateButtonStates)
612 wx.CallAfter(self.UpdateProgress)
614 def mcMessage(self, message):
615 wx.CallAfter(self.AddTermLog, message)
617 def mcProgress(self, lineNr):
618 wx.CallAfter(self.UpdateProgress)
620 def mcZChange(self, newZ):
622 if self.cam is not None:
623 wx.CallAfter(self.cam.takeNewImage)
624 wx.CallAfter(self.camPreview.Refresh)
627 class temperatureGraph(wx.Panel):
628 def __init__(self, parent):
629 super(temperatureGraph, self).__init__(parent)
631 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
632 self.Bind(wx.EVT_SIZE, self.OnSize)
633 self.Bind(wx.EVT_PAINT, self.OnDraw)
635 self.lastDraw = time.time() - 1.0
637 self.backBuffer = None
638 self.addPoint([0]*16, [0]*16, 0, 0)
639 self.SetMinSize((320, 200))
641 def OnEraseBackground(self, e):
645 if self.backBuffer is None or self.GetSize() != self.backBuffer.GetSize():
646 self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
647 self.UpdateDrawing(True)
650 dc = wx.BufferedPaintDC(self, self.backBuffer)
652 def UpdateDrawing(self, force=False):
654 if not force and now - self.lastDraw < 1.0:
658 dc.SelectObject(self.backBuffer)
660 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
661 w, h = self.GetSizeTuple()
662 bgLinePen = wx.Pen('#A0A0A0')
663 tempPen = wx.Pen('#FF4040')
664 tempSPPen = wx.Pen('#FFA0A0')
665 tempPenBG = wx.Pen('#FFD0D0')
666 bedTempPen = wx.Pen('#4040FF')
667 bedTempSPPen = wx.Pen('#A0A0FF')
668 bedTempPenBG = wx.Pen('#D0D0FF')
670 #Draw the background up to the current temperatures.
672 t0 = [0] * len(self.points[0][0])
676 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
677 x1 = int(w - (now - t))
678 for x in xrange(x0, x1 + 1):
679 for n in xrange(0, len(temp)):
680 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
682 dc.DrawLine(x, h, x, h - (t * h / 300))
683 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
684 dc.SetPen(bedTempPenBG)
685 dc.DrawLine(x, h, x, h - (bt * h / 300))
693 for x in xrange(w, 0, -30):
695 dc.DrawLine(x, 0, x, h)
697 for y in xrange(h - 1, 0, -h * 50 / 300):
699 dc.DrawLine(0, y, w, y)
700 dc.DrawText(str(tmpNr), 0, y - dc.GetFont().GetPixelSize().GetHeight())
702 dc.DrawLine(0, 0, w, 0)
703 dc.DrawLine(0, 0, 0, h)
707 t0 = [0] * len(self.points[0][0])
709 tSP0 = [0] * len(self.points[0][0])
711 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
712 x1 = int(w - (now - t))
713 for x in xrange(x0, x1 + 1):
714 for n in xrange(0, len(temp)):
715 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
716 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP[n] - tSP0[n]) + tSP0[n]
718 dc.DrawPoint(x, h - (tSP * h / 300))
720 dc.DrawPoint(x, h - (t * h / 300))
721 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
722 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
723 dc.SetPen(bedTempSPPen)
724 dc.DrawPoint(x, h - (btSP * h / 300))
725 dc.SetPen(bedTempPen)
726 dc.DrawPoint(x, h - (bt * h / 300))
734 self.Refresh(eraseBackground=False)
737 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
740 def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
743 if bedTempSP is None:
745 self.points.append((temp[:], tempSP[:], bedTemp, bedTempSP, time.time()))
746 wx.CallAfter(self.UpdateDrawing)
749 class LogWindow(wx.Frame):
750 def __init__(self, logText):
751 super(LogWindow, self).__init__(None, title="Machine log")
752 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
753 self.SetSize((500, 400))