From a84a5d784ae6eb44e71540368bf11dadc06d837b Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 15 Jul 2015 10:11:19 +0200 Subject: [PATCH 1/9] Fix the platform activity check Contributes to #128 --- cura/CuraApplication.py | 34 ++++++++++++---------------------- resources/qml/Cura.qml | 2 -- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 3c63d07b4f..8c55e598a1 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -77,6 +77,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") @@ -223,24 +224,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 @@ -254,8 +246,7 @@ class CuraApplication(QtApplication): if object: op = RemoveSceneNodeOperation(object) op.push() - self.setPlatformActivity(False) - + ## Create a number of copies of existing object. @pyqtSlot("quint64", int) def multiplyObject(self, object_id, count): @@ -302,8 +293,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/resources/qml/Cura.qml b/resources/qml/Cura.qml index f8e80367eb..6f8a39b53a 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -43,7 +43,6 @@ UM.MainWindow { } onTriggered: { UM.MeshFileHandler.readLocalFile(modelData); - Printer.setPlatformActivity(true) } } onObjectAdded: fileMenu.insertItem(index, object) @@ -415,7 +414,6 @@ UM.MainWindow { onAccepted: { UM.MeshFileHandler.readLocalFile(fileUrl) - Printer.setPlatformActivity(true) } } From 9101415db6d37fa400125897b9ddec9af31cb791 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 22 Jul 2015 09:39:33 +0200 Subject: [PATCH 2/9] support defaults to ZigZag instead of Lines --- resources/settings/fdmprinter.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/settings/fdmprinter.json b/resources/settings/fdmprinter.json index 4067f40c77..3d28e289c2 100644 --- a/resources/settings/fdmprinter.json +++ b/resources/settings/fdmprinter.json @@ -893,7 +893,7 @@ "Lines", "ZigZag" ], - "default": "Lines", + "default": "ZigZag", "visible": true, "active_if": { "setting": "support_enable", From b22b79dcb6038952fa48db979f665eb07f9043ac Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 27 Jul 2015 16:38:42 +0200 Subject: [PATCH 3/9] Bump version --- cura/CuraApplication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 8c55e598a1..dae459ca06 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -53,7 +53,7 @@ class CuraApplication(QtApplication): if not hasattr(sys, "frozen"): Resources.addResourcePath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..")) - super().__init__(name = "cura", version = "15.06.02") + super().__init__(name = "cura", version = "15.06.03") self.setWindowIcon(QIcon(Resources.getPath(Resources.ImagesLocation, "cura-icon.png"))) From b4596b1ae88a2487c883ad15df39a440b3d4f04e Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 27 Jul 2015 16:45:44 +0200 Subject: [PATCH 4/9] Correct the icon for the Cura .desktop file Contributes to Ultimaker/Uranium#42 --- cura.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura.desktop b/cura.desktop index d325dde621..9ce843be93 100644 --- a/cura.desktop +++ b/cura.desktop @@ -5,7 +5,7 @@ GenericName=3D Printing Software Comment= Exec=/usr/bin/cura_app.py TryExec=/usr/bin/cura_app.py -Icon=/usr/share/cura/resources/images/cura_icon.png +Icon=/usr/share/cura/resources/images/cura-icon.png Terminal=false Type=Application Categories=Graphics; From 9d3b36e3ae7091387af9726ac3a83e02ba6c34a5 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Mon, 27 Jul 2015 18:50:10 +0200 Subject: [PATCH 5/9] Fix USB printer detection and reconnect after firmware update on MacOSX Rather than sending an M105 only after receiving a line with T:, always send M105 when we are trying to detect baudrate, otherwise we can end up in an undefined state. Contributes to #82 --- plugins/USBPrinting/PrinterConnection.py | 40 +++++++++++++++--------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/plugins/USBPrinting/PrinterConnection.py b/plugins/USBPrinting/PrinterConnection.py index cf020c4f49..e07dfe8e8d 100644 --- a/plugins/USBPrinting/PrinterConnection.py +++ b/plugins/USBPrinting/PrinterConnection.py @@ -41,7 +41,7 @@ class PrinterConnection(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() @@ -85,6 +85,10 @@ class PrinterConnection(SignalEmitter): self._updating_firmware = False self._firmware_file_name = None + + self.firmwareUpdateComplete.connect(self._onFirmwareUpdateComplete) + + firmwareUpdateComplete = Signal() # TODO: Might need to add check that extruders can not be changed when it started printing or loading these settings from settings object def setNumExtuders(self, num): @@ -162,6 +166,8 @@ class PrinterConnection(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): @@ -187,9 +193,11 @@ class PrinterConnection(SignalEmitter): Logger.log("i", "Could not establish connection on %s, unknown reasons.", self._serial_port) return + Logger.log("d", "Starting baud rate detection...") # If the programmer connected, we know its an atmega based version. Not all that usefull, but it does give some debugging information. for baud_rate in self._getBaudrateList(): # Cycle all baud rates (auto detect) - + Logger.log("d", "Trying baud rate %s", baud_rate) + if self._serial is None: try: self._serial = serial.Serial(str(self._serial_port), baud_rate, timeout=3, writeTimeout=10000) @@ -210,10 +218,9 @@ class PrinterConnection(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 @@ -221,6 +228,8 @@ class PrinterConnection(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) @@ -240,15 +249,6 @@ class PrinterConnection(SignalEmitter): self.connectionStateChanged.emit(self._serial_port) if self._is_connected: self._listen_thread.start() #Start listening - #Application.getInstance().addOutputDevice(self._serial_port, { - #"id": self._serial_port, - #"function": self.printGCode, - #"shortDescription": "Print with USB", - #"description": "Print with USB {0}".format(self._serial_port), - #"icon": "save", - #"priority": 1 - #}) - else: Logger.log("w", "Printer connection state was not changed") @@ -261,6 +261,9 @@ class PrinterConnection(SignalEmitter): self._connect_thread.join() except: pass + + self._connect_thread = threading.Thread(target=self._connect) + self._connect_thread.daemon = True if self._serial is not None: self.setIsConnected(False) try: @@ -268,7 +271,9 @@ class PrinterConnection(SignalEmitter): except: pass self._serial.close() - + + self._listen_thread = threading.Thread(target=self._listen) + self._listen_thread.daemon = True self._serial = None def isConnected(self): @@ -495,3 +500,10 @@ class PrinterConnection(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() From d931f6734a9102b730022d3b0f49db3f51d3d493 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Sun, 2 Aug 2015 13:24:28 +0200 Subject: [PATCH 6/9] Fix poor visibility of MessageStack against grid --- resources/themes/cura/theme.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/themes/cura/theme.json b/resources/themes/cura/theme.json index 5b247045b8..c9d534bb75 100644 --- a/resources/themes/cura/theme.json +++ b/resources/themes/cura/theme.json @@ -120,7 +120,7 @@ "save_button_printtime_text": [12, 169, 227, 255], "save_button_background": [249, 249, 249, 255], - "message": [205, 202, 201, 255], + "message": [160, 163, 171, 255], "message_text": [35, 35, 35, 255], "tool_panel_background": [255, 255, 255, 255] From 85a13b7ad7d10c5ff431090cb1de63b84e68ff08 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 18 Aug 2015 15:18:56 +0200 Subject: [PATCH 7/9] Add proper translation contexts to RemovableDriveOutputDevice --- .../RemovableDriveOutputDevice.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From a2ecae7d2cb85233accf0197af749698f6876526 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 18 Aug 2015 17:15:35 +0200 Subject: [PATCH 8/9] Port 3MFReader to ETree --- plugins/3MFReader/ThreeMFReader.py | 54 ++++++++++++++++++------------ 1 file changed, 33 insertions(+), 21 deletions(-) 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: From 7e2b89ec54217a2a07e6eb5c83cb300cf4303041 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 18 Aug 2015 18:08:44 +0200 Subject: [PATCH 9/9] Fix a merge issue with USBPrinting --- plugins/USBPrinting/PrinterConnection.py | 31 ++---------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/plugins/USBPrinting/PrinterConnection.py b/plugins/USBPrinting/PrinterConnection.py index 44c5ec1ce2..356bbcd619 100644 --- a/plugins/USBPrinting/PrinterConnection.py +++ b/plugins/USBPrinting/PrinterConnection.py @@ -105,7 +105,6 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): self._firmware_file_name = None -<<<<<<< HEAD self._control_view = None onError = pyqtSignal() @@ -129,12 +128,6 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): def error(self): return self._error_state -======= - self.firmwareUpdateComplete.connect(self._onFirmwareUpdateComplete) - - firmwareUpdateComplete = Signal() - ->>>>>>> 15.06 # TODO: Might need to add check that extruders can not be changed when it started printing or loading these settings from settings object def setNumExtuders(self, num): self._extruder_count = num @@ -242,18 +235,9 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): self._is_connecting = False Logger.log("i", "Could not establish connection on %s, unknown reasons.", self._serial_port) return -<<<<<<< HEAD # If the programmer connected, we know its an atmega based version. Not all that usefull, but it does give some debugging information. for baud_rate in self._getBaudrateList(): # Cycle all baud rates (auto detect) -======= - - Logger.log("d", "Starting baud rate detection...") - # If the programmer connected, we know its an atmega based version. Not all that usefull, but it does give some debugging information. - for baud_rate in self._getBaudrateList(): # Cycle all baud rates (auto detect) - Logger.log("d", "Trying baud rate %s", baud_rate) - ->>>>>>> 15.06 if self._serial is None: try: self._serial = serial.Serial(str(self._serial_port), baud_rate, timeout = 3, writeTimeout = 10000) @@ -273,10 +257,7 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): if line is None: self.setIsConnected(False) # Something went wrong with reading, could be that close was called. return -<<<<<<< HEAD -======= - ->>>>>>> 15.06 + if b"T:" in line: self._serial.timeout = 0.5 sucesfull_responses += 1 @@ -317,17 +298,12 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): if self._connect_thread.isAlive(): try: self._connect_thread.join() -<<<<<<< HEAD except Exception as e: pass # This should work, but it does fail sometimes for some reason -======= - except: - pass - self._connect_thread = threading.Thread(target=self._connect) self._connect_thread.daemon = True ->>>>>>> 15.06 + if self._serial is not None: self.setIsConnected(False) try: @@ -336,11 +312,8 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): pass self._serial.close() -<<<<<<< HEAD -======= self._listen_thread = threading.Thread(target=self._listen) self._listen_thread.daemon = True ->>>>>>> 15.06 self._serial = None def isConnected(self):