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()
78 class PrintCommandButton(buttons.GenBitmapButton):
79 def __init__(self, parent, commandList, bitmapFilename, size=(20, 20)):
80 self.bitmap = wx.Bitmap(getPathForImage(bitmapFilename))
81 super(PrintCommandButton, self).__init__(parent.directControlPanel, -1, self.bitmap, size=size)
83 self.commandList = commandList
87 self.SetUseFocusIndicator(False)
89 self.Bind(wx.EVT_BUTTON, self.OnClick)
92 if self.parent.machineCom is None or self.parent.machineCom.isPrinting():
94 for cmd in self.commandList:
95 self.parent.machineCom.sendCommand(cmd)
99 class printWindow(wx.Frame):
100 "Main user interface window"
103 super(printWindow, self).__init__(None, -1, title='Printing')
104 self.machineCom = None
106 self.gcodeList = None
110 self.bufferLineCount = 4
112 self.feedrateRatioOuterWall = 1.0
113 self.feedrateRatioInnerWall = 1.0
114 self.feedrateRatioFill = 1.0
115 self.feedrateRatioSupport = 1.0
117 self.termHistory = []
118 self.termHistoryIdx = 0
121 if webcam.hasWebcamSupport():
122 self.cam = webcam.webcam()
123 if not self.cam.hasCamera():
126 self.SetSizer(wx.BoxSizer())
127 self.panel = wx.Panel(self)
128 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
129 self.sizer = wx.GridBagSizer(2, 2)
130 self.panel.SetSizer(self.sizer)
132 sb = wx.StaticBox(self.panel, label="Statistics")
133 boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)
135 self.powerWarningText = wx.StaticText(parent=self.panel,
137 label="Your computer is running on battery power.\nConnect your computer to AC power or your print might not finish.",
138 style=wx.ALIGN_CENTER)
139 self.powerWarningText.SetBackgroundColour('red')
140 self.powerWarningText.SetForegroundColour('white')
141 boxsizer.AddF(self.powerWarningText, flags=wx.SizerFlags().Expand().Border(wx.BOTTOM, 10))
142 self.powerManagement = power.PowerManagement()
143 self.powerWarningTimer = wx.Timer(self)
144 self.Bind(wx.EVT_TIMER, self.OnPowerWarningChange, self.powerWarningTimer)
145 self.OnPowerWarningChange(None)
146 self.powerWarningTimer.Start(10000)
148 self.statsText = wx.StaticText(self.panel, -1,
149 "Filament: ####.##m #.##g\nEstimated print time: #####:##\nMachine state:\nDetecting baudrateXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
150 boxsizer.Add(self.statsText, flag=wx.LEFT, border=5)
152 self.sizer.Add(boxsizer, pos=(0, 0), span=(7, 1), flag=wx.EXPAND)
154 self.connectButton = wx.Button(self.panel, -1, 'Connect')
155 #self.loadButton = wx.Button(self.panel, -1, 'Load')
156 self.printButton = wx.Button(self.panel, -1, 'Print')
157 self.pauseButton = wx.Button(self.panel, -1, 'Pause')
158 self.cancelButton = wx.Button(self.panel, -1, 'Cancel print')
159 self.machineLogButton = wx.Button(self.panel, -1, 'Error log')
160 self.progress = wx.Gauge(self.panel, -1)
162 self.sizer.Add(self.connectButton, pos=(1, 1), flag=wx.EXPAND)
163 #self.sizer.Add(self.loadButton, pos=(1,1), flag=wx.EXPAND)
164 self.sizer.Add(self.printButton, pos=(2, 1), flag=wx.EXPAND)
165 self.sizer.Add(self.pauseButton, pos=(3, 1), flag=wx.EXPAND)
166 self.sizer.Add(self.cancelButton, pos=(4, 1), flag=wx.EXPAND)
167 self.sizer.Add(self.machineLogButton, pos=(5, 1), flag=wx.EXPAND)
168 self.sizer.Add(self.progress, pos=(7, 0), span=(1, 7), flag=wx.EXPAND)
170 nb = wx.Notebook(self.panel)
171 self.sizer.Add(nb, pos=(0, 2), span=(7, 4), flag=wx.EXPAND)
173 self.temperaturePanel = wx.Panel(nb)
174 sizer = wx.GridBagSizer(2, 2)
175 self.temperaturePanel.SetSizer(sizer)
177 self.temperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
178 self.temperatureSelect.SetRange(0, 400)
179 self.bedTemperatureLabel = wx.StaticText(self.temperaturePanel, -1, "BedTemp:")
180 self.bedTemperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21),
181 style=wx.SP_ARROW_KEYS)
182 self.bedTemperatureSelect.SetRange(0, 400)
183 self.bedTemperatureLabel.Show(False)
184 self.bedTemperatureSelect.Show(False)
186 self.temperatureGraph = temperatureGraph(self.temperaturePanel)
188 sizer.Add(wx.StaticText(self.temperaturePanel, -1, "Temp:"), pos=(0, 0))
189 sizer.Add(self.temperatureSelect, pos=(0, 1))
190 sizer.Add(self.bedTemperatureLabel, pos=(1, 0))
191 sizer.Add(self.bedTemperatureSelect, pos=(1, 1))
192 sizer.Add(self.temperatureGraph, pos=(2, 0), span=(1, 2), flag=wx.EXPAND)
193 sizer.AddGrowableRow(2)
194 sizer.AddGrowableCol(1)
196 nb.AddPage(self.temperaturePanel, 'Temp')
198 self.directControlPanel = wx.Panel(nb)
200 sizer = wx.GridBagSizer(2, 2)
201 self.directControlPanel.SetSizer(sizer)
202 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y100 F6000', 'G90'], 'print-move-y100.png'), pos=(0, 3))
203 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y10 F6000', 'G90'], 'print-move-y10.png'), pos=(1, 3))
204 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y1 F6000', 'G90'], 'print-move-y1.png'), pos=(2, 3))
206 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-1 F6000', 'G90'], 'print-move-y-1.png'), pos=(4, 3))
207 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-10 F6000', 'G90'], 'print-move-y-10.png'), pos=(5, 3))
208 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-100 F6000', 'G90'], 'print-move-y-100.png'), pos=(6, 3))
210 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-100 F6000', 'G90'], 'print-move-x-100.png'), pos=(3, 0))
211 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-10 F6000', 'G90'], 'print-move-x-10.png'), pos=(3, 1))
212 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-1 F6000', 'G90'], 'print-move-x-1.png'), pos=(3, 2))
214 sizer.Add(PrintCommandButton(self, ['G28 X0 Y0'], 'print-move-home.png'), pos=(3, 3))
216 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X1 F6000', 'G90'], 'print-move-x1.png'), pos=(3, 4))
217 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X10 F6000', 'G90'], 'print-move-x10.png'), pos=(3, 5))
218 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X100 F6000', 'G90'], 'print-move-x100.png'), pos=(3, 6))
220 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z10 F200', 'G90'], 'print-move-z10.png'), pos=(0, 8))
221 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z1 F200', 'G90'], 'print-move-z1.png'), pos=(1, 8))
222 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z0.1 F200', 'G90'], 'print-move-z0.1.png'), pos=(2, 8))
224 sizer.Add(PrintCommandButton(self, ['G28 Z0'], 'print-move-home.png'), pos=(3, 8))
226 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-0.1 F200', 'G90'], 'print-move-z-0.1.png'), pos=(4, 8))
227 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-1 F200', 'G90'], 'print-move-z-1.png'), pos=(5, 8))
228 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-10 F200', 'G90'], 'print-move-z-10.png'), pos=(6, 8))
230 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E2 F120'], 'extrude.png', size=(60, 20)), pos=(1, 10),
231 span=(1, 3), flag=wx.EXPAND)
232 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E-2 F120'], 'retract.png', size=(60, 20)), pos=(2, 10),
233 span=(1, 3), flag=wx.EXPAND)
235 nb.AddPage(self.directControlPanel, 'Jog')
237 self.speedPanel = wx.Panel(nb)
238 sizer = wx.GridBagSizer(2, 2)
239 self.speedPanel.SetSizer(sizer)
241 self.outerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
242 self.outerWallSpeedSelect.SetRange(5, 1000)
243 self.innerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
244 self.innerWallSpeedSelect.SetRange(5, 1000)
245 self.fillSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
246 self.fillSpeedSelect.SetRange(5, 1000)
247 self.supportSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
248 self.supportSpeedSelect.SetRange(5, 1000)
250 sizer.Add(wx.StaticText(self.speedPanel, -1, "Outer wall:"), pos=(0, 0))
251 sizer.Add(self.outerWallSpeedSelect, pos=(0, 1))
252 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(0, 2))
253 sizer.Add(wx.StaticText(self.speedPanel, -1, "Inner wall:"), pos=(1, 0))
254 sizer.Add(self.innerWallSpeedSelect, pos=(1, 1))
255 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(1, 2))
256 sizer.Add(wx.StaticText(self.speedPanel, -1, "Fill:"), pos=(2, 0))
257 sizer.Add(self.fillSpeedSelect, pos=(2, 1))
258 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(2, 2))
259 sizer.Add(wx.StaticText(self.speedPanel, -1, "Support:"), pos=(3, 0))
260 sizer.Add(self.supportSpeedSelect, pos=(3, 1))
261 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(3, 2))
263 nb.AddPage(self.speedPanel, 'Speed')
265 self.termPanel = wx.Panel(nb)
266 sizer = wx.GridBagSizer(2, 2)
267 self.termPanel.SetSizer(sizer)
269 f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
270 self.termLog = wx.TextCtrl(self.termPanel, style=wx.TE_MULTILINE | wx.TE_DONTWRAP)
271 self.termLog.SetFont(f)
272 self.termLog.SetEditable(0)
273 self.termInput = wx.TextCtrl(self.termPanel, style=wx.TE_PROCESS_ENTER)
274 self.termInput.SetFont(f)
276 sizer.Add(self.termLog, pos=(0, 0), flag=wx.EXPAND)
277 sizer.Add(self.termInput, pos=(1, 0), flag=wx.EXPAND)
278 sizer.AddGrowableCol(0)
279 sizer.AddGrowableRow(0)
281 nb.AddPage(self.termPanel, 'Term')
283 if self.cam is not None:
284 self.camPage = wx.Panel(nb)
285 sizer = wx.GridBagSizer(2, 2)
286 self.camPage.SetSizer(sizer)
288 self.timelapsEnable = wx.CheckBox(self.camPage, -1, 'Enable timelapse movie recording')
289 sizer.Add(self.timelapsEnable, pos=(0, 0), span=(1, 2), flag=wx.EXPAND)
291 pages = self.cam.propertyPages()
292 self.cam.buttons = [self.timelapsEnable]
294 button = wx.Button(self.camPage, -1, page)
295 button.index = pages.index(page)
296 sizer.Add(button, pos=(1, pages.index(page)))
297 button.Bind(wx.EVT_BUTTON, self.OnPropertyPageButton)
298 self.cam.buttons.append(button)
300 self.campreviewEnable = wx.CheckBox(self.camPage, -1, 'Show preview')
301 sizer.Add(self.campreviewEnable, pos=(2, 0), span=(1, 2), flag=wx.EXPAND)
303 self.camPreview = wx.Panel(self.camPage)
304 sizer.Add(self.camPreview, pos=(3, 0), span=(1, 2), flag=wx.EXPAND)
306 nb.AddPage(self.camPage, 'Camera')
307 self.camPreview.timer = wx.Timer(self)
308 self.Bind(wx.EVT_TIMER, self.OnCameraTimer, self.camPreview.timer)
309 self.camPreview.timer.Start(500)
310 self.camPreview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
312 self.sizer.AddGrowableRow(6)
313 self.sizer.AddGrowableCol(3)
315 self.Bind(wx.EVT_CLOSE, self.OnClose)
316 self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect)
317 #self.loadButton.Bind(wx.EVT_BUTTON, self.OnLoad)
318 self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint)
319 self.pauseButton.Bind(wx.EVT_BUTTON, self.OnPause)
320 self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)
321 self.machineLogButton.Bind(wx.EVT_BUTTON, self.OnMachineLog)
323 self.Bind(wx.EVT_SPINCTRL, self.OnTempChange, self.temperatureSelect)
324 self.Bind(wx.EVT_SPINCTRL, self.OnBedTempChange, self.bedTemperatureSelect)
326 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.outerWallSpeedSelect)
327 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.innerWallSpeedSelect)
328 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.fillSpeedSelect)
329 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.supportSpeedSelect)
330 self.Bind(wx.EVT_TEXT_ENTER, self.OnTermEnterLine, self.termInput)
331 self.termInput.Bind(wx.EVT_CHAR, self.OnTermKey)
337 self.statsText.SetMinSize(self.statsText.GetSize())
339 self.UpdateButtonStates()
341 #self.UpdateProgress()
343 def OnCameraTimer(self, e):
344 if not self.campreviewEnable.GetValue():
346 if self.machineCom is not None and self.machineCom.isPrinting():
348 self.cam.takeNewImage()
349 self.camPreview.Refresh()
351 def OnCameraEraseBackground(self, e):
354 dc = wx.ClientDC(self)
355 rect = self.GetUpdateRegion().GetBox()
356 dc.SetClippingRect(rect)
357 dc.SetBackground(wx.Brush(self.camPreview.GetBackgroundColour(), wx.SOLID))
358 if self.cam.getLastImage() is not None:
359 self.camPreview.SetMinSize((self.cam.getLastImage().GetWidth(), self.cam.getLastImage().GetHeight()))
361 dc.DrawBitmap(self.cam.getLastImage(), 0, 0)
365 def OnPropertyPageButton(self, e):
366 self.cam.openPropertyPage(e.GetEventObject().index)
368 def UpdateButtonStates(self):
369 self.connectButton.Enable(self.machineCom is None or self.machineCom.isClosedOrError())
370 #self.loadButton.Enable(self.machineCom == None or not (self.machineCom.isPrinting() or self.machineCom.isPaused()))
371 self.printButton.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
372 self.machineCom.isPrinting() or self.machineCom.isPaused()))
373 self.pauseButton.Enable(
374 self.machineCom != None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
375 if self.machineCom != None and self.machineCom.isPaused():
376 self.pauseButton.SetLabel('Resume')
378 self.pauseButton.SetLabel('Pause')
379 self.cancelButton.Enable(
380 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
381 self.temperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
382 self.bedTemperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
383 self.directControlPanel.Enable(
384 self.machineCom is not None and self.machineCom.isOperational() and not self.machineCom.isPrinting())
385 self.machineLogButton.Show(self.machineCom is not None and self.machineCom.isClosedOrError())
387 for button in self.cam.buttons:
388 button.Enable(self.machineCom is None or not self.machineCom.isPrinting())
390 def UpdateProgress(self):
392 if self.gcode == None:
393 status += "Loading gcode...\n"
395 status += "Filament: %.2fm %.2fg\n" % (
396 self.gcode.extrusionAmount / 1000, self.gcode.calculateWeight() * 1000)
397 cost = self.gcode.calculateCost()
399 status += "Filament cost: %s\n" % (cost)
400 status += "Estimated print time: %02d:%02d\n" % (
401 int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60))
402 if self.machineCom is None or not self.machineCom.isPrinting():
403 self.progress.SetValue(0)
404 if self.gcodeList is not None:
405 status += 'Line: -/%d\n' % (len(self.gcodeList))
407 printTime = self.machineCom.getPrintTime() / 60
408 printTimeLeft = self.machineCom.getPrintTimeRemainingEstimate()
409 status += 'Line: %d/%d %d%%\n' % (self.machineCom.getPrintPos(), len(self.gcodeList),
410 self.machineCom.getPrintPos() * 100 / len(self.gcodeList))
411 if self.currentZ > 0:
412 status += 'Height: %0.1f\n' % (self.currentZ)
413 status += 'Print time: %02d:%02d\n' % (int(printTime / 60), int(printTime % 60))
414 if printTimeLeft == None:
415 status += 'Print time left: Unknown\n'
417 status += 'Print time left: %02d:%02d\n' % (int(printTimeLeft / 60), int(printTimeLeft % 60))
418 self.progress.SetValue(self.machineCom.getPrintPos())
419 taskbar.setProgress(self, self.machineCom.getPrintPos(), len(self.gcodeList))
420 if self.machineCom != None:
421 if self.machineCom.getTemp() > 0:
422 status += 'Temp: %d\n' % (self.machineCom.getTemp())
423 if self.machineCom.getBedTemp() > 0:
424 status += 'Bed Temp: %d\n' % (self.machineCom.getBedTemp())
425 self.bedTemperatureLabel.Show(True)
426 self.bedTemperatureSelect.Show(True)
427 self.temperaturePanel.Layout()
428 status += 'Machine state:%s\n' % (self.machineCom.getStateString())
430 self.statsText.SetLabel(status.strip())
432 def OnConnect(self, e):
433 if self.machineCom is not None:
434 self.machineCom.close()
435 self.machineCom = machineCom.MachineCom(callbackObject=self)
436 self.UpdateButtonStates()
437 taskbar.setBusy(self, True)
442 def OnPrint(self, e):
443 if self.machineCom is None or not self.machineCom.isOperational():
445 if self.gcodeList is None:
447 if self.machineCom.isPrinting():
450 if self.cam is not None and self.timelapsEnable.GetValue():
451 self.cam.startTimelapse(self.filename[: self.filename.rfind('.')] + ".mpg")
452 self.machineCom.printGCode(self.gcodeList)
453 self.UpdateButtonStates()
455 def OnCancel(self, e):
456 self.pauseButton.SetLabel('Pause')
457 self.machineCom.cancelPrint()
458 self.machineCom.sendCommand("M84")
459 self.UpdateButtonStates()
461 def OnPause(self, e):
462 if self.machineCom.isPaused():
463 self.machineCom.setPause(False)
465 self.machineCom.setPause(True)
467 def OnMachineLog(self, e):
468 LogWindow('\n'.join(self.machineCom.getLog()))
470 def OnClose(self, e):
471 global printWindowHandle
472 printWindowHandle = None
473 if self.machineCom != None:
474 self.machineCom.close()
477 def OnTempChange(self, e):
478 self.machineCom.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))
480 def OnBedTempChange(self, e):
481 self.machineCom.sendCommand("M140 S%d" % (self.bedTemperatureSelect.GetValue()))
483 def OnSpeedChange(self, e):
484 if self.machineCom is None:
486 self.machineCom.setFeedrateModifier('WALL-OUTER', self.outerWallSpeedSelect.GetValue() / 100.0)
487 self.machineCom.setFeedrateModifier('WALL-INNER', self.innerWallSpeedSelect.GetValue() / 100.0)
488 self.machineCom.setFeedrateModifier('FILL', self.fillSpeedSelect.GetValue() / 100.0)
489 self.machineCom.setFeedrateModifier('SUPPORT', self.supportSpeedSelect.GetValue() / 100.0)
491 def AddTermLog(self, line):
492 self.termLog.AppendText(unicode(line, 'utf-8', 'replace'))
493 l = len(self.termLog.GetValue())
494 self.termLog.SetCaret(wx.Caret(self.termLog, (l, l)))
496 def OnTermEnterLine(self, e):
497 line = self.termInput.GetValue()
500 self.termLog.AppendText('>%s\n' % (line))
501 self.machineCom.sendCommand(line)
502 self.termHistory.append(line)
503 self.termHistoryIdx = len(self.termHistory)
504 self.termInput.SetValue('')
506 def OnTermKey(self, e):
507 if len(self.termHistory) > 0:
508 if e.GetKeyCode() == wx.WXK_UP:
509 self.termHistoryIdx = self.termHistoryIdx - 1
510 if self.termHistoryIdx < 0:
511 self.termHistoryIdx = len(self.termHistory) - 1
512 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
513 if e.GetKeyCode() == wx.WXK_DOWN:
514 self.termHistoryIdx = self.termHistoryIdx - 1
515 if self.termHistoryIdx >= len(self.termHistory):
516 self.termHistoryIdx = 0
517 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
520 def OnPowerWarningChange(self, e):
521 type = self.powerManagement.get_providing_power_source_type()
522 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
523 self.powerWarningText.Hide()
526 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
527 self.powerWarningText.Show()
531 def LoadGCodeFile(self, filename):
532 if self.machineCom is not None and self.machineCom.isPrinting():
534 #Send an initial M110 to reset the line counter to zero.
535 prevLineType = lineType = 'CUSTOM'
537 for line in open(filename, 'r'):
538 if line.startswith(';TYPE:'):
539 lineType = line[6:].strip()
541 line = line[0:line.find(';')]
544 if prevLineType != lineType:
545 gcodeList.append((line, lineType, ))
547 gcodeList.append(line)
548 prevLineType = lineType
549 gcode = gcodeInterpreter.gcode()
550 gcode.loadList(gcodeList)
551 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
552 self.filename = filename
554 self.gcodeList = gcodeList
556 wx.CallAfter(self.progress.SetRange, len(gcodeList))
557 wx.CallAfter(self.UpdateButtonStates)
558 wx.CallAfter(self.UpdateProgress)
559 wx.CallAfter(self.SetTitle, 'Printing: %s' % (filename))
561 def sendLine(self, lineNr):
562 if lineNr >= len(self.gcodeList):
564 line = self.gcodeList[lineNr]
566 if ('M104' in line or 'M109' in line) and 'S' in line:
567 n = int(re.search('S([0-9]*)', line).group(1))
568 wx.CallAfter(self.temperatureSelect.SetValue, n)
569 if ('M140' in line or 'M190' in line) and 'S' in line:
570 n = int(re.search('S([0-9]*)', line).group(1))
571 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
573 print "Unexpected error:", sys.exc_info()
574 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
575 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
578 def mcLog(self, message):
582 def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
583 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
584 # ToFix, this causes problems with setting the temperature with the keyboard
585 # if self.temperatureSelect.GetValue() != targetTemp:
586 # wx.CallAfter(self.temperatureSelect.SetValue, targetTemp)
587 # if self.bedTemperatureSelect.GetValue() != bedTargetTemp:
588 # wx.CallAfter(self.bedTemperatureSelect.SetValue, bedTargetTemp)
590 def mcStateChange(self, state):
591 if self.machineCom is not None:
592 if state == self.machineCom.STATE_OPERATIONAL and self.cam is not None:
593 self.cam.endTimelapse()
594 if state == self.machineCom.STATE_OPERATIONAL:
595 taskbar.setBusy(self, False)
596 if self.machineCom.isClosedOrError():
597 taskbar.setBusy(self, False)
598 if self.machineCom.isPaused():
599 taskbar.setPause(self, True)
600 wx.CallAfter(self.UpdateButtonStates)
601 wx.CallAfter(self.UpdateProgress)
603 def mcMessage(self, message):
604 wx.CallAfter(self.AddTermLog, message)
606 def mcProgress(self, lineNr):
607 wx.CallAfter(self.UpdateProgress)
609 def mcZChange(self, newZ):
611 if self.cam is not None:
612 wx.CallAfter(self.cam.takeNewImage)
613 wx.CallAfter(self.camPreview.Refresh)
616 class temperatureGraph(wx.Panel):
617 def __init__(self, parent):
618 super(temperatureGraph, self).__init__(parent)
620 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
621 self.Bind(wx.EVT_SIZE, self.OnSize)
622 self.Bind(wx.EVT_PAINT, self.OnDraw)
624 self.lastDraw = time.time() - 1.0
626 self.backBuffer = None
627 self.addPoint(0, 0, 0, 0)
628 self.SetMinSize((320, 200))
630 def OnEraseBackground(self, e):
634 if self.backBuffer == None or self.GetSize() != self.backBuffer.GetSize():
635 self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
636 self.UpdateDrawing(True)
639 dc = wx.BufferedPaintDC(self, self.backBuffer)
641 def UpdateDrawing(self, force=False):
643 if not force and now - self.lastDraw < 1.0:
647 dc.SelectObject(self.backBuffer)
649 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
650 w, h = self.GetSizeTuple()
651 bgLinePen = wx.Pen('#A0A0A0')
652 tempPen = wx.Pen('#FF4040')
653 tempSPPen = wx.Pen('#FFA0A0')
654 tempPenBG = wx.Pen('#FFD0D0')
655 bedTempPen = wx.Pen('#4040FF')
656 bedTempSPPen = wx.Pen('#A0A0FF')
657 bedTempPenBG = wx.Pen('#D0D0FF')
659 #Draw the background up to the current temperatures.
665 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
666 x1 = int(w - (now - t))
667 for x in xrange(x0, x1 + 1):
668 t = float(x - x0) / float(x1 - x0 + 1) * (temp - t0) + t0
669 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
671 dc.DrawLine(x, h, x, h - (t * h / 300))
672 dc.SetPen(bedTempPenBG)
673 dc.DrawLine(x, h, x, h - (bt * h / 300))
681 for x in xrange(w, 0, -30):
683 dc.DrawLine(x, 0, x, h)
685 for y in xrange(h - 1, 0, -h * 50 / 300):
687 dc.DrawLine(0, y, w, y)
688 dc.DrawText(str(tmpNr), 0, y - dc.GetFont().GetPixelSize().GetHeight())
690 dc.DrawLine(0, 0, w, 0)
691 dc.DrawLine(0, 0, 0, h)
699 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
700 x1 = int(w - (now - t))
701 for x in xrange(x0, x1 + 1):
702 t = float(x - x0) / float(x1 - x0 + 1) * (temp - t0) + t0
703 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
704 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP - tSP0) + tSP0
705 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
707 dc.DrawPoint(x, h - (tSP * h / 300))
708 dc.SetPen(bedTempSPPen)
709 dc.DrawPoint(x, h - (btSP * h / 300))
711 dc.DrawPoint(x, h - (t * h / 300))
712 dc.SetPen(bedTempPen)
713 dc.DrawPoint(x, h - (bt * h / 300))
721 self.Refresh(eraseBackground=False)
724 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
727 def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
730 if bedTempSP == None:
732 self.points.append((temp, tempSP, bedTemp, bedTempSP, time.time()))
733 wx.CallAfter(self.UpdateDrawing)
736 class LogWindow(wx.Frame):
737 def __init__(self, logText):
738 super(LogWindow, self).__init__(None, title="Machine log")
739 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
740 self.SetSize((500, 400))