From 32de1b533d4b945a2d62543400a8542b9d39898e Mon Sep 17 00:00:00 2001 From: Tamara Hogenhout Date: Mon, 26 Oct 2015 16:35:27 +0100 Subject: [PATCH 01/40] Adds an SVG checksign Because Roboto does not have a checksign in its charachter set Contributes to #CURA-244 --- resources/themes/cura/icons/check.svg | 50 +++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 resources/themes/cura/icons/check.svg diff --git a/resources/themes/cura/icons/check.svg b/resources/themes/cura/icons/check.svg new file mode 100644 index 0000000000..dac3cd1618 --- /dev/null +++ b/resources/themes/cura/icons/check.svg @@ -0,0 +1,50 @@ + + + +image/svg+xml \ No newline at end of file From a6602967685feee946d89d67bebd61aca6533d64 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 27 Oct 2015 15:25:40 +0100 Subject: [PATCH 02/40] Use the skirt size to expand the disallowed area size and add disallowed areas for the borders CURA-138 #Ready-for-Review --- cura/BuildVolume.py | 142 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 117 insertions(+), 25 deletions(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index c745ba168e..33f75df080 100644 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -34,10 +34,14 @@ class BuildVolume(SceneNode): self.setCalculateBoundingBox(False) + self._active_profile = None self._active_instance = None Application.getInstance().getMachineManager().activeMachineInstanceChanged.connect(self._onActiveInstanceChanged) self._onActiveInstanceChanged() + Application.getInstance().getMachineManager().activeProfileChanged.connect(self._onActiveProfileChanged) + self._onActiveProfileChanged() + def setWidth(self, width): if width: self._width = width @@ -72,7 +76,7 @@ class BuildVolume(SceneNode): renderer.queueNode(self, material = self._material, mode = Renderer.RenderLines) renderer.queueNode(self, mesh = self._grid_mesh, material = self._grid_material, force_single_sided = True) if self._disallowed_area_mesh: - renderer.queueNode(self, mesh = self._disallowed_area_mesh, material = self._material) + renderer.queueNode(self, mesh = self._disallowed_area_mesh, material = self._material, transparent = True) return True def rebuild(self): @@ -117,18 +121,20 @@ class BuildVolume(SceneNode): v = self._grid_mesh.getVertex(n) self._grid_mesh.setVertexUVCoordinates(n, v[0], v[2]) + disallowed_area_height = 0.2 disallowed_area_size = 0 if self._disallowed_areas: mb = MeshBuilder() + color = Color(0.0, 0.0, 0.0, 0.15) for polygon in self._disallowed_areas: points = polygon.getPoints() - mb.addQuad( - Vector(points[0, 0], 0.1, points[0, 1]), - Vector(points[1, 0], 0.1, points[1, 1]), - Vector(points[2, 0], 0.1, points[2, 1]), - Vector(points[3, 0], 0.1, points[3, 1]), - color = Color(174, 174, 174, 255) - ) + first = Vector(self._clamp(points[0][0], minW, maxW), disallowed_area_height, self._clamp(points[0][1], minD, maxD)) + previous_point = Vector(self._clamp(points[0][0], minW, maxW), disallowed_area_height, self._clamp(points[0][1], minD, maxD)) + for point in points: + new_point = Vector(self._clamp(point[0], minW, maxW), disallowed_area_height, self._clamp(point[1], minD, maxD)) + mb.addFace(first, previous_point, new_point, color = color) + previous_point = new_point + # Find the largest disallowed area to exclude it from the maximum scale bounds size = abs(numpy.max(points[:, 1]) - numpy.min(points[:, 1])) disallowed_area_size = max(size, disallowed_area_size) @@ -141,16 +147,9 @@ class BuildVolume(SceneNode): skirt_size = 0.0 - #profile = Application.getInstance().getMachineManager().getActiveProfile() - #if profile: - #if profile.getSettingValue("adhesion_type") == "skirt": - #skirt_size = profile.getSettingValue("skirt_line_count") * profile.getSettingValue("skirt_line_width") + profile.getSettingValue("skirt_gap") - #elif profile.getSettingValue("adhesion_type") == "brim": - #skirt_size = profile.getSettingValue("brim_line_count") * profile.getSettingValue("skirt_line_width") - #else: - #skirt_size = profile.getSettingValue("skirt_line_width") - - #skirt_size += profile.getSettingValue("skirt_line_width") + profile = Application.getInstance().getMachineManager().getActiveProfile() + if profile: + skirt_size = self._getSkirtSize(profile) scale_to_max_bounds = AxisAlignedBox( minimum = Vector(minW + skirt_size, minH, minD + skirt_size + disallowed_area_size), @@ -167,12 +166,105 @@ class BuildVolume(SceneNode): self._height = self._active_instance.getMachineSettingValue("machine_height") self._depth = self._active_instance.getMachineSettingValue("machine_depth") - disallowed_areas = self._active_instance.getMachineSettingValue("machine_disallowed_areas") - areas = [] - if disallowed_areas: - for area in disallowed_areas: - areas.append(Polygon(numpy.array(area, numpy.float32))) - - self._disallowed_areas = areas + self._updateDisallowedAreas() self.rebuild() + + def _onActiveProfileChanged(self): + if self._active_profile: + self._active_profile.settingValueChanged.disconnect(self._onSettingValueChanged) + + self._active_profile = Application.getInstance().getMachineManager().getActiveProfile() + if self._active_profile: + self._active_profile.settingValueChanged.connect(self._onSettingValueChanged) + self._updateDisallowedAreas() + self.rebuild() + + def _onSettingValueChanged(self, setting): + if setting in self._skirt_settings: + self._updateDisallowedAreas() + self.rebuild() + + def _updateDisallowedAreas(self): + disallowed_areas = self._active_instance.getMachineSettingValue("machine_disallowed_areas") + areas = [] + + skirt_size = 0.0 + if self._active_profile: + skirt_size = self._getSkirtSize(self._active_profile) + + if disallowed_areas: + for area in disallowed_areas: + poly = Polygon(numpy.array(area, numpy.float32)) + poly = poly.getMinkowskiHull(Polygon(numpy.array([ + [-skirt_size, 0], + [-skirt_size * 0.707, skirt_size * 0.707], + [0, skirt_size], + [skirt_size * 0.707, skirt_size * 0.707], + [skirt_size, 0], + [skirt_size * 0.707, -skirt_size * 0.707], + [0, -skirt_size], + [-skirt_size * 0.707, -skirt_size * 0.707] + ], numpy.float32))) + + areas.append(poly) + + if skirt_size > 0: + half_machine_width = self._active_instance.getMachineSettingValue("machine_width") / 2 + half_machine_depth = self._active_instance.getMachineSettingValue("machine_depth") / 2 + + areas.append(Polygon(numpy.array([ + [-half_machine_width, -half_machine_depth], + [-half_machine_width, half_machine_depth], + [-half_machine_width + skirt_size, half_machine_depth - skirt_size], + [-half_machine_width + skirt_size, -half_machine_depth + skirt_size] + ], numpy.float32))) + + areas.append(Polygon(numpy.array([ + [half_machine_width, half_machine_depth], + [half_machine_width, -half_machine_depth], + [half_machine_width - skirt_size, -half_machine_depth + skirt_size], + [half_machine_width - skirt_size, half_machine_depth - skirt_size] + ], numpy.float32))) + + areas.append(Polygon(numpy.array([ + [-half_machine_width, half_machine_depth], + [half_machine_width, half_machine_depth], + [half_machine_width - skirt_size, half_machine_depth - skirt_size], + [-half_machine_width + skirt_size, half_machine_depth - skirt_size] + ], numpy.float32))) + + areas.append(Polygon(numpy.array([ + [half_machine_width, -half_machine_depth], + [-half_machine_width, -half_machine_depth], + [-half_machine_width + skirt_size, -half_machine_depth + skirt_size], + [half_machine_width - skirt_size, -half_machine_depth + skirt_size] + ], numpy.float32))) + + areas.append(poly) + + self._disallowed_areas = areas + + def _getSkirtSize(self, profile): + skirt_size = 0.0 + + adhesion_type = profile.getSettingValue("adhesion_type") + if adhesion_type == "skirt": + skirt_distance = profile.getSettingValue("skirt_gap") + skirt_line_count = profile.getSettingValue("skirt_line_count") + skirt_size = skirt_distance + (skirt_line_count * profile.getSettingValue("skirt_line_width")) + elif adhesion_type == "brim": + brim_line_count = profile.getSettingValue("brim_line_count") + skirt_size = brim_line_count * profile.getSettingValue("skirt_line_width") + elif adhesion_type == "raft": + skirt_size = profile.getSettingValue("raft_margin") + + if profile.getSettingValue("draft_shield_enabled"): + skirt_size += profile.getSettingValue("draft_shield_dist") + + return skirt_size + + def _clamp(self, value, min_value, max_value): + return max(min(value, max_value), min_value) + + _skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_line_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist"] From 6c20d418636707f9cd922d20af69998c90e9d07d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Oct 2015 16:23:00 +0100 Subject: [PATCH 03/40] Make minimum layer height 1 micron Previously the minimum layer height was 0.1 micron. This was rounded down to 0 microns in the engine, and then a check would cause the engine to return an error. 1 micron is the minimum that the engine can handle. --- resources/machines/fdmprinter.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/machines/fdmprinter.json b/resources/machines/fdmprinter.json index a28a96fd34..1b34c71e0f 100644 --- a/resources/machines/fdmprinter.json +++ b/resources/machines/fdmprinter.json @@ -112,7 +112,7 @@ "unit": "mm", "type": "float", "default": 0.1, - "min_value": "0.0001", + "min_value": "0.001", "min_value_warning": "0.04", "max_value_warning": "0.32" }, @@ -122,7 +122,7 @@ "unit": "mm", "type": "float", "default": 0.3, - "min_value": "0.0001", + "min_value": "0.001", "min_value_warning": "0.04", "max_value_warning": "0.32", "visible": false From fe9e35c062a8e8102ff98e33f6275c6fc553e311 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 29 Oct 2015 11:47:17 +0100 Subject: [PATCH 04/40] Remove invalid line that causes exceptions on startup Contributes to CURA-138 --- cura/BuildVolume.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 33f75df080..42f301fb5a 100644 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -241,8 +241,6 @@ class BuildVolume(SceneNode): [half_machine_width - skirt_size, -half_machine_depth + skirt_size] ], numpy.float32))) - areas.append(poly) - self._disallowed_areas = areas def _getSkirtSize(self, profile): From 4c0b4ae870fb8ad035dc757226d845b4066cd3e0 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 29 Oct 2015 13:54:26 +0100 Subject: [PATCH 05/40] Double check that the active instance and profile are set before updating disallowed Contributes to CURA-138 --- cura/BuildVolume.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 42f301fb5a..223f8a3693 100644 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -186,6 +186,9 @@ class BuildVolume(SceneNode): self.rebuild() def _updateDisallowedAreas(self): + if not self._active_instance or not self._active_profile: + return + disallowed_areas = self._active_instance.getMachineSettingValue("machine_disallowed_areas") areas = [] From 9bf3b2a226367387d646f15a8b3409bd76c2a02c Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 29 Oct 2015 14:29:04 +0100 Subject: [PATCH 06/40] Always show "General" page when reopening preferences --- resources/qml/Cura.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 696b37bdad..a4e81efcce 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -567,7 +567,7 @@ UM.MainWindow addMachine.onTriggered: addMachineWizard.visible = true; - preferences.onTriggered: { preferences.visible = true; } + preferences.onTriggered: { preferences.visible = true; preferences.setPage(0); } configureMachines.onTriggered: { preferences.visible = true; preferences.setPage(3); } manageProfiles.onTriggered: { preferences.visible = true; preferences.setPage(4); } From 9626a604c26e1d1461e49ac58d98eeaa5d631c23 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Fri, 30 Oct 2015 13:44:04 +0100 Subject: [PATCH 07/40] Do not inherit BaseException, but use Exception As indicated by Python docs, custom exceptions should use Exception as base. Contributes to CURA-274 --- plugins/USBPrinting/avr_isp/ispBase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/USBPrinting/avr_isp/ispBase.py b/plugins/USBPrinting/avr_isp/ispBase.py index 1ba1317f91..b7650d7290 100644 --- a/plugins/USBPrinting/avr_isp/ispBase.py +++ b/plugins/USBPrinting/avr_isp/ispBase.py @@ -58,7 +58,7 @@ class IspBase(): raise IspError("Called undefined verifyFlash") -class IspError(BaseException): +class IspError(Exception): def __init__(self, value): self.value = value From 37c977cea6ca904345eb20827d8be2f7ed0a76f6 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Fri, 30 Oct 2015 13:45:19 +0100 Subject: [PATCH 08/40] Properly end firmware update procedure and catch errors during firmware update Contributes to CURA-274 --- plugins/USBPrinting/PrinterConnection.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/plugins/USBPrinting/PrinterConnection.py b/plugins/USBPrinting/PrinterConnection.py index c78dcf1697..341fe425b1 100644 --- a/plugins/USBPrinting/PrinterConnection.py +++ b/plugins/USBPrinting/PrinterConnection.py @@ -45,7 +45,7 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): self._connect_thread.daemon = True self._end_stop_thread = threading.Thread(target = self._pollEndStop) - self._end_stop_thread.deamon = True + self._end_stop_thread.daemon = True self._poll_endstop = -1 # Printer is connected @@ -64,7 +64,8 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): self._listen_thread.daemon = True self._update_firmware_thread = threading.Thread(target= self._updateFirmware) - self._update_firmware_thread.deamon = True + self._update_firmware_thread.daemon = True + self.firmwareUpdateComplete.connect(self._onFirmwareUpdateComplete) self._heatup_wait_start_time = time.time() @@ -197,6 +198,8 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): ## Private fuction (threaded) that actually uploads the firmware. def _updateFirmware(self): + self.setProgress(0, 100) + if self._is_connecting or self._is_connected: self.close() hex_file = intelHex.readHex(self._firmware_file_name) @@ -207,7 +210,11 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): programmer = stk500v2.Stk500v2() programmer.progressCallback = self.setProgress - programmer.connect(self._serial_port) + + try: + programmer.connect(self._serial_port) + except Exception: + pass time.sleep(1) # Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases. @@ -336,8 +343,8 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): self._connect_thread = threading.Thread(target=self._connect) self._connect_thread.daemon = True + self.setIsConnected(False) if self._serial is not None: - self.setIsConnected(False) try: self._listen_thread.join() except: @@ -622,6 +629,6 @@ class PrinterConnection(OutputDevice, QObject, SignalEmitter): def _onFirmwareUpdateComplete(self): self._update_firmware_thread.join() self._update_firmware_thread = threading.Thread(target= self._updateFirmware) - self._update_firmware_thread.deamon = True + self._update_firmware_thread.daemon = True self.connect() From 67e00221ebf0f2be3a24a0746be8092b5ed654df Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Fri, 30 Oct 2015 13:46:01 +0100 Subject: [PATCH 09/40] Display a message that there are no connected printers when trying to update firmware Contributes to CURA-274 --- plugins/USBPrinting/USBPrinterManager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/USBPrinting/USBPrinterManager.py b/plugins/USBPrinting/USBPrinterManager.py index 9d9b0b4a02..c1308197f2 100644 --- a/plugins/USBPrinting/USBPrinterManager.py +++ b/plugins/USBPrinting/USBPrinterManager.py @@ -95,6 +95,10 @@ class USBPrinterManager(QObject, SignalEmitter, OutputDevicePlugin, Extension): @pyqtSlot() def updateAllFirmware(self): + if not self._printer_connections: + Message("Cannot update firmware, there were no connected printers found.").show() + return + self.spawnFirmwareInterface("") for printer_connection in self._printer_connections: try: From a1087150efeef62c301db5b2f999defa40956438 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Fri, 30 Oct 2015 13:46:31 +0100 Subject: [PATCH 10/40] Properly clean up printer connections that are no longer available. Contributes to CURA-274 --- plugins/USBPrinting/USBPrinterManager.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/USBPrinting/USBPrinterManager.py b/plugins/USBPrinting/USBPrinterManager.py index c1308197f2..1e1cd538b3 100644 --- a/plugins/USBPrinting/USBPrinterManager.py +++ b/plugins/USBPrinting/USBPrinterManager.py @@ -11,6 +11,7 @@ from UM.Logger import Logger from UM.PluginRegistry import PluginRegistry from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from UM.Qt.ListModel import ListModel +from UM.Message import Message from cura.CuraApplication import CuraApplication @@ -163,6 +164,16 @@ class USBPrinterManager(QObject, SignalEmitter, OutputDevicePlugin, Extension): continue self._serial_port_list = list(serial_ports) + connections_to_remove = [] + for port, connection in self._printer_connections.items(): + if port not in self._serial_port_list: + connection.close() + connections_to_remove.append(port) + + for port in connections_to_remove: + del self._printer_connections[port] + + ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addConnection(self, serial_port): connection = PrinterConnection.PrinterConnection(serial_port) From 7eb9845dedb0f292c837fd02cc876dd8e958c0f9 Mon Sep 17 00:00:00 2001 From: Tamara Hogenhout Date: Fri, 30 Oct 2015 16:29:20 +0100 Subject: [PATCH 11/40] temporary hack so Ruben can test the update firmware fix --- resources/qml/Cura.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index a4e81efcce..526f8a0a3b 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -356,7 +356,8 @@ UM.MainWindow left: parent.left; //leftMargin: UM.Theme.sizes.loadfile_margin.width } - action: actions.open; + //action: actions.open; + onClicked: UM.Models.extensionModel.subMenuTriggered('Firmware', 'Update Firmware') } Image From 2efcbc3c1fdd4259a19dafed66cf15e7d7b7ad19 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Sun, 1 Nov 2015 21:32:08 +0100 Subject: [PATCH 12/40] Revert "temporary hack so Ruben can test the update firmware fix" This reverts commit 7eb9845dedb0f292c837fd02cc876dd8e958c0f9. --- resources/qml/Cura.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 526f8a0a3b..a4e81efcce 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -356,8 +356,7 @@ UM.MainWindow left: parent.left; //leftMargin: UM.Theme.sizes.loadfile_margin.width } - //action: actions.open; - onClicked: UM.Models.extensionModel.subMenuTriggered('Firmware', 'Update Firmware') + action: actions.open; } Image From 8d0924849fe2bce3c94ac2739fd727afac623c83 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Sun, 1 Nov 2015 22:02:54 +0100 Subject: [PATCH 13/40] Make it possible to set Cura version from CMake This helps with automating build so we can set version from one single spot. --- CMakeLists.txt | 9 +++++++-- cura/CuraApplication.py | 7 ++++++- cura/CuraVersion.py.in | 4 ++++ 3 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 cura/CuraVersion.py.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d3d6f5c2b..d47e8b967d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,9 @@ include(GNUInstallDirs) set(URANIUM_SCRIPTS_DIR "${CMAKE_SOURCE_DIR}/../uranium/scripts" CACHE DIRECTORY "The location of the scripts directory of the Uranium repository") +set(CURA_VERSION "master" CACHE STRING "Version name of Cura") +configure_file(cura/CuraVersion.py.in CuraVersion.py @ONLY) + if(NOT ${URANIUM_SCRIPTS_DIR} STREQUAL "") # Extract Strings add_custom_target(extract-messages ${URANIUM_SCRIPTS_DIR}/extract-messages ${CMAKE_SOURCE_DIR} cura) @@ -60,9 +63,11 @@ install(DIRECTORY resources DESTINATION ${CMAKE_INSTALL_DATADIR}/cura) install(DIRECTORY plugins DESTINATION lib/cura) install(FILES cura_app.py DESTINATION ${CMAKE_INSTALL_BINDIR} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) if(NOT APPLE AND NOT WIN32) - install(DIRECTORY cura DESTINATION lib/python${PYTHON_VERSION_MAJOR}/dist-packages) + install(DIRECTORY cura DESTINATION lib/python${PYTHON_VERSION_MAJOR}/dist-packages FILES_MATCHING PATTERN *.py) + install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py DESTINATION lib/python${PYTHON_VERSION_MAJOR}/dist-packages/cura) install(FILES cura.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) else() - install(DIRECTORY cura DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages) + install(DIRECTORY cura DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages FILES_MATCHING PATTERN *.py) + install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py DESTINATION lib/python${PYTHON_VERSION_MAJOR}/site-packages/cura) endif() diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 689ae888a6..64551948dc 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -51,6 +51,11 @@ import numpy import copy numpy.seterr(all="ignore") +try: + from cura.CuraVersion import CuraVersion +except ImportError: + CuraVersion = "master" + class CuraApplication(QtApplication): class ResourceTypes: QmlFiles = Resources.UserType + 1 @@ -62,7 +67,7 @@ class CuraApplication(QtApplication): if not hasattr(sys, "frozen"): Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..")) - super().__init__(name = "cura", version = "15.09.91") + super().__init__(name = "cura", version = CuraVersion) self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png"))) diff --git a/cura/CuraVersion.py.in b/cura/CuraVersion.py.in new file mode 100644 index 0000000000..bb69319ee6 --- /dev/null +++ b/cura/CuraVersion.py.in @@ -0,0 +1,4 @@ +# Copyright (c) 2015 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +CuraVersion = "@CURA_VERSION@" From 98d2acaba7c4174a10d52b16943d9ffccc27391c Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Sun, 1 Nov 2015 22:35:07 +0100 Subject: [PATCH 14/40] Install the CuraVersion file in the right location on OSX/Win --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d47e8b967d..13cb2b5a48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,6 @@ if(NOT APPLE AND NOT WIN32) install(FILES cura.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) else() install(DIRECTORY cura DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages FILES_MATCHING PATTERN *.py) - install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py DESTINATION lib/python${PYTHON_VERSION_MAJOR}/site-packages/cura) + install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages/cura) endif() From 75b8466065195cee8618fe818265ea8251dcd015 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 3 Nov 2015 11:05:28 +0100 Subject: [PATCH 15/40] Speed up building of the layerdata mesh Use numpy copies rather than python iteration since it is far faster. Contributes to CURA-224 --- plugins/CuraEngineBackend/LayerData.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/plugins/CuraEngineBackend/LayerData.py b/plugins/CuraEngineBackend/LayerData.py index 8d20a009e0..e59abf597c 100644 --- a/plugins/CuraEngineBackend/LayerData.py +++ b/plugins/CuraEngineBackend/LayerData.py @@ -63,6 +63,7 @@ class LayerData(MeshData): offset = data.build(offset, vertices, colors, indices) self._element_counts[layer] = data.elementCount + self.clear() self.addVertices(vertices) self.addColors(colors) self.addIndices(indices.flatten()) @@ -198,18 +199,14 @@ class Polygon(): def build(self, offset, vertices, colors, indices): self._begin = offset + self._end = self._begin + len(self._data) - 1 color = self.getColor() color.setValues(color.r * 0.5, color.g * 0.5, color.b * 0.5, color.a) + color = numpy.array([color.r, color.g, color.b, color.a], numpy.float32) - for i in range(len(self._data)): - vertices[offset + i, :] = self._data[i, :] - colors[offset + i, 0] = color.r - colors[offset + i, 1] = color.g - colors[offset + i, 2] = color.b - colors[offset + i, 3] = color.a - - self._end = self._begin + len(self._data) - 1 + vertices[self._begin:self._end + 1, :] = self._data[:, :] + colors[self._begin:self._end + 1, :] = color for i in range(self._begin, self._end): indices[i, 0] = i From 653b46d8255ba3e5751c77cc30b4b7ec1413559b Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 4 Nov 2015 13:04:22 +0100 Subject: [PATCH 16/40] Properly report Layer data processing progress Contributes to CURA-224 --- .../ProcessSlicedObjectListJob.py | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py b/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py index 02dcecb80b..0a9322b7cb 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py @@ -54,6 +54,20 @@ class ProcessSlicedObjectListJob(Job): mesh = MeshData() layer_data = LayerData.LayerData() + + #Add layerdata decorator to scene node to indicate that the node has layerdata + decorator = LayerDataDecorator.LayerDataDecorator() + decorator.setLayerData(layer_data) + new_node.addDecorator(decorator) + + new_node.setMeshData(mesh) + new_node.setParent(self._scene.getRoot()) + + layer_count = 0 + for object in self._message.objects: + layer_count += len(object.layers) + + current_layer = 0 for object in self._message.objects: try: node = objectIdMap[object.id] @@ -73,23 +87,24 @@ class ProcessSlicedObjectListJob(Job): points[:,2] *= -1 - points -= numpy.array(center) + points -= center layer_data.addPolygon(layer.id, polygon.type, points, polygon.line_width) + current_layer += 1 + progress = (current_layer / layer_count) * 100 + # TODO: Rebuild the layer data mesh once the layer has been processed. + # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh. + + if self._progress: + self._progress.setProgress(progress) # We are done processing all the layers we got from the engine, now create a mesh out of the data layer_data.build() - - #Add layerdata decorator to scene node to indicate that the node has layerdata - decorator = LayerDataDecorator.LayerDataDecorator() - decorator.setLayerData(layer_data) - new_node.addDecorator(decorator) - - new_node.setMeshData(mesh) - new_node.setParent(self._scene.getRoot()) - + if self._progress: + self._progress.setProgress(100) + view = Application.getInstance().getController().getActiveView() if view.getPluginId() == "LayerView": view.resetLayerData() From 0b2f0b26042d9c119262a8f5426c0253fc824b9e Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 4 Nov 2015 13:06:40 +0100 Subject: [PATCH 17/40] Add thread yields to several long running and heavy processing jobs Contributes to CURA-358 --- cura/ConvexHullJob.py | 2 ++ plugins/3MFReader/ThreeMFReader.py | 6 ++++++ plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py | 4 +++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cura/ConvexHullJob.py b/cura/ConvexHullJob.py index 63eec87fb3..2388d1c9aa 100644 --- a/cura/ConvexHullJob.py +++ b/cura/ConvexHullJob.py @@ -31,6 +31,8 @@ class ConvexHullJob(Job): self._node.callDecoration("setConvexHullJob", None) return + Job.yieldThread() + else: if not self._node.getMeshData(): return diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index 9ca8875543..2b20954988 100644 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -10,6 +10,7 @@ from UM.Scene.SceneNode import SceneNode from UM.Scene.GroupDecorator import GroupDecorator from UM.Math.Quaternion import Quaternion +from UM.Job import Job import os import struct @@ -53,6 +54,7 @@ class ThreeMFReader(MeshReader): #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")]) + Job.yieldThread() triangles = object.findall(".//3mf:triangle", self._namespaces) @@ -64,6 +66,8 @@ class ThreeMFReader(MeshReader): 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]) + Job.yieldThread() + #TODO: We currently do not check for normals and simply recalculate them. mesh.calculateNormals() node.setMeshData(mesh) @@ -116,6 +120,8 @@ class ThreeMFReader(MeshReader): node.rotate(rotation) result.addChild(node) + Job.yieldThread() + #If there is more then one object, group them. try: if len(objects) > 1: diff --git a/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py b/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py index 0a9322b7cb..67a24c368d 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py @@ -38,10 +38,10 @@ class ProcessSlicedObjectListJob(Job): for node in DepthFirstIterator(self._scene.getRoot()): if type(node) is SceneNode and node.getMeshData(): if node.callDecoration("getLayerData"): - #if hasattr(node.getMeshData(), "layerData"): self._scene.getRoot().removeChild(node) else: objectIdMap[id(node)] = node + Job.yieldThread() settings = Application.getInstance().getMachineManager().getActiveProfile() layerHeight = settings.getSettingValue("layer_height") @@ -91,6 +91,8 @@ class ProcessSlicedObjectListJob(Job): layer_data.addPolygon(layer.id, polygon.type, points, polygon.line_width) + Job.yieldThread() + current_layer += 1 progress = (current_layer / layer_count) * 100 # TODO: Rebuild the layer data mesh once the layer has been processed. From 20b828eceeeea3d7672838d77f6a2ba65bb68c0e Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 4 Nov 2015 13:08:03 +0100 Subject: [PATCH 18/40] Add a Job subclass that handles sending data to the engine This can be used by the CuraEngine backend to reduce lag when we start slicing. Contributes to CURA-358 --- plugins/CuraEngineBackend/StartSliceJob.py | 123 +++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 plugins/CuraEngineBackend/StartSliceJob.py diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py new file mode 100644 index 0000000000..1a2dacf38a --- /dev/null +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -0,0 +1,123 @@ +# Copyright (c) 2015 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +import time +import numpy + +from UM.Job import Job +from UM.Application import Application +from UM.Logger import Logger + +from UM.Scene.SceneNode import SceneNode +from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator + +from cura.OneAtATimeIterator import OneAtATimeIterator + +from . import Cura_pb2 + +class StartSliceJob(Job): + def __init__(self, profile, socket): + super().__init__() + + self._scene = Application.getInstance().getController().getScene() + self._profile = profile + self._socket = socket + + def run(self): + self._scene.acquireLock() + + for node in DepthFirstIterator(self._scene.getRoot()): + if node.callDecoration("getLayerData"): + node.getParent().removeChild(node) + break + + object_groups = [] + if self._profile.getSettingValue("print_sequence") == "one_at_a_time": + for node in OneAtATimeIterator(self._scene.getRoot()): + temp_list = [] + + if getattr(node, "_outside_buildarea", False): + continue + + children = node.getAllChildren() + children.append(node) + for child_node in children: + if type(child_node) is SceneNode and child_node.getMeshData() and child_node.getMeshData().getVertices() is not None: + temp_list.append(child_node) + + object_groups.append(temp_list) + Job.yieldThread() + else: + temp_list = [] + for node in DepthFirstIterator(self._scene.getRoot()): + if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None: + if not getattr(node, "_outside_buildarea", False): + temp_list.append(node) + Job.yieldThread() + object_groups.append(temp_list) + + self._scene.releaseLock() + + if not object_groups: + return + + self._sendSettings(self._profile) + + slice_message = Cura_pb2.Slice() + + for group in object_groups: + group_message = slice_message.object_lists.add() + for object in group: + print(object) + mesh_data = object.getMeshData().getTransformed(object.getWorldTransformation()) + + obj = group_message.objects.add() + obj.id = id(object) + + verts = numpy.array(mesh_data.getVertices()) + verts[:,[1,2]] = verts[:,[2,1]] + verts[:,1] *= -1 + obj.vertices = verts.tostring() + + self._handlePerObjectSettings(object, obj) + + Job.yieldThread() + + # Hack to add per-object settings also to the "MeshGroup" in CuraEngine + # We really should come up with a better solution for this. + self._handlePerObjectSettings(group[0], group_message) + + Logger.log("d", "Sending data to engine for slicing.") + self._socket.sendMessage(slice_message) + + self.setResult(True) + + def _sendSettings(self, profile): + msg = Cura_pb2.SettingList() + for key, value in profile.getAllSettingValues(include_machine = True).items(): + s = msg.settings.add() + s.name = key + s.value = str(value).encode("utf-8") + + self._socket.sendMessage(msg) + + def _handlePerObjectSettings(self, node, message): + profile = node.callDecoration("getProfile") + if profile: + for key, value in profile.getChangedSettingValues().items(): + setting = message.settings.add() + setting.name = key + setting.value = str(value).encode() + + Job.yieldThread() + + object_settings = node.callDecoration("getAllSettingValues") + if not object_settings: + return + + for key, value in object_settings.items(): + setting = message.settings.add() + setting.name = key + setting.value = str(value).encode() + + Job.yieldThread() From cb05aee3914cb7ae39f04495adffd566c517fd9c Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 4 Nov 2015 14:08:20 +0100 Subject: [PATCH 19/40] Move the start of slicing to a proper job This way it can be properly threaded (with a generous sprinkling of "yieldThread") so we do not block the UI when slicing starts. Contributes to CURA-358 --- .../CuraEngineBackend/CuraEngineBackend.py | 197 +++++------------- plugins/CuraEngineBackend/StartSliceJob.py | 9 +- 2 files changed, 54 insertions(+), 152 deletions(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index c612a6656c..7ce8cd33f6 100644 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -17,6 +17,7 @@ from cura.OneAtATimeIterator import OneAtATimeIterator from . import Cura_pb2 from . import ProcessSlicedObjectListJob from . import ProcessGCodeJob +from . import StartSliceJob import os import sys @@ -67,12 +68,8 @@ class CuraEngineBackend(Backend): self._slicing = False self._restart = False - - self._save_gcode = True - self._save_polygons = True - self._report_progress = True - self._enabled = True + self._always_restart = True self._message = None @@ -97,24 +94,12 @@ class CuraEngineBackend(Backend): ## Emitted whne the slicing process is aborted forcefully. slicingCancelled = Signal() - ## Perform a slice of the scene with the given set of settings. - # - # \param kwargs Keyword arguments. - # Valid values are: - # - settings: The settings to use for the slice. The default is the active machine. - # - save_gcode: True if the generated gcode should be saved, False if not. True by default. - # - save_polygons: True if the generated polygon data should be saved, False if not. True by default. - # - force_restart: True if the slicing process should be forcefully restarted if it is already slicing. - # If False, this method will do nothing when already slicing. True by default. - # - report_progress: True if the slicing progress should be reported, False if not. Default is True. - def slice(self, **kwargs): + ## Perform a slice of the scene. + def slice(self): if not self._enabled: return if self._slicing: - if not kwargs.get("force_restart", True): - return - self._slicing = False self._restart = True if self._process is not None: @@ -123,41 +108,15 @@ class CuraEngineBackend(Backend): self._process.terminate() except: # terminating a process that is already terminating causes an exception, silently ignore this. pass - self.slicingCancelled.emit() - return - Logger.log("d", "Preparing to send slice data to engine.") - object_groups = [] - if self._profile.getSettingValue("print_sequence") == "one_at_a_time": - for node in OneAtATimeIterator(self._scene.getRoot()): - temp_list = [] - children = node.getAllChildren() - children.append(node) - for child_node in children: - if type(child_node) is SceneNode and child_node.getMeshData() and child_node.getMeshData().getVertices() is not None: - temp_list.append(child_node) - object_groups.append(temp_list) - else: - temp_list = [] - for node in DepthFirstIterator(self._scene.getRoot()): - if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None: - if not getattr(node, "_outside_buildarea", False): - temp_list.append(node) - if len(temp_list) == 0: - self.processingProgress.emit(0.0) - return - object_groups.append(temp_list) - #for node in DepthFirstIterator(self._scene.getRoot()): - # if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None: - # if not getattr(node, "_outside_buildarea", False): - # objects.append(node) - if len(object_groups) == 0: if self._message: self._message.hide() self._message = None - return #No point in slicing an empty build plate - if kwargs.get("profile", self._profile).hasErrorValue(): + self.slicingCancelled.emit() + return + + if self._profile.hasErrorValue(): Logger.log('w', "Profile has error values. Aborting slicing") if self._message: self._message.hide() @@ -165,62 +124,27 @@ class CuraEngineBackend(Backend): self._message = Message(catalog.i18nc("@info:status", "Unable to slice. Please check your setting values for errors.")) self._message.show() return #No slicing if we have error values since those are by definition illegal values. - # Remove existing layer data (if any) - for node in DepthFirstIterator(self._scene.getRoot()): - if type(node) is SceneNode and node.getMeshData(): - if node.callDecoration("getLayerData"): - Application.getInstance().getController().getScene().getRoot().removeChild(node) - break - Application.getInstance().getController().getScene().gcode_list = None + + self.processingProgress.emit(0.0) + if not self._message: + self._message = Message(catalog.i18nc("@info:status", "Slicing..."), 0, False, -1) + self._message.show() + else: + self._message.setProgress(-1) + + self._scene.gcode_list = [] self._slicing = True - self.slicingStarted.emit() - self._report_progress = kwargs.get("report_progress", True) - if self._report_progress: - self.processingProgress.emit(0.0) - if not self._message: - self._message = Message(catalog.i18nc("@info:status", "Slicing..."), 0, False, -1) - self._message.show() - else: - self._message.setProgress(-1) + job = StartSliceJob.StartSliceJob(self._profile, self._socket) + job.start() + job.finished.connect(self._onStartSliceCompleted) - self._sendSettings(kwargs.get("profile", self._profile)) - - self._scene.acquireLock() - - # Set the gcode as an empty list. This will be filled with strings by GCodeLayer messages. - # This is done so the gcode can be fragmented in memory and does not need a continues memory space. - # (AKA. This prevents MemoryErrors) - self._save_gcode = kwargs.get("save_gcode", True) - if self._save_gcode: - setattr(self._scene, "gcode_list", []) - - self._save_polygons = kwargs.get("save_polygons", True) - - slice_message = Cura_pb2.Slice() - - for group in object_groups: - group_message = slice_message.object_lists.add() - for object in group: - mesh_data = object.getMeshData().getTransformed(object.getWorldTransformation()) - - obj = group_message.objects.add() - obj.id = id(object) - - verts = numpy.array(mesh_data.getVertices()) - verts[:,[1,2]] = verts[:,[2,1]] - verts[:,1] *= -1 - obj.vertices = verts.tostring() - - self._handlePerObjectSettings(object, obj) - - # Hack to add per-object settings also to the "MeshGroup" in CuraEngine - # We really should come up with a better solution for this. - self._handlePerObjectSettings(group[0], group_message) - - self._scene.releaseLock() - Logger.log("d", "Sending data to engine for slicing.") - self._socket.sendMessage(slice_message) + def _onStartSliceCompleted(self, job): + if job.getError() or job.getResult() != True: + if self._message: + self._message.hide() + self._message = None + return def _onSceneChanged(self, source): if type(source) is not SceneNode: @@ -250,41 +174,42 @@ class CuraEngineBackend(Backend): self._onChanged() def _onSlicedObjectListMessage(self, message): - if self._save_polygons: - if self._layer_view_active: - job = ProcessSlicedObjectListJob.ProcessSlicedObjectListJob(message) - job.start() - else : - self._stored_layer_data = message + if self._layer_view_active: + job = ProcessSlicedObjectListJob.ProcessSlicedObjectListJob(message) + job.start() + else : + self._stored_layer_data = message def _onProgressMessage(self, message): - if message.amount >= 0.99: - self._slicing = False - - if self._message: - self._message.setProgress(100) - self._message.hide() - self._message = None - if self._message: self._message.setProgress(round(message.amount * 100)) - if self._report_progress: - self.processingProgress.emit(message.amount) + self.processingProgress.emit(message.amount) def _onGCodeLayerMessage(self, message): - if self._save_gcode: - job = ProcessGCodeJob.ProcessGCodeLayerJob(message) - job.start() + self._scene.gcode_list.append(message.data.decode("utf-8", "replace")) def _onGCodePrefixMessage(self, message): - if self._save_gcode: - self._scene.gcode_list.insert(0, message.data.decode("utf-8", "replace")) + self._scene.gcode_list.insert(0, message.data.decode("utf-8", "replace")) def _onObjectPrintTimeMessage(self, message): self.printDurationMessage.emit(message.time, message.material_amount) self.processingProgress.emit(1.0) + self._slicing = False + + if self._message: + self._message.setProgress(100) + self._message.hide() + self._message = None + + if self._always_restart: + try: + self._process.terminate() + self._createSocket() + except: # terminating a process that is already terminating causes an exception, silently ignore this. + pass + def _createSocket(self): super()._createSocket() @@ -306,15 +231,6 @@ class CuraEngineBackend(Backend): self._change_timer.start() - def _sendSettings(self, profile): - msg = Cura_pb2.SettingList() - for key, value in profile.getAllSettingValues(include_machine = True).items(): - s = msg.settings.add() - s.name = key - s.value = str(value).encode("utf-8") - - self._socket.sendMessage(msg) - def _onBackendConnected(self): if self._restart: self._onChanged() @@ -338,20 +254,3 @@ class CuraEngineBackend(Backend): self._stored_layer_data = None else: self._layer_view_active = False - - def _handlePerObjectSettings(self, node, message): - profile = node.callDecoration("getProfile") - if profile: - for key, value in profile.getChangedSettingValues().items(): - setting = message.settings.add() - setting.name = key - setting.value = str(value).encode() - - object_settings = node.callDecoration("getAllSettingValues") - if not object_settings: - return - - for key, value in object_settings.items(): - setting = message.settings.add() - setting.name = key - setting.value = str(value).encode() diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 1a2dacf38a..f629819e8c 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -15,6 +15,7 @@ from cura.OneAtATimeIterator import OneAtATimeIterator from . import Cura_pb2 +## Job class that handles sending the current scene data to CuraEngine class StartSliceJob(Job): def __init__(self, profile, socket): super().__init__() @@ -45,7 +46,8 @@ class StartSliceJob(Job): if type(child_node) is SceneNode and child_node.getMeshData() and child_node.getMeshData().getVertices() is not None: temp_list.append(child_node) - object_groups.append(temp_list) + if temp_list: + object_groups.append(temp_list) Job.yieldThread() else: temp_list = [] @@ -54,7 +56,9 @@ class StartSliceJob(Job): if not getattr(node, "_outside_buildarea", False): temp_list.append(node) Job.yieldThread() - object_groups.append(temp_list) + + if temp_list: + object_groups.append(temp_list) self._scene.releaseLock() @@ -68,7 +72,6 @@ class StartSliceJob(Job): for group in object_groups: group_message = slice_message.object_lists.add() for object in group: - print(object) mesh_data = object.getMeshData().getTransformed(object.getWorldTransformation()) obj = group_message.objects.add() From 1140c853d143abc61655d8ae2ab43585ffc3c59c Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 4 Nov 2015 16:42:07 +0100 Subject: [PATCH 20/40] Try to use Protobuf CPP implementation if it is available The C++ implementation is far faster so should always be used if available. If not, we log a warning since it makes a big difference. --- cura/CuraApplication.py | 3 +++ cura_app.py | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 64551948dc..c194aedc61 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -134,6 +134,9 @@ class CuraApplication(QtApplication): parser.add_argument("--debug", dest="debug-mode", action="store_true", default=False, help="Enable detailed crash reports.") def run(self): + if not "PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION" in os.environ or os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] != "cpp": + Logger.log("w", "Using Python implementation of Protobuf, expect bad performance!") + self._i18n_catalog = i18nCatalog("cura"); i18nCatalog.setTagReplacements({ diff --git a/cura_app.py b/cura_app.py index e71fbd6515..92624be76f 100755 --- a/cura_app.py +++ b/cura_app.py @@ -4,6 +4,7 @@ # Cura is released under the terms of the AGPLv3 or higher. import sys +import os def exceptHook(type, value, traceback): import cura.CrashHandler @@ -11,6 +12,13 @@ def exceptHook(type, value, traceback): sys.excepthook = exceptHook +try: + from google.protobuf.pyext import _message +except ImportError: + pass +else: + os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "cpp" + import cura.CuraApplication if sys.platform == "win32" and hasattr(sys, "frozen"): From 35ba7d59f4cbe24e30fec2848def7fbad552f5fe Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 4 Nov 2015 17:16:00 +0100 Subject: [PATCH 21/40] JSON: support bottom stair step height defaults changed so that the bottom distance to the model isn't violated too much --- resources/machines/fdmprinter.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/machines/fdmprinter.json b/resources/machines/fdmprinter.json index 1b34c71e0f..38d3ff3049 100644 --- a/resources/machines/fdmprinter.json +++ b/resources/machines/fdmprinter.json @@ -1100,7 +1100,7 @@ "unit": "mm", "min_value": "0", "max_value_warning": "10", - "default": 0.15, + "default": 0.1, "type": "float", "visible": false, "enabled": "support_enable" @@ -1141,7 +1141,7 @@ "description": "The height of the steps of the stair-like bottom of support resting on the model. Small steps can cause the support to be hard to remove from the top of the model.", "unit": "mm", "type": "float", - "default": 2, + "default": 0.3, "visible": false, "enabled": "support_enable" }, From b2f4e2bf262a2044b7e100d79074b3810a385786 Mon Sep 17 00:00:00 2001 From: Tamara Hogenhout Date: Wed, 4 Nov 2015 17:36:20 +0100 Subject: [PATCH 22/40] Shows an error message when a user tries to add a printer with a name that already excists. Also auto-generates numbering in the name, when the automatically generated machine-name is non-unique contributes to #CURA-325 --- resources/qml/WizardPages/AddMachine.qml | 100 ++++++++++++++++++++--- 1 file changed, 89 insertions(+), 11 deletions(-) diff --git a/resources/qml/WizardPages/AddMachine.qml b/resources/qml/WizardPages/AddMachine.qml index 2ac479d276..3c548fdfa6 100644 --- a/resources/qml/WizardPages/AddMachine.qml +++ b/resources/qml/WizardPages/AddMachine.qml @@ -18,18 +18,70 @@ Item property variant wizard: null; + property bool visibility: base.wizard.visible + onVisibilityChanged: + { + machineName.text = getMachineName() + errorMessage.show = false + } + + function editMachineName(word) + { + //Adds '#2' at the end or increases the number by 1 if the word ends with '#' and 1 or more digits + var regEx = /[#][\d]+$///ends with '#' and then 1 or more digit + var result = word.match(regEx) + + if (result != null) + { + result = result[0].split('') + + var numberString = '' + for (var i = 1; i < result.length; i++){//starting at 1, makes it ignore the '#' + numberString += result[i] + } + var newNumber = Number(numberString) + 1 + + var newWord = word.replace(/[\d]+$/, newNumber)//replaces the last digits in the string by the same number + 1 + return newWord + } + else { + return word + ' #2' + } + } + + function getMachineName() + { + var name = machineList.model.getItem(machineList.currentIndex).name + + //if the automatically assigned name is not unique, the editMachineName function keeps editing it untill it is. + while (UM.MachineManager.getNameUniqueness(name) == false) + { + name = editMachineName(name) + } + return name + } + Connections { target: base.wizard onNextClicked: //You can add functions here that get triggered when the final button is clicked in the wizard-element { - var old_page_count = base.wizard.getPageCount() - // Delete old pages (if any) - for (var i = old_page_count - 1; i > 0; i--) + var name = machineName.text + if (UM.MachineManager.getNameUniqueness(name) == false) { - base.wizard.removePage(i) + errorMessage.show = true + } + else + { + var old_page_count = base.wizard.getPageCount() + // Delete old pages (if any) + for (var i = old_page_count - 1; i > 0; i--) + { + base.wizard.removePage(i) + } + saveMachine() + base.wizard.visible = false } - saveMachine() } onBackClicked: { @@ -63,7 +115,8 @@ Item { id: machinesHolder - anchors{ + anchors + { left: parent.left; top: subTitle.bottom; right: parent.right; @@ -110,6 +163,7 @@ Item onClicked: { base.activeManufacturer = section; machineList.currentIndex = machineList.model.find("manufacturer", section) + machineName.text = getMachineName() } } @@ -128,7 +182,10 @@ Item text: model.name - onClicked: ListView.view.currentIndex = index; + onClicked: { + ListView.view.currentIndex = index; + machineName.text = getMachineName() + } Label { @@ -169,11 +226,33 @@ Item } } - Item + + + Column { id: machineNameHolder - height: childrenRect.height anchors.bottom: parent.bottom; + //height: insertNameLabel.lineHeight * (2 + errorMessage.lineCount) + + Item + { + height: errorMessage.lineHeight + anchors.bottom: insertNameLabel.top + anchors.bottomMargin: insertNameLabel.height * errorMessage.lineCount + Label + { + id: errorMessage + property bool show: false + width: base.width + height: errorMessage.show ? errorMessage.lineHeight : 0 + visible: errorMessage.show + text: catalog.i18nc("@label", "This printer name has already been used. Please choose a different printer name."); + wrapMode: Text.WordWrap + Behavior on height {NumberAnimation {duration: 75; }} + color: UM.Theme.colors.error + } + } + Label { id: insertNameLabel @@ -182,8 +261,7 @@ Item TextField { id: machineName; - anchors.top: insertNameLabel.bottom - text: machineList.model.getItem(machineList.currentIndex).name + text: getMachineName() implicitWidth: UM.Theme.sizes.standard_list_input.width } } From 1a35971edf578c2f8ec09b6d05b9835ac42c5d39 Mon Sep 17 00:00:00 2001 From: Tamara Hogenhout Date: Wed, 4 Nov 2015 17:38:45 +0100 Subject: [PATCH 23/40] Adds a color for the error-messages Menno designed the use of the color orange for error messages, but this is not yet the definitive hue of orange. But I kinda needed it now. Contributes to #CURA-325 --- resources/themes/cura/theme.json | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/themes/cura/theme.json b/resources/themes/cura/theme.json index 63e04c6e6e..52f4fbcd47 100644 --- a/resources/themes/cura/theme.json +++ b/resources/themes/cura/theme.json @@ -65,6 +65,7 @@ "text_hover": [35, 35, 35, 255], "text_pressed": [12, 169, 227, 255], + "error": [255, 140, 0, 255], "sidebar_header_bar": [12, 169, 227, 255], "button": [139, 143, 153, 255], From 6303062f45ce9458799cfd024909ffdbf7c43527 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Thu, 5 Nov 2015 11:24:27 +0100 Subject: [PATCH 24/40] JSON: workaround for stutter in spiralize vase: set travel speed to printing speed --- resources/machines/fdmprinter.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/machines/fdmprinter.json b/resources/machines/fdmprinter.json index 38d3ff3049..b3c73da93a 100644 --- a/resources/machines/fdmprinter.json +++ b/resources/machines/fdmprinter.json @@ -758,7 +758,8 @@ "type": "float", "min_value": "0.1", "max_value_warning": "300", - "default": 150 + "default": 150, + "inherit_function": "speed_print if magic_spiralize else 150" }, "speed_layer_0": { "label": "Bottom Layer Speed", From d5bd39cc3b66381330229228cf33dc13dd781a13 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 5 Nov 2015 13:46:23 +0100 Subject: [PATCH 25/40] Also account for "xy_offset" setting for the disallowed areas Contributes to CURA-138 --- cura/BuildVolume.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 223f8a3693..f9058dd3ae 100644 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -263,6 +263,8 @@ class BuildVolume(SceneNode): if profile.getSettingValue("draft_shield_enabled"): skirt_size += profile.getSettingValue("draft_shield_dist") + skirt_size += profile.getSettingValue("xy_offset") + return skirt_size def _clamp(self, value, min_value, max_value): From 14abec095cb09e77dadb957c278c9dd0dc6e9dc1 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 5 Nov 2015 14:42:17 +0100 Subject: [PATCH 26/40] Only add layer data node after all processing This way we trigger a proper scene update and do not get odd incomplete layers. Contributes to CURA-224 Contributes to CURA-388 --- .../ProcessSlicedObjectListJob.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py b/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py index 67a24c368d..bec952de9e 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py @@ -55,14 +55,6 @@ class ProcessSlicedObjectListJob(Job): mesh = MeshData() layer_data = LayerData.LayerData() - #Add layerdata decorator to scene node to indicate that the node has layerdata - decorator = LayerDataDecorator.LayerDataDecorator() - decorator.setLayerData(layer_data) - new_node.addDecorator(decorator) - - new_node.setMeshData(mesh) - new_node.setParent(self._scene.getRoot()) - layer_count = 0 for object in self._message.objects: layer_count += len(object.layers) @@ -104,6 +96,14 @@ class ProcessSlicedObjectListJob(Job): # We are done processing all the layers we got from the engine, now create a mesh out of the data layer_data.build() + #Add layerdata decorator to scene node to indicate that the node has layerdata + decorator = LayerDataDecorator.LayerDataDecorator() + decorator.setLayerData(layer_data) + new_node.addDecorator(decorator) + + new_node.setMeshData(mesh) + new_node.setParent(self._scene.getRoot()) + if self._progress: self._progress.setProgress(100) From 831419d11039ea53f12653e45d80b237b57fcae3 Mon Sep 17 00:00:00 2001 From: Tamara Hogenhout Date: Thu, 5 Nov 2015 18:17:04 +0100 Subject: [PATCH 27/40] Only hides the window when there are no more pages so it doens't hide before the error message is shown and it doesn't hide when there are extra pages (like for the UMO) Contributes to #CURA-256 Contributes to #429 --- resources/qml/WizardPages/AddMachine.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/qml/WizardPages/AddMachine.qml b/resources/qml/WizardPages/AddMachine.qml index 3c548fdfa6..d435e7a08f 100644 --- a/resources/qml/WizardPages/AddMachine.qml +++ b/resources/qml/WizardPages/AddMachine.qml @@ -7,8 +7,6 @@ import QtQuick.Window 2.1 import QtQuick.Controls.Styles 1.1 import UM 1.1 as UM -import Cura 1.0 as Cura -import ".." Item { @@ -80,7 +78,6 @@ Item base.wizard.removePage(i) } saveMachine() - base.wizard.visible = false } } onBackClicked: @@ -296,6 +293,9 @@ Item break; } } + if(base.wizard.lastPage == true){ + base.wizard.visible = false + } } } From 968d72863ec0c4ecc393811ae52d10bca34c3df4 Mon Sep 17 00:00:00 2001 From: Tamara Hogenhout Date: Thu, 5 Nov 2015 18:17:47 +0100 Subject: [PATCH 28/40] Wizardpages without hack Contributes to #CURA-256 Contributes to #429 --- resources/qml/WizardPages/Bedleveling.qml | 20 ++++-- .../qml/WizardPages/SelectUpgradedParts.qml | 13 ++-- .../qml/WizardPages/UltimakerCheckup.qml | 64 ++++++++----------- 3 files changed, 48 insertions(+), 49 deletions(-) diff --git a/resources/qml/WizardPages/Bedleveling.qml b/resources/qml/WizardPages/Bedleveling.qml index 9bb9f4f652..94402f5fbd 100644 --- a/resources/qml/WizardPages/Bedleveling.qml +++ b/resources/qml/WizardPages/Bedleveling.qml @@ -7,6 +7,8 @@ import QtQuick.Layouts 1.1 import QtQuick.Window 2.1 import UM 1.1 as UM +import Cura 1.0 as Cura +import ".." Item { @@ -15,11 +17,22 @@ Item property bool three_point_leveling: true property int platform_width: UM.MachineManager.getSettingValue("machine_width") property int platform_height: UM.MachineManager.getSettingValue("machine_depth") - property bool alreadyTested: base.addOriginalProgress.bedLeveling anchors.fill: parent; property variant printer_connection: UM.USBPrinterManager.connectedPrinterList.getItem(0).printer Component.onCompleted: printer_connection.homeHead() UM.I18nCatalog { id: catalog; name:"cura"} + property variant wizard: null; + + Connections + { + target: wizardPage.wizard + onNextClicked: //You can add functions here that get triggered when the final button is clicked in the wizard-element + { + if(wizardPage.wizard.lastPage == true){ + wizardPage.wizard.visible = false + } + } + } Label { @@ -61,7 +74,6 @@ Item id: bedlevelingButton anchors.top: parent.top anchors.left: parent.left - enabled: !alreadyTested text: catalog.i18nc("@action:button","Move to Next Position"); onClicked: { @@ -79,7 +91,6 @@ Item } wizardPage.leveling_state++ if (wizardPage.leveling_state >= 3){ - base.addOriginalProgress.bedLeveling = true resultText.visible = true skipBedlevelingButton.enabled = false bedlevelingButton.enabled = false @@ -91,7 +102,6 @@ Item Button { id: skipBedlevelingButton - enabled: !alreadyTested anchors.top: parent.width < wizardPage.width ? parent.top : bedlevelingButton.bottom anchors.topMargin: parent.width < wizardPage.width ? 0 : UM.Theme.sizes.default_margin.height/2 anchors.left: parent.width < wizardPage.width ? bedlevelingButton.right : parent.left @@ -104,7 +114,7 @@ Item Label { id: resultText - visible: alreadyTested + visible: false anchors.top: bedlevelingWrapper.bottom anchors.topMargin: UM.Theme.sizes.default_margin.height anchors.left: parent.left diff --git a/resources/qml/WizardPages/SelectUpgradedParts.qml b/resources/qml/WizardPages/SelectUpgradedParts.qml index 4e84e61ec6..c8ccc4fe8d 100644 --- a/resources/qml/WizardPages/SelectUpgradedParts.qml +++ b/resources/qml/WizardPages/SelectUpgradedParts.qml @@ -17,11 +17,8 @@ Item Component.onDestruction: { - base.addOriginalProgress.upgrades[0] = extruderCheckBox.checked - base.addOriginalProgress.upgrades[1] = heatedBedCheckBox1.checked - base.addOriginalProgress.upgrades[2] = heatedBedCheckBox2.checked if (extruderCheckBox.checked == true){ - UM.MachineManager.setMachineSettingValue("machine_extruder_drive_upgrade", true); + UM.MachineManager.setMachineSettingValue("machine_extruder_drive_upgrade", true) } if (heatedBedCheckBox1.checked == true || heatedBedCheckBox2.checked == true){ UM.MachineManager.setMachineSettingValue("machine_heated_bed", true) @@ -58,14 +55,14 @@ Item { id: extruderCheckBox text: catalog.i18nc("@option:check","Extruder driver ugrades") - checked: base.addOriginalProgress.upgrades[0] + checked: true } CheckBox { id: heatedBedCheckBox1 - text: catalog.i18nc("@option:check","Heated printer bed (standard kit)") + text: catalog.i18nc("@option:check","Heated printer bed") y: extruderCheckBox.height * 1 - checked: base.addOriginalProgress.upgrades[1] + checked: false onClicked: { if (heatedBedCheckBox2.checked == true) heatedBedCheckBox2.checked = false @@ -76,7 +73,7 @@ Item id: heatedBedCheckBox2 text: catalog.i18nc("@option:check","Heated printer bed (self built)") y: extruderCheckBox.height * 2 - checked: base.addOriginalProgress.upgrades[2] + checked: false onClicked: { if (heatedBedCheckBox1.checked == true) heatedBedCheckBox1.checked = false diff --git a/resources/qml/WizardPages/UltimakerCheckup.qml b/resources/qml/WizardPages/UltimakerCheckup.qml index 6ef87ef069..db538ed7d6 100644 --- a/resources/qml/WizardPages/UltimakerCheckup.qml +++ b/resources/qml/WizardPages/UltimakerCheckup.qml @@ -14,35 +14,40 @@ Item property int leftRow: wizardPage.width*0.40 property int rightRow: wizardPage.width*0.60 anchors.fill: parent; - property bool alreadyTested: base.addOriginalProgress.checkUp[base.addOriginalProgress.checkUp.length-1] property bool x_min_pressed: false property bool y_min_pressed: false property bool z_min_pressed: false property bool heater_works: false property int extruder_target_temp: 0 property int bed_target_temp: 0 + UM.I18nCatalog { id: catalog; name:"cura"} + property var checkupProgress: { + "connection": false, + "endstopX": wizardPage.x_min_pressed, + "endstopY": wizardPage.y_min_pressed, + "endstopZ": wizardPage.z_min_pressed, + "nozzleTemp": false, + "bedTemp": false + } + property variant printer_connection: { if (UM.USBPrinterManager.connectedPrinterList.rowCount() != 0){ - base.addOriginalProgress.checkUp[0] = true - checkTotalCheckUp() + wizardPage.checkupProgress.connection = true return UM.USBPrinterManager.connectedPrinterList.getItem(0).printer } else { return null } } - //property variant printer_connection: UM.USBPrinterManager.connectedPrinterList.getItem(0).printer - UM.I18nCatalog { id: catalog; name:"cura"} function checkTotalCheckUp(){ var allDone = true - for (var i = 0; i < (base.addOriginalProgress.checkUp.length - 1); i++){ - if (base.addOriginalProgress.checkUp[i] == false){ + for(var property in checkupProgress){ + if (checkupProgress[property] == false){ allDone = false } } if (allDone == true){ - base.addOriginalProgress.checkUp[base.addOriginalProgress.checkUp.length] = true skipCheckButton.enabled = false resultText.visible = true } @@ -91,7 +96,7 @@ Item id: startCheckButton anchors.top: parent.top anchors.left: parent.left - enabled: !alreadyTested + //enabled: !alreadyTested text: catalog.i18nc("@action:button","Start Printer Check"); onClicked: { checkupContent.visible = true @@ -107,7 +112,7 @@ Item anchors.topMargin: parent.width < wizardPage.width ? 0 : UM.Theme.sizes.default_margin.height/2 anchors.left: parent.width < wizardPage.width ? startCheckButton.right : parent.left anchors.leftMargin: parent.width < wizardPage.width ? UM.Theme.sizes.default_margin.width : 0 - enabled: !alreadyTested + //enabled: !alreadyTested text: catalog.i18nc("@action:button","Skip Printer Check"); onClicked: { base.currentPage += 1 @@ -119,7 +124,7 @@ Item id: checkupContent anchors.top: startStopButtons.bottom anchors.topMargin: UM.Theme.sizes.default_margin.height - visible: alreadyTested + visible: false ////////////////////////////////////////////////////////// Label { @@ -156,7 +161,7 @@ Item anchors.left: endstopXLabel.right anchors.top: connectionLabel.bottom wrapMode: Text.WordWrap - text: x_min_pressed || base.addOriginalProgress.checkUp[1] ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked") + text: x_min_pressed ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked") } ////////////////////////////////////////////////////////////// Label @@ -175,7 +180,7 @@ Item anchors.left: endstopYLabel.right anchors.top: endstopXLabel.bottom wrapMode: Text.WordWrap - text: y_min_pressed || base.addOriginalProgress.checkUp[2] ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked") + text: y_min_pressed ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked") } ///////////////////////////////////////////////////////////////////// Label @@ -194,7 +199,7 @@ Item anchors.left: endstopZLabel.right anchors.top: endstopYLabel.bottom wrapMode: Text.WordWrap - text: z_min_pressed || base.addOriginalProgress.checkUp[3] ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked") + text: z_min_pressed ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked") } //////////////////////////////////////////////////////////// Label @@ -233,14 +238,9 @@ Item { if(printer_connection != null) { - if (alreadyTested){ - nozzleTempStatus.text = catalog.i18nc("@info:status","Works") - } - else { - nozzleTempStatus.text = catalog.i18nc("@info:progress","Checking") - printer_connection.heatupNozzle(190) - wizardPage.extruder_target_temp = 190 - } + nozzleTempStatus.text = catalog.i18nc("@info:progress","Checking") + printer_connection.heatupNozzle(190) + wizardPage.extruder_target_temp = 190 } } } @@ -294,14 +294,9 @@ Item { if(printer_connection != null) { - if (alreadyTested){ - bedTempStatus.text = catalog.i18nc("@info:status","Works") - } - else { - bedTempStatus.text = catalog.i18nc("@info:progress","Checking") - printer_connection.heatupBed(60) - wizardPage.bed_target_temp = 60 - } + bedTempStatus.text = catalog.i18nc("@info:progress","Checking") + printer_connection.heatupBed(60) + wizardPage.bed_target_temp = 60 } } } @@ -320,7 +315,7 @@ Item Label { id: resultText - visible: base.addOriginalProgress.checkUp[base.addOriginalProgress.checkUp.length-1] + visible: false anchors.top: bedTemp.bottom anchors.topMargin: UM.Theme.sizes.default_margin.height anchors.left: parent.left @@ -338,19 +333,16 @@ Item { if(key == "x_min") { - base.addOriginalProgress.checkUp[1] = true x_min_pressed = true checkTotalCheckUp() } if(key == "y_min") { - base.addOriginalProgress.checkUp[2] = true y_min_pressed = true checkTotalCheckUp() } if(key == "z_min") { - base.addOriginalProgress.checkUp[3] = true z_min_pressed = true checkTotalCheckUp() } @@ -363,7 +355,7 @@ Item if(printer_connection != null) { nozzleTempStatus.text = catalog.i18nc("@info:status","Works") - base.addOriginalProgress.checkUp[4] = true + wizardPage.checkupProgress.nozzleTemp = true checkTotalCheckUp() printer_connection.heatupNozzle(0) } @@ -374,7 +366,7 @@ Item if(printer_connection.bedTemperature > wizardPage.bed_target_temp - 5 && printer_connection.bedTemperature < wizardPage.bed_target_temp + 5) { bedTempStatus.text = catalog.i18nc("@info:status","Works") - base.addOriginalProgress.checkUp[5] = true + wizardPage.checkupProgress.bedTemp = true checkTotalCheckUp() printer_connection.heatupBed(0) } From aa895c1b154bec961309b47baadc6034bdb13b52 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 5 Nov 2015 18:17:56 +0100 Subject: [PATCH 29/40] Properly trigger a reslice when the active instance is changed Contributes to CURA-394 --- plugins/CuraEngineBackend/CuraEngineBackend.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 7ce8cd33f6..c3c5331008 100644 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -50,6 +50,7 @@ class CuraEngineBackend(Backend): self._onActiveViewChanged() self._stored_layer_data = None + Application.getInstance().getMachineManager().activeMachineInstanceChanged.connect(self._onChanged) self._profile = None Application.getInstance().getMachineManager().activeProfileChanged.connect(self._onActiveProfileChanged) From 33d0db8ed5a108a27f690c2d7776b42fd50438d9 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Fri, 6 Nov 2015 11:59:11 +0100 Subject: [PATCH 30/40] Add xy_offset setting to list of settings that trigger a disallowed area update Contributes to CURA-138 --- cura/BuildVolume.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index f9058dd3ae..7fdf5b4d1d 100644 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -270,4 +270,4 @@ class BuildVolume(SceneNode): def _clamp(self, value, min_value, max_value): return max(min(value, max_value), min_value) - _skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_line_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist"] + _skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_line_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist", "xy_offset"] From 3029409b892b829d63105c12940fc1bf07c85da4 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Fri, 6 Nov 2015 15:43:11 +0100 Subject: [PATCH 31/40] Properly emit writeStarted in RemovableDriveOutputDevice Contributes to CURA-319 --- .../RemovableDriveOutputDevice/RemovableDriveOutputDevice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py b/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py index 0eb6ed3066..89e4f6ff81 100644 --- a/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py +++ b/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py @@ -52,6 +52,8 @@ class RemovableDriveOutputDevice(OutputDevice): message = Message(catalog.i18nc("@info:progress", "Saving to Removable Drive {0}").format(self.getName()), 0, False, -1) message.show() + self.writeStarted.emit(self) + job._message = message job.start() except PermissionError as e: From f75b6bb0461b8d52e6fc13baaa7dd5438377f77e Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Fri, 6 Nov 2015 17:55:36 +0100 Subject: [PATCH 32/40] Make sure to send all settings when an object overrides the profile Contributes to CURA-255 --- plugins/CuraEngineBackend/StartSliceJob.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index f629819e8c..157bf5321f 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -107,7 +107,7 @@ class StartSliceJob(Job): def _handlePerObjectSettings(self, node, message): profile = node.callDecoration("getProfile") if profile: - for key, value in profile.getChangedSettingValues().items(): + for key, value in profile.getAllSettingValues().items(): setting = message.settings.add() setting.name = key setting.value = str(value).encode() From 288ad0c201222b2e4c3d3799f5a6ed0208cac07f Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Fri, 6 Nov 2015 17:56:51 +0100 Subject: [PATCH 33/40] Remove per-group settings for now This drops Layer height as a setting that can be changed per-object but makes per-object settings work correctly. Contributes to CURA-255 --- plugins/CuraEngineBackend/StartSliceJob.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 157bf5321f..117f2992b2 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -86,10 +86,6 @@ class StartSliceJob(Job): Job.yieldThread() - # Hack to add per-object settings also to the "MeshGroup" in CuraEngine - # We really should come up with a better solution for this. - self._handlePerObjectSettings(group[0], group_message) - Logger.log("d", "Sending data to engine for slicing.") self._socket.sendMessage(slice_message) From 2987da6dcf693a8c6e44182d079fc4314f03e656 Mon Sep 17 00:00:00 2001 From: Tamara Hogenhout Date: Mon, 9 Nov 2015 16:54:56 +0100 Subject: [PATCH 34/40] Sets the languageComboBox to the default language Fixes #CURA-327 --- resources/qml/GeneralPage.qml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/resources/qml/GeneralPage.qml b/resources/qml/GeneralPage.qml index aef53e1324..4de94adc38 100644 --- a/resources/qml/GeneralPage.qml +++ b/resources/qml/GeneralPage.qml @@ -13,6 +13,15 @@ UM.PreferencesPage //: General configuration page title title: catalog.i18nc("@title:tab","General"); + function setDefaultLanguage(languageCode){ + //loops trough the languageList and sets the language using the languageCode + for(var i = 0; i < languageList.count; i++){ + if (languageComboBox.model.get(i).code == languageCode){ + languageComboBox.currentIndex = i + } + } + } + function reset() { UM.Preferences.resetPreference("general/language") @@ -22,7 +31,8 @@ UM.PreferencesPage pushFreeCheckbox.checked = boolCheck(UM.Preferences.getValue("physics/automatic_push_free")) sendDataCheckbox.checked = boolCheck(UM.Preferences.getValue("info/send_slice_info")) scaleToFitCheckbox.checked = boolCheck(UM.Preferences.getValue("mesh/scale_to_fit")) - languageComboBox.currentIndex = 0 + var defaultLanguage = UM.Preferences.getValue("general/language") + setDefaultLanguage(defaultLanguage) } GridLayout From 9320fc1af5bf64e0a5bd291d8f47323b0d037466 Mon Sep 17 00:00:00 2001 From: Tamara Hogenhout Date: Mon, 9 Nov 2015 18:01:35 +0100 Subject: [PATCH 35/40] uses a different method to check whether a machine name excists contributes to #CURA-325 --- resources/qml/WizardPages/AddMachine.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/qml/WizardPages/AddMachine.qml b/resources/qml/WizardPages/AddMachine.qml index d435e7a08f..a98f3ce8ef 100644 --- a/resources/qml/WizardPages/AddMachine.qml +++ b/resources/qml/WizardPages/AddMachine.qml @@ -52,7 +52,7 @@ Item var name = machineList.model.getItem(machineList.currentIndex).name //if the automatically assigned name is not unique, the editMachineName function keeps editing it untill it is. - while (UM.MachineManager.getNameUniqueness(name) == false) + while (UM.MachineManager.checkInstanceExists(name) != false) { name = editMachineName(name) } @@ -65,7 +65,7 @@ Item onNextClicked: //You can add functions here that get triggered when the final button is clicked in the wizard-element { var name = machineName.text - if (UM.MachineManager.getNameUniqueness(name) == false) + if (UM.MachineManager.checkInstanceExists(name) != false) { errorMessage.show = true } From 860a3dccdc734492a4c0bc1d5f9a1c1534ffcaa5 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 10 Nov 2015 14:28:43 +0100 Subject: [PATCH 36/40] Adjust initial view to be slightly from the side --- cura/CuraApplication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index c194aedc61..34ad2ff790 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -169,7 +169,7 @@ class CuraApplication(QtApplication): self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume) camera = Camera("3d", root) - camera.setPosition(Vector(0, 250, 900)) + camera.setPosition(Vector(-80, 250, 700)) camera.setPerspective(True) camera.lookAt(Vector(0, 0, 0)) controller.getScene().setActiveCamera("3d") From c746c24e2bf81e09f96abe766f288189dafae404 Mon Sep 17 00:00:00 2001 From: Tamara Hogenhout Date: Tue, 10 Nov 2015 16:45:42 +0100 Subject: [PATCH 37/40] fix typo's.. Contributes to #CURA-256 Contributes to #429 --- resources/qml/WizardPages/Bedleveling.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/WizardPages/Bedleveling.qml b/resources/qml/WizardPages/Bedleveling.qml index 94402f5fbd..a6c471341c 100644 --- a/resources/qml/WizardPages/Bedleveling.qml +++ b/resources/qml/WizardPages/Bedleveling.qml @@ -120,7 +120,7 @@ Item anchors.left: parent.left width: parent.width wrapMode: Text.WordWrap - text: catalog.i18nc("@label", "Everythink is in order! You're done with bedeleveling.") + text: catalog.i18nc("@label", "Everything is in order! You're done with bedleveling.") } function threePointLeveling(width, height) From ce08ab2b9aeef9cb32e8687367dee714ea0322e2 Mon Sep 17 00:00:00 2001 From: Tamara Hogenhout Date: Tue, 10 Nov 2015 17:02:33 +0100 Subject: [PATCH 38/40] conforming to code style Fixes #CURA-327 --- resources/qml/GeneralPage.qml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/resources/qml/GeneralPage.qml b/resources/qml/GeneralPage.qml index 4de94adc38..c04903b961 100644 --- a/resources/qml/GeneralPage.qml +++ b/resources/qml/GeneralPage.qml @@ -13,10 +13,13 @@ UM.PreferencesPage //: General configuration page title title: catalog.i18nc("@title:tab","General"); - function setDefaultLanguage(languageCode){ + function setDefaultLanguage(languageCode) + { //loops trough the languageList and sets the language using the languageCode - for(var i = 0; i < languageList.count; i++){ - if (languageComboBox.model.get(i).code == languageCode){ + for(var i = 0; i < languageList.count; i++) + { + if (languageComboBox.model.get(i).code == languageCode) + { languageComboBox.currentIndex = i } } From b7471d6b1d209c8abe2c4920e5d86d8a542758b4 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 11 Nov 2015 10:59:34 +0100 Subject: [PATCH 39/40] Remove unused import in StartSliceJob Contributes to CURA-358 --- plugins/CuraEngineBackend/StartSliceJob.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 117f2992b2..50c3fadb0e 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -1,7 +1,6 @@ # Copyright (c) 2015 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -import time import numpy from UM.Job import Job From 15747dedec000c1648eea7618e4ad9c43851eeb6 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Fri, 13 Nov 2015 11:32:43 +0100 Subject: [PATCH 40/40] Update Per Object Settings tool with changes made in 15.10 --- .../PerObjectSettingsPanel.qml | 87 +++++++++++++++---- .../SettingOverrideModel.py | 6 +- 2 files changed, 70 insertions(+), 23 deletions(-) diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml index 820238a86e..781b7b0f1c 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml @@ -41,6 +41,33 @@ Item { anchors.fill: parent; } + Button { + id: closeButton; + width: UM.Theme.sizes.message_close.width; + height: UM.Theme.sizes.message_close.height; + anchors { + right: parent.right; + rightMargin: UM.Theme.sizes.default_margin.width / 2; + top: parent.top; + topMargin: UM.Theme.sizes.default_margin.width / 2; + } + UM.RecolorImage { + anchors.fill: parent; + sourceSize.width: width + sourceSize.height: width + color: UM.Theme.colors.message_dismiss + source: UM.Theme.icons.cross2; + } + + onClicked: settingsPanel.opacity = 0 + + style: ButtonStyle { + background: Rectangle { + color: UM.Theme.colors.message_background + } + } + } + Column { id: items anchors.top: parent.top; @@ -58,7 +85,6 @@ Item { name: catalog.i18nc("@label", "Profile") type: "enum" - perObjectSetting: true style: UM.Theme.styles.setting_item; @@ -88,8 +114,6 @@ Item { description: model.description; unit: model.unit; valid: model.valid; - perObjectSetting: true - dismissable: true options: model.options style: UM.Theme.styles.setting_item; @@ -98,18 +122,36 @@ Item { settings.model.setSettingValue(model.key, value) } -// Button { -// anchors.left: parent.right; -// text: "x"; -// -// width: UM.Theme.sizes.setting.height; -// height: UM.Theme.sizes.setting.height; -// -// opacity: parent.hovered || hovered ? 1 : 0; -// onClicked: UM.ActiveTool.properties.Model.removeSettingOverride(UM.ActiveTool.properties.Model.getItem(base.currentIndex).id, model.key) -// -// style: ButtonStyle { } -// } + Button + { + anchors.left: parent.horizontalCenter; + anchors.leftMargin: UM.Theme.sizes.default_margin.width; + + width: UM.Theme.sizes.setting.height; + height: UM.Theme.sizes.setting.height; + + opacity: parent.hovered || hovered ? 1 : 0; + onClicked: UM.ActiveTool.properties.Model.removeSettingOverride(UM.ActiveTool.properties.Model.getItem(base.currentIndex).id, model.key) + + style: ButtonStyle + { + background: Rectangle + { + color: control.hovered ? control.parent.style.controlHighlightColor : control.parent.style.controlColor; + UM.RecolorImage + { + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + width: parent.width/2 + height: parent.height/2 + sourceSize.width: width + sourceSize.height: width + color: UM.Theme.colors.setting_control_revert + source: UM.Theme.icons.cross1 + } + } + } + } } } @@ -172,12 +214,19 @@ Item { checkable: true; onClicked: { - base.currentIndex = index; + if(settingsPanel.opacity < 0.5) //Per-object panel is not currently displayed. + { + base.currentIndex = index; - settingsPanel.anchors.left = right; - settingsPanel.anchors.top = top; + settingsPanel.anchors.left = right; + settingsPanel.anchors.top = top; - settingsPanel.opacity = 1; + settingsPanel.opacity = 1; + } + else //Per-object panel is already displayed. Deactivate it (same behaviour as the close button). + { + settingsPanel.opacity = 0; + } } style: ButtonStyle diff --git a/plugins/PerObjectSettingsTool/SettingOverrideModel.py b/plugins/PerObjectSettingsTool/SettingOverrideModel.py index 505146290e..74696f0ee6 100644 --- a/plugins/PerObjectSettingsTool/SettingOverrideModel.py +++ b/plugins/PerObjectSettingsTool/SettingOverrideModel.py @@ -44,9 +44,7 @@ class SettingOverrideModel(ListModel): if not self._decorator: return - self._ignore_setting_change = key self._decorator.setSettingValue(key, value) - self._ignore_setting_change = None def _onDecoratorsChanged(self, node): if not self._node.getDecorator(SettingOverrideDecorator): @@ -97,6 +95,6 @@ class SettingOverrideModel(ListModel): def _onSettingValueChanged(self, setting): index = self.find("key", setting.getKey()) value = self._decorator.getSettingValue(setting.getKey()) - if index != -1 and self._ignore_setting_change != setting.getKey(): + if index != -1: self.setProperty(index, "value", str(value)) - self.setProperty(index, "valid", setting.validate(value)) + self.setProperty(index, "valid", setting.validate(value))