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 sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
100 resources.setupLocalization(profile.getPreference('language'))
101 printWindowHandle = printWindow()
102 printWindowHandle.Show(True)
103 printWindowHandle.Raise()
104 printWindowHandle.OnConnect(None)
105 t = threading.Thread(target=printWindowHandle.LoadGCodeFile, args=(filename,))
110 class PrintCommandButton(buttons.GenBitmapButton):
111 def __init__(self, parent, commandList, bitmapFilename, size=(20, 20)):
112 self.bitmap = wx.Bitmap(resources.getPathForImage(bitmapFilename))
113 super(PrintCommandButton, self).__init__(parent.directControlPanel, -1, self.bitmap, size=size)
115 self.commandList = commandList
118 self.SetBezelWidth(1)
119 self.SetUseFocusIndicator(False)
121 self.Bind(wx.EVT_BUTTON, self.OnClick)
123 def OnClick(self, e):
124 if self.parent.machineCom is None or self.parent.machineCom.isPrinting():
126 for cmd in self.commandList:
127 self.parent.machineCom.sendCommand(cmd)
131 class printWindow(wx.Frame):
132 "Main user interface window"
135 super(printWindow, self).__init__(None, -1, title=_("Printing"))
137 self.machineCom = None
139 self.gcodeList = None
143 self.bufferLineCount = 4
145 self.feedrateRatioOuterWall = 1.0
146 self.feedrateRatioInnerWall = 1.0
147 self.feedrateRatioFill = 1.0
148 self.feedrateRatioSupport = 1.0
150 self.termHistory = []
151 self.termHistoryIdx = 0
155 self.SetSizer(wx.BoxSizer())
156 self.panel = wx.Panel(self)
157 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
158 self.sizer = wx.GridBagSizer(2, 2)
159 self.panel.SetSizer(self.sizer)
161 sb = wx.StaticBox(self.panel, label=_("Statistics"))
162 boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)
164 self.powerWarningText = wx.StaticText(parent=self.panel,
166 label=_("Your computer is running on battery power.\nConnect your computer to AC power or your print might not finish."),
167 style=wx.ALIGN_CENTER)
168 self.powerWarningText.SetBackgroundColour('red')
169 self.powerWarningText.SetForegroundColour('white')
170 boxsizer.AddF(self.powerWarningText, flags=wx.SizerFlags().Expand().Border(wx.BOTTOM, 10))
171 self.powerManagement = power.PowerManagement()
172 self.powerWarningTimer = wx.Timer(self)
173 self.Bind(wx.EVT_TIMER, self.OnPowerWarningChange, self.powerWarningTimer)
174 self.OnPowerWarningChange(None)
175 self.powerWarningTimer.Start(10000)
177 self.statsText = wx.StaticText(self.panel, -1, _("Filament: ####.##m #.##g\nEstimated print time: #####:##\nMachine state:\nDetecting baudrateXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"))
178 boxsizer.Add(self.statsText, flag=wx.LEFT, border=5)
180 self.sizer.Add(boxsizer, pos=(0, 0), span=(7, 1), flag=wx.EXPAND)
182 self.connectButton = wx.Button(self.panel, -1, _("Connect"))
183 #self.loadButton = wx.Button(self.panel, -1, 'Load')
184 self.printButton = wx.Button(self.panel, -1, _("Print"))
185 self.pauseButton = wx.Button(self.panel, -1, _("Pause"))
186 self.cancelButton = wx.Button(self.panel, -1, _("Cancel print"))
187 self.machineLogButton = wx.Button(self.panel, -1, _("Error log"))
188 self.progress = wx.Gauge(self.panel, -1)
190 self.sizer.Add(self.connectButton, pos=(1, 1), flag=wx.EXPAND)
191 #self.sizer.Add(self.loadButton, pos=(1,1), flag=wx.EXPAND)
192 self.sizer.Add(self.printButton, pos=(2, 1), flag=wx.EXPAND)
193 self.sizer.Add(self.pauseButton, pos=(3, 1), flag=wx.EXPAND)
194 self.sizer.Add(self.cancelButton, pos=(4, 1), flag=wx.EXPAND)
195 self.sizer.Add(self.machineLogButton, pos=(5, 1), flag=wx.EXPAND)
196 self.sizer.Add(self.progress, pos=(7, 0), span=(1, 7), flag=wx.EXPAND)
198 nb = wx.Notebook(self.panel)
200 self.sizer.Add(nb, pos=(0, 2), span=(7, 4), flag=wx.EXPAND)
202 self.temperaturePanel = wx.Panel(nb)
203 sizer = wx.GridBagSizer(2, 2)
204 self.temperaturePanel.SetSizer(sizer)
206 self.temperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
207 self.temperatureSelect.SetRange(0, 400)
208 self.temperatureHeatUp = wx.Button(self.temperaturePanel, -1, str(int(profile.getProfileSettingFloat('print_temperature'))) + 'C')
209 self.bedTemperatureLabel = wx.StaticText(self.temperaturePanel, -1, _("BedTemp:"))
210 self.bedTemperatureSelect = wx.SpinCtrl(self.temperaturePanel, -1, '0', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
211 self.bedTemperatureSelect.SetRange(0, 400)
212 self.bedTemperatureLabel.Show(False)
213 self.bedTemperatureSelect.Show(False)
215 self.temperatureGraph = temperatureGraph(self.temperaturePanel)
217 sizer.Add(wx.StaticText(self.temperaturePanel, -1, _("Temp:")), pos=(0, 0))
218 sizer.Add(self.temperatureSelect, pos=(0, 1))
219 sizer.Add(self.temperatureHeatUp, pos=(0, 2))
220 sizer.Add(self.bedTemperatureLabel, pos=(1, 0))
221 sizer.Add(self.bedTemperatureSelect, pos=(1, 1))
222 sizer.Add(self.temperatureGraph, pos=(2, 0), span=(1, 3), flag=wx.EXPAND)
223 sizer.AddGrowableRow(2)
224 sizer.AddGrowableCol(2)
226 nb.AddPage(self.temperaturePanel, 'Temp')
228 self.directControlPanel = wx.Panel(nb)
230 sizer = wx.GridBagSizer(2, 2)
231 self.directControlPanel.SetSizer(sizer)
232 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y100 F6000', 'G90'], 'print-move-y100.png'), pos=(0, 3))
233 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y10 F6000', 'G90'], 'print-move-y10.png'), pos=(1, 3))
234 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y1 F6000', 'G90'], 'print-move-y1.png'), pos=(2, 3))
236 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-1 F6000', 'G90'], 'print-move-y-1.png'), pos=(4, 3))
237 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-10 F6000', 'G90'], 'print-move-y-10.png'), pos=(5, 3))
238 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Y-100 F6000', 'G90'], 'print-move-y-100.png'), pos=(6, 3))
240 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-100 F6000', 'G90'], 'print-move-x-100.png'), pos=(3, 0))
241 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-10 F6000', 'G90'], 'print-move-x-10.png'), pos=(3, 1))
242 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X-1 F6000', 'G90'], 'print-move-x-1.png'), pos=(3, 2))
244 sizer.Add(PrintCommandButton(self, ['G28 X0 Y0'], 'print-move-home.png'), pos=(3, 3))
246 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X1 F6000', 'G90'], 'print-move-x1.png'), pos=(3, 4))
247 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X10 F6000', 'G90'], 'print-move-x10.png'), pos=(3, 5))
248 sizer.Add(PrintCommandButton(self, ['G91', 'G1 X100 F6000', 'G90'], 'print-move-x100.png'), pos=(3, 6))
250 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z10 F200', 'G90'], 'print-move-z10.png'), pos=(0, 8))
251 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z1 F200', 'G90'], 'print-move-z1.png'), pos=(1, 8))
252 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z0.1 F200', 'G90'], 'print-move-z0.1.png'), pos=(2, 8))
254 sizer.Add(PrintCommandButton(self, ['G28 Z0'], 'print-move-home.png'), pos=(3, 8))
256 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-0.1 F200', 'G90'], 'print-move-z-0.1.png'), pos=(4, 8))
257 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-1 F200', 'G90'], 'print-move-z-1.png'), pos=(5, 8))
258 sizer.Add(PrintCommandButton(self, ['G91', 'G1 Z-10 F200', 'G90'], 'print-move-z-10.png'), pos=(6, 8))
260 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E2 F120'], 'extrude.png', size=(60, 20)), pos=(1, 10),
261 span=(1, 3), flag=wx.EXPAND)
262 sizer.Add(PrintCommandButton(self, ['G92 E0', 'G1 E-2 F120'], 'retract.png', size=(60, 20)), pos=(2, 10),
263 span=(1, 3), flag=wx.EXPAND)
265 nb.AddPage(self.directControlPanel, _("Jog"))
267 self.speedPanel = wx.Panel(nb)
268 sizer = wx.GridBagSizer(2, 2)
269 self.speedPanel.SetSizer(sizer)
271 self.outerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
272 self.outerWallSpeedSelect.SetRange(5, 1000)
273 self.innerWallSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
274 self.innerWallSpeedSelect.SetRange(5, 1000)
275 self.fillSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
276 self.fillSpeedSelect.SetRange(5, 1000)
277 self.supportSpeedSelect = wx.SpinCtrl(self.speedPanel, -1, '100', size=(21 * 3, 21), style=wx.SP_ARROW_KEYS)
278 self.supportSpeedSelect.SetRange(5, 1000)
280 sizer.Add(wx.StaticText(self.speedPanel, -1, _("Outer wall:")), pos=(0, 0))
281 sizer.Add(self.outerWallSpeedSelect, pos=(0, 1))
282 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(0, 2))
283 sizer.Add(wx.StaticText(self.speedPanel, -1, _("Inner wall:")), pos=(1, 0))
284 sizer.Add(self.innerWallSpeedSelect, pos=(1, 1))
285 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(1, 2))
286 sizer.Add(wx.StaticText(self.speedPanel, -1, _("Fill:")), pos=(2, 0))
287 sizer.Add(self.fillSpeedSelect, pos=(2, 1))
288 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(2, 2))
289 sizer.Add(wx.StaticText(self.speedPanel, -1, _("Support:")), pos=(3, 0))
290 sizer.Add(self.supportSpeedSelect, pos=(3, 1))
291 sizer.Add(wx.StaticText(self.speedPanel, -1, "%"), pos=(3, 2))
293 nb.AddPage(self.speedPanel, _("Speed"))
295 self.termPanel = wx.Panel(nb)
296 sizer = wx.GridBagSizer(2, 2)
297 self.termPanel.SetSizer(sizer)
299 f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
300 self.termLog = wx.TextCtrl(self.termPanel, style=wx.TE_MULTILINE | wx.TE_DONTWRAP)
301 self.termLog.SetFont(f)
302 self.termLog.SetEditable(0)
303 self.termInput = wx.TextCtrl(self.termPanel, style=wx.TE_PROCESS_ENTER)
304 self.termInput.SetFont(f)
306 sizer.Add(self.termLog, pos=(0, 0), flag=wx.EXPAND)
307 sizer.Add(self.termInput, pos=(1, 0), flag=wx.EXPAND)
308 sizer.AddGrowableCol(0)
309 sizer.AddGrowableRow(0)
311 nb.AddPage(self.termPanel, _("Term"))
313 self.sizer.AddGrowableRow(6)
314 self.sizer.AddGrowableCol(3)
316 self.Bind(wx.EVT_CLOSE, self.OnClose)
317 self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect)
318 #self.loadButton.Bind(wx.EVT_BUTTON, self.OnLoad)
319 self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint)
320 self.pauseButton.Bind(wx.EVT_BUTTON, self.OnPause)
321 self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)
322 self.machineLogButton.Bind(wx.EVT_BUTTON, self.OnMachineLog)
324 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)
325 self.Bind(wx.EVT_SPINCTRL, self.OnTempChange, self.temperatureSelect)
326 self.Bind(wx.EVT_SPINCTRL, self.OnBedTempChange, self.bedTemperatureSelect)
328 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.outerWallSpeedSelect)
329 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.innerWallSpeedSelect)
330 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.fillSpeedSelect)
331 self.Bind(wx.EVT_SPINCTRL, self.OnSpeedChange, self.supportSpeedSelect)
332 self.Bind(wx.EVT_TEXT_ENTER, self.OnTermEnterLine, self.termInput)
333 self.termInput.Bind(wx.EVT_CHAR, self.OnTermKey)
339 self.statsText.SetMinSize(self.statsText.GetSize())
341 self.UpdateButtonStates()
343 #self.UpdateProgress()
344 self._thread = threading.Thread(target=self._stdinMonitor)
345 self._thread.daemon = True
348 if webcam.hasWebcamSupport():
349 #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.
350 wx.CallAfter(self._webcamCheck)
352 def _stdinMonitor(self):
354 line = sys.stdin.readline().rstrip()
355 if line.startswith('LOAD:'):
356 if not self.LoadGCodeFile(line[5:]):
359 def _webcamCheck(self):
360 self.cam = webcam.webcam()
361 if self.cam.hasCamera():
362 self.camPage = wx.Panel(self.tabs)
363 sizer = wx.GridBagSizer(2, 2)
364 self.camPage.SetSizer(sizer)
366 self.timelapsEnable = wx.CheckBox(self.camPage, -1, _("Enable timelapse movie recording"))
367 self.timelapsSavePath = wx.TextCtrl(self.camPage, -1, os.path.expanduser('~/timelaps_' + datetime.datetime.now().strftime('%Y-%m-%d_%H-%M') + '.mpg'))
368 sizer.Add(self.timelapsEnable, pos=(0, 0), span=(1, 2), flag=wx.EXPAND)
369 sizer.Add(self.timelapsSavePath, pos=(1, 0), span=(1, 2), flag=wx.EXPAND)
371 pages = self.cam.propertyPages()
372 self.cam.buttons = [self.timelapsEnable, self.timelapsSavePath]
374 button = wx.Button(self.camPage, -1, page)
375 button.index = pages.index(page)
376 sizer.Add(button, pos=(2, pages.index(page)))
377 button.Bind(wx.EVT_BUTTON, self.OnPropertyPageButton)
378 self.cam.buttons.append(button)
380 self.campreviewEnable = wx.CheckBox(self.camPage, -1, _("Show preview"))
381 sizer.Add(self.campreviewEnable, pos=(3, 0), span=(1, 2), flag=wx.EXPAND)
383 self.camPreview = wx.Panel(self.camPage)
384 sizer.Add(self.camPreview, pos=(4, 0), span=(1, 2), flag=wx.EXPAND)
386 self.tabs.AddPage(self.camPage, _("Camera"))
387 self.camPreview.timer = wx.Timer(self)
388 self.Bind(wx.EVT_TIMER, self.OnCameraTimer, self.camPreview.timer)
389 self.camPreview.timer.Start(500)
390 self.camPreview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
394 def OnCameraTimer(self, e):
395 if not self.campreviewEnable.GetValue():
397 if self.machineCom is not None and self.machineCom.isPrinting():
399 self.cam.takeNewImage()
400 self.camPreview.Refresh()
402 def OnCameraEraseBackground(self, e):
405 dc = wx.ClientDC(self)
406 rect = self.GetUpdateRegion().GetBox()
407 dc.SetClippingRect(rect)
408 dc.SetBackground(wx.Brush(self.camPreview.GetBackgroundColour(), wx.SOLID))
409 if self.cam.getLastImage() is not None:
410 self.camPreview.SetMinSize((self.cam.getLastImage().GetWidth(), self.cam.getLastImage().GetHeight()))
412 dc.DrawBitmap(self.cam.getLastImage(), 0, 0)
416 def OnPropertyPageButton(self, e):
417 self.cam.openPropertyPage(e.GetEventObject().index)
419 def UpdateButtonStates(self):
420 self.connectButton.Enable(self.machineCom is None or self.machineCom.isClosedOrError())
421 #self.loadButton.Enable(self.machineCom == None or not (self.machineCom.isPrinting() or self.machineCom.isPaused()))
422 self.printButton.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
423 self.machineCom.isPrinting() or self.machineCom.isPaused()))
424 self.temperatureHeatUp.Enable(self.machineCom is not None and self.machineCom.isOperational() and not (
425 self.machineCom.isPrinting() or self.machineCom.isPaused()))
426 self.pauseButton.Enable(
427 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
428 if self.machineCom is not None and self.machineCom.isPaused():
429 self.pauseButton.SetLabel(_("Resume"))
431 self.pauseButton.SetLabel(_("Pause"))
432 self.cancelButton.Enable(
433 self.machineCom is not None and (self.machineCom.isPrinting() or self.machineCom.isPaused()))
434 self.temperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
435 self.bedTemperatureSelect.Enable(self.machineCom is not None and self.machineCom.isOperational())
436 self.directControlPanel.Enable(
437 self.machineCom is not None and self.machineCom.isOperational() and not self.machineCom.isPrinting())
438 self.machineLogButton.Show(self.machineCom is not None and self.machineCom.isClosedOrError())
439 if self.cam is not None:
440 for button in self.cam.buttons:
441 button.Enable(self.machineCom is None or not self.machineCom.isPrinting())
443 def UpdateProgress(self):
445 if self.gcode is None:
446 status += _("Loading gcode...\n")
448 status += _("Filament: %(amount).2fm %(weight).2fg\n") % {'amount': self.gcode.extrusionAmount / 1000, 'weight': self.gcode.calculateWeight() * 1000}
449 cost = self.gcode.calculateCost()
451 status += _("Filament cost: %s\n") % (cost)
452 #status += "Estimated print time: %02d:%02d\n" % (int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60))
453 if self.machineCom is None or not self.machineCom.isPrinting():
454 self.progress.SetValue(0)
455 if self.gcodeList is not None:
456 status += 'Line: -/%d\n' % (len(self.gcodeList))
458 printTime = self.machineCom.getPrintTime() / 60
459 printTimeLeft = self.machineCom.getPrintTimeRemainingEstimate()
460 status += 'Line: %d/%d %d%%\n' % (self.machineCom.getPrintPos(), len(self.gcodeList),
461 self.machineCom.getPrintPos() * 100 / len(self.gcodeList))
462 if self.currentZ > 0:
463 status += 'Height: %0.1f\n' % (self.currentZ)
464 status += 'Print time: %02d:%02d\n' % (int(printTime / 60), int(printTime % 60))
465 if printTimeLeft is None:
466 status += 'Print time left: Unknown\n'
468 status += 'Print time left: %02d:%02d\n' % (int(printTimeLeft / 60), int(printTimeLeft % 60))
469 self.progress.SetValue(self.machineCom.getPrintPos())
470 taskbar.setProgress(self, self.machineCom.getPrintPos(), len(self.gcodeList))
471 if self.machineCom is not None:
472 if self.machineCom.getTemp() > 0:
473 status += 'Temp: %s\n' % (' ,'.join(map(str, self.machineCom.getTemp())))
474 if self.machineCom.getBedTemp() > 0:
475 status += 'Bed Temp: %d\n' % (self.machineCom.getBedTemp())
476 self.bedTemperatureLabel.Show(True)
477 self.bedTemperatureSelect.Show(True)
478 self.temperaturePanel.Layout()
479 status += 'Machine state:%s\n' % (self.machineCom.getStateString())
481 self.statsText.SetLabel(status.strip())
483 def OnConnect(self, e):
484 if self.machineCom is not None:
485 self.machineCom.close()
486 self.machineCom = machineCom.MachineCom(callbackObject=self)
487 self.UpdateButtonStates()
488 taskbar.setBusy(self, True)
493 def OnPrint(self, e):
494 if self.machineCom is None or not self.machineCom.isOperational():
496 if self.gcodeList is None:
498 if self.machineCom.isPrinting():
501 if self.cam is not None and self.timelapsEnable.GetValue():
502 self.cam.startTimelapse(self.timelapsSavePath.GetValue())
503 self.machineCom.printGCode(self.gcodeList)
504 self.UpdateButtonStates()
506 def OnCancel(self, e):
507 self.pauseButton.SetLabel('Pause')
508 self.machineCom.cancelPrint()
509 self.machineCom.sendCommand("M84")
510 self.machineCom.sendCommand("M104 S0")
511 self.UpdateButtonStates()
513 def OnPause(self, e):
514 if self.machineCom.isPaused():
515 self.machineCom.setPause(False)
517 self.machineCom.setPause(True)
519 def OnMachineLog(self, e):
520 LogWindow('\n'.join(self.machineCom.getLog()))
522 def OnClose(self, e):
523 global printWindowHandle
524 printWindowHandle = None
525 if self.machineCom is not None:
526 self.machineCom.close()
529 def OnTempChange(self, e):
530 self.machineCom.sendCommand("M104 S%d" % (self.temperatureSelect.GetValue()))
532 def OnBedTempChange(self, e):
533 self.machineCom.sendCommand("M140 S%d" % (self.bedTemperatureSelect.GetValue()))
535 def OnSpeedChange(self, e):
536 if self.machineCom is None:
538 self.machineCom.setFeedrateModifier('WALL-OUTER', self.outerWallSpeedSelect.GetValue() / 100.0)
539 self.machineCom.setFeedrateModifier('WALL-INNER', self.innerWallSpeedSelect.GetValue() / 100.0)
540 self.machineCom.setFeedrateModifier('FILL', self.fillSpeedSelect.GetValue() / 100.0)
541 self.machineCom.setFeedrateModifier('SUPPORT', self.supportSpeedSelect.GetValue() / 100.0)
543 def AddTermLog(self, line):
544 if len(self.termLog.GetValue()) > 10000:
545 self.termLog.SetValue(self.termLog.GetValue()[-10000:])
546 self.termLog.SetInsertionPointEnd()
547 self.termLog.AppendText(unicode(line, 'utf-8', 'replace'))
548 #l = self.termLog.GetLastPosition() # if needed (windows? mac?)
549 #self.termLog.ShowPosition(l)
551 def OnTermEnterLine(self, e):
552 line = self.termInput.GetValue()
555 self.termLog.AppendText('>%s\n' % (line))
556 self.machineCom.sendCommand(line)
557 self.termHistory.append(line)
558 self.termHistoryIdx = len(self.termHistory)
559 self.termInput.SetValue('')
561 def OnTermKey(self, e):
562 if len(self.termHistory) > 0:
563 if e.GetKeyCode() == wx.WXK_UP:
564 self.termHistoryIdx -= 1
565 if self.termHistoryIdx < 0:
566 self.termHistoryIdx = len(self.termHistory) - 1
567 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
568 if e.GetKeyCode() == wx.WXK_DOWN:
569 self.termHistoryIdx -= 1
570 if self.termHistoryIdx >= len(self.termHistory):
571 self.termHistoryIdx = 0
572 self.termInput.SetValue(self.termHistory[self.termHistoryIdx])
575 def OnPowerWarningChange(self, e):
576 type = self.powerManagement.get_providing_power_source_type()
577 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
578 self.powerWarningText.Hide()
581 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
582 self.powerWarningText.Show()
586 def LoadGCodeFile(self, filename):
587 if self.machineCom is not None and self.machineCom.isPrinting():
589 #Send an initial M110 to reset the line counter to zero.
590 prevLineType = lineType = 'CUSTOM'
592 for line in open(filename, 'r'):
593 if line.startswith(';TYPE:'):
594 lineType = line[6:].strip()
596 line = line[0:line.find(';')]
599 if prevLineType != lineType:
600 gcodeList.append((line, lineType, ))
602 gcodeList.append(line)
603 prevLineType = lineType
604 gcode = gcodeInterpreter.gcode()
605 gcode.loadList(gcodeList)
606 #print "Loaded: %s (%d)" % (filename, len(gcodeList))
607 self.filename = filename
609 self.gcodeList = gcodeList
611 wx.CallAfter(self.progress.SetRange, len(gcodeList))
612 wx.CallAfter(self.UpdateButtonStates)
613 wx.CallAfter(self.UpdateProgress)
616 def sendLine(self, lineNr):
617 if lineNr >= len(self.gcodeList):
619 line = self.gcodeList[lineNr]
621 if ('M104' in line or 'M109' in line) and 'S' in line:
622 n = int(re.search('S([0-9]*)', line).group(1))
623 wx.CallAfter(self.temperatureSelect.SetValue, n)
624 if ('M140' in line or 'M190' in line) and 'S' in line:
625 n = int(re.search('S([0-9]*)', line).group(1))
626 wx.CallAfter(self.bedTemperatureSelect.SetValue, n)
628 print "Unexpected error:", sys.exc_info()
629 checksum = reduce(lambda x, y: x ^ y, map(ord, "N%d%s" % (lineNr, line)))
630 self.machineCom.sendCommand("N%d%s*%d" % (lineNr, line, checksum))
633 def mcLog(self, message):
637 def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
638 self.temperatureGraph.addPoint(temp, targetTemp, bedTemp, bedTargetTemp)
639 wx.CallAfter(self._mcTempUpdate, temp, bedTemp, targetTemp, bedTargetTemp)
641 def _mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
642 if self.temperatureSelect.GetValue() != targetTemp[0] and wx.Window.FindFocus() != self.temperatureSelect:
643 self.temperatureSelect.SetValue(targetTemp[0])
644 if self.bedTemperatureSelect.GetValue() != bedTargetTemp and wx.Window.FindFocus() != self.bedTemperatureSelect:
645 self.bedTemperatureSelect.SetValue(bedTargetTemp)
647 def mcStateChange(self, state):
648 if self.machineCom is not None:
649 if state == self.machineCom.STATE_OPERATIONAL and self.cam is not None:
650 self.cam.endTimelapse()
651 if state == self.machineCom.STATE_OPERATIONAL:
652 taskbar.setBusy(self, False)
653 if self.machineCom.isClosedOrError():
654 taskbar.setBusy(self, False)
655 if self.machineCom.isPaused():
656 taskbar.setPause(self, True)
657 if self.machineCom.isClosedOrError():
659 elif self.machineCom.isPrinting():
660 print 'STATE:PRINTING'
663 wx.CallAfter(self.UpdateButtonStates)
664 wx.CallAfter(self.UpdateProgress)
666 def mcMessage(self, message):
667 wx.CallAfter(self.AddTermLog, message)
669 def mcProgress(self, lineNr):
670 wx.CallAfter(self.UpdateProgress)
672 def mcZChange(self, newZ):
675 if self.cam is not None:
676 wx.CallAfter(self.cam.takeNewImage)
677 wx.CallAfter(self.camPreview.Refresh)
680 class temperatureGraph(wx.Panel):
681 def __init__(self, parent):
682 super(temperatureGraph, self).__init__(parent)
684 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
685 self.Bind(wx.EVT_SIZE, self.OnSize)
686 self.Bind(wx.EVT_PAINT, self.OnDraw)
688 self.lastDraw = time.time() - 1.0
690 self.backBuffer = None
691 self.addPoint([0]*16, [0]*16, 0, 0)
692 self.SetMinSize((320, 200))
694 def OnEraseBackground(self, e):
698 if self.backBuffer is None or self.GetSize() != self.backBuffer.GetSize():
699 self.backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
700 self.UpdateDrawing(True)
703 dc = wx.BufferedPaintDC(self, self.backBuffer)
705 def UpdateDrawing(self, force=False):
707 if not force and now - self.lastDraw < 1.0:
711 dc.SelectObject(self.backBuffer)
713 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
714 w, h = self.GetSizeTuple()
715 bgLinePen = wx.Pen('#A0A0A0')
716 tempPen = wx.Pen('#FF4040')
717 tempSPPen = wx.Pen('#FFA0A0')
718 tempPenBG = wx.Pen('#FFD0D0')
719 bedTempPen = wx.Pen('#4040FF')
720 bedTempSPPen = wx.Pen('#A0A0FF')
721 bedTempPenBG = wx.Pen('#D0D0FF')
723 #Draw the background up to the current temperatures.
725 t0 = [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]
735 dc.DrawLine(x, h, x, h - (t * h / 300))
736 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
737 dc.SetPen(bedTempPenBG)
738 dc.DrawLine(x, h, x, h - (bt * h / 300))
746 for x in xrange(w, 0, -30):
748 dc.DrawLine(x, 0, x, h)
750 for y in xrange(h - 1, 0, -h * 50 / 300):
752 dc.DrawLine(0, y, w, y)
753 dc.DrawText(str(tmpNr), 0, y - dc.GetFont().GetPixelSize().GetHeight())
755 dc.DrawLine(0, 0, w, 0)
756 dc.DrawLine(0, 0, 0, h)
760 t0 = [0] * len(self.points[0][0])
762 tSP0 = [0] * len(self.points[0][0])
764 for temp, tempSP, bedTemp, bedTempSP, t in self.points:
765 x1 = int(w - (now - t))
766 for x in xrange(x0, x1 + 1):
767 for n in xrange(0, len(temp)):
768 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
769 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP[n] - tSP0[n]) + tSP0[n]
771 dc.DrawPoint(x, h - (tSP * h / 300))
773 dc.DrawPoint(x, h - (t * h / 300))
774 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
775 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
776 dc.SetPen(bedTempSPPen)
777 dc.DrawPoint(x, h - (btSP * h / 300))
778 dc.SetPen(bedTempPen)
779 dc.DrawPoint(x, h - (bt * h / 300))
787 self.Refresh(eraseBackground=False)
790 if len(self.points) > 0 and (time.time() - self.points[0][4]) > w + 20:
793 def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
796 if bedTempSP is None:
798 self.points.append((temp[:], tempSP[:], bedTemp, bedTempSP, time.time()))
799 wx.CallAfter(self.UpdateDrawing)
802 class LogWindow(wx.Frame):
803 def __init__(self, logText):
804 super(LogWindow, self).__init__(None, title="Machine log")
805 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
806 self.SetSize((500, 400))