From: daid Date: Mon, 3 Feb 2014 13:33:42 +0000 (+0100) Subject: Add version 0.1 of serial communication with printerConnection. X-Git-Tag: 14.02-RC1~42 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=dedd5f0dd62b55a58d0f334e9c193db24ecaff74;p=cura.git Add version 0.1 of serial communication with printerConnection. --- diff --git a/Cura/gui/newVersionDialog.py b/Cura/gui/newVersionDialog.py index fa884606..d154d358 100644 --- a/Cura/gui/newVersionDialog.py +++ b/Cura/gui/newVersionDialog.py @@ -26,14 +26,8 @@ class newVersionDialog(wx.Dialog): s.Add(wx.StaticText(p, -1, '(This dialog is only shown once)')) s.Add(wx.StaticLine(p), flag=wx.EXPAND|wx.TOP|wx.BOTTOM, border=10) s.Add(wx.StaticText(p, -1, 'New in this version:')) - s.Add(wx.StaticText(p, -1, '* Fixed a rare bug that caused the CuraEngine to crash on some models.')) - s.Add(wx.StaticText(p, -1, '* Added support for multiple Doodle3D boxes on the same network.')) - s.Add(wx.StaticText(p, -1, '* Made it possible to switch between "all at once" and "one at a time printing"')) - s.Add(wx.StaticText(p, -1, '* Improved USB communication stability.')) - s.Add(wx.StaticText(p, -1, '* Improved USB auto-detection for none-Ultimaker printers.')) - s.Add(wx.StaticText(p, -1, '* Set retraction enabled by default and in the quickprint profiles.')) - s.Add(wx.StaticText(p, -1, '* Fixed a bug that caused loading of really small objects to fail.')) - s.Add(wx.StaticText(p, -1, '* Made camera keyboard controls accessible in the GCode view, use shift+up/down for layer changes now.')) + s.Add(wx.StaticText(p, -1, '* Improved the LayerView rendering speed.')) + s.Add(wx.StaticText(p, -1, '* Made the LayerView update during slicing, so you can see the result before it is ready.')) self.hasUltimaker = None self.hasUltimaker2 = None @@ -49,13 +43,10 @@ class newVersionDialog(wx.Dialog): button = wx.Button(p, -1, 'Install now') self.Bind(wx.EVT_BUTTON, self.OnUltimakerFirmware, button) s.Add(button, flag=wx.TOP, border=5) - if self.hasUltimaker2 is not None: + if self.hasUltimaker2 is not None and False: s.Add(wx.StaticLine(p), flag=wx.EXPAND|wx.TOP|wx.BOTTOM, border=10) s.Add(wx.StaticText(p, -1, 'New firmware for your Ultimaker2:')) - s.Add(wx.StaticText(p, -1, '* Fixed the bug where aborting a print caused massive retraction.')) - s.Add(wx.StaticText(p, -1, '* Fixed a bug where going into move-material when the printer was still moving caused a bed-crash.')) - s.Add(wx.StaticText(p, -1, '* Added bed temperature when cooling down the printer.')) - s.Add(wx.StaticText(p, -1, '* Allow abort if bed-leveling is selected.')) + s.Add(wx.StaticText(p, -1, '* .')) button = wx.Button(p, -1, 'Install now') self.Bind(wx.EVT_BUTTON, self.OnUltimaker2Firmware, button) s.Add(button, flag=wx.TOP, border=5) diff --git a/Cura/gui/sceneView.py b/Cura/gui/sceneView.py index 6eda047b..80750c96 100644 --- a/Cura/gui/sceneView.py +++ b/Cura/gui/sceneView.py @@ -217,9 +217,7 @@ class SceneView(openglGui.glGuiPanel): def OnPrintButton(self, button): if button == 1: connectionGroup = self._printerConnectionManager.getAvailableGroup() - if machineCom.machineIsConnected(): - self.showPrintWindow() - elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0): + if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0): drives = removableStorage.getPossibleSDcardDrives() if len(drives) > 1: dlg = wx.SingleChoiceDialog(self, "Select SD drive", "Multiple removable drives have been found,\nplease select your SD card drive", map(lambda n: n[0], drives)) @@ -248,7 +246,6 @@ class SceneView(openglGui.glGuiPanel): self.showSaveGCode() if button == 3: menu = wx.Menu() - self.Bind(wx.EVT_MENU, lambda e: self.showPrintWindow(), menu.Append(-1, _("Print with USB"))) connections = self._printerConnectionManager.getAvailableConnections() menu.connectionMap = {} for connection in connections: @@ -272,17 +269,6 @@ class SceneView(openglGui.glGuiPanel): else: self.notification.message("Failed to start print...") - def showPrintWindow(self): - if self._gcodeFilename is None: - return - if profile.getMachineSetting('gcode_flavor') == 'UltiGCode': - wx.MessageBox(_("USB printing on the Ultimaker2 is not supported."), _("USB Printing Error"), wx.OK | wx.ICON_WARNING) - return - #TODO: Fix for _engine.getResult - self._usbPrintMonitor.loadFile(self._gcodeFilename, self._engine.getID()) - if self._gcodeFilename is None: - self._engine.submitInfoOnline() - def showSaveGCode(self): if len(self._scene._objectList) < 1: return @@ -881,10 +867,7 @@ class SceneView(openglGui.glGuiPanel): def OnPaint(self,e): connectionGroup = self._printerConnectionManager.getAvailableGroup() - if machineCom.machineIsConnected(): - self.printButton._imageID = 6 - self.printButton._tooltip = _("Print") - elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0): + if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0): self.printButton._imageID = 2 self.printButton._tooltip = _("Toolpath to SD") elif connectionGroup is not None: diff --git a/Cura/serialCommunication.py b/Cura/serialCommunication.py new file mode 100644 index 00000000..27ae9fc5 --- /dev/null +++ b/Cura/serialCommunication.py @@ -0,0 +1,63 @@ +__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License" + +# Serial communication with the printer for printing is done from a separate process, +# this to ensure that the PIL does not block the serial printing. + +import sys +import time +import os + +from Cura.util import machineCom + +class serialComm(object): + def __init__(self, portName): + self._comm = None + self._gcodeList = [] + + self._comm = machineCom.MachineCom(portName, callbackObject=self) + + def mcLog(self, message): + sys.stdout.write('log:%s\n' % (message)) + + def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp): + sys.stdout.write('temp:%s\n' % str(temp)) + + def mcStateChange(self, state): + if self._comm is None: + return + sys.stdout.write('state:%d:%s\n' % (state, self._comm.getStateString())) + + def mcMessage(self, message): + sys.stdout.write('message:%s\n' % (message)) + + def mcProgress(self, lineNr): + sys.stdout.write('progress:%d\n' % (lineNr)) + + def mcZChange(self, newZ): + sys.stdout.write('changeZ:%d\n' % (newZ)) + + def monitorStdin(self): + + while not self._comm.isClosed(): + line = sys.stdin.readline().strip() + line = line.split(':', 1) + if line[0] == 'STOP': + self._comm.cancelPrint() + self._gcodeList = [] + elif line[0] == 'G': + self._gcodeList.append(line[1]) + elif line[0] == 'START': + self._comm.printGCode(self._gcodeList) + else: + sys.stderr.write(str(line)) + +def main(): + if len(sys.argv) != 2: + return + sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) + portName = sys.argv[1] + comm = serialComm(portName) + comm.monitorStdin() + +if __name__ == '__main__': + main() diff --git a/Cura/util/machineCom.py b/Cura/util/machineCom.py index f04a21de..0385f33e 100644 --- a/Cura/util/machineCom.py +++ b/Cura/util/machineCom.py @@ -230,13 +230,16 @@ class MachineCom(object): return "?%d?" % (self._state) def getShortErrorString(self): - if len(self._errorValue) < 30: + if len(self._errorValue) < 35: return self._errorValue - return self._errorValue[:30] + "..." + return self._errorValue[:35] + "..." def getErrorString(self): return self._errorValue - + + def isClosed(self): + return self._state == self.STATE_CLOSED_WITH_ERROR or self._state == self.STATE_CLOSED + def isClosedOrError(self): return self._state == self.STATE_ERROR or self._state == self.STATE_CLOSED_WITH_ERROR or self._state == self.STATE_CLOSED diff --git a/Cura/util/objectScene.py b/Cura/util/objectScene.py index 6a0e9375..757f063d 100644 --- a/Cura/util/objectScene.py +++ b/Cura/util/objectScene.py @@ -241,6 +241,8 @@ class Scene(object): def checkPlatform(self, obj): area = obj._printAreaHull + obj.getPosition() + if obj.getSize()[2] > self._machineSize[2]: + return False if not polygon.fullInside(area, self._machinePolygons[0]): return False #Check the "no go zones" diff --git a/Cura/util/printerConnection/doodle3dConnect.py b/Cura/util/printerConnection/doodle3dConnect.py index 918ee7a0..ad3ea51b 100644 --- a/Cura/util/printerConnection/doodle3dConnect.py +++ b/Cura/util/printerConnection/doodle3dConnect.py @@ -34,12 +34,6 @@ class doodle3dConnectionGroup(printerConnectionBase.printerConnectionGroup): def getPriority(self): return 100 - def __cmp__(self, other): - return self.getPriority() - other.getPriority() - - def __repr__(self): - return self.name - def _doodle3DThread(self): self._waitDelay = 0 while True: diff --git a/Cura/util/printerConnection/dummyConnection.py b/Cura/util/printerConnection/dummyConnection.py index 98c7f94b..be3c57ea 100644 --- a/Cura/util/printerConnection/dummyConnection.py +++ b/Cura/util/printerConnection/dummyConnection.py @@ -16,6 +16,12 @@ class dummyConnectionGroup(printerConnectionBase.printerConnectionGroup): def getAvailableConnections(self): return self._list + def getIconID(self): + return 5 + + def getPriority(self): + return -100 + #Dummy printer class which is always class dummyConnection(printerConnectionBase.printerConnectionBase): def __init__(self, name): diff --git a/Cura/util/printerConnection/printerConnectionBase.py b/Cura/util/printerConnection/printerConnectionBase.py index ab556a5f..e0870669 100644 --- a/Cura/util/printerConnection/printerConnectionBase.py +++ b/Cura/util/printerConnection/printerConnectionBase.py @@ -16,13 +16,13 @@ class printerConnectionGroup(object): return 5 def getPriority(self): - return -1 + return -100 def __cmp__(self, other): return self.getPriority() - other.getPriority() def __repr__(self): - return self.name + return '%s %d' % (self._name, self.getPriority()) #Base class for different printer connection implementations. # A printer connection can connect to printers in different ways, trough network, USB or carrier pigeons. diff --git a/Cura/util/printerConnection/printerConnectionManager.py b/Cura/util/printerConnection/printerConnectionManager.py index 62f77df6..0e00c2cb 100644 --- a/Cura/util/printerConnection/printerConnectionManager.py +++ b/Cura/util/printerConnection/printerConnectionManager.py @@ -2,6 +2,7 @@ __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AG from Cura.util import version from Cura.util.printerConnection import dummyConnection +from Cura.util.printerConnection import serialConnection from Cura.util.printerConnection import doodle3dConnect class PrinterConnectionManager(object): @@ -9,6 +10,7 @@ class PrinterConnectionManager(object): self._groupList = [] if version.isDevVersion(): self._groupList.append(dummyConnection.dummyConnectionGroup()) + self._groupList.append(serialConnection.serialConnectionGroup()) self._groupList.append(doodle3dConnect.doodle3dConnectionGroup()) #Sort the connections by highest priority first. diff --git a/Cura/util/printerConnection/serialConnection.py b/Cura/util/printerConnection/serialConnection.py new file mode 100644 index 00000000..83cce9c4 --- /dev/null +++ b/Cura/util/printerConnection/serialConnection.py @@ -0,0 +1,144 @@ +__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License" + +import threading +import time +import platform +import os +import sys +import subprocess + +from Cura.util import machineCom +from Cura.util.printerConnection import printerConnectionBase + +class serialConnectionGroup(printerConnectionBase.printerConnectionGroup): + def __init__(self): + super(serialConnectionGroup, self).__init__("USB") + self._connectionMap = {} + + def getAvailableConnections(self): + serialList = machineCom.serialList(True) + for port in machineCom.serialList(True): + if port not in self._connectionMap: + self._connectionMap[port] = serialConnection(port) + for key in self._connectionMap.keys(): + if key not in serialList and not self._connectionMap[key].isActiveConnectionOpen(): + self._connectionMap[key] = None + return self._connectionMap.values() + + def getIconID(self): + return 6 + + def getPriority(self): + return 50 + +class serialConnection(printerConnectionBase.printerConnectionBase): + def __init__(self, port): + super(serialConnection, self).__init__(port) + self._portName = port + + self._process = None + self._thread = None + + self._lineCount = 0 + self._commState = None + self._commStateString = None + self._gcodeData = [] + + #Load the data into memory for printing, returns True on success + def loadGCodeData(self, dataStream): + if self.isPrinting() is None: + return False + self._gcodeData = [] + for line in dataStream: + #Strip out comments, we do not need to send comments + if ';' in line: + line = line[:line.index(';')] + #Strip out whitespace at the beginning/end this saves data to send. + line = line.strip() + + if len(line) < 1: + continue + self._gcodeData.append(line) + return True + + #Start printing the previously loaded file + def startPrint(self): + if self.isPrinting() or len(self._gcodeData) < 1 or self._process is None: + return + self._process.stdin.write('STOP\n') + for line in self._gcodeData: + self._process.stdin.write('G:%s\n' % (line)) + self._process.stdin.write('START\n') + + #Abort the previously loaded print file + def cancelPrint(self): + pass + + def isPrinting(self): + return self._commState == machineCom.MachineCom.STATE_PRINTING + + #Amount of progression of the current print file. 0.0 to 1.0 + def getPrintProgress(self): + if self._lineCount < 1: + return 0.0 + return float(self._progressLine) / float(self._lineCount) + + # Return if the printer with this connection type is available + def isAvailable(self): + return True + + # Get the connection status string. This is displayed to the user and can be used to communicate + # various information to the user. + def getStatusString(self): + return "%s" % (self._commStateString) + + #Returns true if we need to establish an active connection. True for serial connections. + def hasActiveConnection(self): + return True + + #Open the active connection to the printer so we can send commands + def openActiveConnection(self): + self.closeActiveConnection() + self._thread = threading.Thread(target=self._serialCommunicationThread) + self._thread.daemon = True + self._thread.start() + + #Close the active connection to the printer + def closeActiveConnection(self): + if self._process is not None: + self._process.terminate() + self._thread.join() + + #Is the active connection open right now. + def isActiveConnectionOpen(self): + if self._process is None: + return False + return self._commState == machineCom.MachineCom.STATE_OPERATIONAL or self._commState == machineCom.MachineCom.STATE_PRINTING or self._commState == machineCom.MachineCom.STATE_PAUSED + + def _serialCommunicationThread(self): + if platform.system() == "Darwin" and hasattr(sys, 'frozen'): + cmdList = [os.path.join(os.path.dirname(sys.executable), 'Cura')] + else: + cmdList = [sys.executable, '-m', 'Cura.serialCommunication'] + cmdList += [self._portName] + if platform.system() == "Darwin": + if platform.machine() == 'i386': + cmdList = ['arch', '-i386'] + cmdList + self._process = subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + line = self._process.stdout.readline() + while len(line) > 0: + line = line.strip() + line = line.split(':', 1) + if line[0] == 'log': + pass + elif line[0] == 'temp': + pass + elif line[0] == 'state': + line = line[1].split(':', 1) + self._commState = int(line[0]) + self._commStateString = line[1] + self._doCallback() + else: + print line + line = self._process.stdout.readline() + self._process = None diff --git a/Cura/util/sliceEngine.py b/Cura/util/sliceEngine.py index c35bad02..12388e76 100644 --- a/Cura/util/sliceEngine.py +++ b/Cura/util/sliceEngine.py @@ -257,7 +257,7 @@ class Engine(object): extruderCount = max(extruderCount, profile.minimalExtruderCount()) - commandList = [getEngineFilename(), '-vvv'] + commandList = [getEngineFilename(), '-v', '-p'] for k, v in self._engineSettings(extruderCount).iteritems(): commandList += ['-s', '%s=%s' % (k, str(v))] commandList += ['-g', '%d' % self._serverPortNr]