1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
4 from wx.lib.intctrl import IntCtrl
11 from Cura.util import resources
13 #TODO: This does not belong here!
14 if sys.platform.startswith('win'):
15 def preventComputerFromSleeping(frame, prevent):
17 Function used to prevent the computer from going into sleep mode.
18 :param prevent: True = Prevent the system from going to sleep from this point on.
19 :param prevent: False = No longer prevent the system from going to sleep.
21 ES_CONTINUOUS = 0x80000000
22 ES_SYSTEM_REQUIRED = 0x00000001
23 ES_AWAYMODE_REQUIRED = 0x00000040
24 #SetThreadExecutionState returns 0 when failed, which is ignored. The function should be supported from windows XP and up.
26 # For Vista and up we use ES_AWAYMODE_REQUIRED to prevent a print from failing if the PC does go to sleep
27 # As it's not supported on XP, we catch the error and fallback to using ES_SYSTEM_REQUIRED only
28 if ctypes.windll.kernel32.SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_AWAYMODE_REQUIRED) == 0:
29 ctypes.windll.kernel32.SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED)
31 ctypes.windll.kernel32.SetThreadExecutionState(ES_CONTINUOUS)
33 elif sys.platform.startswith('darwin'):
35 bundle = objc.initFrameworkWrapper("IOKit",
36 frameworkIdentifier="com.apple.iokit",
37 frameworkPath=objc.pathForFramework("/System/Library/Frameworks/IOKit.framework"),
39 objc.loadBundleFunctions(bundle, globals(), [("IOPMAssertionCreateWithName", b"i@I@o^I")])
40 objc.loadBundleFunctions(bundle, globals(), [("IOPMAssertionRelease", b"iI")])
41 def preventComputerFromSleeping(frame, prevent):
43 success, preventComputerFromSleeping.assertionID = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, "Cura is printing", None)
44 if success != kIOReturnSuccess:
45 preventComputerFromSleeping.assertionID = None
47 if hasattr(preventComputerFromSleeping, "assertionID"):
48 if preventComputerFromSleeping.assertionID is not None:
49 IOPMAssertionRelease(preventComputerFromSleeping.assertionID)
50 preventComputerFromSleeping.assertionID = None
52 def preventComputerFromSleeping(frame, prevent):
53 if os.path.isfile("/usr/bin/xdg-screensaver"):
55 cmd = ['xdg-screensaver', 'suspend' if prevent else 'resume', str(frame.GetHandle())]
60 class printWindowPlugin(wx.Frame):
61 def __init__(self, parent, printerConnection, filename):
62 super(printWindowPlugin, self).__init__(parent, -1, style=wx.CLOSE_BOX|wx.CLIP_CHILDREN|wx.CAPTION|wx.SYSTEM_MENU|wx.FRAME_FLOAT_ON_PARENT|wx.MINIMIZE_BOX, title=_("Printing on %s") % (printerConnection.getName()))
63 self._printerConnection = printerConnection
64 self._basePath = os.path.dirname(filename)
65 self._backgroundImage = None
66 self._colorCommandMap = {}
69 self._termInput = None
70 self._termHistory = []
71 self._termHistoryIdx = 0
72 self._progressBar = None
73 self._tempGraph = None
75 self._lastUpdateTime = time.time()
76 self._isPrinting = False
79 'setImage': self.script_setImage,
80 'addColorCommand': self.script_addColorCommand,
81 'addTerminal': self.script_addTerminal,
82 'addTemperatureGraph': self.script_addTemperatureGraph,
83 'addProgressbar': self.script_addProgressbar,
84 'addButton': self.script_addButton,
85 'addSpinner': self.script_addSpinner,
86 'addTextButton': self.script_addTextButton,
88 'sendGCode': self.script_sendGCode,
89 'sendMovementGCode': self.script_sendMovementGCode,
90 'connect': self.script_connect,
91 'startPrint': self.script_startPrint,
92 'pausePrint': self.script_pausePrint,
93 'cancelPrint': self.script_cancelPrint,
94 'showErrorLog': self.script_showErrorLog,
96 execfile(filename, variables, variables)
98 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
99 self.Bind(wx.EVT_PAINT, self.OnDraw)
100 self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftClick)
101 self.Bind(wx.EVT_CLOSE, self.OnClose)
103 self._updateButtonStates()
105 self._printerConnection.addCallback(self._doPrinterConnectionUpdate)
107 if self._printerConnection.hasActiveConnection() and not self._printerConnection.isActiveConnectionOpen():
108 self._printerConnection.openActiveConnection()
110 def script_setImage(self, guiImage, mapImage):
111 self._backgroundImage = wx.BitmapFromImage(wx.Image(os.path.join(self._basePath, guiImage)))
112 self._mapImage = wx.Image(os.path.join(self._basePath, mapImage))
113 self.SetClientSize(self._mapImage.GetSize())
115 def script_addColorCommand(self, r, g, b, command, data = None):
116 self._colorCommandMap[(r, g, b)] = (command, data)
118 def script_addTerminal(self, r, g, b):
119 x, y, w, h = self._getColoredRect(r, g, b)
120 if x < 0 or self._termLog is not None:
122 f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
123 self._termLog = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_DONTWRAP)
124 self._termLog.SetFont(f)
125 self._termLog.SetEditable(0)
126 self._termInput = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER)
127 self._termInput.SetFont(f)
129 self._termLog.SetPosition((x, y))
130 self._termLog.SetSize((w, h - self._termInput.GetSize().GetHeight()))
131 self._termInput.SetPosition((x, y + h - self._termInput.GetSize().GetHeight()))
132 self._termInput.SetSize((w, self._termInput.GetSize().GetHeight()))
133 self.Bind(wx.EVT_TEXT_ENTER, self.OnTermEnterLine, self._termInput)
134 self._termInput.Bind(wx.EVT_CHAR, self.OnTermKey)
136 def script_addTemperatureGraph(self, r, g, b):
137 x, y, w, h = self._getColoredRect(r, g, b)
138 if x < 0 or self._tempGraph is not None:
140 self._tempGraph = TemperatureGraph(self)
142 self._tempGraph.SetPosition((x, y))
143 self._tempGraph.SetSize((w, h))
145 def script_addProgressbar(self, r, g, b):
146 x, y, w, h = self._getColoredRect(r, g, b)
149 self._progressBar = wx.Gauge(self, -1, range=1000)
151 self._progressBar.SetPosition((x, y))
152 self._progressBar.SetSize((w, h))
154 def script_addButton(self, r, g, b, text, command, data = None):
155 x, y, w, h = self._getColoredRect(r, g, b)
158 button = wx.Button(self, -1, _(text))
159 button.SetPosition((x, y))
160 button.SetSize((w, h))
161 button.command = command
163 self._buttonList.append(button)
164 self.Bind(wx.EVT_BUTTON, lambda e: command(data), button)
166 def script_addSpinner(self, r, g, b, command, data):
167 x, y, w, h = self._getColoredRect(r, g, b)
171 def run_command(spinner):
172 value = spinner.GetValue()
173 print "Value (%s) and (%s)" % (spinner.last_value, value)
174 if spinner.last_value != '' and value != 0:
175 spinner.command(spinner.data % value)
176 spinner.last_value = value
178 spinner = wx.SpinCtrl(self, -1, style=wx.TE_PROCESS_ENTER)
179 spinner.SetRange(0, 300)
180 spinner.SetPosition((x, y))
181 spinner.SetSize((w, h))
183 spinner.command = command
185 spinner.last_value = ''
186 self._buttonList.append(spinner)
187 self.Bind(wx.EVT_SPINCTRL, lambda e: run_command(spinner), spinner)
189 def script_addTextButton(self, r_text, g_text, b_text, r_button, g_button, b_button, button_text, command, data):
190 x_text, y_text, w_text, h_text = self._getColoredRect(r_text, g_text, b_text)
193 x_button, y_button, w_button, h_button = self._getColoredRect(r_button, g_button, b_button)
196 from wx.lib.intctrl import IntCtrl
197 text = IntCtrl(self, -1)
198 text.SetBounds(0, 300)
199 text.SetPosition((x_text, y_text))
200 text.SetSize((w_text, h_text))
202 button = wx.Button(self, -1, _(button_text))
203 button.SetPosition((x_button, y_button))
204 button.SetSize((w_button, h_button))
205 button.command = command
207 self._buttonList.append(button)
208 self.Bind(wx.EVT_BUTTON, lambda e: command(data % text.GetValue()), button)
210 def _getColoredRect(self, r, g, b):
211 for x in xrange(0, self._mapImage.GetWidth()):
212 for y in xrange(0, self._mapImage.GetHeight()):
213 if self._mapImage.GetRed(x, y) == r and self._mapImage.GetGreen(x, y) == g and self._mapImage.GetBlue(x, y) == b:
215 while x+w < self._mapImage.GetWidth() and self._mapImage.GetRed(x + w, y) == r and self._mapImage.GetGreen(x + w, y) == g and self._mapImage.GetBlue(x + w, y) == b:
218 while y+h < self._mapImage.GetHeight() and self._mapImage.GetRed(x, y + h) == r and self._mapImage.GetGreen(x, y + h) == g and self._mapImage.GetBlue(x, y + h) == b:
221 print "Failed to find color: ", r, g, b
224 def script_sendGCode(self, data = None):
225 for line in data.split(';'):
228 self._printerConnection.sendCommand(line)
230 def script_sendMovementGCode(self, data = None):
231 if not self._printerConnection.isPaused() and not self._printerConnection.isPrinting():
232 self.script_sendGCode(data)
234 def script_connect(self, data = None):
235 self._printerConnection.openActiveConnection()
237 def script_startPrint(self, data = None):
238 if self._printerConnection.isPrinting() or self._printerConnection.isPaused():
239 self._printerConnection.pause(not self._printerConnection.isPaused())
241 self._printerConnection.startPrint()
243 def script_cancelPrint(self, e):
244 self._printerConnection.cancelPrint()
246 def script_pausePrint(self, e):
247 self._printerConnection.pause(not self._printerConnection.isPaused())
249 def script_showErrorLog(self, e):
250 LogWindow(self._printerConnection.getErrorLog())
252 def OnEraseBackground(self, e):
256 dc = wx.BufferedPaintDC(self, self._backgroundImage)
258 def OnLeftClick(self, e):
259 r = self._mapImage.GetRed(e.GetX(), e.GetY())
260 g = self._mapImage.GetGreen(e.GetX(), e.GetY())
261 b = self._mapImage.GetBlue(e.GetX(), e.GetY())
262 if (r, g, b) in self._colorCommandMap:
263 command = self._colorCommandMap[(r, g, b)]
264 command[0](command[1])
266 def OnClose(self, e):
267 if self._printerConnection.hasActiveConnection():
268 if self._printerConnection.isPrinting() or self._printerConnection.isPaused():
269 pass #TODO: Give warning that the close will kill the print.
270 self._printerConnection.closeActiveConnection()
271 self._printerConnection.removeCallback(self._doPrinterConnectionUpdate)
272 #TODO: When multiple printer windows are open, closing one will enable sleeping again.
273 preventComputerFromSleeping(self, False)
274 self._printerConnection.cancelPrint()
277 def OnTermEnterLine(self, e):
278 if not self._printerConnection.isAbleToSendDirectCommand():
280 line = self._termInput.GetValue()
283 self._addTermLog('> %s\n' % (line))
284 self._printerConnection.sendCommand(line)
285 self._termHistory.append(line)
286 self._termHistoryIdx = len(self._termHistory)
287 self._termInput.SetValue('')
289 def OnTermKey(self, e):
290 if len(self._termHistory) > 0:
291 if e.GetKeyCode() == wx.WXK_UP:
292 self._termHistoryIdx -= 1
293 if self._termHistoryIdx < 0:
294 self._termHistoryIdx = len(self._termHistory) - 1
295 self._termInput.SetValue(self._termHistory[self._termHistoryIdx])
296 if e.GetKeyCode() == wx.WXK_DOWN:
297 self._termHistoryIdx -= 1
298 if self._termHistoryIdx >= len(self._termHistory):
299 self._termHistoryIdx = 0
300 self._termInput.SetValue(self._termHistory[self._termHistoryIdx])
303 def _addTermLog(self, line):
304 if self._termLog is not None:
305 if len(self._termLog.GetValue()) > 10000:
306 self._termLog.SetValue(self._termLog.GetValue()[-10000:])
307 self._termLog.SetInsertionPointEnd()
308 if type(line) != unicode:
309 line = unicode(line, 'utf-8', 'replace')
310 self._termLog.AppendText(line.encode('utf-8', 'replace'))
312 def _updateButtonStates(self):
313 hasPauseButton = False
314 for button in self._buttonList:
315 if button.command == self.script_pausePrint:
316 hasPauseButton = True
319 for button in self._buttonList:
320 if button.command == self.script_connect:
321 button.Show(self._printerConnection.hasActiveConnection())
322 button.Enable(not self._printerConnection.isActiveConnectionOpen() and \
323 not self._printerConnection.isActiveConnectionOpening())
324 elif button.command == self.script_pausePrint:
325 button.Show(self._printerConnection.hasPause())
326 if not self._printerConnection.hasActiveConnection() or \
327 self._printerConnection.isActiveConnectionOpen():
328 button.Enable(self._printerConnection.isPrinting() or \
329 self._printerConnection.isPaused())
330 if self._printerConnection.isPaused():
331 button.SetLabel(_("Resume"))
333 button.SetLabel(_("Pause"))
336 elif button.command == self.script_startPrint:
337 if hasPauseButton or not self._printerConnection.hasPause():
338 if not self._printerConnection.hasActiveConnection() or \
339 self._printerConnection.isActiveConnectionOpen():
340 button.Enable(not self._printerConnection.isPrinting() and \
341 not self._printerConnection.isPaused())
345 if not self._printerConnection.hasActiveConnection() or \
346 self._printerConnection.isActiveConnectionOpen():
347 if self._printerConnection.isPrinting():
348 button.SetLabel(_("Pause"))
350 if self._printerConnection.isPaused():
351 button.SetLabel(_("Resume"))
353 button.SetLabel(_("Print"))
357 elif button.command == self.script_cancelPrint:
358 if not self._printerConnection.hasActiveConnection() or \
359 self._printerConnection.isActiveConnectionOpen():
360 button.Enable(self._printerConnection.isPrinting() or \
361 self._printerConnection.isPaused())
364 elif button.command == self.script_showErrorLog:
365 button.Show(self._printerConnection.isInErrorState())
366 if self._termInput is not None:
367 self._termInput.Enable(self._printerConnection.isAbleToSendDirectCommand())
369 def _doPrinterConnectionUpdate(self, connection, extraInfo = None):
370 wx.CallAfter(self.__doPrinterConnectionUpdate, connection, extraInfo)
371 if self._tempGraph is not None:
373 for n in xrange(0, 4):
374 t = connection.getTemperature(0)
379 self._tempGraph.addPoint(temp, [0] * len(temp), connection.getBedTemperature(), 0)
381 def __doPrinterConnectionUpdate(self, connection, extraInfo):
383 if self._lastUpdateTime + 0.5 > t and extraInfo is None:
385 self._lastUpdateTime = t
387 if extraInfo is not None and len(extraInfo) > 0:
388 self._addTermLog('< %s\n' % (extraInfo))
390 self._updateButtonStates()
391 isPrinting = connection.isPrinting() or connection.isPaused()
392 if self._progressBar is not None:
394 self._progressBar.SetValue(connection.getPrintProgress() * 1000)
396 self._progressBar.SetValue(0)
397 info = connection.getStatusString()
399 if self._printerConnection.getTemperature(0) is not None:
400 info += 'Temperature: %d' % (self._printerConnection.getTemperature(0))
401 if self._printerConnection.getBedTemperature() > 0:
402 info += ' Bed: %d' % (self._printerConnection.getBedTemperature())
403 if self._infoText is not None:
404 self._infoText.SetLabel(info)
406 self.SetTitle(info.replace('\n', ', ').strip(', '))
407 if isPrinting != self._isPrinting:
408 self._isPrinting = isPrinting
409 preventComputerFromSleeping(self, self._isPrinting)
411 class printWindowBasic(wx.Frame):
413 Printing window for USB printing, network printing, and any other type of printer connection we can think off.
414 This is only a basic window with minimal information.
416 def __init__(self, parent, printerConnection):
417 super(printWindowBasic, self).__init__(parent, -1, style=wx.CLOSE_BOX|wx.CLIP_CHILDREN|wx.CAPTION|wx.SYSTEM_MENU|wx.FRAME_TOOL_WINDOW|wx.FRAME_FLOAT_ON_PARENT, title=_("Printing on %s") % (printerConnection.getName()))
418 self._printerConnection = printerConnection
419 self._lastUpdateTime = 0
420 self._isPrinting = False
422 self.SetSizer(wx.BoxSizer())
423 self.panel = wx.Panel(self)
424 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
425 self.sizer = wx.GridBagSizer(2, 2)
426 self.panel.SetSizer(self.sizer)
428 self.powerWarningText = wx.StaticText(parent=self.panel,
430 label=_("Your computer is running on battery power.\nConnect your computer to AC power or your print might not finish."),
431 style=wx.ALIGN_CENTER)
432 self.powerWarningText.SetBackgroundColour('red')
433 self.powerWarningText.SetForegroundColour('white')
434 self.powerManagement = power.PowerManagement()
435 self.powerWarningTimer = wx.Timer(self)
436 self.Bind(wx.EVT_TIMER, self.OnPowerWarningChange, self.powerWarningTimer)
437 self.OnPowerWarningChange(None)
438 self.powerWarningTimer.Start(10000)
440 self.statsText = wx.StaticText(self.panel, -1, _("InfoLine from printer connection\nInfoLine from dialog\nExtra line\nMore lines for layout\nMore lines for layout\nMore lines for layout"))
442 self.connectButton = wx.Button(self.panel, -1, _("Connect"))
443 #self.loadButton = wx.Button(self.panel, -1, 'Load')
444 self.printButton = wx.Button(self.panel, -1, _("Print"))
445 self.pauseButton = wx.Button(self.panel, -1, _("Pause"))
446 self.cancelButton = wx.Button(self.panel, -1, _("Cancel print"))
447 self.errorLogButton = wx.Button(self.panel, -1, _("Error log"))
448 self.progress = wx.Gauge(self.panel, -1, range=1000)
450 self.sizer.Add(self.powerWarningText, pos=(0, 0), span=(1, 5), flag=wx.EXPAND|wx.BOTTOM, border=5)
451 self.sizer.Add(self.statsText, pos=(1, 0), span=(1, 5), flag=wx.LEFT, border=5)
452 self.sizer.Add(self.connectButton, pos=(2, 0))
453 #self.sizer.Add(self.loadButton, pos=(2,1))
454 self.sizer.Add(self.printButton, pos=(2, 1))
455 self.sizer.Add(self.pauseButton, pos=(2, 2))
456 self.sizer.Add(self.cancelButton, pos=(2, 3))
457 self.sizer.Add(self.errorLogButton, pos=(2, 4))
458 self.sizer.Add(self.progress, pos=(3, 0), span=(1, 5), flag=wx.EXPAND)
460 self.Bind(wx.EVT_CLOSE, self.OnClose)
461 self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect)
462 #self.loadButton.Bind(wx.EVT_BUTTON, self.OnLoad)
463 self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint)
464 self.pauseButton.Bind(wx.EVT_BUTTON, self.OnPause)
465 self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)
466 self.errorLogButton.Bind(wx.EVT_BUTTON, self.OnErrorLog)
472 self.progress.SetMinSize(self.progress.GetSize())
473 self.statsText.SetLabel('\n\n\n\n\n\n')
474 self._updateButtonStates()
476 self._printerConnection.addCallback(self._doPrinterConnectionUpdate)
478 if self._printerConnection.hasActiveConnection() and not self._printerConnection.isActiveConnectionOpen():
479 self._printerConnection.openActiveConnection()
481 def OnPowerWarningChange(self, e):
482 type = self.powerManagement.get_providing_power_source_type()
483 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
484 self.powerWarningText.Hide()
489 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
490 self.powerWarningText.Show()
496 def OnClose(self, e):
497 if self._printerConnection.hasActiveConnection():
498 if self._printerConnection.isPrinting() or self._printerConnection.isPaused():
499 pass #TODO: Give warning that the close will kill the print.
500 self._printerConnection.closeActiveConnection()
501 self._printerConnection.removeCallback(self._doPrinterConnectionUpdate)
502 #TODO: When multiple printer windows are open, closing one will enable sleeping again.
503 preventComputerFromSleeping(self, False)
506 def OnConnect(self, e):
507 self._printerConnection.openActiveConnection()
512 def OnPrint(self, e):
513 self._printerConnection.startPrint()
515 def OnCancel(self, e):
516 self._printerConnection.cancelPrint()
518 def OnPause(self, e):
519 self._printerConnection.pause(not self._printerConnection.isPaused())
521 def OnErrorLog(self, e):
522 LogWindow(self._printerConnection.getErrorLog())
524 def _doPrinterConnectionUpdate(self, connection, extraInfo = None):
525 wx.CallAfter(self.__doPrinterConnectionUpdate, connection, extraInfo)
526 #temp = [connection.getTemperature(0)]
527 #self.temperatureGraph.addPoint(temp, [0], connection.getBedTemperature(), 0)
529 def __doPrinterConnectionUpdate(self, connection, extraInfo):
531 if self._lastUpdateTime + 0.5 > now and extraInfo is None:
533 self._lastUpdateTime = now
535 if extraInfo is not None and len(extraInfo) > 0:
536 self._addTermLog('< %s\n' % (extraInfo))
538 self._updateButtonStates()
539 onGoingPrint = connection.isPrinting() or connection.isPaused()
541 self.progress.SetValue(connection.getPrintProgress() * 1000)
543 self.progress.SetValue(0)
544 info = connection.getStatusString()
546 if self._printerConnection.getTemperature(0) is not None:
547 info += 'Temperature: %d' % (self._printerConnection.getTemperature(0))
548 if self._printerConnection.getBedTemperature() > 0:
549 info += ' Bed: %d' % (self._printerConnection.getBedTemperature())
551 self.statsText.SetLabel(info)
552 if onGoingPrint != self._isPrinting:
553 self._isPrinting = onGoingPrint
554 preventComputerFromSleeping(self, self._isPrinting)
556 def _addTermLog(self, msg):
559 def _updateButtonStates(self):
560 self.connectButton.Show(self._printerConnection.hasActiveConnection())
561 self.connectButton.Enable(not self._printerConnection.isActiveConnectionOpen() and not self._printerConnection.isActiveConnectionOpening())
562 self.pauseButton.Show(self._printerConnection.hasPause())
563 if not self._printerConnection.hasActiveConnection() or self._printerConnection.isActiveConnectionOpen():
564 self.printButton.Enable(not self._printerConnection.isPrinting() and \
565 not self._printerConnection.isPaused())
566 self.pauseButton.Enable(self._printerConnection.isPrinting())
567 self.cancelButton.Enable(self._printerConnection.isPrinting())
569 self.printButton.Enable(False)
570 self.pauseButton.Enable(False)
571 self.cancelButton.Enable(False)
572 self.errorLogButton.Show(self._printerConnection.isInErrorState())
574 class printWindowAdvanced(wx.Frame):
575 def __init__(self, parent, printerConnection):
576 super(printWindowAdvanced, self).__init__(parent, -1, style=wx.CLOSE_BOX|wx.CLIP_CHILDREN|wx.CAPTION|wx.SYSTEM_MENU|wx.FRAME_FLOAT_ON_PARENT|wx.MINIMIZE_BOX, title=_("Printing on %s") % (printerConnection.getName()))
577 self._printerConnection = printerConnection
578 self._lastUpdateTime = time.time()
579 self._isPrinting = False
581 self.SetSizer(wx.BoxSizer(wx.VERTICAL))
582 self.toppanel = wx.Panel(self)
583 self.topsizer = wx.GridBagSizer(2, 2)
584 self.toppanel.SetSizer(self.topsizer)
585 self.toppanel.SetBackgroundColour(wx.WHITE)
586 self.topsizer.SetEmptyCellSize((125, 1))
587 self.panel = wx.Panel(self)
588 self.sizer = wx.GridBagSizer(2, 2)
589 self.sizer.SetEmptyCellSize((125, 1))
590 self.panel.SetSizer(self.sizer)
591 self.panel.SetBackgroundColour(wx.WHITE)
592 self.GetSizer().Add(self.toppanel, 0, flag=wx.EXPAND)
593 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
595 self._fullscreenTemperature = None
596 self._termHistory = []
597 self._termHistoryIdx = 0
599 self._mapImage = wx.Image(resources.getPathForImage('print-window-map.png'))
600 self._colorCommandMap = {}
603 self._addMovementCommand(0, 0, 255, self._moveX, 100)
604 self._addMovementCommand(0, 0, 240, self._moveX, 10)
605 self._addMovementCommand(0, 0, 220, self._moveX, 1)
606 self._addMovementCommand(0, 0, 200, self._moveX, 0.1)
607 self._addMovementCommand(0, 0, 180, self._moveX, -0.1)
608 self._addMovementCommand(0, 0, 160, self._moveX, -1)
609 self._addMovementCommand(0, 0, 140, self._moveX, -10)
610 self._addMovementCommand(0, 0, 120, self._moveX, -100)
613 self._addMovementCommand(0, 255, 0, self._moveY, -100)
614 self._addMovementCommand(0, 240, 0, self._moveY, -10)
615 self._addMovementCommand(0, 220, 0, self._moveY, -1)
616 self._addMovementCommand(0, 200, 0, self._moveY, -0.1)
617 self._addMovementCommand(0, 180, 0, self._moveY, 0.1)
618 self._addMovementCommand(0, 160, 0, self._moveY, 1)
619 self._addMovementCommand(0, 140, 0, self._moveY, 10)
620 self._addMovementCommand(0, 120, 0, self._moveY, 100)
623 self._addMovementCommand(255, 0, 0, self._moveZ, 10)
624 self._addMovementCommand(220, 0, 0, self._moveZ, 1)
625 self._addMovementCommand(200, 0, 0, self._moveZ, 0.1)
626 self._addMovementCommand(180, 0, 0, self._moveZ, -0.1)
627 self._addMovementCommand(160, 0, 0, self._moveZ, -1)
628 self._addMovementCommand(140, 0, 0, self._moveZ, -10)
631 self._addMovementCommand(255, 80, 0, self._moveE, 10)
632 self._addMovementCommand(255, 180, 0, self._moveE, -10)
635 self._addMovementCommand(255, 255, 0, self._homeXYZ, None)
636 self._addMovementCommand(240, 255, 0, self._homeXYZ, "X")
637 self._addMovementCommand(220, 255, 0, self._homeXYZ, "Y")
638 self._addMovementCommand(200, 255, 0, self._homeXYZ, "Z")
640 self.powerWarningText = wx.StaticText(parent=self.toppanel,
642 label=_("Your computer is running on battery power.\nConnect your computer to AC power or your print might not finish."),
643 style=wx.ALIGN_CENTER)
644 self.powerWarningText.SetBackgroundColour('red')
645 self.powerWarningText.SetForegroundColour('white')
646 self.powerManagement = power.PowerManagement()
647 self.powerWarningTimer = wx.Timer(self)
648 self.Bind(wx.EVT_TIMER, self.OnPowerWarningChange, self.powerWarningTimer)
649 self.OnPowerWarningChange(None)
650 self.powerWarningTimer.Start(10000)
652 self.connectButton = wx.Button(self.toppanel, -1, _("Connect"), size=(125, 30))
653 self.printButton = wx.Button(self.toppanel, -1, _("Print"), size=(125, 30))
654 self.cancelButton = wx.Button(self.toppanel, -1, _("Cancel"), size=(125, 30))
655 self.errorLogButton = wx.Button(self.toppanel, -1, _("Error log"), size=(125, 30))
656 self.motorsOffButton = wx.Button(self.toppanel, -1, _("Motors off"), size=(125, 30))
657 self.movementBitmap = wx.StaticBitmap(self.panel, -1, wx.BitmapFromImage(wx.Image(
658 resources.getPathForImage('print-window.png'))), (0, 0))
659 self.temperatureBitmap = wx.StaticBitmap(self.panel, -1, wx.BitmapFromImage(wx.Image(
660 resources.getPathForImage('print-window-temperature.png'))), (0, 0))
661 self.temperatureField = TemperatureField(self.panel, self._setHotendTemperature)
662 self.temperatureBedBitmap = wx.StaticBitmap(self.panel, -1, wx.BitmapFromImage(wx.Image(
663 resources.getPathForImage('print-window-temperature-bed.png'))), (0, 0))
664 self.temperatureBedField = TemperatureField(self.panel, self._setBedTemperature)
665 self.temperatureGraph = TemperatureGraph(self.panel)
666 self.temperatureGraph.SetMinSize((250, 100))
667 self.progress = wx.Gauge(self.panel, -1, range=1000)
669 f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
670 self._termLog = wx.TextCtrl(self.panel, style=wx.TE_MULTILINE)
671 self._termLog.SetFont(f)
672 self._termLog.SetEditable(0)
673 self._termLog.SetMinSize((385, -1))
674 self._termInput = wx.TextCtrl(self.panel, style=wx.TE_PROCESS_ENTER)
675 self._termInput.SetFont(f)
677 self.Bind(wx.EVT_TEXT_ENTER, self.OnTermEnterLine, self._termInput)
678 self._termInput.Bind(wx.EVT_CHAR, self.OnTermKey)
680 self.topsizer.Add(self.powerWarningText, pos=(0, 0), span=(1, 6), flag=wx.EXPAND|wx.BOTTOM, border=5)
681 self.topsizer.Add(self.connectButton, pos=(1, 0), flag=wx.LEFT, border=2)
682 self.topsizer.Add(self.printButton, pos=(1, 1), flag=wx.LEFT, border=2)
683 self.topsizer.Add(self.cancelButton, pos=(1, 2), flag=wx.LEFT, border=2)
684 self.topsizer.Add(self.errorLogButton, pos=(1, 4), flag=wx.LEFT, border=2)
685 self.topsizer.Add(self.motorsOffButton, pos=(1, 5), flag=wx.LEFT|wx.RIGHT, border=2)
686 self.sizer.Add(self.movementBitmap, pos=(0, 0), span=(2, 3))
687 self.sizer.Add(self.temperatureGraph, pos=(2, 0), span=(4, 2), flag=wx.EXPAND)
688 self.sizer.Add(self.temperatureBitmap, pos=(2, 2))
689 self.sizer.Add(self.temperatureField, pos=(3, 2))
690 self.sizer.Add(self.temperatureBedBitmap, pos=(4, 2))
691 self.sizer.Add(self.temperatureBedField, pos=(5, 2))
692 self.sizer.Add(self._termLog, pos=(0, 3), span=(5, 3), flag=wx.EXPAND|wx.RIGHT, border=5)
693 self.sizer.Add(self._termInput, pos=(5, 3), span=(1, 3), flag=wx.EXPAND|wx.RIGHT, border=5)
694 self.sizer.Add(self.progress, pos=(6, 0), span=(1, 6), flag=wx.EXPAND|wx.BOTTOM)
696 self.Bind(wx.EVT_SIZE, self.OnSize)
697 self.Bind(wx.EVT_CLOSE, self.OnClose)
698 self.movementBitmap.Bind(wx.EVT_LEFT_DOWN, self.OnMovementClick)
699 self.temperatureGraph.Bind(wx.EVT_LEFT_UP, self.OnTemperatureClick)
700 self.connectButton.Bind(wx.EVT_BUTTON, self.OnConnect)
701 self.printButton.Bind(wx.EVT_BUTTON, self.OnPrint)
702 self.cancelButton.Bind(wx.EVT_BUTTON, self.OnCancel)
703 self.errorLogButton.Bind(wx.EVT_BUTTON, self.OnErrorLog)
704 self.motorsOffButton.Bind(wx.EVT_BUTTON, self.OnMotorsOff)
709 self.progress.SetMinSize(self.progress.GetSize())
710 self._updateButtonStates()
712 self._printerConnection.addCallback(self._doPrinterConnectionUpdate)
714 if self._printerConnection.hasActiveConnection() and \
715 not self._printerConnection.isActiveConnectionOpen():
716 self._printerConnection.openActiveConnection()
719 # HACK ALERT: This is needed for some reason otherwise the window
720 # will be bigger than it should be until a power warning change
725 def OnClose(self, e):
726 if self._printerConnection.hasActiveConnection():
727 if self._printerConnection.isPrinting() or self._printerConnection.isPaused():
728 pass #TODO: Give warning that the close will kill the print.
729 self._printerConnection.closeActiveConnection()
730 self._printerConnection.removeCallback(self._doPrinterConnectionUpdate)
731 #TODO: When multiple printer windows are open, closing one will enable sleeping again.
732 preventComputerFromSleeping(self, False)
733 self._printerConnection.cancelPrint()
736 def OnPowerWarningChange(self, e):
737 type = self.powerManagement.get_providing_power_source_type()
738 if type == power.POWER_TYPE_AC and self.powerWarningText.IsShown():
739 self.powerWarningText.Hide()
740 self.toppanel.Layout()
744 elif type != power.POWER_TYPE_AC and not self.powerWarningText.IsShown():
745 self.powerWarningText.Show()
746 self.toppanel.Layout()
751 def OnConnect(self, e):
752 self._printerConnection.openActiveConnection()
754 def OnPrint(self, e):
755 if self._printerConnection.isPrinting() or self._printerConnection.isPaused():
756 self._printerConnection.pause(not self._printerConnection.isPaused())
758 self._printerConnection.startPrint()
760 def OnCancel(self, e):
761 self._printerConnection.cancelPrint()
763 def OnErrorLog(self, e):
764 LogWindow(self._printerConnection.getErrorLog())
766 def OnMotorsOff(self, e):
767 self._printerConnection.sendCommand("M18")
769 def GetMapRGB(self, x, y):
770 r = self._mapImage.GetRed(x, y)
771 g = self._mapImage.GetGreen(x, y)
772 b = self._mapImage.GetBlue(x, y)
775 def OnMovementClick(self, e):
776 (r, g, b) = self.GetMapRGB(e.GetX(), e.GetY())
777 if (r, g, b) in self._colorCommandMap:
778 command = self._colorCommandMap[(r, g, b)]
779 command[0](command[1])
781 def _addMovementCommand(self, r, g, b, command, step):
782 self._colorCommandMap[(r, g, b)] = (command, step)
784 def _moveXYZE(self, motor, step, feedrate):
785 # Prevent Z movement when paused and all moves when printing
786 if (not self._printerConnection.hasActiveConnection() or \
787 self._printerConnection.isActiveConnectionOpen()) and \
788 (not (self._printerConnection.isPaused() and motor == 'Z') and \
789 not self._printerConnection.isPrinting()):
790 self._printerConnection.sendCommand("G91")
791 self._printerConnection.sendCommand("G1 %s%.1f F%d" % (motor, step, feedrate))
792 self._printerConnection.sendCommand("G90")
794 def _moveX(self, step):
795 self._moveXYZE("X", step, 2000)
797 def _moveY(self, step):
798 self._moveXYZE("Y", step, 2000)
800 def _moveZ(self, step):
801 self._moveXYZE("Z", step, 200)
803 def _moveE(self, step):
805 from Cura.util import profile
806 toolhead_name = profile.getMachineSetting("toolhead")
807 toolhead_name = toolhead_name.lower()
808 if "flexy" in toolhead_name:
810 self._moveXYZE("E", step, feedrate)
812 def _homeXYZ(self, direction):
813 if not self._printerConnection.isPaused() and not self._printerConnection.isPrinting():
814 if direction is None:
815 self._printerConnection.sendCommand("G28")
817 self._printerConnection.sendCommand("G28 %s0" % direction)
819 def _setHotendTemperature(self, value):
820 self._printerConnection.sendCommand("M104 S%d" % value)
822 def _setBedTemperature(self, value):
823 self._printerConnection.sendCommand("M140 S%d" % value)
825 def OnTemperatureClick(self, e):
826 wx.CallAfter(self.ToggleFullScreenTemperature)
828 def ToggleFullScreenTemperature(self):
829 sizer = self.GetSizer()
830 if self._fullscreenTemperature:
831 self._fullscreenTemperature.Show(False)
832 sizer.Detach(self._fullscreenTemperature)
833 self._fullscreenTemperature.Destroy()
834 self._fullscreenTemperature = None
835 self.panel.Show(True)
837 self._fullscreenTemperature = self.temperatureGraph.Clone(self)
838 self._fullscreenTemperature.Bind(wx.EVT_LEFT_UP, self.OnTemperatureClick)
839 self._fullscreenTemperature.SetMinSize(self.panel.GetSize())
840 sizer.Add(self._fullscreenTemperature, 1, flag=wx.EXPAND)
841 self.panel.Show(False)
845 def OnTermEnterLine(self, e):
846 if not self._printerConnection.isAbleToSendDirectCommand():
848 line = self._termInput.GetValue()
851 self._addTermLog('> %s\n' % (line))
852 self._printerConnection.sendCommand(line)
853 self._termHistory.append(line)
854 self._termHistoryIdx = len(self._termHistory)
855 self._termInput.SetValue('')
857 def OnTermKey(self, e):
858 if len(self._termHistory) > 0:
859 if e.GetKeyCode() == wx.WXK_UP:
860 self._termHistoryIdx -= 1
861 if self._termHistoryIdx < 0:
862 self._termHistoryIdx = len(self._termHistory) - 1
863 self._termInput.SetValue(self._termHistory[self._termHistoryIdx])
864 if e.GetKeyCode() == wx.WXK_DOWN:
865 self._termHistoryIdx -= 1
866 if self._termHistoryIdx >= len(self._termHistory):
867 self._termHistoryIdx = 0
868 self._termInput.SetValue(self._termHistory[self._termHistoryIdx])
871 def _addTermLog(self, line):
872 if self._termLog is not None:
873 if len(self._termLog.GetValue()) > 10000:
874 self._termLog.SetValue(self._termLog.GetValue()[-10000:])
875 self._termLog.SetInsertionPointEnd()
876 if type(line) != unicode:
877 line = unicode(line, 'utf-8', 'replace')
878 self._termLog.AppendText(line.encode('utf-8', 'replace'))
880 def _updateButtonStates(self):
881 self.connectButton.Show(self._printerConnection.hasActiveConnection())
882 self.connectButton.Enable(not self._printerConnection.isActiveConnectionOpen() and \
883 not self._printerConnection.isActiveConnectionOpening())
884 if not self._printerConnection.hasPause():
885 if not self._printerConnection.hasActiveConnection() or \
886 self._printerConnection.isActiveConnectionOpen():
887 self.printButton.Enable(not self._printerConnection.isPrinting() and \
888 not self._printerConnection.isPaused())
890 self.printButton.Enable(False)
892 if not self._printerConnection.hasActiveConnection() or \
893 self._printerConnection.isActiveConnectionOpen():
894 if self._printerConnection.isPrinting():
895 self.printButton.SetLabel(_("Pause"))
897 if self._printerConnection.isPaused():
898 self.printButton.SetLabel(_("Resume"))
900 self.printButton.SetLabel(_("Print"))
901 self.printButton.Enable(True)
903 self.printButton.Enable(False)
904 if not self._printerConnection.hasActiveConnection() or \
905 self._printerConnection.isActiveConnectionOpen():
906 self.cancelButton.Enable(self._printerConnection.isPrinting() or \
907 self._printerConnection.isPaused())
909 self.cancelButton.Enable(False)
910 self.errorLogButton.Show(self._printerConnection.isInErrorState())
911 self._termInput.Enable(self._printerConnection.isAbleToSendDirectCommand())
914 def _doPrinterConnectionUpdate(self, connection, extraInfo = None):
915 wx.CallAfter(self.__doPrinterConnectionUpdate, connection, extraInfo)
917 for n in xrange(0, 4):
918 t = connection.getTemperature(0)
923 self.temperatureGraph.addPoint(temp, [0] * len(temp), connection.getBedTemperature(), 0)
924 if self._fullscreenTemperature is not None:
925 self._fullscreenTemperature.addPoint(temp, [0] * len(temp), connection.getBedTemperature(), 0)
927 def __doPrinterConnectionUpdate(self, connection, extraInfo):
929 if self._lastUpdateTime + 0.5 > t and extraInfo is None:
931 self._lastUpdateTime = t
933 if extraInfo is not None and len(extraInfo) > 0:
934 self._addTermLog('< %s\n' % (extraInfo))
936 self._updateButtonStates()
937 isPrinting = connection.isPrinting() or connection.isPaused()
939 self.progress.SetValue(connection.getPrintProgress() * 1000)
941 self.progress.SetValue(0)
942 info = connection.getStatusString()
944 if self._printerConnection.getTemperature(0) is not None:
945 info += 'Temperature: %d' % (self._printerConnection.getTemperature(0))
946 if self._printerConnection.getBedTemperature() > 0:
947 info += ' Bed: %d' % (self._printerConnection.getBedTemperature())
948 self.SetTitle(info.replace('\n', ', ').strip(', '))
949 if isPrinting != self._isPrinting:
950 self._isPrinting = isPrinting
951 preventComputerFromSleeping(self, self._isPrinting)
953 class TemperatureField(wx.Panel):
954 def __init__(self, parent, callback):
955 super(TemperatureField, self).__init__(parent)
956 self.callback = callback
958 self.SetBackgroundColour(wx.WHITE)
960 self.text = IntCtrl(self, -1)
961 self.text.SetBounds(0, 300)
962 self.text.SetSize((60, 28))
964 self.unit = wx.StaticBitmap(self, -1, wx.BitmapFromImage(wx.Image(
965 resources.getPathForImage('print-window-temperature-unit.png'))), (0, 0))
967 self.button = wx.Button(self, -1, _("Set"))
968 self.button.SetSize((35, 25))
969 self.Bind(wx.EVT_BUTTON, lambda e: self.callback(self.text.GetValue()), self.button)
971 self.text.SetPosition((0, 0))
972 self.unit.SetPosition((60, 0))
973 self.button.SetPosition((90, 0))
976 class TemperatureGraph(wx.Panel):
977 def __init__(self, parent):
978 super(TemperatureGraph, self).__init__(parent)
980 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
981 self.Bind(wx.EVT_SIZE, self.OnSize)
982 self.Bind(wx.EVT_PAINT, self.OnDraw)
984 self._lastDraw = time.time() - 1.0
986 self._backBuffer = None
987 self.addPoint([0]*16, [0]*16, 0, 0)
989 def Clone(self, parent):
990 clone = TemperatureGraph(parent)
991 clone._points = list(self._points)
994 def OnEraseBackground(self, e):
998 if self._backBuffer is None or self.GetSize() != self._backBuffer.GetSize():
999 self._backBuffer = wx.EmptyBitmap(*self.GetSizeTuple())
1000 self.UpdateDrawing(True)
1002 def OnDraw(self, e):
1003 dc = wx.BufferedPaintDC(self, self._backBuffer)
1005 def UpdateDrawing(self, force=False):
1007 if (not force and now - self._lastDraw < 1.0) or self._backBuffer is None:
1009 self._lastDraw = now
1011 dc.SelectObject(self._backBuffer)
1012 dc.SetBackground(wx.Brush(wx.WHITE))
1014 dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT))
1015 w, h = self.GetSizeTuple()
1016 bgLinePen = wx.Pen('#A0A0A0')
1017 tempPen = wx.Pen('#FF4040')
1018 tempSPPen = wx.Pen('#FFA0A0')
1019 tempPenBG = wx.Pen('#FFD0D0')
1020 bedTempPen = wx.Pen('#4040FF')
1021 bedTempSPPen = wx.Pen('#A0A0FF')
1022 bedTempPenBG = wx.Pen('#D0D0FF')
1024 #Draw the background up to the current temperatures.
1030 for temp, tempSP, bedTemp, bedTempSP, t in self._points:
1031 x1 = int(w - (now - t))
1032 for x in xrange(x0, x1 + 1):
1033 for n in xrange(0, min(len(t0), len(temp))):
1034 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
1035 dc.SetPen(tempPenBG)
1036 dc.DrawLine(x, h, x, h - (t * h / 350))
1037 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
1038 dc.SetPen(bedTempPenBG)
1039 dc.DrawLine(x, h, x, h - (bt * h / 350))
1047 for x in xrange(w, 0, -30):
1048 dc.SetPen(bgLinePen)
1049 dc.DrawLine(x, 0, x, h)
1051 for y in xrange(h - 1, 0, -h * 50 / 350):
1052 dc.SetPen(bgLinePen)
1053 dc.DrawLine(0, y, w, y)
1054 dc.DrawText(str(tmpNr), 0, y - dc.GetFont().GetPixelSize().GetHeight())
1056 dc.DrawLine(0, 0, w, 0)
1057 dc.DrawLine(0, 0, 0, h)
1059 #Draw the main lines
1065 for temp, tempSP, bedTemp, bedTempSP, t in self._points:
1066 x1 = int(w - (now - t))
1067 for x in xrange(x0, x1 + 1):
1068 for n in xrange(0, min(len(t0), len(temp))):
1069 t = float(x - x0) / float(x1 - x0 + 1) * (temp[n] - t0[n]) + t0[n]
1070 tSP = float(x - x0) / float(x1 - x0 + 1) * (tempSP[n] - tSP0[n]) + tSP0[n]
1071 dc.SetPen(tempSPPen)
1072 dc.DrawPoint(x, h - (tSP * h / 350))
1074 dc.DrawPoint(x, h - (t * h / 350))
1075 bt = float(x - x0) / float(x1 - x0 + 1) * (bedTemp - bt0) + bt0
1076 btSP = float(x - x0) / float(x1 - x0 + 1) * (bedTempSP - btSP0) + btSP0
1077 dc.SetPen(bedTempSPPen)
1078 dc.DrawPoint(x, h - (btSP * h / 350))
1079 dc.SetPen(bedTempPen)
1080 dc.DrawPoint(x, h - (bt * h / 350))
1088 self.Refresh(eraseBackground=False)
1091 if len(self._points) > 0 and (time.time() - self._points[0][4]) > w + 20:
1094 def addPoint(self, temp, tempSP, bedTemp, bedTempSP):
1095 if len(self._points) > 0 and time.time() - self._points[-1][4] < 0.5:
1097 for n in xrange(0, len(temp)):
1100 for n in xrange(0, len(tempSP)):
1101 if tempSP[n] is None:
1105 if bedTempSP is None:
1107 self._points.append((temp[:], tempSP[:], bedTemp, bedTempSP, time.time()))
1108 wx.CallAfter(self.UpdateDrawing)
1110 class LogWindow(wx.Frame):
1111 def __init__(self, logText):
1112 super(LogWindow, self).__init__(None, title=_("Error log"))
1113 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_READONLY)
1114 self.SetSize((500, 400))