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 import resources
22 from Cura.util import profile
24 #The printProcessMonitor is used from the main GUI python process. This monitors the printing python process.
25 # This class also handles starting of the 2nd process for printing and all communications with it.
26 class printProcessMonitor():
27 def __init__(self, callback = None):
29 self._state = 'CLOSED'
31 self._callback = callback
34 def loadFile(self, filename, id):
35 if self.handle is None:
36 if platform.system() == "Darwin" and hasattr(sys, 'frozen'):
37 cmdList = [os.path.join(os.path.dirname(sys.executable), 'Cura')]
39 cmdList = [sys.executable, '-m', 'Cura.cura']
41 cmdList.append(filename)
42 if platform.system() == "Darwin":
43 if platform.machine() == 'i386':
44 cmdList.insert(0, 'arch')
45 cmdList.insert(1, '-i386')
46 self.handle = subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
47 self.thread = threading.Thread(target=self.Monitor)
50 self.handle.stdin.write('LOAD:%s\n' % filename)
55 line = p.stdout.readline()
59 if line.startswith('Z:'):
60 self._z = float(line[2:])
62 elif line.startswith('STATE:'):
63 self._state = line[6:]
67 #print '>' + line.rstrip()
68 line = p.stdout.readline()
69 line = p.stderr.readline()
71 print '>>' + line.rstrip()
72 line = p.stderr.readline()
86 def _callCallback(self):
87 if self._callback is not None:
90 def startPrintInterface(filename):
91 #startPrintInterface is called from the main script when we want the printer interface to run in a separate process.
92 # 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).
94 resources.setupLocalization(profile.getPreference('language'))
95 printWindowHandle = printWindow()
96 printWindowHandle.Show(True)
97 printWindowHandle.Raise()
98 printWindowHandle.OnConnect(None)
99 t = threading.Thread(target=printWindowHandle.LoadGCodeFile, args=(filename,))
104 class PrintCommandButton(buttons.GenBitmapButton):
105 def __init__(self, parent, commandList, bitmapFilename, size=(20, 20)):
106 self.bitmap = wx.Bitmap(resources.getPathForImage(bitmapFilename))
107 super(PrintCommandButton, self).__init__(parent.directControlPanel, -1, self.bitmap, size=size)
109 self.commandList = commandList
112 self.SetBezelWidth(1)
113 self.SetUseFocusIndicator(False)
115 self.Bind(wx.EVT_BUTTON, self.OnClick)
117 def OnClick(self, e):
118 if self.parent.machineCom is None or self.parent.machineCom.isPrinting():
120 for cmd in self.commandList:
121 self.parent.machineCom.sendCommand(cmd)
125 class printWindow(wx.Frame):
126 "Main user interface window"
129 super(printWindow, self).__init__(None, -1, title=_("Printing"))
130 self.machineCom = None
132 self.gcodeList = None
136 self.bufferLineCount = 4
138 self.feedrateRatioOuterWall = 1.0
139 self.feedrateRatioInnerWall = 1.0
140 self.feedrateRatioFill = 1.0
141 self.feedrateRatioSupport = 1.0
143 self.termHistory = []
144 self.termHistoryIdx = 0
147 if webcam.hasWebcamSupport():
148 self.cam = webcam.webcam()
149 if not self.cam.hasCamera():
152 self.SetSizer(wx.BoxSizer())
153 self.panel = wx.Panel(self)
154 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
155 self.sizer = wx.GridBagSizer(2, 2)
156 self.panel.SetSizer(self.sizer)
158 sb = wx.StaticBox(self.panel, label=_("Statistics"))
159 boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)
161 self.powerWarningText = wx.StaticText(parent=self.panel,
163 label=_("Your computer is running on battery power.\nConnect your computer to AC power or your print might not finish."),
164 style=wx.ALIGN_CENTER)
165 self.powerWarningText.SetBackgroundColour('red')
166 self.powerWarningText.SetForegroundColour('white')
167 boxsizer.AddF(self.powerWarningText, flags=wx.SizerFlags().Expand().Border(wx.BOTTOM, 10))
168 self.powerManagement = power.PowerManagement()
169 self.powerWarningTimer = wx.Timer(self)
170 self.Bind(wx.EVT_TIMER, self.OnPowerWarningChange, self.powerWarningTimer)
171 self.OnPowerWarningChange(None)
172 self.powerWarningTimer.Start(10000)
174 self.statsText = wx.StaticText(self.panel, -1, _("Filament: ####.##m #.##g\nEstimated print time: #####:##\nMachine state:\nDetecting baudrateXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"))
175 boxsizer.Add(self.statsText, flag=wx.LEFT, border=5)
177 self.sizer.Add(boxsizer, pos=(0, 0), span=(7, 1), flag=wx.EXPAND)
179 self.connectButton = wx.Button(self.panel, -1, _("Connect"))
180 #self.loadButton = wx.Button(self.panel, -1, 'Load')
181 self.printButton = wx.Button(self.panel, -1, _("Print"))
182 self.pauseButton = wx.Button(self.panel, -1, _("Pause"))
183 self.cancelButton = wx.Button(self.panel, -1, _("Cancel print"))
184 self.machineLogButton = wx.Button(self.panel, -1, _("Error log"))
185 self.progress = wx.Gauge(self.panel, -1)
187 self.sizer.Add(self.connectButton, pos=(1, 1), flag=wx.EXPAND)
188 #self.sizer.Add(self.loadButton, pos=(1,1), flag=wx.EXPAND)
189 self.sizer.Add(self.printButton, pos=(2, 1), flag=wx.EXPAND)
190 self.sizer.Add(self.pauseButton, pos=(3, 1), flag=wx.EXPAND)
191 self.sizer.Add(self.cancelButton, pos=(4, 1), flag=wx.EXPAND)
192 self.sizer.Add(self.machineLogButton, pos=(5, 1), flag=wx.EXPAND)
193 self.sizer.Add(self.progress, pos=(7, 0), span=(1, 7), flag=wx.EXPAND)
195 nb = wx.Notebook(self.panel)
196 self.sizer.Add(nb, pos=(0, 2), span=(7, 4), flag=wx.EXPAND)
198 self.temperaturePanel = wx.Panel(nb)
199 sizer = wx.GridBagSizer(2, 2)
200 self.temperaturePanel.SetSizer(sizer)
202 self.temperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
203 self.temperatureSelect.SetRange(0, 400)
204 self.temperatureHeatUp = wx.Button(self.temperaturePanel, -1, str(int(profile.getProfileSettingFloat('print_temperature'))) + 'C')
205 self.bedTemperatureLabel = wx.StaticText(self.temperaturePanel, -1, _("BedTemp:"))
206 self.bedTemperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
207 self.bedTemperatureSelect.SetRange(0, 400)
208 self.bedTemperatureLabel.Show(False)
209 self.bedTemperatureSelect.Show(False)
211 self.temperatureGraph = temperatureGraph(self.temperaturePanel)
213 sizer.Add(wx.StaticText(self.temperaturePanel, -1, _("Temp:")), pos=(0, 0))
214 sizer.Add(self.temperatureSelect, pos=(0, 1))
215 sizer.Add(self.temperatureHeatUp, pos=(0, 2))
216 sizer.Add(self.bedTemperatureLabel, pos=(1, 0))
217 sizer.Add(self.bedTemperatureSelect, pos=(1, 1))
218 sizer.Add(self.temperatureGraph, pos=(2, 0), span=(1, 3), flag=wx.EXPAND)
219 sizer.AddGrowableRow(2)
220 sizer.AddGrowableCol(2)
222 nb.AddPage(self.temperaturePanel, 'Temp')
224 self.directControlPanel = wx.Panel(nb)
226 sizer = wx.GridBagSizer(2, 2)
227 self.directControlPanel.SetSizer(sizer)
228 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y100 F6000', 'G90'], 'print-move-y100.png'), pos=(0, 3))
229 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y10 F6000', 'G90'], 'print-move-y10.png'), pos=(1, 3))
230 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y1 F6000', 'G90'], 'print-move-y1.png'), pos=(2, 3))
232 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-1 F6000', 'G90'], 'print-move-y-1.png'), pos=(4, 3))
233 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-10 F6000', 'G90'], 'print-move-y-10.png'), pos=(5, 3))
234 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-100 F6000', 'G90'], 'print-move-y-100.png'), pos=(6, 3))
236 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-100 F6000', 'G90'], 'print-move-x-100.png'), pos=(3, 0))
237 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-10 F6000', 'G90'], 'print-move-x-10.png'), pos=(3, 1))
238 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-1 F6000', 'G90'], 'print-move-x-1.png'), pos=(3, 2))
240 sizer.Add(PrintCommandButton(self, ['G28 X0 Y0'], 'print-move-home.png'), pos=(3, 3))
242 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X1 F6000', 'G90'], 'print-move-x1.png'), pos=(3, 4))
243 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X10 F6000', 'G90'], 'print-move-x10.png'), pos=(3, 5))
244 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X100 F6000', 'G90'], 'print-move-x100.png'), pos=(3, 6))
246 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z10 F200', 'G90'], 'print-move-z10.png'), pos=(0, 8))
247 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z1 F200', 'G90'], 'print-move-z1.png'), pos=(1, 8))
248 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z0.1 F200', 'G90'], 'print-move-z0.1.png'), pos=(2, 8))
250 sizer.Add(PrintCommandButton(self, ['G28 Z0'], 'print-move-home.png'), pos=(3, 8))
252 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-0.1 F200', 'G90'], 'print-move-z-0.1.png'), pos=(4, 8))
253 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-1 F200', 'G90'], 'print-move-z-1.png'), pos=(5, 8))
254 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-10 F200', 'G90'], 'print-move-z-10.png'), pos=(6, 8))
256 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E2 F120'], 'extrude.png', size=(60, 20)), pos=(1, 10),
257 span=(1, 3), flag=wx.EXPAND)
258 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E-2 F120'], 'retract.png', size=(60, 20)), pos=(2, 10),
259 span=(1, 3), flag=wx.EXPAND)
261 nb.AddPage(self.directControlPanel, _("Jog"))
263 self.speedPanel = wx.Panel(nb)
264 sizer = wx.GridBagSizer(2, 2)
265 self.speedPanel.SetSizer(sizer)
267 self.outerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
268 self.outerWallSpeedSelect.SetRange(5, 1000)
269 self.innerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
270 self.innerWallSpeedSelect.SetRange(5, 1000)
271 self.fillSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
272 self.fillSpeedSelect.SetRange(5, 1000)
273 self.supportSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
274 self.supportSpeedSelect.SetRange(5, 1000)
276 sizer.Add(wx.StaticText(self.speedPanel, -1, _("Outer wall:")), pos=(0, 0))
277 sizer.Add(self.outerWallSpeedSelect, pos=(0, 1))
278 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(0, 2))
279 sizer.Add(wx.StaticText(self.speedPanel, -1, _("Inner wall:")), pos=(1, 0))
280 sizer.Add(self.innerWallSpeedSelect, pos=(1, 1))
281 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(1, 2))
282 sizer.Add(wx.StaticText(self.speedPanel, -1, _("Fill:")), pos=(2, 0))
283 sizer.Add(self.fillSpeedSelect, pos=(2, 1))
284 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(2, 2))
285 sizer.Add(wx.StaticText(self.speedPanel, -1, _("Support:")), pos=(3, 0))
286 sizer.Add(self.supportSpeedSelect, pos=(3, 1))
287 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(3, 2))
289 nb.AddPage(self.speedPanel, _("Speed"))
291 self.termPanel = wx.Panel(nb)
292 sizer = wx.GridBagSizer(2, 2)
293 self.termPanel.SetSizer(sizer)
295 f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
296 self.termLog = wx.TextCtrl(self.termPanel, style=wx.TE_MULTILINE | wx.TE_DONTWRAP)
297 self.termLog.SetFont(f)
298 self.termLog.SetEditable(0)
299 self.termInput = wx.TextCtrl(self.termPanel, style=wx.TE_PROCESS_ENTER)
300 self.termInput.SetFont(f)
302 sizer.Add(self.termLog, pos=(0, 0), flag=wx.EXPAND)
303 sizer.Add(self.termInput, pos=(1, 0), flag=wx.EXPAND)
304 sizer.AddGrowableCol(0)
305 sizer.AddGrowableRow(0)
307 nb.AddPage(self.termPanel, _("Term"))
309 if self.cam is not None:
310 self.camPage = wx.Panel(nb)
311 sizer = wx.GridBagSizer(2, 2)
312 self.camPage.SetSizer(sizer)
314 self.timelapsEnable = wx.CheckBox(self.camPage, -1, _("Enable timelapse movie recording"))
315 self.timelapsSavePath = wx.TextCtrl(self.camPage, -1, os.path.expanduser('~/timelaps_' + datetime.datetime.now().strftime('%Y-%m-%d_%H:%M') + '.mpg'))
316 sizer.Add(self.timelapsEnable, pos=(0, 0), span=(1, 2), flag=wx.EXPAND)
317 sizer.Add(self.timelapsSavePath, pos=(1, 0), span=(1, 2), flag=wx.EXPAND)
319 pages = self.cam.propertyPages()
320 self.cam.buttons = [self.timelapsEnable, self.timelapsSavePath]
322 button = wx.Button(self.camPage, -1, page)
323 button.index = pages.index(page)
324 sizer.Add(button, pos=(2, pages.index(page)))
325 button.Bind(wx.EVT_BUTTON, self.OnPropertyPageButton)
326 self.cam.buttons.append(button)
328 self.campreviewEnable = wx.CheckBox(self.camPage, -1, _("Show preview"))
329 sizer.Add(self.campreviewEnable, pos=(3, 0), span=(1, 2), flag=wx.EXPAND)
331 self.camPreview = wx.Panel(self.camPage)
332 sizer.Add(self.camPreview, pos=(4, 0), span=(1, 2), flag=wx.EXPAND)
334 nb.AddPage(self.camPage, _("Camera"))
335 self.camPreview.timer = wx.Timer(self)
336 self.Bind(wx.EVT_TIMER, self.OnCameraTimer, self.camPreview.timer)
337 self.camPreview.timer.Start(500)
338 self.camPreview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
340 self.sizer.AddGrowableRow(6)
341 self.sizer.AddGrowableCol(3)
343 self.Bind(wx.EVT_CLOSE, self.OnClose)
344 self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect)
345 #self.loadButton.Bind(wx.EVT_BUTTON, self.OnLoad)
346 self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint)
347 self.pauseButton.Bind(wx.EVT_BUTTON, self.OnPause)
348 self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)
349 self.machineLogButton.Bind(wx.EVT_BUTTON, self.OnMachineLog)
351 self.Bind(wx.EVT_BUTTON, lambda e: (self.temperatureSelect.SetValue(int(profile.getProfileSettingFloat('print_temperature'))), self.machineCom.sendCommand("M104 S%d" % (int(profile.getProfileSettingFloat('print_temperature'))))), self.temperatureHeatUp)
352 self.Bind(wx.EVT_SPINCTRL, self.OnTempChange, self.temperatureSelect)
353 self.Bind(wx.EVT_SPINCTRL, self.OnBedTempChange, self.bedTemperatureSelect)
355 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.outerWallSpeedSelect)
356 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.innerWallSpeedSelect)
357 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.fillSpeedSelect)
358 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.supportSpeedSelect)
359 self.Bind(wx.EVT_TEXT_ENTER, self.OnTermEnterLine, self.termInput)
360 self.termInput.Bind(wx.EVT_CHAR, self.OnTermKey)
366 self.statsText.SetMinSize(self.statsText.GetSize())
368 self.UpdateButtonStates()
370 #self.UpdateProgress()
371 self._thread = threading.Thread(target=self._stdinMonitor)
372 self._thread.daemon = True
375 def _stdinMonitor(self):
377 line = sys.stdin.readline().rstrip()
378 if line.startswith('LOAD:'):
379 if not self.LoadGCodeFile(line[5:]):
382 def OnCameraTimer(self, e):
383 if not self.campreviewEnable.GetValue():
385 if self.machineCom is not None and self.machineCom.isPrinting():
387 self.cam.takeNewImage()
388 self.camPreview.Refresh()
390 def OnCameraEraseBackground(self, e):
393 dc = wx.ClientDC(self)
394 rect = self.GetUpdateRegion().GetBox()
395 dc.SetClippingRect(rect)
396 dc.SetBackground(wx.Brush(self.camPreview.GetBackgroundColour(), wx.SOLID))
397 if self.cam.getLastImage() is not None:
398 self.camPreview.SetMinSize((self.cam.getLastImage().GetWidth(), self.cam.getLastImage().GetHeight()))
400 dc.DrawBitmap(self.cam.getLastImage(), 0, 0)
404 def OnPropertyPageButton(self, e):
405 self.cam.openPropertyPage(e.GetEventObject().index)
407 def UpdateButtonStates(self):
408 self.connectButton.Enable(self.machineCom is None or self.machineCom.isClosedOrError())
409 #self.loadButton.Enable(self.machineCom == None or not (self.machineCom.isPrinting() or self.machineCom.isPaused()))
410 self.printButton.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
411 self.machineCom.isPrinting() or self.machineCom.isPaused()))
412 self.temperatureHeatUp.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
413 self.machineCom.isPrinting() or self.machineCom.isPaused()))
414 self.pauseButton.Enable(
415 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
416 if self.machineCom is not None and self.machineCom.isPaused():
417 self.pauseButton.SetLabel(_("Resume"))
419 self.pauseButton.SetLabel(_("Pause"))
420 self.cancelButton.Enable(
421 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
422 self.temperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
423 self.bedTemperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
424 self.directControlPanel.Enable(
425 self.machineCom is not None and self.machineCom.isOperational() and not self.machineCom.isPrinting())
426 self.machineLogButton.Show(self.machineCom is not None and self.machineCom.isClosedOrError())
428 for button in self.cam.buttons:
429 button.Enable(self.machineCom is None or not self.machineCom.isPrinting())
431 def UpdateProgress(self):
433 if self.gcode is None:
434 status += _("Loading gcode...\n")
436 status += _("Filament: %(amount).2fm %(weight).2fg\n") % {'amount': self.gcode.extrusionAmount / 1000, 'weight': self.gcode.calculateWeight() * 1000}
437 cost = self.gcode.calculateCost()
439 status += _("Filament cost: %s\n") % (cost)
440 #status += "Estimated print time: %02d:%02d\n" % (int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60))
441 if self.machineCom is None or not self.machineCom.isPrinting():
442 self.progress.SetValue(0)
443 if self.gcodeList is not None:
444 status += 'Line: -/%d\n' % (len(self.gcodeList))
446 printTime = self.machineCom.getPrintTime() / 60
447 printTimeLeft = self.machineCom.getPrintTimeRemainingEstimate()
448 status += 'Line: %d/%d %d%%\n' % (self.machineCom.getPrintPos(), len(self.gcodeList),
449 self.machineCom.getPrintPos() * 100 / len(self.gcodeList))
450 if self.currentZ > 0:
451 status += 'Height: %0.1f\n' % (self.currentZ)
452 status += 'Print time: %02d:%02d\n' % (int(printTime / 60), int(printTime % 60))
453 if printTimeLeft is None:
454 status += 'Print time left: Unknown\n'
456 status += 'Print time left: %02d:%02d\n' % (int(printTimeLeft / 60), int(printTimeLeft % 60))
457 self.progress.SetValue(self.machineCom.getPrintPos())
458 taskbar.setProgress(self, self.machineCom.getPrintPos(), len(self.gcodeList))
459 if self.machineCom is not None:
460 if self.machineCom.getTemp() > 0:
461 status += 'Temp: %s\n' % (' ,'.join(map(str, self.machineCom.getTemp())))
462 if self.machineCom.getBedTemp() > 0:
463 status += 'Bed Temp: %d\n' % (self.machineCom.getBedTemp())
464 self.bedTemperatureLabel.Show(True)
465 self.bedTemperatureSelect.Show(True)
466 self.temperaturePanel.Layout()
467 status += 'Machine state:%s\n' % (self.machineCom.getStateString())
469 self.statsText.SetLabel(status.strip())
471 def OnConnect(self, e):
472 if self.machineCom is not None:
473 self.machineCom.close()
474 self.machineCom = machineCom.MachineCom(callbackObject=self)
475 self.UpdateButtonStates()
476 taskbar.setBusy(self, True)
481 def OnPrint(self, e):
482 if self.machineCom is None or not self.machineCom.isOperational():
484 if self.gcodeList is None:
486 if self.machineCom.isPrinting():
489 if self.cam is not None and self.timelapsEnable.GetValue():
490 self.cam.startTimelapse(self.timelapsSavePath.GetValue())
491 self.machineCom.printGCode(self.gcodeList)
492 self.UpdateButtonStates()
494 def OnCancel(self, e):
495 self.pauseButton.SetLabel('Pause')
496 self.machineCom.cancelPrint()
497 self.machineCom.sendCommand("M84")
498 self.machineCom.sendCommand("M104 S0")
499 self.UpdateButtonStates()
501 def OnPause(self, e):
502 if self.machineCom.isPaused():
503 self.machineCom.setPause(False)
505 self.machineCom.setPause(True)
507 def OnMachineLog(self, e):
508 LogWindow('\n'.join(self.machineCom.getLog()))
510 def OnClose(self, e):
511 global printWindowHandle
512 printWindowHandle = None
513 if self.machineCom is not None:
514 self.machineCom.close()
517 def OnTempChange(self, e):
518 self.machineCom.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))
520 def OnBedTempChange(self, e):
521 self.machineCom.sendCommand("M140 S%d" % (self.bedTemperatureSelect.GetValue()))
523 def OnSpeedChange(self, e):
524 if self.machineCom is None:
526 self.machineCom.setFeedrateModifier('WALL-OUTER', self.outerWallSpeedSelect.GetValue() / 100.0)
527 self.machineCom.setFeedrateModifier('WALL-INNER', self.innerWallSpeedSelect.GetValue() / 100.0)
528 self.machineCom.setFeedrateModifier('FILL', self.fillSpeedSelect.GetValue() / 100.0)
529 self.machineCom.setFeedrateModifier('SUPPORT', self.supportSpeedSelect.GetValue() / 100.0)
531 def AddTermLog(self, line):
532 if len(self.termLog.GetValue()) > 10000:
533 self.termLog.SetValue(self.termLog.GetValue()[-10000:])
534 self.termLog.SetInsertionPointEnd()
535 self.termLog.AppendText(unicode(line, 'utf-8', 'replace'))
536 #l = self.termLog.GetLastPosition() # if needed (windows? mac?)
537 #self.termLog.ShowPosition(l)
539 def OnTermEnterLine(self, e):
540 line = self.termInput.GetValue()
543 self.termLog.AppendText('>%s\n' % (line))
544 self.machineCom.sendCommand(line)
545 self.termHistory.append(line)
546 self.termHistoryIdx = len(self.termHistory)
547 self.termInput.SetValue('')
549 def OnTermKey(self, e):
550 if len(self.termHistory) > 0:
551 if e.GetKeyCode() == wx.WXK_UP:
552 self.termHistoryIdx -= 1
553 if self.termHistoryIdx < 0:
554 self.termHistoryIdx = len(self.termHistory) - 1
555 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
556 if e.GetKeyCode() == wx.WXK_DOWN:
557 self.termHistoryIdx -= 1
558 if self.termHistoryIdx >= len(self.termHistory):
559 self.termHistoryIdx = 0
560 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
563 def OnPowerWarningChange(self, e):
564 type = self.powerManagement.get_providing_power_source_type()
565 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
566 self.powerWarningText.Hide()
569 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
570 self.powerWarningText.Show()
574 def LoadGCodeFile(self, filename):
575 if self.machineCom is not None and self.machineCom.isPrinting():
577 #Send an initial M110 to reset the line counter to zero.
578 prevLineType = lineType = 'CUSTOM'
580 for line in open(filename, 'r'):
581 if line.startswith(';TYPE:'):
582 lineType = line[6:].strip()
584 line = line[0:line.find(';')]
587 if prevLineType != lineType:
588 gcodeList.append((line, lineType, ))
590 gcodeList.append(line)
591 prevLineType = lineType
592 gcode = gcodeInterpreter.gcode()
593 gcode.loadList(gcodeList)
594 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
595 self.filename = filename
597 self.gcodeList = gcodeList
599 wx.CallAfter(self.progress.SetRange, len(gcodeList))
600 wx.CallAfter(self.UpdateButtonStates)
601 wx.CallAfter(self.UpdateProgress)
604 def sendLine(self, lineNr):
605 if lineNr >= len(self.gcodeList):
607 line = self.gcodeList[lineNr]
609 if ('M104' in line or 'M109' in line) and 'S' in line:
610 n = int(re.search('S([0-9]*)', line).group(1))
611 wx.CallAfter(self.temperatureSelect.SetValue, n)
612 if ('M140' in line or 'M190' in line) and 'S' in line:
613 n = int(re.search('S([0-9]*)', line).group(1))
614 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
616 print "Unexpected error:", sys.exc_info()
617 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
618 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
621 def mcLog(self, message):
625 def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
626 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
627 wx.CallAfter(self._mcTempUpdate, temp, bedTemp, targetTemp, bedTargetTemp)
629 def _mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
630 if self.temperatureSelect.GetValue() != targetTemp[0] and wx.Window.FindFocus() != self.temperatureSelect:
631 self.temperatureSelect.SetValue(targetTemp[0])
632 if self.bedTemperatureSelect.GetValue() != bedTargetTemp and wx.Window.FindFocus() != self.bedTemperatureSelect:
633 self.bedTemperatureSelect.SetValue(bedTargetTemp)
635 def mcStateChange(self, state):
636 if self.machineCom is not None:
637 if state == self.machineCom.STATE_OPERATIONAL and self.cam is not None:
638 self.cam.endTimelapse()
639 if state == self.machineCom.STATE_OPERATIONAL:
640 taskbar.setBusy(self, False)
641 if self.machineCom.isClosedOrError():
642 taskbar.setBusy(self, False)
643 if self.machineCom.isPaused():
644 taskbar.setPause(self, True)
645 if self.machineCom.isClosedOrError():
647 elif self.machineCom.isPrinting():
648 print 'STATE:PRINTING'
651 wx.CallAfter(self.UpdateButtonStates)
652 wx.CallAfter(self.UpdateProgress)
654 def mcMessage(self, message):
655 wx.CallAfter(self.AddTermLog, message)
657 def mcProgress(self, lineNr):
658 wx.CallAfter(self.UpdateProgress)
660 def mcZChange(self, newZ):
663 if self.cam is not None:
664 wx.CallAfter(self.cam.takeNewImage)
665 wx.CallAfter(self.camPreview.Refresh)
668 class temperatureGraph(wx.Panel):
669 def __init__(self, parent):
670 super(temperatureGraph, self).__init__(parent)
672 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
673 self.Bind(wx.EVT_SIZE, self.OnSize)
674 self.Bind(wx.EVT_PAINT, self.OnDraw)
676 self.lastDraw = time.time() - 1.0
678 self.backBuffer = None
679 self.addPoint([0]*16, [0]*16, 0, 0)
680 self.SetMinSize((320, 200))
682 def OnEraseBackground(self, e):
686 if self.backBuffer is None or self.GetSize() != self.backBuffer.GetSize():
687 self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
688 self.UpdateDrawing(True)
691 dc = wx.BufferedPaintDC(self, self.backBuffer)
693 def UpdateDrawing(self, force=False):
695 if not force and now - self.lastDraw < 1.0:
699 dc.SelectObject(self.backBuffer)
701 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
702 w, h = self.GetSizeTuple()
703 bgLinePen = wx.Pen('#A0A0A0')
704 tempPen = wx.Pen('#FF4040')
705 tempSPPen = wx.Pen('#FFA0A0')
706 tempPenBG = wx.Pen('#FFD0D0')
707 bedTempPen = wx.Pen('#4040FF')
708 bedTempSPPen = wx.Pen('#A0A0FF')
709 bedTempPenBG = wx.Pen('#D0D0FF')
711 #Draw the background up to the current temperatures.
713 t0 = [0] * len(self.points[0][0])
717 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
718 x1 = int(w - (now - t))
719 for x in xrange(x0, x1 + 1):
720 for n in xrange(0, len(temp)):
721 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
723 dc.DrawLine(x, h, x, h - (t * h / 300))
724 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
725 dc.SetPen(bedTempPenBG)
726 dc.DrawLine(x, h, x, h - (bt * h / 300))
734 for x in xrange(w, 0, -30):
736 dc.DrawLine(x, 0, x, h)
738 for y in xrange(h - 1, 0, -h * 50 / 300):
740 dc.DrawLine(0, y, w, y)
741 dc.DrawText(str(tmpNr), 0, y - dc.GetFont().GetPixelSize().GetHeight())
743 dc.DrawLine(0, 0, w, 0)
744 dc.DrawLine(0, 0, 0, h)
748 t0 = [0] * len(self.points[0][0])
750 tSP0 = [0] * len(self.points[0][0])
752 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
753 x1 = int(w - (now - t))
754 for x in xrange(x0, x1 + 1):
755 for n in xrange(0, len(temp)):
756 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
757 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP[n] - tSP0[n]) + tSP0[n]
759 dc.DrawPoint(x, h - (tSP * h / 300))
761 dc.DrawPoint(x, h - (t * h / 300))
762 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
763 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
764 dc.SetPen(bedTempSPPen)
765 dc.DrawPoint(x, h - (btSP * h / 300))
766 dc.SetPen(bedTempPen)
767 dc.DrawPoint(x, h - (bt * h / 300))
775 self.Refresh(eraseBackground=False)
778 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
781 def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
784 if bedTempSP is None:
786 self.points.append((temp[:], tempSP[:], bedTemp, bedTempSP, time.time()))
787 wx.CallAfter(self.UpdateDrawing)
790 class LogWindow(wx.Frame):
791 def __init__(self, logText):
792 super(LogWindow, self).__init__(None, title="Machine log")
793 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
794 self.SetSize((500, 400))