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 separate process.
34 # It needs to run in a separate process, as any running python code blocks the GCode sender python code (http://wiki.python.org/moin/GlobalInterpreterLock).
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, stderr=subprocess.PIPE)
62 self.thread = threading.Thread(target=self.Monitor)
65 self.handle.stdin.write('LOAD:%s\n' % filename)
69 line = p.stdout.readline()
71 #print '>' + line.rstrip()
72 line = p.stdout.readline()
73 line = p.stderr.readline()
75 #print '>>' + line.rstrip()
76 line = p.stderr.readline()
81 class PrintCommandButton(buttons.GenBitmapButton):
82 def __init__(self, parent, commandList, bitmapFilename, size=(20, 20)):
83 self.bitmap = wx.Bitmap(getPathForImage(bitmapFilename))
84 super(PrintCommandButton, self).__init__(parent.directControlPanel, -1, self.bitmap, size=size)
86 self.commandList = commandList
90 self.SetUseFocusIndicator(False)
92 self.Bind(wx.EVT_BUTTON, self.OnClick)
95 if self.parent.machineCom is None or self.parent.machineCom.isPrinting():
97 for cmd in self.commandList:
98 self.parent.machineCom.sendCommand(cmd)
102 class printWindow(wx.Frame):
103 "Main user interface window"
106 super(printWindow, self).__init__(None, -1, title='Printing')
107 self.machineCom = None
109 self.gcodeList = None
113 self.bufferLineCount = 4
115 self.feedrateRatioOuterWall = 1.0
116 self.feedrateRatioInnerWall = 1.0
117 self.feedrateRatioFill = 1.0
118 self.feedrateRatioSupport = 1.0
120 self.termHistory = []
121 self.termHistoryIdx = 0
124 if webcam.hasWebcamSupport():
125 self.cam = webcam.webcam()
126 if not self.cam.hasCamera():
129 self.SetSizer(wx.BoxSizer())
130 self.panel = wx.Panel(self)
131 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
132 self.sizer = wx.GridBagSizer(2, 2)
133 self.panel.SetSizer(self.sizer)
135 sb = wx.StaticBox(self.panel, label="Statistics")
136 boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)
138 self.powerWarningText = wx.StaticText(parent=self.panel,
140 label="Your computer is running on battery power.\nConnect your computer to AC power or your print might not finish.",
141 style=wx.ALIGN_CENTER)
142 self.powerWarningText.SetBackgroundColour('red')
143 self.powerWarningText.SetForegroundColour('white')
144 boxsizer.AddF(self.powerWarningText, flags=wx.SizerFlags().Expand().Border(wx.BOTTOM, 10))
145 self.powerManagement = power.PowerManagement()
146 self.powerWarningTimer = wx.Timer(self)
147 self.Bind(wx.EVT_TIMER, self.OnPowerWarningChange, self.powerWarningTimer)
148 self.OnPowerWarningChange(None)
149 self.powerWarningTimer.Start(10000)
151 self.statsText = wx.StaticText(self.panel, -1, "Filament: ####.##m #.##g\nEstimated print time: #####:##\nMachine state:\nDetecting baudrateXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
152 boxsizer.Add(self.statsText, flag=wx.LEFT, border=5)
154 self.sizer.Add(boxsizer, pos=(0, 0), span=(7, 1), flag=wx.EXPAND)
156 self.connectButton = wx.Button(self.panel, -1, 'Connect')
157 #self.loadButton = wx.Button(self.panel, -1, 'Load')
158 self.printButton = wx.Button(self.panel, -1, 'Print')
159 self.pauseButton = wx.Button(self.panel, -1, 'Pause')
160 self.cancelButton = wx.Button(self.panel, -1, 'Cancel print')
161 self.machineLogButton = wx.Button(self.panel, -1, 'Error log')
162 self.progress = wx.Gauge(self.panel, -1)
164 self.sizer.Add(self.connectButton, pos=(1, 1), flag=wx.EXPAND)
165 #self.sizer.Add(self.loadButton, pos=(1,1), flag=wx.EXPAND)
166 self.sizer.Add(self.printButton, pos=(2, 1), flag=wx.EXPAND)
167 self.sizer.Add(self.pauseButton, pos=(3, 1), flag=wx.EXPAND)
168 self.sizer.Add(self.cancelButton, pos=(4, 1), flag=wx.EXPAND)
169 self.sizer.Add(self.machineLogButton, pos=(5, 1), flag=wx.EXPAND)
170 self.sizer.Add(self.progress, pos=(7, 0), span=(1, 7), flag=wx.EXPAND)
172 nb = wx.Notebook(self.panel)
173 self.sizer.Add(nb, pos=(0, 2), span=(7, 4), flag=wx.EXPAND)
175 self.temperaturePanel = wx.Panel(nb)
176 sizer = wx.GridBagSizer(2, 2)
177 self.temperaturePanel.SetSizer(sizer)
179 self.temperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
180 self.temperatureSelect.SetRange(0, 400)
181 self.temperatureHeatUpPLA = wx.Button(self.temperaturePanel, -1, '210C')
182 self.bedTemperatureLabel = wx.StaticText(self.temperaturePanel, -1, "BedTemp:")
183 self.bedTemperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
184 self.bedTemperatureSelect.SetRange(0, 400)
185 self.bedTemperatureLabel.Show(False)
186 self.bedTemperatureSelect.Show(False)
188 self.temperatureGraph = temperatureGraph(self.temperaturePanel)
190 sizer.Add(wx.StaticText(self.temperaturePanel, -1, "Temp:"), pos=(0, 0))
191 sizer.Add(self.temperatureSelect, pos=(0, 1))
192 sizer.Add(self.temperatureHeatUpPLA, pos=(0, 2))
193 sizer.Add(self.bedTemperatureLabel, pos=(1, 0))
194 sizer.Add(self.bedTemperatureSelect, pos=(1, 1))
195 sizer.Add(self.temperatureGraph, pos=(2, 0), span=(1, 3), flag=wx.EXPAND)
196 sizer.AddGrowableRow(2)
197 sizer.AddGrowableCol(2)
199 nb.AddPage(self.temperaturePanel, 'Temp')
201 self.directControlPanel = wx.Panel(nb)
203 sizer = wx.GridBagSizer(2, 2)
204 self.directControlPanel.SetSizer(sizer)
205 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y100 F6000', 'G90'], 'print-move-y100.png'), pos=(0, 3))
206 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y10 F6000', 'G90'], 'print-move-y10.png'), pos=(1, 3))
207 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y1 F6000', 'G90'], 'print-move-y1.png'), pos=(2, 3))
209 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-1 F6000', 'G90'], 'print-move-y-1.png'), pos=(4, 3))
210 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-10 F6000', 'G90'], 'print-move-y-10.png'), pos=(5, 3))
211 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-100 F6000', 'G90'], 'print-move-y-100.png'), pos=(6, 3))
213 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-100 F6000', 'G90'], 'print-move-x-100.png'), pos=(3, 0))
214 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-10 F6000', 'G90'], 'print-move-x-10.png'), pos=(3, 1))
215 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-1 F6000', 'G90'], 'print-move-x-1.png'), pos=(3, 2))
217 sizer.Add(PrintCommandButton(self, ['G28 X0 Y0'], 'print-move-home.png'), pos=(3, 3))
219 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X1 F6000', 'G90'], 'print-move-x1.png'), pos=(3, 4))
220 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X10 F6000', 'G90'], 'print-move-x10.png'), pos=(3, 5))
221 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X100 F6000', 'G90'], 'print-move-x100.png'), pos=(3, 6))
223 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z10 F200', 'G90'], 'print-move-z10.png'), pos=(0, 8))
224 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z1 F200', 'G90'], 'print-move-z1.png'), pos=(1, 8))
225 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z0.1 F200', 'G90'], 'print-move-z0.1.png'), pos=(2, 8))
227 sizer.Add(PrintCommandButton(self, ['G28 Z0'], 'print-move-home.png'), pos=(3, 8))
229 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-0.1 F200', 'G90'], 'print-move-z-0.1.png'), pos=(4, 8))
230 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-1 F200', 'G90'], 'print-move-z-1.png'), pos=(5, 8))
231 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-10 F200', 'G90'], 'print-move-z-10.png'), pos=(6, 8))
233 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E2 F120'], 'extrude.png', size=(60, 20)), pos=(1, 10),
234 span=(1, 3), flag=wx.EXPAND)
235 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E-2 F120'], 'retract.png', size=(60, 20)), pos=(2, 10),
236 span=(1, 3), flag=wx.EXPAND)
238 nb.AddPage(self.directControlPanel, 'Jog')
240 self.speedPanel = wx.Panel(nb)
241 sizer = wx.GridBagSizer(2, 2)
242 self.speedPanel.SetSizer(sizer)
244 self.outerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
245 self.outerWallSpeedSelect.SetRange(5, 1000)
246 self.innerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
247 self.innerWallSpeedSelect.SetRange(5, 1000)
248 self.fillSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
249 self.fillSpeedSelect.SetRange(5, 1000)
250 self.supportSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
251 self.supportSpeedSelect.SetRange(5, 1000)
253 sizer.Add(wx.StaticText(self.speedPanel, -1, "Outer wall:"), pos=(0, 0))
254 sizer.Add(self.outerWallSpeedSelect, pos=(0, 1))
255 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(0, 2))
256 sizer.Add(wx.StaticText(self.speedPanel, -1, "Inner wall:"), pos=(1, 0))
257 sizer.Add(self.innerWallSpeedSelect, pos=(1, 1))
258 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(1, 2))
259 sizer.Add(wx.StaticText(self.speedPanel, -1, "Fill:"), pos=(2, 0))
260 sizer.Add(self.fillSpeedSelect, pos=(2, 1))
261 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(2, 2))
262 sizer.Add(wx.StaticText(self.speedPanel, -1, "Support:"), pos=(3, 0))
263 sizer.Add(self.supportSpeedSelect, pos=(3, 1))
264 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(3, 2))
266 nb.AddPage(self.speedPanel, 'Speed')
268 self.termPanel = wx.Panel(nb)
269 sizer = wx.GridBagSizer(2, 2)
270 self.termPanel.SetSizer(sizer)
272 f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
273 self.termLog = wx.TextCtrl(self.termPanel, style=wx.TE_MULTILINE | wx.TE_DONTWRAP)
274 self.termLog.SetFont(f)
275 self.termLog.SetEditable(0)
276 self.termInput = wx.TextCtrl(self.termPanel, style=wx.TE_PROCESS_ENTER)
277 self.termInput.SetFont(f)
279 sizer.Add(self.termLog, pos=(0, 0), flag=wx.EXPAND)
280 sizer.Add(self.termInput, pos=(1, 0), flag=wx.EXPAND)
281 sizer.AddGrowableCol(0)
282 sizer.AddGrowableRow(0)
284 nb.AddPage(self.termPanel, 'Term')
286 if self.cam is not None:
287 self.camPage = wx.Panel(nb)
288 sizer = wx.GridBagSizer(2, 2)
289 self.camPage.SetSizer(sizer)
291 self.timelapsEnable = wx.CheckBox(self.camPage, -1, 'Enable timelapse movie recording')
292 self.timelapsSavePath = wx.TextCtrl(self.camPage, -1, os.path.expanduser('~/timelaps_' + datetime.datetime.now().strftime('%Y-%m-%d_%H:%M') + '.mpg'))
293 sizer.Add(self.timelapsEnable, pos=(0, 0), span=(1, 2), flag=wx.EXPAND)
294 sizer.Add(self.timelapsSavePath, pos=(1, 0), span=(1, 2), flag=wx.EXPAND)
296 pages = self.cam.propertyPages()
297 self.cam.buttons = [self.timelapsEnable, self.timelapsSavePath]
299 button = wx.Button(self.camPage, -1, page)
300 button.index = pages.index(page)
301 sizer.Add(button, pos=(2, pages.index(page)))
302 button.Bind(wx.EVT_BUTTON, self.OnPropertyPageButton)
303 self.cam.buttons.append(button)
305 self.campreviewEnable = wx.CheckBox(self.camPage, -1, 'Show preview')
306 sizer.Add(self.campreviewEnable, pos=(3, 0), span=(1, 2), flag=wx.EXPAND)
308 self.camPreview = wx.Panel(self.camPage)
309 sizer.Add(self.camPreview, pos=(4, 0), span=(1, 2), flag=wx.EXPAND)
311 nb.AddPage(self.camPage, 'Camera')
312 self.camPreview.timer = wx.Timer(self)
313 self.Bind(wx.EVT_TIMER, self.OnCameraTimer, self.camPreview.timer)
314 self.camPreview.timer.Start(500)
315 self.camPreview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
317 self.sizer.AddGrowableRow(6)
318 self.sizer.AddGrowableCol(3)
320 self.Bind(wx.EVT_CLOSE, self.OnClose)
321 self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect)
322 #self.loadButton.Bind(wx.EVT_BUTTON, self.OnLoad)
323 self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint)
324 self.pauseButton.Bind(wx.EVT_BUTTON, self.OnPause)
325 self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)
326 self.machineLogButton.Bind(wx.EVT_BUTTON, self.OnMachineLog)
328 self.Bind(wx.EVT_BUTTON, lambda e: (self.temperatureSelect.SetValue(210), self.machineCom.sendCommand("M104 S210")), self.temperatureHeatUpPLA)
329 self.Bind(wx.EVT_SPINCTRL, self.OnTempChange, self.temperatureSelect)
330 self.Bind(wx.EVT_SPINCTRL, self.OnBedTempChange, self.bedTemperatureSelect)
332 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.outerWallSpeedSelect)
333 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.innerWallSpeedSelect)
334 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.fillSpeedSelect)
335 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.supportSpeedSelect)
336 self.Bind(wx.EVT_TEXT_ENTER, self.OnTermEnterLine, self.termInput)
337 self.termInput.Bind(wx.EVT_CHAR, self.OnTermKey)
343 self.statsText.SetMinSize(self.statsText.GetSize())
345 self.UpdateButtonStates()
347 #self.UpdateProgress()
348 self._thread = threading.Thread(target=self._stdinMonitor)
349 self._thread.daemon = True
352 def _stdinMonitor(self):
354 line = sys.stdin.readline().rstrip()
355 if line.startswith('LOAD:'):
356 if not self.LoadGCodeFile(line[5:]):
359 def OnCameraTimer(self, e):
360 if not self.campreviewEnable.GetValue():
362 if self.machineCom is not None and self.machineCom.isPrinting():
364 self.cam.takeNewImage()
365 self.camPreview.Refresh()
367 def OnCameraEraseBackground(self, e):
370 dc = wx.ClientDC(self)
371 rect = self.GetUpdateRegion().GetBox()
372 dc.SetClippingRect(rect)
373 dc.SetBackground(wx.Brush(self.camPreview.GetBackgroundColour(), wx.SOLID))
374 if self.cam.getLastImage() is not None:
375 self.camPreview.SetMinSize((self.cam.getLastImage().GetWidth(), self.cam.getLastImage().GetHeight()))
377 dc.DrawBitmap(self.cam.getLastImage(), 0, 0)
381 def OnPropertyPageButton(self, e):
382 self.cam.openPropertyPage(e.GetEventObject().index)
384 def UpdateButtonStates(self):
385 self.connectButton.Enable(self.machineCom is None or self.machineCom.isClosedOrError())
386 #self.loadButton.Enable(self.machineCom == None or not (self.machineCom.isPrinting() or self.machineCom.isPaused()))
387 self.printButton.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
388 self.machineCom.isPrinting() or self.machineCom.isPaused()))
389 self.temperatureHeatUpPLA.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
390 self.machineCom.isPrinting() or self.machineCom.isPaused()))
391 self.pauseButton.Enable(
392 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
393 if self.machineCom is not None and self.machineCom.isPaused():
394 self.pauseButton.SetLabel('Resume')
396 self.pauseButton.SetLabel('Pause')
397 self.cancelButton.Enable(
398 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
399 self.temperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
400 self.bedTemperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
401 self.directControlPanel.Enable(
402 self.machineCom is not None and self.machineCom.isOperational() and not self.machineCom.isPrinting())
403 self.machineLogButton.Show(self.machineCom is not None and self.machineCom.isClosedOrError())
405 for button in self.cam.buttons:
406 button.Enable(self.machineCom is None or not self.machineCom.isPrinting())
408 def UpdateProgress(self):
410 if self.gcode is None:
411 status += "Loading gcode...\n"
413 status += "Filament: %.2fm %.2fg\n" % (
414 self.gcode.extrusionAmount / 1000, self.gcode.calculateWeight() * 1000)
415 cost = self.gcode.calculateCost()
417 status += "Filament cost: %s\n" % (cost)
418 #status += "Estimated print time: %02d:%02d\n" % (int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60))
419 if self.machineCom is None or not self.machineCom.isPrinting():
420 self.progress.SetValue(0)
421 if self.gcodeList is not None:
422 status += 'Line: -/%d\n' % (len(self.gcodeList))
424 printTime = self.machineCom.getPrintTime() / 60
425 printTimeLeft = self.machineCom.getPrintTimeRemainingEstimate()
426 status += 'Line: %d/%d %d%%\n' % (self.machineCom.getPrintPos(), len(self.gcodeList),
427 self.machineCom.getPrintPos() * 100 / len(self.gcodeList))
428 if self.currentZ > 0:
429 status += 'Height: %0.1f\n' % (self.currentZ)
430 status += 'Print time: %02d:%02d\n' % (int(printTime / 60), int(printTime % 60))
431 if printTimeLeft is None:
432 status += 'Print time left: Unknown\n'
434 status += 'Print time left: %02d:%02d\n' % (int(printTimeLeft / 60), int(printTimeLeft % 60))
435 self.progress.SetValue(self.machineCom.getPrintPos())
436 taskbar.setProgress(self, self.machineCom.getPrintPos(), len(self.gcodeList))
437 if self.machineCom is not None:
438 if self.machineCom.getTemp() > 0:
439 status += 'Temp: %s\n' % (' ,'.join(map(str, self.machineCom.getTemp())))
440 if self.machineCom.getBedTemp() > 0:
441 status += 'Bed Temp: %d\n' % (self.machineCom.getBedTemp())
442 self.bedTemperatureLabel.Show(True)
443 self.bedTemperatureSelect.Show(True)
444 self.temperaturePanel.Layout()
445 status += 'Machine state:%s\n' % (self.machineCom.getStateString())
447 self.statsText.SetLabel(status.strip())
449 def OnConnect(self, e):
450 if self.machineCom is not None:
451 self.machineCom.close()
452 self.machineCom = machineCom.MachineCom(callbackObject=self)
453 self.UpdateButtonStates()
454 taskbar.setBusy(self, True)
459 def OnPrint(self, e):
460 if self.machineCom is None or not self.machineCom.isOperational():
462 if self.gcodeList is None:
464 if self.machineCom.isPrinting():
467 if self.cam is not None and self.timelapsEnable.GetValue():
468 self.cam.startTimelapse(self.timelapsSavePath)
469 self.machineCom.printGCode(self.gcodeList)
470 self.UpdateButtonStates()
472 def OnCancel(self, e):
473 self.pauseButton.SetLabel('Pause')
474 self.machineCom.cancelPrint()
475 self.machineCom.sendCommand("M84")
476 self.machineCom.sendCommand("M104 S0")
477 self.UpdateButtonStates()
479 def OnPause(self, e):
480 if self.machineCom.isPaused():
481 self.machineCom.setPause(False)
483 self.machineCom.setPause(True)
485 def OnMachineLog(self, e):
486 LogWindow('\n'.join(self.machineCom.getLog()))
488 def OnClose(self, e):
489 global printWindowHandle
490 printWindowHandle = None
491 if self.machineCom is not None:
492 self.machineCom.close()
495 def OnTempChange(self, e):
496 self.machineCom.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))
498 def OnBedTempChange(self, e):
499 self.machineCom.sendCommand("M140 S%d" % (self.bedTemperatureSelect.GetValue()))
501 def OnSpeedChange(self, e):
502 if self.machineCom is None:
504 self.machineCom.setFeedrateModifier('WALL-OUTER', self.outerWallSpeedSelect.GetValue() / 100.0)
505 self.machineCom.setFeedrateModifier('WALL-INNER', self.innerWallSpeedSelect.GetValue() / 100.0)
506 self.machineCom.setFeedrateModifier('FILL', self.fillSpeedSelect.GetValue() / 100.0)
507 self.machineCom.setFeedrateModifier('SUPPORT', self.supportSpeedSelect.GetValue() / 100.0)
509 def AddTermLog(self, line):
510 self.termLog.AppendText(unicode(line, 'utf-8', 'replace'))
511 l = len(self.termLog.GetValue())
512 self.termLog.SetCaret(wx.Caret(self.termLog, (l, l)))
514 def OnTermEnterLine(self, e):
515 line = self.termInput.GetValue()
518 self.termLog.AppendText('>%s\n' % (line))
519 self.machineCom.sendCommand(line)
520 self.termHistory.append(line)
521 self.termHistoryIdx = len(self.termHistory)
522 self.termInput.SetValue('')
524 def OnTermKey(self, e):
525 if len(self.termHistory) > 0:
526 if e.GetKeyCode() == wx.WXK_UP:
527 self.termHistoryIdx -= 1
528 if self.termHistoryIdx < 0:
529 self.termHistoryIdx = len(self.termHistory) - 1
530 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
531 if e.GetKeyCode() == wx.WXK_DOWN:
532 self.termHistoryIdx -= 1
533 if self.termHistoryIdx >= len(self.termHistory):
534 self.termHistoryIdx = 0
535 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
538 def OnPowerWarningChange(self, e):
539 type = self.powerManagement.get_providing_power_source_type()
540 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
541 self.powerWarningText.Hide()
544 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
545 self.powerWarningText.Show()
549 def LoadGCodeFile(self, filename):
550 if self.machineCom is not None and self.machineCom.isPrinting():
552 #Send an initial M110 to reset the line counter to zero.
553 prevLineType = lineType = 'CUSTOM'
555 for line in open(filename, 'r'):
556 if line.startswith(';TYPE:'):
557 lineType = line[6:].strip()
559 line = line[0:line.find(';')]
562 if prevLineType != lineType:
563 gcodeList.append((line, lineType, ))
565 gcodeList.append(line)
566 prevLineType = lineType
567 gcode = gcodeInterpreter.gcode()
568 gcode.loadList(gcodeList)
569 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
570 self.filename = filename
572 self.gcodeList = gcodeList
574 wx.CallAfter(self.progress.SetRange, len(gcodeList))
575 wx.CallAfter(self.UpdateButtonStates)
576 wx.CallAfter(self.UpdateProgress)
579 def sendLine(self, lineNr):
580 if lineNr >= len(self.gcodeList):
582 line = self.gcodeList[lineNr]
584 if ('M104' in line or 'M109' in line) and 'S' in line:
585 n = int(re.search('S([0-9]*)', line).group(1))
586 wx.CallAfter(self.temperatureSelect.SetValue, n)
587 if ('M140' in line or 'M190' in line) and 'S' in line:
588 n = int(re.search('S([0-9]*)', line).group(1))
589 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
591 print "Unexpected error:", sys.exc_info()
592 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
593 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
596 def mcLog(self, message):
600 def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
601 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
602 wx.CallAfter(self._mcTempUpdate, temp, bedTemp, targetTemp, bedTargetTemp)
604 def _mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
605 if self.temperatureSelect.GetValue() != targetTemp[0] and wx.Window.FindFocus() != self.temperatureSelect:
606 self.temperatureSelect.SetValue(targetTemp[0])
607 if self.bedTemperatureSelect.GetValue() != bedTargetTemp and wx.Window.FindFocus() != self.bedTemperatureSelect:
608 self.bedTemperatureSelect.SetValue(bedTargetTemp)
610 def mcStateChange(self, state):
611 if self.machineCom is not None:
612 if state == self.machineCom.STATE_OPERATIONAL and self.cam is not None:
613 self.cam.endTimelapse()
614 if state == self.machineCom.STATE_OPERATIONAL:
615 taskbar.setBusy(self, False)
616 if self.machineCom.isClosedOrError():
617 taskbar.setBusy(self, False)
618 if self.machineCom.isPaused():
619 taskbar.setPause(self, True)
620 wx.CallAfter(self.UpdateButtonStates)
621 wx.CallAfter(self.UpdateProgress)
623 def mcMessage(self, message):
624 wx.CallAfter(self.AddTermLog, message)
626 def mcProgress(self, lineNr):
627 wx.CallAfter(self.UpdateProgress)
629 def mcZChange(self, newZ):
631 if self.cam is not None:
632 wx.CallAfter(self.cam.takeNewImage)
633 wx.CallAfter(self.camPreview.Refresh)
636 class temperatureGraph(wx.Panel):
637 def __init__(self, parent):
638 super(temperatureGraph, self).__init__(parent)
640 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
641 self.Bind(wx.EVT_SIZE, self.OnSize)
642 self.Bind(wx.EVT_PAINT, self.OnDraw)
644 self.lastDraw = time.time() - 1.0
646 self.backBuffer = None
647 self.addPoint([0]*16, [0]*16, 0, 0)
648 self.SetMinSize((320, 200))
650 def OnEraseBackground(self, e):
654 if self.backBuffer is None or self.GetSize() != self.backBuffer.GetSize():
655 self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
656 self.UpdateDrawing(True)
659 dc = wx.BufferedPaintDC(self, self.backBuffer)
661 def UpdateDrawing(self, force=False):
663 if not force and now - self.lastDraw < 1.0:
667 dc.SelectObject(self.backBuffer)
669 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
670 w, h = self.GetSizeTuple()
671 bgLinePen = wx.Pen('#A0A0A0')
672 tempPen = wx.Pen('#FF4040')
673 tempSPPen = wx.Pen('#FFA0A0')
674 tempPenBG = wx.Pen('#FFD0D0')
675 bedTempPen = wx.Pen('#4040FF')
676 bedTempSPPen = wx.Pen('#A0A0FF')
677 bedTempPenBG = wx.Pen('#D0D0FF')
679 #Draw the background up to the current temperatures.
681 t0 = [0] * len(self.points[0][0])
685 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
686 x1 = int(w - (now - t))
687 for x in xrange(x0, x1 + 1):
688 for n in xrange(0, len(temp)):
689 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
691 dc.DrawLine(x, h, x, h - (t * h / 300))
692 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
693 dc.SetPen(bedTempPenBG)
694 dc.DrawLine(x, h, x, h - (bt * h / 300))
702 for x in xrange(w, 0, -30):
704 dc.DrawLine(x, 0, x, h)
706 for y in xrange(h - 1, 0, -h * 50 / 300):
708 dc.DrawLine(0, y, w, y)
709 dc.DrawText(str(tmpNr), 0, y - dc.GetFont().GetPixelSize().GetHeight())
711 dc.DrawLine(0, 0, w, 0)
712 dc.DrawLine(0, 0, 0, h)
716 t0 = [0] * len(self.points[0][0])
718 tSP0 = [0] * len(self.points[0][0])
720 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
721 x1 = int(w - (now - t))
722 for x in xrange(x0, x1 + 1):
723 for n in xrange(0, len(temp)):
724 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
725 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP[n] - tSP0[n]) + tSP0[n]
727 dc.DrawPoint(x, h - (tSP * h / 300))
729 dc.DrawPoint(x, h - (t * h / 300))
730 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
731 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
732 dc.SetPen(bedTempSPPen)
733 dc.DrawPoint(x, h - (btSP * h / 300))
734 dc.SetPen(bedTempPen)
735 dc.DrawPoint(x, h - (bt * h / 300))
743 self.Refresh(eraseBackground=False)
746 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
749 def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
752 if bedTempSP is None:
754 self.points.append((temp[:], tempSP[:], bedTemp, bedTempSP, time.time()))
755 wx.CallAfter(self.UpdateDrawing)
758 class LogWindow(wx.Frame):
759 def __init__(self, logText):
760 super(LogWindow, self).__init__(None, title="Machine log")
761 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
762 self.SetSize((500, 400))