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.bedTemperatureLabel = wx.StaticText(self.temperaturePanel, -1, "BedTemp:")
184 self.bedTemperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21),
185 style=wx.SP_ARROW_KEYS)
186 self.bedTemperatureSelect.SetRange(0, 400)
187 self.bedTemperatureLabel.Show(False)
188 self.bedTemperatureSelect.Show(False)
190 self.temperatureGraph = temperatureGraph(self.temperaturePanel)
192 sizer.Add(wx.StaticText(self.temperaturePanel, -1, "Temp:"), pos=(0, 0))
193 sizer.Add(self.temperatureSelect, pos=(0, 1))
194 sizer.Add(self.bedTemperatureLabel, pos=(1, 0))
195 sizer.Add(self.bedTemperatureSelect, pos=(1, 1))
196 sizer.Add(self.temperatureGraph, pos=(2, 0), span=(1, 2), flag=wx.EXPAND)
197 sizer.AddGrowableRow(2)
198 sizer.AddGrowableCol(1)
200 nb.AddPage(self.temperaturePanel, 'Temp')
202 self.directControlPanel = wx.Panel(nb)
204 sizer = wx.GridBagSizer(2, 2)
205 self.directControlPanel.SetSizer(sizer)
206 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y100 F6000', 'G90'], 'print-move-y100.png'), pos=(0, 3))
207 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y10 F6000', 'G90'], 'print-move-y10.png'), pos=(1, 3))
208 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y1 F6000', 'G90'], 'print-move-y1.png'), pos=(2, 3))
210 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-1 F6000', 'G90'], 'print-move-y-1.png'), pos=(4, 3))
211 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-10 F6000', 'G90'], 'print-move-y-10.png'), pos=(5, 3))
212 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-100 F6000', 'G90'], 'print-move-y-100.png'), pos=(6, 3))
214 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-100 F6000', 'G90'], 'print-move-x-100.png'), pos=(3, 0))
215 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-10 F6000', 'G90'], 'print-move-x-10.png'), pos=(3, 1))
216 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-1 F6000', 'G90'], 'print-move-x-1.png'), pos=(3, 2))
218 sizer.Add(PrintCommandButton(self, ['G28 X0 Y0'], 'print-move-home.png'), pos=(3, 3))
220 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X1 F6000', 'G90'], 'print-move-x1.png'), pos=(3, 4))
221 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X10 F6000', 'G90'], 'print-move-x10.png'), pos=(3, 5))
222 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X100 F6000', 'G90'], 'print-move-x100.png'), pos=(3, 6))
224 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z10 F200', 'G90'], 'print-move-z10.png'), pos=(0, 8))
225 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z1 F200', 'G90'], 'print-move-z1.png'), pos=(1, 8))
226 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z0.1 F200', 'G90'], 'print-move-z0.1.png'), pos=(2, 8))
228 sizer.Add(PrintCommandButton(self, ['G28 Z0'], 'print-move-home.png'), pos=(3, 8))
230 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-0.1 F200', 'G90'], 'print-move-z-0.1.png'), pos=(4, 8))
231 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-1 F200', 'G90'], 'print-move-z-1.png'), pos=(5, 8))
232 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-10 F200', 'G90'], 'print-move-z-10.png'), pos=(6, 8))
234 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E2 F120'], 'extrude.png', size=(60, 20)), pos=(1, 10),
235 span=(1, 3), flag=wx.EXPAND)
236 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E-2 F120'], 'retract.png', size=(60, 20)), pos=(2, 10),
237 span=(1, 3), flag=wx.EXPAND)
239 nb.AddPage(self.directControlPanel, 'Jog')
241 self.speedPanel = wx.Panel(nb)
242 sizer = wx.GridBagSizer(2, 2)
243 self.speedPanel.SetSizer(sizer)
245 self.outerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
246 self.outerWallSpeedSelect.SetRange(5, 1000)
247 self.innerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
248 self.innerWallSpeedSelect.SetRange(5, 1000)
249 self.fillSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
250 self.fillSpeedSelect.SetRange(5, 1000)
251 self.supportSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
252 self.supportSpeedSelect.SetRange(5, 1000)
254 sizer.Add(wx.StaticText(self.speedPanel, -1, "Outer wall:"), pos=(0, 0))
255 sizer.Add(self.outerWallSpeedSelect, pos=(0, 1))
256 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(0, 2))
257 sizer.Add(wx.StaticText(self.speedPanel, -1, "Inner wall:"), pos=(1, 0))
258 sizer.Add(self.innerWallSpeedSelect, pos=(1, 1))
259 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(1, 2))
260 sizer.Add(wx.StaticText(self.speedPanel, -1, "Fill:"), pos=(2, 0))
261 sizer.Add(self.fillSpeedSelect, pos=(2, 1))
262 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(2, 2))
263 sizer.Add(wx.StaticText(self.speedPanel, -1, "Support:"), pos=(3, 0))
264 sizer.Add(self.supportSpeedSelect, pos=(3, 1))
265 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(3, 2))
267 nb.AddPage(self.speedPanel, 'Speed')
269 self.termPanel = wx.Panel(nb)
270 sizer = wx.GridBagSizer(2, 2)
271 self.termPanel.SetSizer(sizer)
273 f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
274 self.termLog = wx.TextCtrl(self.termPanel, style=wx.TE_MULTILINE | wx.TE_DONTWRAP)
275 self.termLog.SetFont(f)
276 self.termLog.SetEditable(0)
277 self.termInput = wx.TextCtrl(self.termPanel, style=wx.TE_PROCESS_ENTER)
278 self.termInput.SetFont(f)
280 sizer.Add(self.termLog, pos=(0, 0), flag=wx.EXPAND)
281 sizer.Add(self.termInput, pos=(1, 0), flag=wx.EXPAND)
282 sizer.AddGrowableCol(0)
283 sizer.AddGrowableRow(0)
285 nb.AddPage(self.termPanel, 'Term')
287 if self.cam is not None:
288 self.camPage = wx.Panel(nb)
289 sizer = wx.GridBagSizer(2, 2)
290 self.camPage.SetSizer(sizer)
292 self.timelapsEnable = wx.CheckBox(self.camPage, -1, 'Enable timelapse movie recording')
293 sizer.Add(self.timelapsEnable, pos=(0, 0), span=(1, 2), flag=wx.EXPAND)
295 pages = self.cam.propertyPages()
296 self.cam.buttons = [self.timelapsEnable]
298 button = wx.Button(self.camPage, -1, page)
299 button.index = pages.index(page)
300 sizer.Add(button, pos=(1, pages.index(page)))
301 button.Bind(wx.EVT_BUTTON, self.OnPropertyPageButton)
302 self.cam.buttons.append(button)
304 self.campreviewEnable = wx.CheckBox(self.camPage, -1, 'Show preview')
305 sizer.Add(self.campreviewEnable, pos=(2, 0), span=(1, 2), flag=wx.EXPAND)
307 self.camPreview = wx.Panel(self.camPage)
308 sizer.Add(self.camPreview, pos=(3, 0), span=(1, 2), flag=wx.EXPAND)
310 nb.AddPage(self.camPage, 'Camera')
311 self.camPreview.timer = wx.Timer(self)
312 self.Bind(wx.EVT_TIMER, self.OnCameraTimer, self.camPreview.timer)
313 self.camPreview.timer.Start(500)
314 self.camPreview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
316 self.sizer.AddGrowableRow(6)
317 self.sizer.AddGrowableCol(3)
319 self.Bind(wx.EVT_CLOSE, self.OnClose)
320 self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect)
321 #self.loadButton.Bind(wx.EVT_BUTTON, self.OnLoad)
322 self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint)
323 self.pauseButton.Bind(wx.EVT_BUTTON, self.OnPause)
324 self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)
325 self.machineLogButton.Bind(wx.EVT_BUTTON, self.OnMachineLog)
327 self.Bind(wx.EVT_SPINCTRL, self.OnTempChange, self.temperatureSelect)
328 self.Bind(wx.EVT_SPINCTRL, self.OnBedTempChange, self.bedTemperatureSelect)
330 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.outerWallSpeedSelect)
331 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.innerWallSpeedSelect)
332 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.fillSpeedSelect)
333 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.supportSpeedSelect)
334 self.Bind(wx.EVT_TEXT_ENTER, self.OnTermEnterLine, self.termInput)
335 self.termInput.Bind(wx.EVT_CHAR, self.OnTermKey)
341 self.statsText.SetMinSize(self.statsText.GetSize())
343 self.UpdateButtonStates()
345 #self.UpdateProgress()
347 def OnCameraTimer(self, e):
348 if not self.campreviewEnable.GetValue():
350 if self.machineCom is not None and self.machineCom.isPrinting():
352 self.cam.takeNewImage()
353 self.camPreview.Refresh()
355 def OnCameraEraseBackground(self, e):
358 dc = wx.ClientDC(self)
359 rect = self.GetUpdateRegion().GetBox()
360 dc.SetClippingRect(rect)
361 dc.SetBackground(wx.Brush(self.camPreview.GetBackgroundColour(), wx.SOLID))
362 if self.cam.getLastImage() is not None:
363 self.camPreview.SetMinSize((self.cam.getLastImage().GetWidth(), self.cam.getLastImage().GetHeight()))
365 dc.DrawBitmap(self.cam.getLastImage(), 0, 0)
369 def OnPropertyPageButton(self, e):
370 self.cam.openPropertyPage(e.GetEventObject().index)
372 def UpdateButtonStates(self):
373 self.connectButton.Enable(self.machineCom is None or self.machineCom.isClosedOrError())
374 #self.loadButton.Enable(self.machineCom == None or not (self.machineCom.isPrinting() or self.machineCom.isPaused()))
375 self.printButton.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
376 self.machineCom.isPrinting() or self.machineCom.isPaused()))
377 self.pauseButton.Enable(
378 self.machineCom != None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
379 if self.machineCom != None and self.machineCom.isPaused():
380 self.pauseButton.SetLabel('Resume')
382 self.pauseButton.SetLabel('Pause')
383 self.cancelButton.Enable(
384 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
385 self.temperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
386 self.bedTemperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
387 self.directControlPanel.Enable(
388 self.machineCom is not None and self.machineCom.isOperational() and not self.machineCom.isPrinting())
389 self.machineLogButton.Show(self.machineCom is not None and self.machineCom.isClosedOrError())
391 for button in self.cam.buttons:
392 button.Enable(self.machineCom is None or not self.machineCom.isPrinting())
394 def UpdateProgress(self):
396 if self.gcode == None:
397 status += "Loading gcode...\n"
399 status += "Filament: %.2fm %.2fg\n" % (
400 self.gcode.extrusionAmount / 1000, self.gcode.calculateWeight() * 1000)
401 cost = self.gcode.calculateCost()
403 status += "Filament cost: %s\n" % (cost)
404 status += "Estimated print time: %02d:%02d\n" % (
405 int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60))
406 if self.machineCom is None or not self.machineCom.isPrinting():
407 self.progress.SetValue(0)
408 if self.gcodeList is not None:
409 status += 'Line: -/%d\n' % (len(self.gcodeList))
411 printTime = self.machineCom.getPrintTime() / 60
412 printTimeLeft = self.machineCom.getPrintTimeRemainingEstimate()
413 status += 'Line: %d/%d %d%%\n' % (self.machineCom.getPrintPos(), len(self.gcodeList),
414 self.machineCom.getPrintPos() * 100 / len(self.gcodeList))
415 if self.currentZ > 0:
416 status += 'Height: %0.1f\n' % (self.currentZ)
417 status += 'Print time: %02d:%02d\n' % (int(printTime / 60), int(printTime % 60))
418 if printTimeLeft == None:
419 status += 'Print time left: Unknown\n'
421 status += 'Print time left: %02d:%02d\n' % (int(printTimeLeft / 60), int(printTimeLeft % 60))
422 self.progress.SetValue(self.machineCom.getPrintPos())
423 taskbar.setProgress(self, self.machineCom.getPrintPos(), len(self.gcodeList))
424 if self.machineCom != None:
425 if self.machineCom.getTemp() > 0:
426 status += 'Temp: %d\n' % (self.machineCom.getTemp())
427 if self.machineCom.getBedTemp() > 0:
428 status += 'Bed Temp: %d\n' % (self.machineCom.getBedTemp())
429 self.bedTemperatureLabel.Show(True)
430 self.bedTemperatureSelect.Show(True)
431 self.temperaturePanel.Layout()
432 status += 'Machine state:%s\n' % (self.machineCom.getStateString())
434 self.statsText.SetLabel(status.strip())
436 def OnConnect(self, e):
437 if self.machineCom is not None:
438 self.machineCom.close()
439 self.machineCom = machineCom.MachineCom(callbackObject=self)
440 self.UpdateButtonStates()
441 taskbar.setBusy(self, True)
446 def OnPrint(self, e):
447 if self.machineCom is None or not self.machineCom.isOperational():
449 if self.gcodeList is None:
451 if self.machineCom.isPrinting():
454 if self.cam is not None and self.timelapsEnable.GetValue():
455 self.cam.startTimelapse(self.filename[: self.filename.rfind('.')] + ".mpg")
456 self.machineCom.printGCode(self.gcodeList)
457 self.UpdateButtonStates()
459 def OnCancel(self, e):
460 self.pauseButton.SetLabel('Pause')
461 self.machineCom.cancelPrint()
462 self.machineCom.sendCommand("M84")
463 self.machineCom.sendCommand("M104 S0")
464 self.UpdateButtonStates()
466 def OnPause(self, e):
467 if self.machineCom.isPaused():
468 self.machineCom.setPause(False)
470 self.machineCom.setPause(True)
472 def OnMachineLog(self, e):
473 LogWindow('\n'.join(self.machineCom.getLog()))
475 def OnClose(self, e):
476 global printWindowHandle
477 printWindowHandle = None
478 if self.machineCom is not None:
479 self.machineCom.close()
482 def OnTempChange(self, e):
483 self.machineCom.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))
485 def OnBedTempChange(self, e):
486 self.machineCom.sendCommand("M140 S%d" % (self.bedTemperatureSelect.GetValue()))
488 def OnSpeedChange(self, e):
489 if self.machineCom is None:
491 self.machineCom.setFeedrateModifier('WALL-OUTER', self.outerWallSpeedSelect.GetValue() / 100.0)
492 self.machineCom.setFeedrateModifier('WALL-INNER', self.innerWallSpeedSelect.GetValue() / 100.0)
493 self.machineCom.setFeedrateModifier('FILL', self.fillSpeedSelect.GetValue() / 100.0)
494 self.machineCom.setFeedrateModifier('SUPPORT', self.supportSpeedSelect.GetValue() / 100.0)
496 def AddTermLog(self, line):
497 self.termLog.AppendText(unicode(line, 'utf-8', 'replace'))
498 l = len(self.termLog.GetValue())
499 self.termLog.SetCaret(wx.Caret(self.termLog, (l, l)))
501 def OnTermEnterLine(self, e):
502 line = self.termInput.GetValue()
505 self.termLog.AppendText('>%s\n' % (line))
506 self.machineCom.sendCommand(line)
507 self.termHistory.append(line)
508 self.termHistoryIdx = len(self.termHistory)
509 self.termInput.SetValue('')
511 def OnTermKey(self, e):
512 if len(self.termHistory) > 0:
513 if e.GetKeyCode() == wx.WXK_UP:
514 self.termHistoryIdx = self.termHistoryIdx - 1
515 if self.termHistoryIdx < 0:
516 self.termHistoryIdx = len(self.termHistory) - 1
517 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
518 if e.GetKeyCode() == wx.WXK_DOWN:
519 self.termHistoryIdx = self.termHistoryIdx - 1
520 if self.termHistoryIdx >= len(self.termHistory):
521 self.termHistoryIdx = 0
522 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
525 def OnPowerWarningChange(self, e):
526 type = self.powerManagement.get_providing_power_source_type()
527 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
528 self.powerWarningText.Hide()
531 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
532 self.powerWarningText.Show()
536 def LoadGCodeFile(self, filename):
537 if self.machineCom is not None and self.machineCom.isPrinting():
539 #Send an initial M110 to reset the line counter to zero.
540 prevLineType = lineType = 'CUSTOM'
542 for line in open(filename, 'r'):
543 if line.startswith(';TYPE:'):
544 lineType = line[6:].strip()
546 line = line[0:line.find(';')]
549 if prevLineType != lineType:
550 gcodeList.append((line, lineType, ))
552 gcodeList.append(line)
553 prevLineType = lineType
554 gcode = gcodeInterpreter.gcode()
555 gcode.loadList(gcodeList)
556 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
557 self.filename = filename
559 self.gcodeList = gcodeList
561 wx.CallAfter(self.progress.SetRange, len(gcodeList))
562 wx.CallAfter(self.UpdateButtonStates)
563 wx.CallAfter(self.UpdateProgress)
564 wx.CallAfter(self.SetTitle, 'Printing: %s' % (filename))
566 def sendLine(self, lineNr):
567 if lineNr >= len(self.gcodeList):
569 line = self.gcodeList[lineNr]
571 if ('M104' in line or 'M109' in line) and 'S' in line:
572 n = int(re.search('S([0-9]*)', line).group(1))
573 wx.CallAfter(self.temperatureSelect.SetValue, n)
574 if ('M140' in line or 'M190' in line) and 'S' in line:
575 n = int(re.search('S([0-9]*)', line).group(1))
576 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
578 print "Unexpected error:", sys.exc_info()
579 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
580 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
583 def mcLog(self, message):
587 def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
588 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
589 wx.CallAfter(self._mcTempUpdate, temp, bedTemp, targetTemp, bedTargetTemp)
591 def _mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
592 if self.temperatureSelect.GetValue() != targetTemp and wx.Window.FindFocus() != self.temperatureSelect:
593 self.temperatureSelect.SetValue(targetTemp)
594 if self.bedTemperatureSelect.GetValue() != bedTargetTemp and wx.Window.FindFocus() != self.bedTemperatureSelect:
595 self.bedTemperatureSelect.SetValue(bedTargetTemp)
597 def mcStateChange(self, state):
598 if self.machineCom is not None:
599 if state == self.machineCom.STATE_OPERATIONAL and self.cam is not None:
600 self.cam.endTimelapse()
601 if state == self.machineCom.STATE_OPERATIONAL:
602 taskbar.setBusy(self, False)
603 if self.machineCom.isClosedOrError():
604 taskbar.setBusy(self, False)
605 if self.machineCom.isPaused():
606 taskbar.setPause(self, True)
607 wx.CallAfter(self.UpdateButtonStates)
608 wx.CallAfter(self.UpdateProgress)
610 def mcMessage(self, message):
611 wx.CallAfter(self.AddTermLog, message)
613 def mcProgress(self, lineNr):
614 wx.CallAfter(self.UpdateProgress)
616 def mcZChange(self, newZ):
618 if self.cam is not None:
619 wx.CallAfter(self.cam.takeNewImage)
620 wx.CallAfter(self.camPreview.Refresh)
623 class temperatureGraph(wx.Panel):
624 def __init__(self, parent):
625 super(temperatureGraph, self).__init__(parent)
627 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
628 self.Bind(wx.EVT_SIZE, self.OnSize)
629 self.Bind(wx.EVT_PAINT, self.OnDraw)
631 self.lastDraw = time.time() - 1.0
633 self.backBuffer = None
634 self.addPoint(0, 0, 0, 0)
635 self.SetMinSize((320, 200))
637 def OnEraseBackground(self, e):
641 if self.backBuffer == None or self.GetSize() != self.backBuffer.GetSize():
642 self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
643 self.UpdateDrawing(True)
646 dc = wx.BufferedPaintDC(self, self.backBuffer)
648 def UpdateDrawing(self, force=False):
650 if not force and now - self.lastDraw < 1.0:
654 dc.SelectObject(self.backBuffer)
656 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
657 w, h = self.GetSizeTuple()
658 bgLinePen = wx.Pen('#A0A0A0')
659 tempPen = wx.Pen('#FF4040')
660 tempSPPen = wx.Pen('#FFA0A0')
661 tempPenBG = wx.Pen('#FFD0D0')
662 bedTempPen = wx.Pen('#4040FF')
663 bedTempSPPen = wx.Pen('#A0A0FF')
664 bedTempPenBG = wx.Pen('#D0D0FF')
666 #Draw the background up to the current temperatures.
672 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
673 x1 = int(w - (now - t))
674 for x in xrange(x0, x1 + 1):
675 t = float(x - x0) / float(x1 - x0 + 1) * (temp - t0) + t0
676 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
678 dc.DrawLine(x, h, x, h - (t * h / 300))
679 dc.SetPen(bedTempPenBG)
680 dc.DrawLine(x, h, x, h - (bt * h / 300))
688 for x in xrange(w, 0, -30):
690 dc.DrawLine(x, 0, x, h)
692 for y in xrange(h - 1, 0, -h * 50 / 300):
694 dc.DrawLine(0, y, w, y)
695 dc.DrawText(str(tmpNr), 0, y - dc.GetFont().GetPixelSize().GetHeight())
697 dc.DrawLine(0, 0, w, 0)
698 dc.DrawLine(0, 0, 0, h)
706 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
707 x1 = int(w - (now - t))
708 for x in xrange(x0, x1 + 1):
709 t = float(x - x0) / float(x1 - x0 + 1) * (temp - t0) + t0
710 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
711 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP - tSP0) + tSP0
712 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
714 dc.DrawPoint(x, h - (tSP * h / 300))
715 dc.SetPen(bedTempSPPen)
716 dc.DrawPoint(x, h - (btSP * h / 300))
718 dc.DrawPoint(x, h - (t * h / 300))
719 dc.SetPen(bedTempPen)
720 dc.DrawPoint(x, h - (bt * h / 300))
728 self.Refresh(eraseBackground=False)
731 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
734 def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
737 if bedTempSP == None:
739 self.points.append((temp, tempSP, bedTemp, bedTempSP, time.time()))
740 wx.CallAfter(self.UpdateDrawing)
743 class LogWindow(wx.Frame):
744 def __init__(self, logText):
745 super(LogWindow, self).__init__(None, title="Machine log")
746 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
747 self.SetSize((500, 400))