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 #The printProcessMonitor is used from the main GUI python process. This monitors the printing python process.
24 # This class also handles starting of the 2nd process for printing and all communications with it.
25 class printProcessMonitor():
28 self._state = 'CLOSED'
31 def loadFile(self, filename):
32 if self.handle is None:
33 if platform.system() == "Darwin" and hasattr(sys, 'frozen'):
34 cmdList = [os.path.join(os.path.dirname(sys.executable), 'Cura')]
36 cmdList = [sys.executable, '-m', 'Cura.cura']
38 cmdList.append(filename)
39 if platform.system() == "Darwin":
40 if platform.machine() == 'i386':
41 cmdList.insert(0, 'arch')
42 cmdList.insert(1, '-i386')
43 self.handle = subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
44 self.thread = threading.Thread(target=self.Monitor)
47 self.handle.stdin.write('LOAD:%s\n' % filename)
51 line = p.stdout.readline()
54 if line.startswith('Z:'):
55 self._z = float(line[2:])
56 elif line.startswith('STATE:'):
57 self._state = float(line[6:])
60 #print '>' + line.rstrip()
61 line = p.stdout.readline()
62 line = p.stderr.readline()
64 print '>>' + line.rstrip()
65 line = p.stderr.readline()
70 def startPrintInterface(filename):
71 #startPrintInterface is called from the main script when we want the printer interface to run in a separate process.
72 # 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).
74 printWindowHandle = printWindow()
75 printWindowHandle.Show(True)
76 printWindowHandle.Raise()
77 printWindowHandle.OnConnect(None)
78 t = threading.Thread(target=printWindowHandle.LoadGCodeFile, args=(filename,))
83 class PrintCommandButton(buttons.GenBitmapButton):
84 def __init__(self, parent, commandList, bitmapFilename, size=(20, 20)):
85 self.bitmap = wx.Bitmap(getPathForImage(bitmapFilename))
86 super(PrintCommandButton, self).__init__(parent.directControlPanel, -1, self.bitmap, size=size)
88 self.commandList = commandList
92 self.SetUseFocusIndicator(False)
94 self.Bind(wx.EVT_BUTTON, self.OnClick)
97 if self.parent.machineCom is None or self.parent.machineCom.isPrinting():
99 for cmd in self.commandList:
100 self.parent.machineCom.sendCommand(cmd)
104 class printWindow(wx.Frame):
105 "Main user interface window"
108 super(printWindow, self).__init__(None, -1, title='Printing')
109 self.machineCom = None
111 self.gcodeList = None
115 self.bufferLineCount = 4
117 self.feedrateRatioOuterWall = 1.0
118 self.feedrateRatioInnerWall = 1.0
119 self.feedrateRatioFill = 1.0
120 self.feedrateRatioSupport = 1.0
122 self.termHistory = []
123 self.termHistoryIdx = 0
126 if webcam.hasWebcamSupport():
127 self.cam = webcam.webcam()
128 if not self.cam.hasCamera():
131 self.SetSizer(wx.BoxSizer())
132 self.panel = wx.Panel(self)
133 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
134 self.sizer = wx.GridBagSizer(2, 2)
135 self.panel.SetSizer(self.sizer)
137 sb = wx.StaticBox(self.panel, label="Statistics")
138 boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)
140 self.powerWarningText = wx.StaticText(parent=self.panel,
142 label="Your computer is running on battery power.\nConnect your computer to AC power or your print might not finish.",
143 style=wx.ALIGN_CENTER)
144 self.powerWarningText.SetBackgroundColour('red')
145 self.powerWarningText.SetForegroundColour('white')
146 boxsizer.AddF(self.powerWarningText, flags=wx.SizerFlags().Expand().Border(wx.BOTTOM, 10))
147 self.powerManagement = power.PowerManagement()
148 self.powerWarningTimer = wx.Timer(self)
149 self.Bind(wx.EVT_TIMER, self.OnPowerWarningChange, self.powerWarningTimer)
150 self.OnPowerWarningChange(None)
151 self.powerWarningTimer.Start(10000)
153 self.statsText = wx.StaticText(self.panel, -1, "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), 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.temperatureHeatUpPLA, pos=(0, 2))
195 sizer.Add(self.bedTemperatureLabel, pos=(1, 0))
196 sizer.Add(self.bedTemperatureSelect, pos=(1, 1))
197 sizer.Add(self.temperatureGraph, pos=(2, 0), span=(1, 3), flag=wx.EXPAND)
198 sizer.AddGrowableRow(2)
199 sizer.AddGrowableCol(2)
201 nb.AddPage(self.temperaturePanel, 'Temp')
203 self.directControlPanel = wx.Panel(nb)
205 sizer = wx.GridBagSizer(2, 2)
206 self.directControlPanel.SetSizer(sizer)
207 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y100 F6000', 'G90'], 'print-move-y100.png'), pos=(0, 3))
208 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y10 F6000', 'G90'], 'print-move-y10.png'), pos=(1, 3))
209 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y1 F6000', 'G90'], 'print-move-y1.png'), pos=(2, 3))
211 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-1 F6000', 'G90'], 'print-move-y-1.png'), pos=(4, 3))
212 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-10 F6000', 'G90'], 'print-move-y-10.png'), pos=(5, 3))
213 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-100 F6000', 'G90'], 'print-move-y-100.png'), pos=(6, 3))
215 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-100 F6000', 'G90'], 'print-move-x-100.png'), pos=(3, 0))
216 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-10 F6000', 'G90'], 'print-move-x-10.png'), pos=(3, 1))
217 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-1 F6000', 'G90'], 'print-move-x-1.png'), pos=(3, 2))
219 sizer.Add(PrintCommandButton(self, ['G28 X0 Y0'], 'print-move-home.png'), pos=(3, 3))
221 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X1 F6000', 'G90'], 'print-move-x1.png'), pos=(3, 4))
222 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X10 F6000', 'G90'], 'print-move-x10.png'), pos=(3, 5))
223 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X100 F6000', 'G90'], 'print-move-x100.png'), pos=(3, 6))
225 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z10 F200', 'G90'], 'print-move-z10.png'), pos=(0, 8))
226 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z1 F200', 'G90'], 'print-move-z1.png'), pos=(1, 8))
227 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z0.1 F200', 'G90'], 'print-move-z0.1.png'), pos=(2, 8))
229 sizer.Add(PrintCommandButton(self, ['G28 Z0'], 'print-move-home.png'), pos=(3, 8))
231 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-0.1 F200', 'G90'], 'print-move-z-0.1.png'), pos=(4, 8))
232 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-1 F200', 'G90'], 'print-move-z-1.png'), pos=(5, 8))
233 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-10 F200', 'G90'], 'print-move-z-10.png'), pos=(6, 8))
235 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E2 F120'], 'extrude.png', size=(60, 20)), pos=(1, 10),
236 span=(1, 3), flag=wx.EXPAND)
237 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E-2 F120'], 'retract.png', size=(60, 20)), pos=(2, 10),
238 span=(1, 3), flag=wx.EXPAND)
240 nb.AddPage(self.directControlPanel, 'Jog')
242 self.speedPanel = wx.Panel(nb)
243 sizer = wx.GridBagSizer(2, 2)
244 self.speedPanel.SetSizer(sizer)
246 self.outerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
247 self.outerWallSpeedSelect.SetRange(5, 1000)
248 self.innerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
249 self.innerWallSpeedSelect.SetRange(5, 1000)
250 self.fillSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
251 self.fillSpeedSelect.SetRange(5, 1000)
252 self.supportSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
253 self.supportSpeedSelect.SetRange(5, 1000)
255 sizer.Add(wx.StaticText(self.speedPanel, -1, "Outer wall:"), pos=(0, 0))
256 sizer.Add(self.outerWallSpeedSelect, pos=(0, 1))
257 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(0, 2))
258 sizer.Add(wx.StaticText(self.speedPanel, -1, "Inner wall:"), pos=(1, 0))
259 sizer.Add(self.innerWallSpeedSelect, pos=(1, 1))
260 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(1, 2))
261 sizer.Add(wx.StaticText(self.speedPanel, -1, "Fill:"), pos=(2, 0))
262 sizer.Add(self.fillSpeedSelect, pos=(2, 1))
263 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(2, 2))
264 sizer.Add(wx.StaticText(self.speedPanel, -1, "Support:"), pos=(3, 0))
265 sizer.Add(self.supportSpeedSelect, pos=(3, 1))
266 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(3, 2))
268 nb.AddPage(self.speedPanel, 'Speed')
270 self.termPanel = wx.Panel(nb)
271 sizer = wx.GridBagSizer(2, 2)
272 self.termPanel.SetSizer(sizer)
274 f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
275 self.termLog = wx.TextCtrl(self.termPanel, style=wx.TE_MULTILINE | wx.TE_DONTWRAP)
276 self.termLog.SetFont(f)
277 self.termLog.SetEditable(0)
278 self.termInput = wx.TextCtrl(self.termPanel, style=wx.TE_PROCESS_ENTER)
279 self.termInput.SetFont(f)
281 sizer.Add(self.termLog, pos=(0, 0), flag=wx.EXPAND)
282 sizer.Add(self.termInput, pos=(1, 0), flag=wx.EXPAND)
283 sizer.AddGrowableCol(0)
284 sizer.AddGrowableRow(0)
286 nb.AddPage(self.termPanel, 'Term')
288 if self.cam is not None:
289 self.camPage = wx.Panel(nb)
290 sizer = wx.GridBagSizer(2, 2)
291 self.camPage.SetSizer(sizer)
293 self.timelapsEnable = wx.CheckBox(self.camPage, -1, 'Enable timelapse movie recording')
294 self.timelapsSavePath = wx.TextCtrl(self.camPage, -1, os.path.expanduser('~/timelaps_' + datetime.datetime.now().strftime('%Y-%m-%d_%H:%M') + '.mpg'))
295 sizer.Add(self.timelapsEnable, pos=(0, 0), span=(1, 2), flag=wx.EXPAND)
296 sizer.Add(self.timelapsSavePath, pos=(1, 0), span=(1, 2), flag=wx.EXPAND)
298 pages = self.cam.propertyPages()
299 self.cam.buttons = [self.timelapsEnable, self.timelapsSavePath]
301 button = wx.Button(self.camPage, -1, page)
302 button.index = pages.index(page)
303 sizer.Add(button, pos=(2, pages.index(page)))
304 button.Bind(wx.EVT_BUTTON, self.OnPropertyPageButton)
305 self.cam.buttons.append(button)
307 self.campreviewEnable = wx.CheckBox(self.camPage, -1, 'Show preview')
308 sizer.Add(self.campreviewEnable, pos=(3, 0), span=(1, 2), flag=wx.EXPAND)
310 self.camPreview = wx.Panel(self.camPage)
311 sizer.Add(self.camPreview, pos=(4, 0), span=(1, 2), flag=wx.EXPAND)
313 nb.AddPage(self.camPage, 'Camera')
314 self.camPreview.timer = wx.Timer(self)
315 self.Bind(wx.EVT_TIMER, self.OnCameraTimer, self.camPreview.timer)
316 self.camPreview.timer.Start(500)
317 self.camPreview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
319 self.sizer.AddGrowableRow(6)
320 self.sizer.AddGrowableCol(3)
322 self.Bind(wx.EVT_CLOSE, self.OnClose)
323 self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect)
324 #self.loadButton.Bind(wx.EVT_BUTTON, self.OnLoad)
325 self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint)
326 self.pauseButton.Bind(wx.EVT_BUTTON, self.OnPause)
327 self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)
328 self.machineLogButton.Bind(wx.EVT_BUTTON, self.OnMachineLog)
330 self.Bind(wx.EVT_BUTTON, lambda e: (self.temperatureSelect.SetValue(210), self.machineCom.sendCommand("M104 S210")), self.temperatureHeatUpPLA)
331 self.Bind(wx.EVT_SPINCTRL, self.OnTempChange, self.temperatureSelect)
332 self.Bind(wx.EVT_SPINCTRL, self.OnBedTempChange, self.bedTemperatureSelect)
334 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.outerWallSpeedSelect)
335 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.innerWallSpeedSelect)
336 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.fillSpeedSelect)
337 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.supportSpeedSelect)
338 self.Bind(wx.EVT_TEXT_ENTER, self.OnTermEnterLine, self.termInput)
339 self.termInput.Bind(wx.EVT_CHAR, self.OnTermKey)
345 self.statsText.SetMinSize(self.statsText.GetSize())
347 self.UpdateButtonStates()
349 #self.UpdateProgress()
350 self._thread = threading.Thread(target=self._stdinMonitor)
351 self._thread.daemon = True
354 def _stdinMonitor(self):
356 line = sys.stdin.readline().rstrip()
357 if line.startswith('LOAD:'):
358 if not self.LoadGCodeFile(line[5:]):
361 def OnCameraTimer(self, e):
362 if not self.campreviewEnable.GetValue():
364 if self.machineCom is not None and self.machineCom.isPrinting():
366 self.cam.takeNewImage()
367 self.camPreview.Refresh()
369 def OnCameraEraseBackground(self, e):
372 dc = wx.ClientDC(self)
373 rect = self.GetUpdateRegion().GetBox()
374 dc.SetClippingRect(rect)
375 dc.SetBackground(wx.Brush(self.camPreview.GetBackgroundColour(), wx.SOLID))
376 if self.cam.getLastImage() is not None:
377 self.camPreview.SetMinSize((self.cam.getLastImage().GetWidth(), self.cam.getLastImage().GetHeight()))
379 dc.DrawBitmap(self.cam.getLastImage(), 0, 0)
383 def OnPropertyPageButton(self, e):
384 self.cam.openPropertyPage(e.GetEventObject().index)
386 def UpdateButtonStates(self):
387 self.connectButton.Enable(self.machineCom is None or self.machineCom.isClosedOrError())
388 #self.loadButton.Enable(self.machineCom == None or not (self.machineCom.isPrinting() or self.machineCom.isPaused()))
389 self.printButton.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
390 self.machineCom.isPrinting() or self.machineCom.isPaused()))
391 self.temperatureHeatUpPLA.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
392 self.machineCom.isPrinting() or self.machineCom.isPaused()))
393 self.pauseButton.Enable(
394 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
395 if self.machineCom is not None and self.machineCom.isPaused():
396 self.pauseButton.SetLabel('Resume')
398 self.pauseButton.SetLabel('Pause')
399 self.cancelButton.Enable(
400 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
401 self.temperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
402 self.bedTemperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
403 self.directControlPanel.Enable(
404 self.machineCom is not None and self.machineCom.isOperational() and not self.machineCom.isPrinting())
405 self.machineLogButton.Show(self.machineCom is not None and self.machineCom.isClosedOrError())
407 for button in self.cam.buttons:
408 button.Enable(self.machineCom is None or not self.machineCom.isPrinting())
410 def UpdateProgress(self):
412 if self.gcode is None:
413 status += "Loading gcode...\n"
415 status += "Filament: %.2fm %.2fg\n" % (
416 self.gcode.extrusionAmount / 1000, self.gcode.calculateWeight() * 1000)
417 cost = self.gcode.calculateCost()
419 status += "Filament cost: %s\n" % (cost)
420 #status += "Estimated print time: %02d:%02d\n" % (int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60))
421 if self.machineCom is None or not self.machineCom.isPrinting():
422 self.progress.SetValue(0)
423 if self.gcodeList is not None:
424 status += 'Line: -/%d\n' % (len(self.gcodeList))
426 printTime = self.machineCom.getPrintTime() / 60
427 printTimeLeft = self.machineCom.getPrintTimeRemainingEstimate()
428 status += 'Line: %d/%d %d%%\n' % (self.machineCom.getPrintPos(), len(self.gcodeList),
429 self.machineCom.getPrintPos() * 100 / len(self.gcodeList))
430 if self.currentZ > 0:
431 status += 'Height: %0.1f\n' % (self.currentZ)
432 status += 'Print time: %02d:%02d\n' % (int(printTime / 60), int(printTime % 60))
433 if printTimeLeft is None:
434 status += 'Print time left: Unknown\n'
436 status += 'Print time left: %02d:%02d\n' % (int(printTimeLeft / 60), int(printTimeLeft % 60))
437 self.progress.SetValue(self.machineCom.getPrintPos())
438 taskbar.setProgress(self, self.machineCom.getPrintPos(), len(self.gcodeList))
439 if self.machineCom is not None:
440 if self.machineCom.getTemp() > 0:
441 status += 'Temp: %s\n' % (' ,'.join(map(str, self.machineCom.getTemp())))
442 if self.machineCom.getBedTemp() > 0:
443 status += 'Bed Temp: %d\n' % (self.machineCom.getBedTemp())
444 self.bedTemperatureLabel.Show(True)
445 self.bedTemperatureSelect.Show(True)
446 self.temperaturePanel.Layout()
447 status += 'Machine state:%s\n' % (self.machineCom.getStateString())
449 self.statsText.SetLabel(status.strip())
451 def OnConnect(self, e):
452 if self.machineCom is not None:
453 self.machineCom.close()
454 self.machineCom = machineCom.MachineCom(callbackObject=self)
455 self.UpdateButtonStates()
456 taskbar.setBusy(self, True)
461 def OnPrint(self, e):
462 if self.machineCom is None or not self.machineCom.isOperational():
464 if self.gcodeList is None:
466 if self.machineCom.isPrinting():
469 if self.cam is not None and self.timelapsEnable.GetValue():
470 self.cam.startTimelapse(self.timelapsSavePath)
471 self.machineCom.printGCode(self.gcodeList)
472 self.UpdateButtonStates()
474 def OnCancel(self, e):
475 self.pauseButton.SetLabel('Pause')
476 self.machineCom.cancelPrint()
477 self.machineCom.sendCommand("M84")
478 self.machineCom.sendCommand("M104 S0")
479 self.UpdateButtonStates()
481 def OnPause(self, e):
482 if self.machineCom.isPaused():
483 self.machineCom.setPause(False)
485 self.machineCom.setPause(True)
487 def OnMachineLog(self, e):
488 LogWindow('\n'.join(self.machineCom.getLog()))
490 def OnClose(self, e):
491 global printWindowHandle
492 printWindowHandle = None
493 if self.machineCom is not None:
494 self.machineCom.close()
497 def OnTempChange(self, e):
498 self.machineCom.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))
500 def OnBedTempChange(self, e):
501 self.machineCom.sendCommand("M140 S%d" % (self.bedTemperatureSelect.GetValue()))
503 def OnSpeedChange(self, e):
504 if self.machineCom is None:
506 self.machineCom.setFeedrateModifier('WALL-OUTER', self.outerWallSpeedSelect.GetValue() / 100.0)
507 self.machineCom.setFeedrateModifier('WALL-INNER', self.innerWallSpeedSelect.GetValue() / 100.0)
508 self.machineCom.setFeedrateModifier('FILL', self.fillSpeedSelect.GetValue() / 100.0)
509 self.machineCom.setFeedrateModifier('SUPPORT', self.supportSpeedSelect.GetValue() / 100.0)
511 def AddTermLog(self, line):
512 self.termLog.AppendText(unicode(line, 'utf-8', 'replace'))
513 l = len(self.termLog.GetValue())
514 self.termLog.SetCaret(wx.Caret(self.termLog, (l, l)))
516 def OnTermEnterLine(self, e):
517 line = self.termInput.GetValue()
520 self.termLog.AppendText('>%s\n' % (line))
521 self.machineCom.sendCommand(line)
522 self.termHistory.append(line)
523 self.termHistoryIdx = len(self.termHistory)
524 self.termInput.SetValue('')
526 def OnTermKey(self, e):
527 if len(self.termHistory) > 0:
528 if e.GetKeyCode() == wx.WXK_UP:
529 self.termHistoryIdx -= 1
530 if self.termHistoryIdx < 0:
531 self.termHistoryIdx = len(self.termHistory) - 1
532 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
533 if e.GetKeyCode() == wx.WXK_DOWN:
534 self.termHistoryIdx -= 1
535 if self.termHistoryIdx >= len(self.termHistory):
536 self.termHistoryIdx = 0
537 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
540 def OnPowerWarningChange(self, e):
541 type = self.powerManagement.get_providing_power_source_type()
542 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
543 self.powerWarningText.Hide()
546 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
547 self.powerWarningText.Show()
551 def LoadGCodeFile(self, filename):
552 if self.machineCom is not None and self.machineCom.isPrinting():
554 #Send an initial M110 to reset the line counter to zero.
555 prevLineType = lineType = 'CUSTOM'
557 for line in open(filename, 'r'):
558 if line.startswith(';TYPE:'):
559 lineType = line[6:].strip()
561 line = line[0:line.find(';')]
564 if prevLineType != lineType:
565 gcodeList.append((line, lineType, ))
567 gcodeList.append(line)
568 prevLineType = lineType
569 gcode = gcodeInterpreter.gcode()
570 gcode.loadList(gcodeList)
571 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
572 self.filename = filename
574 self.gcodeList = gcodeList
576 wx.CallAfter(self.progress.SetRange, len(gcodeList))
577 wx.CallAfter(self.UpdateButtonStates)
578 wx.CallAfter(self.UpdateProgress)
581 def sendLine(self, lineNr):
582 if lineNr >= len(self.gcodeList):
584 line = self.gcodeList[lineNr]
586 if ('M104' in line or 'M109' in line) and 'S' in line:
587 n = int(re.search('S([0-9]*)', line).group(1))
588 wx.CallAfter(self.temperatureSelect.SetValue, n)
589 if ('M140' in line or 'M190' in line) and 'S' in line:
590 n = int(re.search('S([0-9]*)', line).group(1))
591 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
593 print "Unexpected error:", sys.exc_info()
594 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
595 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
598 def mcLog(self, message):
602 def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
603 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
604 wx.CallAfter(self._mcTempUpdate, temp, bedTemp, targetTemp, bedTargetTemp)
606 def _mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
607 if self.temperatureSelect.GetValue() != targetTemp[0] and wx.Window.FindFocus() != self.temperatureSelect:
608 self.temperatureSelect.SetValue(targetTemp[0])
609 if self.bedTemperatureSelect.GetValue() != bedTargetTemp and wx.Window.FindFocus() != self.bedTemperatureSelect:
610 self.bedTemperatureSelect.SetValue(bedTargetTemp)
612 def mcStateChange(self, state):
613 if self.machineCom is not None:
614 if state == self.machineCom.STATE_OPERATIONAL and self.cam is not None:
615 self.cam.endTimelapse()
616 if state == self.machineCom.STATE_OPERATIONAL:
617 taskbar.setBusy(self, False)
618 if self.machineCom.isClosedOrError():
619 taskbar.setBusy(self, False)
620 if self.machineCom.isPaused():
621 taskbar.setPause(self, True)
622 if self.machineCom.isClosedOrError():
624 elif self.machineCom.isPrinting():
625 print 'STATE:PRINTING'
628 wx.CallAfter(self.UpdateButtonStates)
629 wx.CallAfter(self.UpdateProgress)
631 def mcMessage(self, message):
632 wx.CallAfter(self.AddTermLog, message)
634 def mcProgress(self, lineNr):
635 wx.CallAfter(self.UpdateProgress)
637 def mcZChange(self, newZ):
640 if self.cam is not None:
641 wx.CallAfter(self.cam.takeNewImage)
642 wx.CallAfter(self.camPreview.Refresh)
645 class temperatureGraph(wx.Panel):
646 def __init__(self, parent):
647 super(temperatureGraph, self).__init__(parent)
649 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
650 self.Bind(wx.EVT_SIZE, self.OnSize)
651 self.Bind(wx.EVT_PAINT, self.OnDraw)
653 self.lastDraw = time.time() - 1.0
655 self.backBuffer = None
656 self.addPoint([0]*16, [0]*16, 0, 0)
657 self.SetMinSize((320, 200))
659 def OnEraseBackground(self, e):
663 if self.backBuffer is None or self.GetSize() != self.backBuffer.GetSize():
664 self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
665 self.UpdateDrawing(True)
668 dc = wx.BufferedPaintDC(self, self.backBuffer)
670 def UpdateDrawing(self, force=False):
672 if not force and now - self.lastDraw < 1.0:
676 dc.SelectObject(self.backBuffer)
678 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
679 w, h = self.GetSizeTuple()
680 bgLinePen = wx.Pen('#A0A0A0')
681 tempPen = wx.Pen('#FF4040')
682 tempSPPen = wx.Pen('#FFA0A0')
683 tempPenBG = wx.Pen('#FFD0D0')
684 bedTempPen = wx.Pen('#4040FF')
685 bedTempSPPen = wx.Pen('#A0A0FF')
686 bedTempPenBG = wx.Pen('#D0D0FF')
688 #Draw the background up to the current temperatures.
690 t0 = [0] * len(self.points[0][0])
694 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
695 x1 = int(w - (now - t))
696 for x in xrange(x0, x1 + 1):
697 for n in xrange(0, len(temp)):
698 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
700 dc.DrawLine(x, h, x, h - (t * h / 300))
701 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
702 dc.SetPen(bedTempPenBG)
703 dc.DrawLine(x, h, x, h - (bt * h / 300))
711 for x in xrange(w, 0, -30):
713 dc.DrawLine(x, 0, x, h)
715 for y in xrange(h - 1, 0, -h * 50 / 300):
717 dc.DrawLine(0, y, w, y)
718 dc.DrawText(str(tmpNr), 0, y - dc.GetFont().GetPixelSize().GetHeight())
720 dc.DrawLine(0, 0, w, 0)
721 dc.DrawLine(0, 0, 0, h)
725 t0 = [0] * len(self.points[0][0])
727 tSP0 = [0] * len(self.points[0][0])
729 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
730 x1 = int(w - (now - t))
731 for x in xrange(x0, x1 + 1):
732 for n in xrange(0, len(temp)):
733 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
734 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP[n] - tSP0[n]) + tSP0[n]
736 dc.DrawPoint(x, h - (tSP * h / 300))
738 dc.DrawPoint(x, h - (t * h / 300))
739 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
740 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
741 dc.SetPen(bedTempSPPen)
742 dc.DrawPoint(x, h - (btSP * h / 300))
743 dc.SetPen(bedTempPen)
744 dc.DrawPoint(x, h - (bt * h / 300))
752 self.Refresh(eraseBackground=False)
755 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
758 def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
761 if bedTempSP is None:
763 self.points.append((temp[:], tempSP[:], bedTemp, bedTempSP, time.time()))
764 wx.CallAfter(self.UpdateDrawing)
767 class LogWindow(wx.Frame):
768 def __init__(self, logText):
769 super(LogWindow, self).__init__(None, title="Machine log")
770 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
771 self.SetSize((500, 400))