diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index e2908c9f97..319fa60758 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -79,6 +79,7 @@ class CuraApplication(QtApplication): self._platform_activity = False self.activeMachineChanged.connect(self._onActiveMachineChanged) + self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity) Preferences.getInstance().addPreference("cura/active_machine", "") Preferences.getInstance().addPreference("cura/active_mode", "simple") @@ -214,24 +215,15 @@ class CuraApplication(QtApplication): def getPlatformActivity(self): return self._platform_activity - @pyqtSlot(bool) - def setPlatformActivity(self, activity): - ##Sets the _platform_activity variable on true or false depending on whether there is a mesh on the platform - if activity == True: - self._platform_activity = activity - elif activity == False: - nodes = [] - for node in DepthFirstIterator(self.getController().getScene().getRoot()): - if type(node) is not SceneNode or not node.getMeshData(): - continue - nodes.append(node) - i = 0 - for node in nodes: - if not node.getMeshData(): - continue - i += 1 - if i <= 1: ## i == 0 when the meshes are removed using the deleteAll function; i == 1 when the last remaining mesh is removed using the deleteObject function - self._platform_activity = activity + def updatePlatformActivity(self, node = None): + count = 0 + for node in DepthFirstIterator(self.getController().getScene().getRoot()): + if type(node) is not SceneNode or not node.getMeshData(): + continue + + count += 1 + + self._platform_activity = True if count > 0 else False self.activityChanged.emit() ## Remove an object from the scene @@ -252,8 +244,7 @@ class CuraApplication(QtApplication): group_node = group_node.getParent() op = RemoveSceneNodeOperation(group_node) op.push() - self.setPlatformActivity(False) - + ## Create a number of copies of existing object. @pyqtSlot("quint64", int) def multiplyObject(self, object_id, count): @@ -300,8 +291,7 @@ class CuraApplication(QtApplication): op.addOperation(RemoveSceneNodeOperation(node)) op.push() - self.setPlatformActivity(False) - + ## Reset all translation on nodes with mesh data. @pyqtSlot() def resetAllTranslation(self): diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index 993f15c42a..ca3904bc4e 100644 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -15,16 +15,21 @@ import os import struct import math from os import listdir -import untangle import zipfile +import xml.etree.ElementTree as ET ## Base implementation for reading 3MF files. Has no support for textures. Only loads meshes! class ThreeMFReader(MeshReader): def __init__(self): super(ThreeMFReader, self).__init__() self._supported_extension = ".3mf" - + + self._namespaces = { + "3mf": "http://schemas.microsoft.com/3dmanufacturing/core/2015/02", + "cura": "http://software.ultimaker.com/xml/cura/3mf/2015/10" + } + def read(self, file_name): result = None extension = os.path.splitext(file_name)[1] @@ -33,32 +38,39 @@ class ThreeMFReader(MeshReader): # The base object of 3mf is a zipped archive. archive = zipfile.ZipFile(file_name, 'r') try: - # The model is always stored in this place. - root = untangle.parse(archive.read("3D/3dmodel.model").decode("utf-8")) - for object in root.model.resources.object: # There can be multiple objects, try to load all of them. + root = ET.parse(archive.open("3D/3dmodel.model")) + + # There can be multiple objects, try to load all of them. + objects = root.findall("./3mf:resources/3mf:object", self._namespaces) + for object in objects: mesh = MeshData() node = SceneNode() vertex_list = [] - for vertex in object.mesh.vertices.vertex: - vertex_list.append([vertex['x'],vertex['y'],vertex['z']]) + #for vertex in object.mesh.vertices.vertex: + for vertex in object.findall(".//3mf:vertex", self._namespaces): + vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")]) + + triangles = object.findall(".//3mf:triangle", self._namespaces) + + mesh.reserveFaceCount(len(triangles)) - mesh.reserveFaceCount(len(object.mesh.triangles.triangle)) - - for triangle in object.mesh.triangles.triangle: - v1 = int(triangle["v1"]) - v2 = int(triangle["v2"]) - v3 = int(triangle["v3"]) + #for triangle in object.mesh.triangles.triangle: + for triangle in triangles: + v1 = int(triangle.get("v1")) + v2 = int(triangle.get("v2")) + v3 = int(triangle.get("v3")) mesh.addFace(vertex_list[v1][0],vertex_list[v1][1],vertex_list[v1][2],vertex_list[v2][0],vertex_list[v2][1],vertex_list[v2][2],vertex_list[v3][0],vertex_list[v3][1],vertex_list[v3][2]) #TODO: We currently do not check for normals and simply recalculate them. mesh.calculateNormals() node.setMeshData(mesh) node.setSelectable(True) - # Magical python comprehension; looks for the matching transformation - transformation = next((x for x in root.model.build.item if x["objectid"] == object["id"]), None) - - if transformation["transform"]: - splitted_transformation = transformation["transform"].split() + transformation = root.findall("./3mf:build/3mf:item[@objectid='{0}']".format(object.get("id")), self._namespaces) + if transformation: + transformation = transformation[0] + + if transformation.get("transform"): + splitted_transformation = transformation.get("transform").split() ## Transformation is saved as: ## M00 M01 M02 0.0 ## M10 M11 M12 0.0 @@ -99,10 +111,10 @@ class ThreeMFReader(MeshReader): rotation = Quaternion.fromAngleAxis(-0.5 * math.pi, Vector(1,0,0)) node.rotate(rotation) result.addChild(node) - - # If there is more then one object, group them. + + #If there is more then one object, group them. try: - if len(root.model.resources.object) > 1: + if len(objects) > 1: group_decorator = GroupDecorator() result.addDecorator(group_decorator) except: diff --git a/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py b/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py index 2728dfd90b..9c9afa4246 100644 --- a/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py +++ b/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py @@ -17,8 +17,8 @@ class RemovableDriveOutputDevice(OutputDevice): super().__init__(device_id) self.setName(device_name) - self.setShortDescription(catalog.i18nc("", "Save to Removable Drive")) - self.setDescription(catalog.i18nc("", "Save to Removable Drive {0}").format(device_name)) + self.setShortDescription(catalog.i18nc("@action:button", "Save to Removable Drive")) + self.setDescription(catalog.i18nc("@info:tooltip", "Save to Removable Drive {0}").format(device_name)) self.setIconName("save_sd") self.setPriority(1) @@ -49,7 +49,7 @@ class RemovableDriveOutputDevice(OutputDevice): job.progress.connect(self._onProgress) job.finished.connect(self._onFinished) - message = Message(catalog.i18nc("", "Saving to Removable Drive {0}").format(self.getName()), 0, False, -1) + message = Message(catalog.i18nc("@info:status", "Saving to Removable Drive {0}").format(self.getName()), 0, False, -1) message.show() job._message = message diff --git a/plugins/USBPrinting/PrinterConnection.py b/plugins/USBPrinting/PrinterConnection.py index 0dfab4c1b9..587dd856f1 100644 --- a/plugins/USBPrinting/PrinterConnection.py +++ b/plugins/USBPrinting/PrinterConnection.py @@ -63,8 +63,8 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): self._listen_thread.daemon = True self._update_firmware_thread = threading.Thread(target= self._updateFirmware) - self._update_firmware_thread.demon = True - + self._update_firmware_thread.deamon = True + self._heatup_wait_start_time = time.time() ## Queue for commands that need to be send. Used when command is sent when a print is active. @@ -222,6 +222,8 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): self.setProgress(100, 100) + self.firmwareUpdateComplete.emit() + ## Upload new firmware to machine # \param filename full path of firmware file to be uploaded def updateFirmware(self, file_name): @@ -277,9 +279,9 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): if line is None: self.setIsConnected(False) # Something went wrong with reading, could be that close was called. return + if b"T:" in line: self._serial.timeout = 0.5 - self._sendCommand("M105") sucesfull_responses += 1 if sucesfull_responses >= self._required_responses_auto_baud: self._serial.timeout = 2 #Reset serial timeout @@ -287,6 +289,8 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): Logger.log("i", "Established printer connection on port %s" % self._serial_port) return + self._sendCommand("M105") # Send M105 as long as we are listening, otherwise we end up in an undefined state + Logger.log("e", "Baud rate detection for %s failed", self._serial_port) self.close() # Unable to connect, wrap up. self.setIsConnected(False) @@ -319,6 +323,9 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): except Exception as e: pass # This should work, but it does fail sometimes for some reason + self._connect_thread = threading.Thread(target=self._connect) + self._connect_thread.daemon = True + if self._serial is not None: self.setIsConnected(False) try: @@ -327,6 +334,8 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): pass self._serial.close() + self._listen_thread = threading.Thread(target=self._listen) + self._listen_thread.daemon = True self._serial = None def isConnected(self): @@ -579,3 +588,10 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): def _getBaudrateList(self): ret = [250000, 230400, 115200, 57600, 38400, 19200, 9600] return ret + + def _onFirmwareUpdateComplete(self): + self._update_firmware_thread.join() + self._update_firmware_thread = threading.Thread(target= self._updateFirmware) + self._update_firmware_thread.deamon = True + + self.connect() diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 0bdc9586bd..4cc7e34b6f 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -481,7 +481,6 @@ UM.MainWindow { onAccepted: { UM.MeshFileHandler.readLocalFile(fileUrl) - Printer.setPlatformActivity(true) } }