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 #Save the preferences before starting the print window so we use the proper machine settings.
47 profile.savePreferences(profile.getPreferencePath())
48 self.handle = subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
49 self.thread = threading.Thread(target=self.Monitor)
52 self.handle.stdin.write('LOAD:%s\n' % filename)
57 line = p.stdout.readline()
61 if line.startswith('Z:'):
62 self._z = float(line[2:])
64 elif line.startswith('STATE:'):
65 self._state = line[6:]
68 print '>' + line.rstrip()
71 line = p.stdout.readline()
72 line = p.stderr.readline()
74 print '>>' + line.rstrip()
75 line = p.stderr.readline()
89 def _callCallback(self):
90 if self._callback is not None:
93 def startPrintInterface(filename):
94 #startPrintInterface is called from the main script when we want the printer interface to run in a separate process.
95 # 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).
97 resources.setupLocalization(profile.getPreference('language'))
98 printWindowHandle = printWindow()
99 printWindowHandle.Show(True)
100 printWindowHandle.Raise()
101 printWindowHandle.OnConnect(None)
102 t = threading.Thread(target=printWindowHandle.LoadGCodeFile, args=(filename,))
107 class PrintCommandButton(buttons.GenBitmapButton):
108 def __init__(self, parent, commandList, bitmapFilename, size=(20, 20)):
109 self.bitmap = wx.Bitmap(resources.getPathForImage(bitmapFilename))
110 super(PrintCommandButton, self).__init__(parent.directControlPanel, -1, self.bitmap, size=size)
112 self.commandList = commandList
115 self.SetBezelWidth(1)
116 self.SetUseFocusIndicator(False)
118 self.Bind(wx.EVT_BUTTON, self.OnClick)
120 def OnClick(self, e):
121 if self.parent.machineCom is None or self.parent.machineCom.isPrinting():
123 for cmd in self.commandList:
124 self.parent.machineCom.sendCommand(cmd)
128 class printWindow(wx.Frame):
129 "Main user interface window"
132 super(printWindow, self).__init__(None, -1, title=_("Printing"))
134 self.machineCom = None
136 self.gcodeList = None
140 self.bufferLineCount = 4
142 self.feedrateRatioOuterWall = 1.0
143 self.feedrateRatioInnerWall = 1.0
144 self.feedrateRatioFill = 1.0
145 self.feedrateRatioSupport = 1.0
147 self.termHistory = []
148 self.termHistoryIdx = 0
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)
197 self.sizer.Add(nb, pos=(0, 2), span=(7, 4), flag=wx.EXPAND)
199 self.temperaturePanel = wx.Panel(nb)
200 sizer = wx.GridBagSizer(2, 2)
201 self.temperaturePanel.SetSizer(sizer)
203 self.temperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
204 self.temperatureSelect.SetRange(0, 400)
205 self.temperatureHeatUp = wx.Button(self.temperaturePanel, -1, str(int(profile.getProfileSettingFloat('print_temperature'))) + 'C')
206 self.bedTemperatureLabel = wx.StaticText(self.temperaturePanel, -1, _("BedTemp:"))
207 self.bedTemperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
208 self.bedTemperatureSelect.SetRange(0, 400)
209 self.bedTemperatureLabel.Show(False)
210 self.bedTemperatureSelect.Show(False)
212 self.temperatureGraph = temperatureGraph(self.temperaturePanel)
214 sizer.Add(wx.StaticText(self.temperaturePanel, -1, _("Temp:")), pos=(0, 0))
215 sizer.Add(self.temperatureSelect, pos=(0, 1))
216 sizer.Add(self.temperatureHeatUp, pos=(0, 2))
217 sizer.Add(self.bedTemperatureLabel, pos=(1, 0))
218 sizer.Add(self.bedTemperatureSelect, pos=(1, 1))
219 sizer.Add(self.temperatureGraph, pos=(2, 0), span=(1, 3), flag=wx.EXPAND)
220 sizer.AddGrowableRow(2)
221 sizer.AddGrowableCol(2)
223 nb.AddPage(self.temperaturePanel, 'Temp')
225 self.directControlPanel = wx.Panel(nb)
227 sizer = wx.GridBagSizer(2, 2)
228 self.directControlPanel.SetSizer(sizer)
229 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y100 F6000', 'G90'], 'print-move-y100.png'), pos=(0, 3))
230 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y10 F6000', 'G90'], 'print-move-y10.png'), pos=(1, 3))
231 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y1 F6000', 'G90'], 'print-move-y1.png'), pos=(2, 3))
233 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-1 F6000', 'G90'], 'print-move-y-1.png'), pos=(4, 3))
234 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-10 F6000', 'G90'], 'print-move-y-10.png'), pos=(5, 3))
235 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-100 F6000', 'G90'], 'print-move-y-100.png'), pos=(6, 3))
237 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-100 F6000', 'G90'], 'print-move-x-100.png'), pos=(3, 0))
238 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-10 F6000', 'G90'], 'print-move-x-10.png'), pos=(3, 1))
239 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-1 F6000', 'G90'], 'print-move-x-1.png'), pos=(3, 2))
241 sizer.Add(PrintCommandButton(self, ['G28 X0 Y0'], 'print-move-home.png'), pos=(3, 3))
243 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X1 F6000', 'G90'], 'print-move-x1.png'), pos=(3, 4))
244 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X10 F6000', 'G90'], 'print-move-x10.png'), pos=(3, 5))
245 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X100 F6000', 'G90'], 'print-move-x100.png'), pos=(3, 6))
247 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z10 F200', 'G90'], 'print-move-z10.png'), pos=(0, 8))
248 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z1 F200', 'G90'], 'print-move-z1.png'), pos=(1, 8))
249 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z0.1 F200', 'G90'], 'print-move-z0.1.png'), pos=(2, 8))
251 sizer.Add(PrintCommandButton(self, ['G28 Z0'], 'print-move-home.png'), pos=(3, 8))
253 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-0.1 F200', 'G90'], 'print-move-z-0.1.png'), pos=(4, 8))
254 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-1 F200', 'G90'], 'print-move-z-1.png'), pos=(5, 8))
255 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-10 F200', 'G90'], 'print-move-z-10.png'), pos=(6, 8))
257 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E2 F120'], 'extrude.png', size=(60, 20)), pos=(1, 10),
258 span=(1, 3), flag=wx.EXPAND)
259 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E-2 F120'], 'retract.png', size=(60, 20)), pos=(2, 10),
260 span=(1, 3), flag=wx.EXPAND)
262 nb.AddPage(self.directControlPanel, _("Jog"))
264 self.speedPanel = wx.Panel(nb)
265 sizer = wx.GridBagSizer(2, 2)
266 self.speedPanel.SetSizer(sizer)
268 self.outerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
269 self.outerWallSpeedSelect.SetRange(5, 1000)
270 self.innerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
271 self.innerWallSpeedSelect.SetRange(5, 1000)
272 self.fillSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
273 self.fillSpeedSelect.SetRange(5, 1000)
274 self.supportSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
275 self.supportSpeedSelect.SetRange(5, 1000)
277 sizer.Add(wx.StaticText(self.speedPanel, -1, _("Outer wall:")), pos=(0, 0))
278 sizer.Add(self.outerWallSpeedSelect, pos=(0, 1))
279 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(0, 2))
280 sizer.Add(wx.StaticText(self.speedPanel, -1, _("Inner wall:")), pos=(1, 0))
281 sizer.Add(self.innerWallSpeedSelect, pos=(1, 1))
282 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(1, 2))
283 sizer.Add(wx.StaticText(self.speedPanel, -1, _("Fill:")), pos=(2, 0))
284 sizer.Add(self.fillSpeedSelect, pos=(2, 1))
285 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(2, 2))
286 sizer.Add(wx.StaticText(self.speedPanel, -1, _("Support:")), pos=(3, 0))
287 sizer.Add(self.supportSpeedSelect, pos=(3, 1))
288 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(3, 2))
290 nb.AddPage(self.speedPanel, _("Speed"))
292 self.termPanel = wx.Panel(nb)
293 sizer = wx.GridBagSizer(2, 2)
294 self.termPanel.SetSizer(sizer)
296 f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
297 self.termLog = wx.TextCtrl(self.termPanel, style=wx.TE_MULTILINE | wx.TE_DONTWRAP)
298 self.termLog.SetFont(f)
299 self.termLog.SetEditable(0)
300 self.termInput = wx.TextCtrl(self.termPanel, style=wx.TE_PROCESS_ENTER)
301 self.termInput.SetFont(f)
303 sizer.Add(self.termLog, pos=(0, 0), flag=wx.EXPAND)
304 sizer.Add(self.termInput, pos=(1, 0), flag=wx.EXPAND)
305 sizer.AddGrowableCol(0)
306 sizer.AddGrowableRow(0)
308 nb.AddPage(self.termPanel, _("Term"))
310 self.sizer.AddGrowableRow(6)
311 self.sizer.AddGrowableCol(3)
313 self.Bind(wx.EVT_CLOSE, self.OnClose)
314 self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect)
315 #self.loadButton.Bind(wx.EVT_BUTTON, self.OnLoad)
316 self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint)
317 self.pauseButton.Bind(wx.EVT_BUTTON, self.OnPause)
318 self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)
319 self.machineLogButton.Bind(wx.EVT_BUTTON, self.OnMachineLog)
321 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)
322 self.Bind(wx.EVT_SPINCTRL, self.OnTempChange, self.temperatureSelect)
323 self.Bind(wx.EVT_SPINCTRL, self.OnBedTempChange, self.bedTemperatureSelect)
325 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.outerWallSpeedSelect)
326 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.innerWallSpeedSelect)
327 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.fillSpeedSelect)
328 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.supportSpeedSelect)
329 self.Bind(wx.EVT_TEXT_ENTER, self.OnTermEnterLine, self.termInput)
330 self.termInput.Bind(wx.EVT_CHAR, self.OnTermKey)
336 self.statsText.SetMinSize(self.statsText.GetSize())
338 self.UpdateButtonStates()
340 #self.UpdateProgress()
341 self._thread = threading.Thread(target=self._stdinMonitor)
342 self._thread.daemon = True
345 if webcam.hasWebcamSupport():
346 #Need to call the camera class on the GUI thread, or else it won't work. Shame as it hangs the GUI for about 2 seconds.
347 wx.CallAfter(self._webcamCheck)
349 def _stdinMonitor(self):
351 line = sys.stdin.readline().rstrip()
352 if line.startswith('LOAD:'):
353 if not self.LoadGCodeFile(line[5:]):
356 def _webcamCheck(self):
357 self.cam = webcam.webcam()
358 if self.cam.hasCamera():
359 self.camPage = wx.Panel(self.tabs)
360 sizer = wx.GridBagSizer(2, 2)
361 self.camPage.SetSizer(sizer)
363 self.timelapsEnable = wx.CheckBox(self.camPage, -1, _("Enable timelapse movie recording"))
364 self.timelapsSavePath = wx.TextCtrl(self.camPage, -1, os.path.expanduser('~/timelaps_' + datetime.datetime.now().strftime('%Y-%m-%d_%H-%M') + '.mpg'))
365 sizer.Add(self.timelapsEnable, pos=(0, 0), span=(1, 2), flag=wx.EXPAND)
366 sizer.Add(self.timelapsSavePath, pos=(1, 0), span=(1, 2), flag=wx.EXPAND)
368 pages = self.cam.propertyPages()
369 self.cam.buttons = [self.timelapsEnable, self.timelapsSavePath]
371 button = wx.Button(self.camPage, -1, page)
372 button.index = pages.index(page)
373 sizer.Add(button, pos=(2, pages.index(page)))
374 button.Bind(wx.EVT_BUTTON, self.OnPropertyPageButton)
375 self.cam.buttons.append(button)
377 self.campreviewEnable = wx.CheckBox(self.camPage, -1, _("Show preview"))
378 sizer.Add(self.campreviewEnable, pos=(3, 0), span=(1, 2), flag=wx.EXPAND)
380 self.camPreview = wx.Panel(self.camPage)
381 sizer.Add(self.camPreview, pos=(4, 0), span=(1, 2), flag=wx.EXPAND)
383 self.tabs.AddPage(self.camPage, _("Camera"))
384 self.camPreview.timer = wx.Timer(self)
385 self.Bind(wx.EVT_TIMER, self.OnCameraTimer, self.camPreview.timer)
386 self.camPreview.timer.Start(500)
387 self.camPreview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
391 def OnCameraTimer(self, e):
392 if not self.campreviewEnable.GetValue():
394 if self.machineCom is not None and self.machineCom.isPrinting():
396 self.cam.takeNewImage()
397 self.camPreview.Refresh()
399 def OnCameraEraseBackground(self, e):
402 dc = wx.ClientDC(self)
403 rect = self.GetUpdateRegion().GetBox()
404 dc.SetClippingRect(rect)
405 dc.SetBackground(wx.Brush(self.camPreview.GetBackgroundColour(), wx.SOLID))
406 if self.cam.getLastImage() is not None:
407 self.camPreview.SetMinSize((self.cam.getLastImage().GetWidth(), self.cam.getLastImage().GetHeight()))
409 dc.DrawBitmap(self.cam.getLastImage(), 0, 0)
413 def OnPropertyPageButton(self, e):
414 self.cam.openPropertyPage(e.GetEventObject().index)
416 def UpdateButtonStates(self):
417 self.connectButton.Enable(self.machineCom is None or self.machineCom.isClosedOrError())
418 #self.loadButton.Enable(self.machineCom == None or not (self.machineCom.isPrinting() or self.machineCom.isPaused()))
419 self.printButton.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
420 self.machineCom.isPrinting() or self.machineCom.isPaused()))
421 self.temperatureHeatUp.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
422 self.machineCom.isPrinting() or self.machineCom.isPaused()))
423 self.pauseButton.Enable(
424 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
425 if self.machineCom is not None and self.machineCom.isPaused():
426 self.pauseButton.SetLabel(_("Resume"))
428 self.pauseButton.SetLabel(_("Pause"))
429 self.cancelButton.Enable(
430 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
431 self.temperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
432 self.bedTemperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
433 self.directControlPanel.Enable(
434 self.machineCom is not None and self.machineCom.isOperational() and not self.machineCom.isPrinting())
435 self.machineLogButton.Show(self.machineCom is not None and self.machineCom.isClosedOrError())
436 if self.cam is not None:
437 for button in self.cam.buttons:
438 button.Enable(self.machineCom is None or not self.machineCom.isPrinting())
440 def UpdateProgress(self):
442 if self.gcode is None:
443 status += _("Loading gcode...\n")
445 status += _("Filament: %(amount).2fm %(weight).2fg\n") % {'amount': self.gcode.extrusionAmount / 1000, 'weight': self.gcode.calculateWeight() * 1000}
446 cost = self.gcode.calculateCost()
448 status += _("Filament cost: %s\n") % (cost)
449 #status += "Estimated print time: %02d:%02d\n" % (int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60))
450 if self.machineCom is None or not self.machineCom.isPrinting():
451 self.progress.SetValue(0)
452 if self.gcodeList is not None:
453 status += 'Line: -/%d\n' % (len(self.gcodeList))
455 printTime = self.machineCom.getPrintTime() / 60
456 printTimeLeft = self.machineCom.getPrintTimeRemainingEstimate()
457 status += 'Line: %d/%d %d%%\n' % (self.machineCom.getPrintPos(), len(self.gcodeList),
458 self.machineCom.getPrintPos() * 100 / len(self.gcodeList))
459 if self.currentZ > 0:
460 status += 'Height: %0.1f\n' % (self.currentZ)
461 status += 'Print time: %02d:%02d\n' % (int(printTime / 60), int(printTime % 60))
462 if printTimeLeft is None:
463 status += 'Print time left: Unknown\n'
465 status += 'Print time left: %02d:%02d\n' % (int(printTimeLeft / 60), int(printTimeLeft % 60))
466 self.progress.SetValue(self.machineCom.getPrintPos())
467 taskbar.setProgress(self, self.machineCom.getPrintPos(), len(self.gcodeList))
468 if self.machineCom is not None:
469 if self.machineCom.getTemp() > 0:
470 status += 'Temp: %s\n' % (' ,'.join(map(str, self.machineCom.getTemp())))
471 if self.machineCom.getBedTemp() > 0:
472 status += 'Bed Temp: %d\n' % (self.machineCom.getBedTemp())
473 self.bedTemperatureLabel.Show(True)
474 self.bedTemperatureSelect.Show(True)
475 self.temperaturePanel.Layout()
476 status += 'Machine state:%s\n' % (self.machineCom.getStateString())
478 self.statsText.SetLabel(status.strip())
480 def OnConnect(self, e):
481 if self.machineCom is not None:
482 self.machineCom.close()
483 self.machineCom = machineCom.MachineCom(callbackObject=self)
484 self.UpdateButtonStates()
485 taskbar.setBusy(self, True)
490 def OnPrint(self, e):
491 if self.machineCom is None or not self.machineCom.isOperational():
493 if self.gcodeList is None:
495 if self.machineCom.isPrinting():
498 if self.cam is not None and self.timelapsEnable.GetValue():
499 self.cam.startTimelapse(self.timelapsSavePath.GetValue())
500 self.machineCom.printGCode(self.gcodeList)
501 self.UpdateButtonStates()
503 def OnCancel(self, e):
504 self.pauseButton.SetLabel('Pause')
505 self.machineCom.cancelPrint()
506 self.machineCom.sendCommand("M84")
507 self.machineCom.sendCommand("M104 S0")
508 self.UpdateButtonStates()
510 def OnPause(self, e):
511 if self.machineCom.isPaused():
512 self.machineCom.setPause(False)
514 self.machineCom.setPause(True)
516 def OnMachineLog(self, e):
517 LogWindow('\n'.join(self.machineCom.getLog()))
519 def OnClose(self, e):
520 global printWindowHandle
521 printWindowHandle = None
522 if self.machineCom is not None:
523 self.machineCom.close()
526 def OnTempChange(self, e):
527 self.machineCom.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))
529 def OnBedTempChange(self, e):
530 self.machineCom.sendCommand("M140 S%d" % (self.bedTemperatureSelect.GetValue()))
532 def OnSpeedChange(self, e):
533 if self.machineCom is None:
535 self.machineCom.setFeedrateModifier('WALL-OUTER', self.outerWallSpeedSelect.GetValue() / 100.0)
536 self.machineCom.setFeedrateModifier('WALL-INNER', self.innerWallSpeedSelect.GetValue() / 100.0)
537 self.machineCom.setFeedrateModifier('FILL', self.fillSpeedSelect.GetValue() / 100.0)
538 self.machineCom.setFeedrateModifier('SUPPORT', self.supportSpeedSelect.GetValue() / 100.0)
540 def AddTermLog(self, line):
541 if len(self.termLog.GetValue()) > 10000:
542 self.termLog.SetValue(self.termLog.GetValue()[-10000:])
543 self.termLog.SetInsertionPointEnd()
544 self.termLog.AppendText(unicode(line, 'utf-8', 'replace'))
545 #l = self.termLog.GetLastPosition() # if needed (windows? mac?)
546 #self.termLog.ShowPosition(l)
548 def OnTermEnterLine(self, e):
549 line = self.termInput.GetValue()
552 self.termLog.AppendText('>%s\n' % (line))
553 self.machineCom.sendCommand(line)
554 self.termHistory.append(line)
555 self.termHistoryIdx = len(self.termHistory)
556 self.termInput.SetValue('')
558 def OnTermKey(self, e):
559 if len(self.termHistory) > 0:
560 if e.GetKeyCode() == wx.WXK_UP:
561 self.termHistoryIdx -= 1
562 if self.termHistoryIdx < 0:
563 self.termHistoryIdx = len(self.termHistory) - 1
564 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
565 if e.GetKeyCode() == wx.WXK_DOWN:
566 self.termHistoryIdx -= 1
567 if self.termHistoryIdx >= len(self.termHistory):
568 self.termHistoryIdx = 0
569 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
572 def OnPowerWarningChange(self, e):
573 type = self.powerManagement.get_providing_power_source_type()
574 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
575 self.powerWarningText.Hide()
578 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
579 self.powerWarningText.Show()
583 def LoadGCodeFile(self, filename):
584 if self.machineCom is not None and self.machineCom.isPrinting():
586 #Send an initial M110 to reset the line counter to zero.
587 prevLineType = lineType = 'CUSTOM'
589 for line in open(filename, 'r'):
590 if line.startswith(';TYPE:'):
591 lineType = line[6:].strip()
593 line = line[0:line.find(';')]
596 if prevLineType != lineType:
597 gcodeList.append((line, lineType, ))
599 gcodeList.append(line)
600 prevLineType = lineType
601 gcode = gcodeInterpreter.gcode()
602 gcode.loadList(gcodeList)
603 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
604 self.filename = filename
606 self.gcodeList = gcodeList
608 wx.CallAfter(self.progress.SetRange, len(gcodeList))
609 wx.CallAfter(self.UpdateButtonStates)
610 wx.CallAfter(self.UpdateProgress)
613 def sendLine(self, lineNr):
614 if lineNr >= len(self.gcodeList):
616 line = self.gcodeList[lineNr]
618 if ('M104' in line or 'M109' in line) and 'S' in line:
619 n = int(re.search('S([0-9]*)', line).group(1))
620 wx.CallAfter(self.temperatureSelect.SetValue, n)
621 if ('M140' in line or 'M190' in line) and 'S' in line:
622 n = int(re.search('S([0-9]*)', line).group(1))
623 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
625 print "Unexpected error:", sys.exc_info()
626 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
627 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
630 def mcLog(self, message):
634 def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
635 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
636 wx.CallAfter(self._mcTempUpdate, temp, bedTemp, targetTemp, bedTargetTemp)
638 def _mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
639 if self.temperatureSelect.GetValue() != targetTemp[0] and wx.Window.FindFocus() != self.temperatureSelect:
640 self.temperatureSelect.SetValue(targetTemp[0])
641 if self.bedTemperatureSelect.GetValue() != bedTargetTemp and wx.Window.FindFocus() != self.bedTemperatureSelect:
642 self.bedTemperatureSelect.SetValue(bedTargetTemp)
644 def mcStateChange(self, state):
645 if self.machineCom is not None:
646 if state == self.machineCom.STATE_OPERATIONAL and self.cam is not None:
647 self.cam.endTimelapse()
648 if state == self.machineCom.STATE_OPERATIONAL:
649 taskbar.setBusy(self, False)
650 if self.machineCom.isClosedOrError():
651 taskbar.setBusy(self, False)
652 if self.machineCom.isPaused():
653 taskbar.setPause(self, True)
654 if self.machineCom.isClosedOrError():
656 elif self.machineCom.isPrinting():
657 print 'STATE:PRINTING'
660 wx.CallAfter(self.UpdateButtonStates)
661 wx.CallAfter(self.UpdateProgress)
663 def mcMessage(self, message):
664 wx.CallAfter(self.AddTermLog, message)
666 def mcProgress(self, lineNr):
667 wx.CallAfter(self.UpdateProgress)
669 def mcZChange(self, newZ):
672 if self.cam is not None:
673 wx.CallAfter(self.cam.takeNewImage)
674 wx.CallAfter(self.camPreview.Refresh)
677 class temperatureGraph(wx.Panel):
678 def __init__(self, parent):
679 super(temperatureGraph, self).__init__(parent)
681 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
682 self.Bind(wx.EVT_SIZE, self.OnSize)
683 self.Bind(wx.EVT_PAINT, self.OnDraw)
685 self.lastDraw = time.time() - 1.0
687 self.backBuffer = None
688 self.addPoint([0]*16, [0]*16, 0, 0)
689 self.SetMinSize((320, 200))
691 def OnEraseBackground(self, e):
695 if self.backBuffer is None or self.GetSize() != self.backBuffer.GetSize():
696 self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
697 self.UpdateDrawing(True)
700 dc = wx.BufferedPaintDC(self, self.backBuffer)
702 def UpdateDrawing(self, force=False):
704 if not force and now - self.lastDraw < 1.0:
708 dc.SelectObject(self.backBuffer)
710 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
711 w, h = self.GetSizeTuple()
712 bgLinePen = wx.Pen('#A0A0A0')
713 tempPen = wx.Pen('#FF4040')
714 tempSPPen = wx.Pen('#FFA0A0')
715 tempPenBG = wx.Pen('#FFD0D0')
716 bedTempPen = wx.Pen('#4040FF')
717 bedTempSPPen = wx.Pen('#A0A0FF')
718 bedTempPenBG = wx.Pen('#D0D0FF')
720 #Draw the background up to the current temperatures.
722 t0 = [0] * len(self.points[0][0])
726 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
727 x1 = int(w - (now - t))
728 for x in xrange(x0, x1 + 1):
729 for n in xrange(0, len(temp)):
730 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
732 dc.DrawLine(x, h, x, h - (t * h / 300))
733 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
734 dc.SetPen(bedTempPenBG)
735 dc.DrawLine(x, h, x, h - (bt * h / 300))
743 for x in xrange(w, 0, -30):
745 dc.DrawLine(x, 0, x, h)
747 for y in xrange(h - 1, 0, -h * 50 / 300):
749 dc.DrawLine(0, y, w, y)
750 dc.DrawText(str(tmpNr), 0, y - dc.GetFont().GetPixelSize().GetHeight())
752 dc.DrawLine(0, 0, w, 0)
753 dc.DrawLine(0, 0, 0, h)
757 t0 = [0] * len(self.points[0][0])
759 tSP0 = [0] * len(self.points[0][0])
761 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
762 x1 = int(w - (now - t))
763 for x in xrange(x0, x1 + 1):
764 for n in xrange(0, len(temp)):
765 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
766 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP[n] - tSP0[n]) + tSP0[n]
768 dc.DrawPoint(x, h - (tSP * h / 300))
770 dc.DrawPoint(x, h - (t * h / 300))
771 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
772 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
773 dc.SetPen(bedTempSPPen)
774 dc.DrawPoint(x, h - (btSP * h / 300))
775 dc.SetPen(bedTempPen)
776 dc.DrawPoint(x, h - (bt * h / 300))
784 self.Refresh(eraseBackground=False)
787 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
790 def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
793 if bedTempSP is None:
795 self.points.append((temp[:], tempSP[:], bedTemp, bedTempSP, time.time()))
796 wx.CallAfter(self.UpdateDrawing)
799 class LogWindow(wx.Frame):
800 def __init__(self, logText):
801 super(LogWindow, self).__init__(None, title="Machine log")
802 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
803 self.SetSize((500, 400))