From fcf89b79c6d1f1caf973555a73f3468964744443 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 6 Sep 2016 08:55:32 +0200 Subject: [PATCH 01/38] Update wording of error messages --- plugins/RemovableDriveOutputDevice/RemovableDrivePlugin.py | 2 +- plugins/USBPrinting/USBPrinterOutputDevice.py | 2 +- plugins/USBPrinting/USBPrinterOutputDeviceManager.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/RemovableDriveOutputDevice/RemovableDrivePlugin.py b/plugins/RemovableDriveOutputDevice/RemovableDrivePlugin.py index 37f4422a11..90adac73b1 100644 --- a/plugins/RemovableDriveOutputDevice/RemovableDrivePlugin.py +++ b/plugins/RemovableDriveOutputDevice/RemovableDrivePlugin.py @@ -49,7 +49,7 @@ class RemovableDrivePlugin(OutputDevicePlugin): message = Message(catalog.i18nc("@info:status", "Ejected {0}. You can now safely remove the drive.").format(device.getName())) message.show() else: - message = Message(catalog.i18nc("@info:status", "Failed to eject {0}. Maybe it is still in use?").format(device.getName())) + message = Message(catalog.i18nc("@info:status", "Failed to eject {0}. Another program may be using the drive.").format(device.getName())) message.show() return result diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 920a71d40f..4838fe9b96 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -140,7 +140,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): # \param gcode_list List with gcode (strings). def printGCode(self, gcode_list): if self._progress or self._connection_state != ConnectionState.connected: - self._error_message = Message(catalog.i18nc("@info:status", "Printer is busy or not connected. Unable to start a new job.")) + self._error_message = Message(catalog.i18nc("@info:status", "Unable to start a new job because the printer is busy or not connected.")) self._error_message.show() Logger.log("d", "Printer is busy or not connected, aborting print") self.writeError.emit(self) diff --git a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py index 7d1b3fea3b..b50b69188b 100644 --- a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py +++ b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py @@ -107,7 +107,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension): def updateAllFirmware(self, file_name): file_name = file_name.replace("file://", "") # File dialogs prepend the path with file://, which we don't need / want if not self._usb_output_devices: - Message(i18n_catalog.i18nc("@info", "Cannot update firmware, there were no connected printers found.")).show() + Message(i18n_catalog.i18nc("@info", "Unable to update firmware because there are no printers connected.")).show() return for printer_connection in self._usb_output_devices: From 6909f88ce6d3ea335fc2bd2f159fe64c2105aa21 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 6 Sep 2016 10:28:19 +0200 Subject: [PATCH 02/38] Increase default limit on feed rate There is no real limit by default now. Only light speed (and the limit on the input element's length). The feed rate limit should be set by a machine definition. If there is no limit set, let there be no limit to what the user can input. Contributes to issue CURA-2284. --- resources/definitions/fdmprinter.def.json | 2 +- resources/definitions/ultimaker.def.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index dc5a07ac46..bdcc864d82 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -403,7 +403,7 @@ "description": "The maximum speed of the filament.", "unit": "mm/s", "type": "float", - "default_value": 25, + "default_value": 299792458000, "settable_per_mesh": false, "settable_per_extruder": false, "settable_per_meshgroup": false diff --git a/resources/definitions/ultimaker.def.json b/resources/definitions/ultimaker.def.json index dc52b00dcc..0a3729c766 100644 --- a/resources/definitions/ultimaker.def.json +++ b/resources/definitions/ultimaker.def.json @@ -9,6 +9,9 @@ "visible": false }, "overrides": { + "machine_max_feedrate_e": { + "default_value": 45 + }, "material_print_temperature": { "minimum_value": "0" }, From 635f26da8e053014356118f40d6fcab68d40683a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 6 Sep 2016 10:30:09 +0200 Subject: [PATCH 03/38] Lower warning values for retraction speeds As discussed with David, the warning should really be quite a bit lower. No printers actually reach 100. Contributes to issue CURA-2284. --- resources/definitions/fdmprinter.def.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index bdcc864d82..e76d277b0c 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -1161,7 +1161,7 @@ "default_value": 25, "minimum_value": "0", "maximum_value": "machine_max_feedrate_e", - "maximum_value_warning": "100", + "maximum_value_warning": "25", "enabled": "retraction_enable", "settable_per_mesh": false, "settable_per_extruder": true, @@ -1174,7 +1174,7 @@ "default_value": 25, "minimum_value": "0", "maximum_value": "machine_max_feedrate_e", - "maximum_value_warning": "100", + "maximum_value_warning": "25", "enabled": "retraction_enable", "value": "retraction_speed", "settable_per_mesh": false, @@ -1188,7 +1188,7 @@ "default_value": 25, "minimum_value": "0", "maximum_value": "machine_max_feedrate_e", - "maximum_value_warning": "100", + "maximum_value_warning": "25", "enabled": "retraction_enable", "value": "retraction_speed", "settable_per_mesh": false, From 7c8e80b751d721032cdc924a956e74245266ec04 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 6 Sep 2016 11:21:48 +0200 Subject: [PATCH 04/38] Properly hide moves and retractions in lower layers Not the neatest solution. We might want to make a preference for this? I sorta like to see those travel moves, actually. Contributes to issue CURA-2049. --- cura/LayerPolygon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/LayerPolygon.py b/cura/LayerPolygon.py index c5eb4b699e..387a369aa3 100644 --- a/cura/LayerPolygon.py +++ b/cura/LayerPolygon.py @@ -16,7 +16,7 @@ class LayerPolygon: MoveRetractionType = 9 SupportInterfaceType = 10 - __jump_map = numpy.logical_or( numpy.arange(11) == NoneType, numpy.arange(11) >= MoveRetractionType ) + __jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(11) == NoneType, numpy.arange(11) == MoveCombingType), numpy.arange(11) == MoveRetractionType) def __init__(self, mesh, extruder, line_types, data, line_widths): self._mesh = mesh From dc01cdbc882faee68b66fcdf924884e7bc95dfa1 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 6 Sep 2016 11:30:38 +0200 Subject: [PATCH 05/38] Fix displaying support interface in layerview CURA-2049 --- cura/LayerPolygon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/LayerPolygon.py b/cura/LayerPolygon.py index 387a369aa3..d3da01e590 100644 --- a/cura/LayerPolygon.py +++ b/cura/LayerPolygon.py @@ -42,7 +42,7 @@ class LayerPolygon: # When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType # Should be generated in better way, not hardcoded. - self._isInfillOrSkinTypeMap = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0], dtype=numpy.bool) + self._isInfillOrSkinTypeMap = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1], dtype=numpy.bool) self._build_cache_line_mesh_mask = None self._build_cache_needed_points = None From 75c1f12d33a6ac817677338d9d1eaf1e2fcd714f Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 6 Sep 2016 11:42:46 +0200 Subject: [PATCH 06/38] If a group node is outside build area, all it's children are also marked as such CURA-403 --- cura/PlatformPhysics.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py index d718f25b87..d25a74bf91 100644 --- a/cura/PlatformPhysics.py +++ b/cura/PlatformPhysics.py @@ -48,6 +48,8 @@ class PlatformPhysics: # same direction. transformed_nodes = [] + group_nodes = [] + for node in BreadthFirstIterator(root): if node is root or type(node) is not SceneNode or node.getBoundingBox() is None: continue @@ -69,6 +71,9 @@ class PlatformPhysics: if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection: node._outside_buildarea = True + if node.callDecoration("isGroup"): + group_nodes.append(node) # Keep list of affected group_nodes + # Move it downwards if bottom is above platform move_vector = Vector() if Preferences.getInstance().getValue("physics/automatic_drop_down") and not (node.getParent() and node.getParent().callDecoration("isGroup")): #If an object is grouped, don't move it down @@ -144,7 +149,6 @@ class PlatformPhysics: overlap = convex_hull.intersectsPolygon(area) if overlap is None: continue - node._outside_buildarea = True if not Vector.Null.equals(move_vector, epsilon=1e-5): @@ -152,6 +156,12 @@ class PlatformPhysics: op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector) op.push() + # Group nodes should override the _outside_buildarea property of their children. + for group_node in group_nodes: + for child_node in group_node.getAllChildren(): + child_node._outside_buildarea = group_node._outside_buildarea + + def _onToolOperationStarted(self, tool): self._enabled = False From fc6e92e10f15c88e3aff3bf39d0faca6bc91ab83 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 6 Sep 2016 13:07:15 +0200 Subject: [PATCH 07/38] Fix uploading custom firmware on windows The simple string replacement left an extraneous "/" in front of the path, which Windows can't handle. QUrl.toLocalFile() does a proper conversion. CURA-955 --- plugins/USBPrinting/USBPrinterOutputDeviceManager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py index 603696f1c6..4dec2e3a06 100644 --- a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py +++ b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py @@ -105,7 +105,8 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension): @pyqtSlot(str) def updateAllFirmware(self, file_name): - file_name = file_name.replace("file://", "") # File dialogs prepend the path with file://, which we don't need / want + if file_name.startswith("file://"): + file_name = QUrl(file_name).toLocalFile() # File dialogs prepend the path with file://, which we don't need / want if not self._usb_output_devices: Message(i18n_catalog.i18nc("@info", "Unable to update firmware because there are no printers connected.")).show() return From 7a6124f221d420ff73f0fa440d42c785700c590e Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 6 Sep 2016 14:58:29 +0200 Subject: [PATCH 08/38] Get the overhang angle from the proper multiextrusion stack CURA-2289 --- plugins/SolidView/SolidView.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 3b56ac1881..2d017f829f 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -13,6 +13,7 @@ from UM.Settings.Validator import ValidatorState from UM.View.GL.OpenGL import OpenGL import cura.Settings +from cura.Settings.ExtruderManager import ExtruderManager import math @@ -45,8 +46,16 @@ class SolidView(View): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: + multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 + + if multi_extrusion: + support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value") + support_angle_stack = ExtruderManager.getInstance().getExtruderStack(support_extruder_nr) + else: + support_angle_stack = global_container_stack + if Preferences.getInstance().getValue("view/show_overhang"): - angle = global_container_stack.getProperty("support_angle", "value") + angle = support_angle_stack.getProperty("support_angle", "value") # Make sure the overhang angle is valid before passing it to the shader # Note: if the overhang angle is set to its default value, it does not need to get validated (validationState = None) if angle is not None and global_container_stack.getProperty("support_angle", "validationState") in [None, ValidatorState.Valid]: @@ -56,7 +65,6 @@ class SolidView(View): else: self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) - multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 for node in DepthFirstIterator(scene.getRoot()): if not node.render(renderer): From a40476eb6236b2c6239f4bfcb7bd87f2e1cdf571 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 6 Sep 2016 15:38:27 +0200 Subject: [PATCH 09/38] Add distinctive titles to profile rename dialogs CURA-2161 --- resources/qml/Preferences/ProfilesPage.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index 56d8bd41a4..03c66cb59d 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -267,6 +267,7 @@ UM.ManagementPage UM.RenameDialog { + title: catalog.i18nc("@title:window", "Rename Profile") id: renameDialog; object: base.currentItem != null ? base.currentItem.name : "" onAccepted: @@ -279,6 +280,7 @@ UM.ManagementPage // Dialog to request a name when creating a new profile UM.RenameDialog { + title: catalog.i18nc("@title:window", "Create Profile") id: newNameDialog; object: ""; onAccepted: @@ -292,6 +294,7 @@ UM.ManagementPage // Dialog to request a name when duplicating a new profile UM.RenameDialog { + title: catalog.i18nc("@title:window", "Duplicate Profile") id: newDuplicateNameDialog; object: ""; onAccepted: From 68ddf90d5807ca7563f6297f995807b7b96a4845 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 6 Sep 2016 15:39:44 +0200 Subject: [PATCH 10/38] Make behavior of Create Profile consistent between prefs and menu CURA-2161 --- resources/qml/Cura.qml | 9 ++++----- resources/qml/Preferences/ProfilesPage.qml | 9 +++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 42c2ad9cf4..005a0892a3 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -464,12 +464,11 @@ UM.MainWindow target: Cura.Actions.addProfile onTriggered: { - Cura.ContainerManager.createQualityChanges(null); preferences.setPage(4); preferences.show(); - // Show the renameDialog after a very short delay so the preference page has time to initiate - showProfileNameDialogTimer.start(); + // Create a new profile after a very short delay so the preference page has time to initiate + createProfileTimer.start(); } } @@ -516,11 +515,11 @@ UM.MainWindow Timer { - id: showProfileNameDialogTimer + id: createProfileTimer repeat: false interval: 1 - onTriggered: preferences.getCurrentItem().showProfileNameDialog() + onTriggered: preferences.getCurrentItem().createProfile() } // BlurSettings is a way to force the focus away from any of the setting items. diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index 03c66cb59d..df8de3f00d 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -141,11 +141,12 @@ UM.ManagementPage scrollviewCaption: catalog.i18nc("@label %1 is printer name","Printer: %1").arg(Cura.MachineManager.activeMachineName) - signal showProfileNameDialog() - onShowProfileNameDialog: + signal createProfile() + onCreateProfile: { - renameDialog.open(); - renameDialog.selectText(); + newNameDialog.object = base.currentItem != null ? base.currentItem.name : ""; + newNameDialog.open(); + newNameDialog.selectText(); } signal selectContainer(string name) From a52bb1422b80291304354b11eced79e208b4b54c Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 6 Sep 2016 17:46:11 +0200 Subject: [PATCH 11/38] Suggest unique name when creating/duplicating profiles CURA-2161 --- cura/Settings/ContainerManager.py | 4 ++++ resources/qml/Preferences/ProfilesPage.qml | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 40e036eba5..c082c64da1 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -254,6 +254,10 @@ class ContainerManager(QObject): return True return False + @pyqtSlot(str, result = str) + def makeUniqueName(self, original_name): + return self._container_registry.uniqueName(original_name) + ## Get a list of string that can be used as name filters for a Qt File Dialog # # This will go through the list of available container types and generate a list of strings diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index df8de3f00d..4e872bdc2b 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -84,7 +84,7 @@ UM.ManagementPage onClicked: { - newNameDialog.object = base.currentItem != null ? base.currentItem.name : ""; + newNameDialog.object = base.currentItem != null ? Cura.ContainerManager.makeUniqueName(base.currentItem.name) : ""; newNameDialog.open(); newNameDialog.selectText(); } @@ -100,7 +100,7 @@ UM.ManagementPage onClicked: { - newDuplicateNameDialog.object = base.currentItem.name; + newDuplicateNameDialog.object = Cura.ContainerManager.makeUniqueName(base.currentItem.name); newDuplicateNameDialog.open(); newDuplicateNameDialog.selectText(); } @@ -144,7 +144,7 @@ UM.ManagementPage signal createProfile() onCreateProfile: { - newNameDialog.object = base.currentItem != null ? base.currentItem.name : ""; + newNameDialog.object = base.currentItem != null ? Cura.ContainerManager.makeUniqueName(base.currentItem.name) : ""; newNameDialog.open(); newNameDialog.selectText(); } From e010c90c435aab94dc8e7814b89c66fae23a226c Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 7 Sep 2016 09:21:19 +0200 Subject: [PATCH 12/38] No longer set move_vector when we don't have to --- cura/PlatformPhysics.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py index d25a74bf91..b1dd1c4c8e 100644 --- a/cura/PlatformPhysics.py +++ b/cura/PlatformPhysics.py @@ -107,7 +107,6 @@ class PlatformPhysics: continue # Other node is already moving, wait for next pass. overlap = (0, 0) # Start loop with no overlap - move_vector = move_vector.set(x=overlap[0] * self._move_factor, z=overlap[1] * self._move_factor) current_overlap_checks = 0 # Continue to check the overlap until we no longer find one. while overlap and current_overlap_checks < self._max_overlap_checks: From 1bafac94bd27817b5e5310618658809e0be3afeb Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 7 Sep 2016 09:55:22 +0200 Subject: [PATCH 13/38] Value functions are now correcty copied when using per-object settings CURA-2224 --- .../PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py b/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py index b4086291ca..25c2290b37 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py +++ b/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py @@ -69,7 +69,7 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand stack = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0] else: stack = UM.Application.getInstance().getGlobalContainerStack() - new_instance.setProperty("value", stack.getProperty(item, "value")) + new_instance.setProperty("value", stack.getRawProperty(item, "value")) new_instance.resetState() # Ensure that the state is not seen as a user state. settings.addInstance(new_instance) visibility_changed = True From 86369ce1da14b91deb28ddc32d8cbdc1aab28b96 Mon Sep 17 00:00:00 2001 From: Simon Edwards Date: Wed, 7 Sep 2016 11:11:44 +0200 Subject: [PATCH 14/38] Profile import now supports 2.1 profiles and does any needed conversion work. Contributes to CURA-2252 Import ini profile fails in 2.3 --- .../CuraProfileReader/CuraProfileReader.py | 92 +++++++++++++++---- 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/plugins/CuraProfileReader/CuraProfileReader.py b/plugins/CuraProfileReader/CuraProfileReader.py index 772b11890b..4f5bb324c0 100644 --- a/plugins/CuraProfileReader/CuraProfileReader.py +++ b/plugins/CuraProfileReader/CuraProfileReader.py @@ -1,8 +1,8 @@ # Copyright (c) 2015 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. +import configparser -import os.path - +from UM import PluginRegistry from UM.Logger import Logger from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make. from cura.ProfileReader import ProfileReader @@ -28,21 +28,75 @@ class CuraProfileReader(ProfileReader): def read(self, file_name): try: archive = zipfile.ZipFile(file_name, "r") - except Exception: - # zipfile doesn't give proper exceptions, so we can only catch broad ones + results = [] + for profile_id in archive.namelist(): + with archive.open(profile_id) as f: + serialized = f.read() + profile = self._loadProfile(serialized.decode("utf-8"), profile_id) + if profile is not None: + results.append(profile) + return results + + except zipfile.BadZipFile: + # It must be an older profile from Cura 2.1. + with open(file_name, encoding="utf-8") as fhandle: + serialized = fhandle.read() + return [self._loadProfile(serialized, profile_id) for serialized, profile_id in self._upgradeProfile(serialized, file_name)] + + ## Convert a profile from an old Cura to this Cura if needed. + # + # \param serialized \type{str} The profile data to convert in the serialized on-disk format. + # \param profile_id \type{str} The name of the profile. + # \return \type{List[Tuple[str,str]]} List of serialized profile strings and matching profile names. + def _upgradeProfile(self, serialized, profile_id): + parser = configparser.ConfigParser(interpolation=None) + parser.read_string(serialized) + + if not "general" in parser: + Logger.log('w', "Missing required section 'general'.") + return None + if not "version" in parser["general"]: + Logger.log('w', "Missing required 'version' property") + return None + + version = int(parser["general"]["version"]) + if InstanceContainer.Version != version: + name = parser["general"]["name"] + return self._upgradeProfileVersion(serialized, name, version) + else: + return [(serialized, profile_id)] + + ## Load a profile from a serialized string. + # + # \param serialized \type{str} The profile data to read. + # \param profile_id \type{str} The name of the profile. + # \return \type{InstanceContainer|None} + def _loadProfile(self, serialized, profile_id): + # Create an empty profile. + profile = InstanceContainer(profile_id) + profile.addMetaDataEntry("type", "quality_changes") + try: + profile.deserialize(serialized) + except Exception as e: # Parsing error. This is not a (valid) Cura profile then. + Logger.log("e", "Error while trying to parse profile: %s", str(e)) + return None + return profile + + ## Upgrade a serialized profile to the current profile format. + # + # \param serialized \type{str} The profile data to convert. + # \param profile_id \type{str} The name of the profile. + # \param source_version \type{int} The profile version of 'serialized'. + # \return \type{List[Tuple[str,str]]} List of serialized profile strings and matching profile names. + def _upgradeProfileVersion(self, serialized, profile_id, source_version): + converter_plugins = PluginRegistry.getInstance().getAllMetaData(filter={"version_upgrade": {} }, active_only=True) + + source_format = ("profile", source_version) + profile_convert_funcs = [plugin["version_upgrade"][source_format][2] for plugin in converter_plugins + if source_format in plugin["version_upgrade"] and plugin["version_upgrade"][source_format][1] == InstanceContainer.Version] + + if not profile_convert_funcs: return [] - results = [] - for profile_id in archive.namelist(): - # Create an empty profile. - profile = InstanceContainer(profile_id) - profile.addMetaDataEntry("type", "quality_changes") - serialized = "" - with archive.open(profile_id) as f: - serialized = f.read() - try: - profile.deserialize(serialized.decode("utf-8") ) - except Exception as e: # Parsing error. This is not a (valid) Cura profile then. - Logger.log("e", "Error while trying to parse profile: %s", str(e)) - continue - results.append(profile) - return results \ No newline at end of file + + filenames, outputs = profile_convert_funcs[0](serialized, profile_id) + return list(zip(outputs, filenames)) From cce94c80432c0d51d3a7f3189acb740ba650e5c8 Mon Sep 17 00:00:00 2001 From: Simon Edwards Date: Wed, 7 Sep 2016 11:29:37 +0200 Subject: [PATCH 15/38] Added skin_overlap <= infill_overlap mapping. Contributes to CURA-844 Profile converter 2.1 ==> 2.2 --- plugins/LegacyProfileReader/DictionaryOfDoom.json | 3 ++- .../VersionUpgrade21to22/VersionUpgrade21to22.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/LegacyProfileReader/DictionaryOfDoom.json b/plugins/LegacyProfileReader/DictionaryOfDoom.json index db0d26b8e4..15ef792f83 100644 --- a/plugins/LegacyProfileReader/DictionaryOfDoom.json +++ b/plugins/LegacyProfileReader/DictionaryOfDoom.json @@ -70,7 +70,8 @@ "magic_spiralize": "spiralize", "prime_tower_enable": "wipe_tower", "prime_tower_size": "math.sqrt(float(wipe_tower_volume) / float(layer_height))", - "ooze_shield_enabled": "ooze_shield" + "ooze_shield_enabled": "ooze_shield", + "skin_overlap": "fill_overlap" }, "defaults": { diff --git a/plugins/VersionUpgrade/VersionUpgrade21to22/VersionUpgrade21to22.py b/plugins/VersionUpgrade/VersionUpgrade21to22/VersionUpgrade21to22.py index 0c3a4d1055..c8ec559702 100644 --- a/plugins/VersionUpgrade/VersionUpgrade21to22/VersionUpgrade21to22.py +++ b/plugins/VersionUpgrade/VersionUpgrade21to22/VersionUpgrade21to22.py @@ -279,6 +279,8 @@ class VersionUpgrade21to22(VersionUpgrade): elif key in _setting_name_translations: del settings[key] settings[_setting_name_translations[key]] = value + if "infill_overlap" in settings: # New setting, added in 2.3 + settings["skin_overlap"] = settings["infill_overlap"] return settings ## Translates a setting name for the change from Cura 2.1 to 2.2. From fe34938bce6462db6b54af39d9a27e24a2432e34 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 7 Sep 2016 13:57:09 +0200 Subject: [PATCH 16/38] Update wording --- plugins/SliceInfoPlugin/SliceInfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SliceInfoPlugin/SliceInfo.py b/plugins/SliceInfoPlugin/SliceInfo.py index 047a03575d..117ac2a178 100644 --- a/plugins/SliceInfoPlugin/SliceInfo.py +++ b/plugins/SliceInfoPlugin/SliceInfo.py @@ -65,7 +65,7 @@ class SliceInfo(Extension): Preferences.getInstance().addPreference("info/asked_send_slice_info", False) if not Preferences.getInstance().getValue("info/asked_send_slice_info"): - self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura automatically sends slice info. You can disable this in preferences"), lifetime = 0, dismissable = False) + self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymised slicing statistics. You can disable this in preferences"), lifetime = 0, dismissable = False) self.send_slice_info_message.addAction("Dismiss", catalog.i18nc("@action:button", "Dismiss"), None, "") self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered) self.send_slice_info_message.show() From bf394a42d5e7c57fa2fd8ecc1d7ae5c95250c739 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 7 Sep 2016 15:26:51 +0200 Subject: [PATCH 17/38] Fix UMO-DualExtrusion prime tower location CURA-2283 --- resources/definitions/ultimaker_original_dual.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/ultimaker_original_dual.def.json b/resources/definitions/ultimaker_original_dual.def.json index 6feaa07470..8d2d5d85d6 100644 --- a/resources/definitions/ultimaker_original_dual.def.json +++ b/resources/definitions/ultimaker_original_dual.def.json @@ -78,7 +78,7 @@ "default_value": 185 }, "prime_tower_position_y": { - "default_value": 175 + "default_value": 160 } } } From bbb211b0f6dcc055f4a3ddf59e78d519a5c1f48a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 7 Sep 2016 15:28:58 +0200 Subject: [PATCH 18/38] Saving profile to g-code with empty container no longer results in empty string --- plugins/GCodeWriter/GCodeWriter.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py index c15b079097..95d6659dd1 100644 --- a/plugins/GCodeWriter/GCodeWriter.py +++ b/plugins/GCodeWriter/GCodeWriter.py @@ -66,7 +66,10 @@ class GCodeWriter(MeshWriter): ## Create a new container with container 2 as base and container 1 written over it. def _createFlattenedContainerInstance(self, instance_container1, instance_container2): flat_container = InstanceContainer(instance_container2.getName()) - flat_container.setDefinition(instance_container2.getDefinition()) + if instance_container1.getDefinition(): + flat_container.setDefinition(instance_container1.getDefinition()) + else: + flat_container.setDefinition(instance_container2.getDefinition()) flat_container.setMetaData(instance_container2.getMetaData()) for key in instance_container2.getAllKeys(): From a998d596936e1d0a38c360729ae6a913d7faee6b Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 7 Sep 2016 15:38:27 +0200 Subject: [PATCH 19/38] Fix setting heated bed upgrade option A new variant was created, but the property was set on the old variant. CURA-2253 --- plugins/UltimakerMachineActions/UMOUpgradeSelection.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/UltimakerMachineActions/UMOUpgradeSelection.py b/plugins/UltimakerMachineActions/UMOUpgradeSelection.py index 64c9ae1180..b92dc30c68 100644 --- a/plugins/UltimakerMachineActions/UMOUpgradeSelection.py +++ b/plugins/UltimakerMachineActions/UMOUpgradeSelection.py @@ -31,7 +31,7 @@ class UMOUpgradeSelection(MachineAction): if variant: if variant.getId() == "empty_variant": variant_index = global_container_stack.getContainerIndex(variant) - self._createVariant(global_container_stack, variant_index) + variant = self._createVariant(global_container_stack, variant_index) variant.setProperty("machine_heated_bed", "value", heated_bed) self.heatedBedChanged.emit() @@ -41,4 +41,5 @@ class UMOUpgradeSelection(MachineAction): new_variant.addMetaDataEntry("type", "variant") new_variant.setDefinition(global_container_stack.getBottom()) UM.Settings.ContainerRegistry.getInstance().addContainer(new_variant) - global_container_stack.replaceContainer(variant_index, new_variant) \ No newline at end of file + global_container_stack.replaceContainer(variant_index, new_variant) + return new_variant \ No newline at end of file From ee55e03911e1ef765b565317f5bfe964b5e7c90e Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 7 Sep 2016 15:46:12 +0200 Subject: [PATCH 20/38] Added max size to prime tower CURA-2234 --- resources/definitions/fdmprinter.def.json | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index e76d277b0c..bbf4df56d8 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -3297,6 +3297,7 @@ "default_value": 15, "value": "15 if prime_tower_enable else 0", "minimum_value": "0", + "maximum_value": "min(0.5 * machine_width, 0.5 * machine_depth)", "maximum_value_warning": "20", "settable_per_mesh": false, "settable_per_extruder": false From 15101ec53dc285e4a02f59fc96c05cff94cc49a6 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 7 Sep 2016 15:51:08 +0200 Subject: [PATCH 21/38] Fix showing material options for 3rd party printers CURA-2287 --- resources/definitions/fdmprinter.def.json | 1 + resources/definitions/ultimaker2.def.json | 1 + 2 files changed, 2 insertions(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index bbf4df56d8..1116506886 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -10,6 +10,7 @@ "manufacturer": "Ultimaker", "file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj;application/x3g", "visible": false, + "has_materials": true, "preferred_material": "*generic_pla*", "preferred_quality": "*normal*", "machine_extruder_trains": diff --git a/resources/definitions/ultimaker2.def.json b/resources/definitions/ultimaker2.def.json index 81723ae10d..a0ead36bff 100644 --- a/resources/definitions/ultimaker2.def.json +++ b/resources/definitions/ultimaker2.def.json @@ -14,6 +14,7 @@ "platform": "ultimaker2_platform.obj", "platform_texture": "Ultimaker2backplate.png", "platform_offset": [9, 0, 0], + "has_materials": false, "supported_actions":["UpgradeFirmware"] }, "overrides": { From b7c2a77277bba3f9b3c88461445cea7f074bd878 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 7 Sep 2016 15:58:01 +0200 Subject: [PATCH 22/38] Code style: Use double quotes for strings Contributes to issue CURA-2252. --- plugins/CuraProfileReader/CuraProfileReader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/CuraProfileReader/CuraProfileReader.py b/plugins/CuraProfileReader/CuraProfileReader.py index 4f5bb324c0..c8d39b7a78 100644 --- a/plugins/CuraProfileReader/CuraProfileReader.py +++ b/plugins/CuraProfileReader/CuraProfileReader.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015 Ultimaker B.V. +# Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. import configparser @@ -53,10 +53,10 @@ class CuraProfileReader(ProfileReader): parser.read_string(serialized) if not "general" in parser: - Logger.log('w', "Missing required section 'general'.") + Logger.log("w", "Missing required section 'general'.") return None if not "version" in parser["general"]: - Logger.log('w', "Missing required 'version' property") + Logger.log("w", "Missing required 'version' property") return None version = int(parser["general"]["version"]) From cab5f4d823c8094274207e98aa3507177b2d629d Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 7 Sep 2016 16:06:42 +0200 Subject: [PATCH 23/38] Fix "Reset all model transformations" for groups CURA-2291 --- cura/CuraApplication.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 892440cba0..32c5191c7c 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -693,17 +693,17 @@ class CuraApplication(QtApplication): continue # Node that doesnt have a mesh and is not a group. if node.getParent() and node.getParent().callDecoration("isGroup"): continue # Grouped nodes don't need resetting as their parent (the group) is resetted) - nodes.append(node) if nodes: op = GroupedOperation() for node in nodes: + # Ensure that the object is above the build platform node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator) op.addOperation(SetTransformOperation(node, Vector(0, node.getWorldPosition().y - node.getBoundingBox().bottom, 0))) op.push() - - ## Reset all transformations on nodes with mesh data. + + ## Reset all transformations on nodes with mesh data. @pyqtSlot() def resetAll(self): Logger.log("i", "Resetting all scene transformations") @@ -719,15 +719,17 @@ class CuraApplication(QtApplication): if nodes: op = GroupedOperation() - for node in nodes: # Ensure that the object is above the build platform node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator) - - op.addOperation(SetTransformOperation(node, Vector(0, node.getMeshData().getCenterPosition().y, 0), Quaternion(), Vector(1, 1, 1))) - + center_y = 0 + if node.callDecoration("isGroup"): + center_y = node.getWorldPosition().y - node.getBoundingBox().bottom + else: + center_y = node.getMeshData().getCenterPosition().y + op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0), Quaternion(), Vector(1, 1, 1))) op.push() - + ## Reload all mesh data on the screen from file. @pyqtSlot() def reloadAll(self): From dc282dc2634171cab1eb4ec1641c25d4e8f4fcfc Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 7 Sep 2016 16:51:03 +0200 Subject: [PATCH 24/38] Fix Per Model Settings crash when switching to multiextrusion printer CURA-2228 --- .../PerObjectSettingsTool.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py index b5c4c0f22c..5c52c89416 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py @@ -7,6 +7,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Application import Application from UM.Preferences import Preferences from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator +from cura.Settings.ExtruderManager import ExtruderManager ## This tool allows the user to add & change settings per node in the scene. @@ -71,11 +72,17 @@ class PerObjectSettingsTool(Tool): global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: self._multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 + + # Ensure that all extruder data is reset if not self._multi_extrusion: - # Ensure that all extruder data is reset - root_node = Application.getInstance().getController().getScene().getRoot() - for node in DepthFirstIterator(root_node): - node.callDecoration("setActiveExtruder", global_container_stack.getId()) + default_stack_id = global_container_stack.getId() + else: + default_stack_id = ExtruderManager.getInstance().getExtruderStack(0).getId() + + root_node = Application.getInstance().getController().getScene().getRoot() + for node in DepthFirstIterator(root_node): + node.callDecoration("setActiveExtruder", default_stack_id) + self._updateEnabled() def _updateEnabled(self): From 35168ddd5a7ce0cf2b974d3f377c58fac876a8bd Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 7 Sep 2016 16:54:52 +0200 Subject: [PATCH 25/38] Prime tower disallowed area is now drawn seperate from others if it colissions with others CURA-2234 --- cura/BuildVolume.py | 52 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index ca4c8e7b34..6ecb33fc78 100644 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -67,6 +67,9 @@ class BuildVolume(SceneNode): self._disallowed_areas = [] self._disallowed_area_mesh = None + self._prime_tower_area = None + self._prime_tower_area_mesh = None + self.setCalculateBoundingBox(False) self._volume_aabb = None @@ -110,6 +113,10 @@ class BuildVolume(SceneNode): if self._disallowed_area_mesh: renderer.queueNode(self, mesh = self._disallowed_area_mesh, shader = self._shader, transparent = True, backface_cull = True, sort = -9) + if self._prime_tower_area_mesh: + renderer.queueNode(self, mesh = self._prime_tower_area_mesh, shader = self._shader, transparent=True, + backface_cull=True, sort=-8) + return True ## Recalculates the build volume & disallowed areas. @@ -184,6 +191,24 @@ class BuildVolume(SceneNode): else: self._disallowed_area_mesh = None + if self._prime_tower_area: + mb = MeshBuilder() + color = Color(1.0, 0.0, 0.0, 0.5) + points = self._prime_tower_area.getPoints() + first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, + self._clamp(points[0][1], min_d, max_d)) + previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, + self._clamp(points[0][1], min_d, max_d)) + for point in points: + new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height, + self._clamp(point[1], min_d, max_d)) + mb.addFace(first, previous_point, new_point, color=color) + previous_point = new_point + + self._prime_tower_area_mesh = mb.build() + else: + self._prime_tower_area_mesh = None + self._volume_aabb = AxisAlignedBox( minimum = Vector(min_w, min_h - 1.0, min_d), maximum = Vector(max_w, max_h - self._raft_thickness, max_d)) @@ -301,14 +326,21 @@ class BuildVolume(SceneNode): machine_width = self._global_container_stack.getProperty("machine_width", "value") machine_depth = self._global_container_stack.getProperty("machine_depth", "value") - + self._prime_tower_area = None # Add prime tower location as disallowed area. if self._global_container_stack.getProperty("prime_tower_enable", "value") == True: prime_tower_size = self._global_container_stack.getProperty("prime_tower_size", "value") prime_tower_x = self._global_container_stack.getProperty("prime_tower_position_x", "value") - machine_width / 2 prime_tower_y = - self._global_container_stack.getProperty("prime_tower_position_y", "value") + machine_depth / 2 - disallowed_areas.append([ + '''disallowed_areas.append([ + [prime_tower_x - prime_tower_size, prime_tower_y - prime_tower_size], + [prime_tower_x, prime_tower_y - prime_tower_size], + [prime_tower_x, prime_tower_y], + [prime_tower_x - prime_tower_size, prime_tower_y], + ])''' + + self._prime_tower_area = Polygon([ [prime_tower_x - prime_tower_size, prime_tower_y - prime_tower_size], [prime_tower_x, prime_tower_y - prime_tower_size], [prime_tower_x, prime_tower_y], @@ -344,6 +376,9 @@ class BuildVolume(SceneNode): areas.append(poly) + if self._prime_tower_area: + self._prime_tower_area = self._prime_tower_area.getMinkowskiHull(Polygon(approximatedCircleVertices(bed_adhesion_size))) + # Add the skirt areas around the borders of the build plate. if bed_adhesion_size > 0: half_machine_width = self._global_container_stack.getProperty("machine_width", "value") / 2 @@ -377,6 +412,19 @@ class BuildVolume(SceneNode): [half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size] ], numpy.float32))) + # Check if the prime tower area intersects with any of the other areas. + # If this is the case, keep the polygon seperate, so it can be drawn in red. + # If not, add it back to disallowed area's, so it's rendered as normal. + collision = False + if self._prime_tower_area: + for area in areas: + if self._prime_tower_area.intersectsPolygon(area) is not None: + collision = True + break + if not collision: + areas.append(self._prime_tower_area) + self._prime_tower_area = None + self._disallowed_areas = areas ## Convenience function to calculate the size of the bed adhesion in directions x, y. From 9a84deb14ece77de330ddd7dfac39ab22cf63015 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 7 Sep 2016 17:19:20 +0200 Subject: [PATCH 26/38] If buildplate has errors, slicing is not possible CURA-2234 --- cura/BuildVolume.py | 9 +++++++-- plugins/CuraEngineBackend/StartSliceJob.py | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 6ecb33fc78..a25604d165 100644 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -85,6 +85,8 @@ class BuildVolume(SceneNode): ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged) self._onActiveExtruderStackChanged() + self._has_errors = False + def setWidth(self, width): if width: self._width = width @@ -316,10 +318,13 @@ class BuildVolume(SceneNode): if rebuild_me: self.rebuild() + def hasErrors(self): + return self._has_errors + def _updateDisallowedAreas(self): if not self._global_container_stack: return - + self._has_errors = False # Reset. disallowed_areas = copy.deepcopy( self._global_container_stack.getProperty("machine_disallowed_areas", "value")) areas = [] @@ -424,7 +429,7 @@ class BuildVolume(SceneNode): if not collision: areas.append(self._prime_tower_area) self._prime_tower_area = None - + self._has_errors = collision self._disallowed_areas = areas ## Convenience function to calculate the size of the bed adhesion in directions x, y. diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 4d40da899d..fc26bd086b 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -78,6 +78,10 @@ class StartSliceJob(Job): self.setResult(StartJobResult.SettingError) return + if Application.getInstance().getBuildVolume().hasErrors(): + self.setResult(StartJobResult.SettingError) + return + # Don't slice if there is a per object setting with an error value. for node in DepthFirstIterator(self._scene.getRoot()): if type(node) is not SceneNode or not node.isSelectable(): From 895cf40ab04ca7893ec9e44ae9225372cdc7d3db Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Wed, 7 Sep 2016 17:31:45 +0200 Subject: [PATCH 27/38] Only show dual category if there is more than one extruder --- resources/definitions/fdmprinter.def.json | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 1116506886..ab1915bde1 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -3221,6 +3221,7 @@ "type": "category", "icon": "category_dual", "description": "Settings used for printing with multiple extruders.", + "enabled": "machine_extruder_count > 1", "children": { "adhesion_extruder_nr": From 29edcb905866593d9d1966f801e3276a1d8151e1 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 8 Sep 2016 08:48:58 +0200 Subject: [PATCH 28/38] Style MessageStack making messages less likely to be overlooked CURA-1886 --- resources/themes/cura/styles.qml | 22 ++++++++++++++++++---- resources/themes/cura/theme.json | 14 +++++++++++--- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/resources/themes/cura/styles.qml b/resources/themes/cura/styles.qml index 3cfb4514ee..91a512b6db 100644 --- a/resources/themes/cura/styles.qml +++ b/resources/themes/cura/styles.qml @@ -199,18 +199,32 @@ QtObject { property Component progressbar: Component{ ProgressBarStyle { - background:Rectangle { + background: Rectangle { implicitWidth: Theme.getSize("message").width - (Theme.getSize("default_margin").width * 2) implicitHeight: Theme.getSize("progressbar").height radius: Theme.getSize("progressbar_radius").width - color: Theme.getColor("progressbar_background") + color: control.hasOwnProperty("backgroundColor") ? control.backgroundColor : Theme.getColor("progressbar_background") } progress: Rectangle { - color: control.indeterminate ? "transparent" : Theme.getColor("progressbar_control") + color: + { + if(control.indeterminate) + { + return "transparent"; + } + else if(control.hasOwnProperty("controlColor")) + { + return control.controlColor; + } + else + { + return Theme.getColor("progressbar_control"); + } + } radius: Theme.getSize("progressbar_radius").width Rectangle{ radius: Theme.getSize("progressbar_radius").width - color: Theme.getColor("progressbar_control") + color: control.hasOwnProperty("controlColor") ? control.controlColor : Theme.getColor("progressbar_control") width: Theme.getSize("progressbar_control").width height: Theme.getSize("progressbar_control").height visible: control.indeterminate diff --git a/resources/themes/cura/theme.json b/resources/themes/cura/theme.json index 69fc2c2c71..f90467c9e8 100644 --- a/resources/themes/cura/theme.json +++ b/resources/themes/cura/theme.json @@ -159,9 +159,17 @@ "tooltip": [12, 169, 227, 255], "tooltip_text": [255, 255, 255, 255], - "message_background": [255, 255, 255, 255], - "message_text": [32, 166, 219, 255], - "message_dismiss": [127, 127, 127, 255], + "message_background": [24, 41, 77, 255], + "message_text": [255, 255, 255, 255], + "message_border": [24, 41, 77, 255], + "message_button": [255, 255, 255, 255], + "message_button_hover": [12, 169, 227, 255], + "message_button_active": [32, 166, 219, 255], + "message_button_text": [24, 41, 77, 255], + "message_button_text_hover": [255, 255, 255, 255], + "message_button_text_active": [255, 255, 255, 255], + "message_progressbar_background": [255, 255, 255, 255], + "message_progressbar_control": [12, 169, 227, 255], "tool_panel_background": [255, 255, 255, 255], From b9215407032bc48c8d169aa450e529822866e02d Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 8 Sep 2016 08:58:40 +0200 Subject: [PATCH 29/38] Remove superfluous margin left by hidden settings Each setting that was made visible, but was disabled due to the value of another setting, would leave a 1 px margin (eg settings in the support category when support was not enabled). Hidden settings now eat that margin, leaving nothing behind. --- resources/qml/Settings/SettingView.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 7f8c1488ae..c50c2cc4e0 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -40,7 +40,7 @@ ScrollView id: delegate width: UM.Theme.getSize("sidebar").width; - height: provider.properties.enabled == "True" ? UM.Theme.getSize("section").height : 0 + height: provider.properties.enabled == "True" ? UM.Theme.getSize("section").height : - contents.spacing Behavior on height { NumberAnimation { duration: 100 } } opacity: provider.properties.enabled == "True" ? 1 : 0 Behavior on opacity { NumberAnimation { duration: 100 } } From a11e2a56a6cbe490bdf1eab7ec5cda5bf1c6d622 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 8 Sep 2016 09:21:50 +0200 Subject: [PATCH 30/38] Fix densities of Nylon, PC & TPU --- resources/materials/generic_nylon.xml.fdm_material | 2 +- resources/materials/generic_pc.xml.fdm_material | 2 +- resources/materials/generic_tpu.xml.fdm_material | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/materials/generic_nylon.xml.fdm_material b/resources/materials/generic_nylon.xml.fdm_material index d7391e2984..d37adf906a 100644 --- a/resources/materials/generic_nylon.xml.fdm_material +++ b/resources/materials/generic_nylon.xml.fdm_material @@ -14,7 +14,7 @@ Generic Nylon profile. Serves as an example file, data in this file is not corre #3DF266 - 1.19 + 1.14 2.85 diff --git a/resources/materials/generic_pc.xml.fdm_material b/resources/materials/generic_pc.xml.fdm_material index 292b3fd098..77d7e60bf5 100644 --- a/resources/materials/generic_pc.xml.fdm_material +++ b/resources/materials/generic_pc.xml.fdm_material @@ -14,7 +14,7 @@ Generic PC profile. Serves as an example file, data in this file is not correct. #F29030 - 1.18 + 1.19 2.85 diff --git a/resources/materials/generic_tpu.xml.fdm_material b/resources/materials/generic_tpu.xml.fdm_material index 455e7b89be..5a0c793cda 100644 --- a/resources/materials/generic_tpu.xml.fdm_material +++ b/resources/materials/generic_tpu.xml.fdm_material @@ -14,7 +14,7 @@ Generic TPU 95A profile. Serves as an example file, data in this file is not cor #B22744 - 1.19 + 1.22 2.85 From ea4333b802b0d2db0e48202992c6a4258d2019d2 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 8 Sep 2016 11:51:33 +0200 Subject: [PATCH 31/38] No longer disable Nylon by default It prints fine on most printers. Just allow people to select it. (And other secret reasons.) --- resources/materials/generic_nylon.xml.fdm_material | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/materials/generic_nylon.xml.fdm_material b/resources/materials/generic_nylon.xml.fdm_material index d37adf906a..02b2d5414b 100644 --- a/resources/materials/generic_nylon.xml.fdm_material +++ b/resources/materials/generic_nylon.xml.fdm_material @@ -18,7 +18,6 @@ Generic Nylon profile. Serves as an example file, data in this file is not corre 2.85 - no 250 60 175 From bb92c0d7fae58f5173333de8f7312b540b3ffdad Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 8 Sep 2016 11:56:11 +0200 Subject: [PATCH 32/38] Fix units for density and diameter --- resources/qml/Preferences/MaterialView.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml index 3041115ad2..6115671563 100644 --- a/resources/qml/Preferences/MaterialView.qml +++ b/resources/qml/Preferences/MaterialView.qml @@ -115,7 +115,7 @@ TabView width: base.secondColumnWidth; value: properties.density; decimals: 2 - suffix: "g/cm" + suffix: "g/cm³" stepSize: 0.01 readOnly: !base.editingEnabled; @@ -128,7 +128,7 @@ TabView width: base.secondColumnWidth; value: properties.diameter; decimals: 2 - suffix: "mm³" + suffix: "mm" stepSize: 0.01 readOnly: !base.editingEnabled; From 09f7d9b999d787b002af967511c194701a61b867 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 8 Sep 2016 12:49:42 +0200 Subject: [PATCH 33/38] We shuffle the list of nodes so that the push free won't endlessly repeat the same 2 steps --- cura/PlatformPhysics.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py index b1dd1c4c8e..2a5bd4091c 100644 --- a/cura/PlatformPhysics.py +++ b/cura/PlatformPhysics.py @@ -15,6 +15,9 @@ from cura.ConvexHullDecorator import ConvexHullDecorator from . import PlatformPhysicsOperation from . import ZOffsetDecorator +import random # used for list shuffling + + class PlatformPhysics: def __init__(self, controller, volume): super().__init__() @@ -49,8 +52,11 @@ class PlatformPhysics: transformed_nodes = [] group_nodes = [] - - for node in BreadthFirstIterator(root): + # We try to shuffle all the nodes to prevent "locked" situations, where iteration B inverts iteration A. + # By shuffling the order of the nodes, this might happen a few times, but at some point it will resolve. + nodes = list(BreadthFirstIterator(root)) + random.shuffle(nodes) + for node in nodes: if node is root or type(node) is not SceneNode or node.getBoundingBox() is None: continue From be6bfdd4af56c2ed9f561f21b4bce19b86ab873b Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 8 Sep 2016 13:58:13 +0200 Subject: [PATCH 34/38] Add a "linked" icon to settings that have "global_inherits_stack" set --- resources/qml/Settings/SettingItem.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/Settings/SettingItem.qml b/resources/qml/Settings/SettingItem.qml index ed16b722dd..cdc557a089 100644 --- a/resources/qml/Settings/SettingItem.qml +++ b/resources/qml/Settings/SettingItem.qml @@ -138,7 +138,7 @@ Item { { id: linkedSettingIcon; - visible: Cura.MachineManager.activeStackId != Cura.MachineManager.activeMachineId && !definition.settable_per_extruder && base.showLinkedSettingIcon + visible: Cura.MachineManager.activeStackId != Cura.MachineManager.activeMachineId && (!definition.settable_per_extruder || definition.global_inherits_stack != "-1") && base.showLinkedSettingIcon height: parent.height; width: height; From 46942d7cf23e32dcdf5dbf00440aa781061f4f65 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 8 Sep 2016 14:02:44 +0200 Subject: [PATCH 35/38] Remove "infill mesh" as a standard visible setting --- cura/CuraApplication.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 32c5191c7c..14e4cd9bf1 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -253,7 +253,6 @@ class CuraApplication(QtApplication): meshfix blackmagic print_sequence - infill_mesh experimental """.replace("\n", ";").replace(" ", "")) From 4de78db87a6d6396590079486b396ae66b522538 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 8 Sep 2016 14:04:58 +0200 Subject: [PATCH 36/38] Revert "Remove "infill mesh" as a standard visible setting" This reverts commit 46942d7cf23e32dcdf5dbf00440aa781061f4f65. It was not my decision to make --- cura/CuraApplication.py | 1 + plugins/3MFReader/ThreeMFReader.py | 2 +- plugins/PerObjectSettingsTool/PerObjectSettingsTool.py | 5 ++++- plugins/SolidView/SolidView.py | 2 ++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 14e4cd9bf1..32c5191c7c 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -253,6 +253,7 @@ class CuraApplication(QtApplication): meshfix blackmagic print_sequence + infill_mesh experimental """.replace("\n", ";").replace(" ", "")) diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index ea3ca30118..89fa5e3e88 100644 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -71,7 +71,7 @@ class ThreeMFReader(MeshReader): rotation.setByRotationAxis(-0.5 * math.pi, Vector(1, 0, 0)) # TODO: We currently do not check for normals and simply recalculate them. - mesh_builder.calculateNormals() + mesh_builder.calculateNormals(flip = True) mesh_builder.setFileName(file_name) node.setMeshData(mesh_builder.build().getTransformed(rotation)) node.setSelectable(True) diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py index 5c52c89416..953f60a33d 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py @@ -77,7 +77,10 @@ class PerObjectSettingsTool(Tool): if not self._multi_extrusion: default_stack_id = global_container_stack.getId() else: - default_stack_id = ExtruderManager.getInstance().getExtruderStack(0).getId() + default_stack = ExtruderManager.getInstance().getExtruderStack(0) + if default_stack: + default_stack_id = default_stack.getId() + else: default_stack_id = global_container_stack.getId() root_node = Application.getInstance().getController().getScene().getRoot() for node in DepthFirstIterator(root_node): diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 2d017f829f..fd9f106334 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -51,6 +51,8 @@ class SolidView(View): if multi_extrusion: support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value") support_angle_stack = ExtruderManager.getInstance().getExtruderStack(support_extruder_nr) + if not support_angle_stack: + support_angle_stack = global_container_stack else: support_angle_stack = global_container_stack From 8a8b55800b905dd479848bf8a4f61a0a6d7ebd1b Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 8 Sep 2016 14:15:00 +0200 Subject: [PATCH 37/38] Duplicating materialContainers should now duplicate all related material containers CURA-2242 --- cura/CuraApplication.py | 2 ++ cura/Settings/ContainerManager.py | 35 +++++++++++++++++++++ resources/qml/Preferences/MaterialsPage.qml | 16 +++------- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 32c5191c7c..7651cad930 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -322,6 +322,7 @@ class CuraApplication(QtApplication): path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name) if path: + instance.setPath(path) with SaveFile(path, "wt", -1, "utf-8") as f: f.write(data) @@ -346,6 +347,7 @@ class CuraApplication(QtApplication): elif stack_type == "extruder_train": path = Resources.getStoragePath(self.ResourceTypes.ExtruderStack, file_name) if path: + stack.setPath(path) with SaveFile(path, "wt", -1, "utf-8") as f: f.write(data) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index c082c64da1..27a9bd90dd 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -167,6 +167,19 @@ class ContainerManager(QObject): return True + @pyqtSlot(str, str, result=str) + def getContainerMetaDataEntry(self, container_id, entry_name): + containers = self._container_registry.findContainers(None, id=container_id) + if not containers: + UM.Logger.log("w", "Could not get metadata of container %s because it was not found.", container_id) + return False + + result = containers[0].getMetaDataEntry(entry_name) + if result: + return result + else: + return "" + ## Set a metadata entry of the specified container. # # This will set the specified entry of the container's metadata to the specified @@ -577,6 +590,28 @@ class ContainerManager(QObject): return new_name + @pyqtSlot(str, result = str) + def duplicateMaterial(self, material_id): + containers = self._container_registry.findInstanceContainers(id=material_id) + if not containers: + UM.Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", material_id) + return "" + + # Ensure all settings are saved. + UM.Application.getInstance().saveSettings() + + # Create a new ID & container to hold the data. + new_id = self._container_registry.uniqueName(material_id) + container_type = type(containers[0]) # Could be either a XMLMaterialProfile or a InstanceContainer + duplicated_container = container_type(new_id) + + # Instead of duplicating we load the data from the basefile again. + # This ensures that the inheritance goes well and all "cut up" subclasses of the xmlMaterial profile + # are also correctly created. + with open(containers[0].getPath(), encoding="utf-8") as f: + duplicated_container.deserialize(f.read()) + self._container_registry.addContainer(duplicated_container) + # Factory function, used by QML @staticmethod def createContainerManager(engine, js_engine): diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml index d2d7c1b0a2..b1f0afe52f 100644 --- a/resources/qml/Preferences/MaterialsPage.qml +++ b/resources/qml/Preferences/MaterialsPage.qml @@ -129,30 +129,24 @@ UM.ManagementPage enabled: base.currentItem != null && base.currentItem.id != Cura.MachineManager.activeMaterialId onClicked: Cura.MachineManager.setActiveMaterial(base.currentItem.id) }, - /* // apparently visible does not work on OS X - Button + // apparently visible does not work on OS X + /*Button { text: catalog.i18nc("@action:button", "Duplicate"); iconName: "list-add"; enabled: base.currentItem != null onClicked: { - var material_id = Cura.ContainerManager.duplicateContainer(base.currentItem.id) + var base_file = Cura.ContainerManager.getContainerMetaDataEntry(base.currentItem.id, "base_file") + // We need to copy the base container instead of the specific variant. + var material_id = base_file == "" ? Cura.ContainerManager.duplicateMaterial(base.currentItem.id): Cura.ContainerManager.duplicateMaterial(base_file) if(material_id == "") { return } - if(Cura.MachineManager.filterQualityByMachine) - { - var quality_id = Cura.ContainerManager.duplicateContainer(Cura.MachineManager.activeQualityId) - Cura.ContainerManager.setContainerMetaDataEntry(quality_id, "material", material_id) - Cura.MachineManager.setActiveQuality(quality_id) - } - Cura.MachineManager.setActiveMaterial(material_id) } - visible: false; }, */ Button From 96a1aafb80e7cb463b22d9185281df096837730d Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 8 Sep 2016 14:23:35 +0200 Subject: [PATCH 38/38] Zipfile is now closed when it's done loading CURA-2252 --- plugins/CuraProfileReader/CuraProfileReader.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/CuraProfileReader/CuraProfileReader.py b/plugins/CuraProfileReader/CuraProfileReader.py index c8d39b7a78..2bccb8e3cb 100644 --- a/plugins/CuraProfileReader/CuraProfileReader.py +++ b/plugins/CuraProfileReader/CuraProfileReader.py @@ -27,15 +27,15 @@ class CuraProfileReader(ProfileReader): # returned. def read(self, file_name): try: - archive = zipfile.ZipFile(file_name, "r") - results = [] - for profile_id in archive.namelist(): - with archive.open(profile_id) as f: - serialized = f.read() - profile = self._loadProfile(serialized.decode("utf-8"), profile_id) - if profile is not None: - results.append(profile) - return results + with zipfile.ZipFile(file_name, "r") as archive: + results = [] + for profile_id in archive.namelist(): + with archive.open(profile_id) as f: + serialized = f.read() + profile = self._loadProfile(serialized.decode("utf-8"), profile_id) + if profile is not None: + results.append(profile) + return results except zipfile.BadZipFile: # It must be an older profile from Cura 2.1.