diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index a91a85f406..ca4c8e7b34 100644 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -280,7 +280,7 @@ class BuildVolume(SceneNode): self._height = self._global_container_stack.getProperty("machine_height", "value") rebuild_me = True - if setting_key in self._skirt_settings or setting_key in self._prime_settings or setting_key in self._tower_settings: + if setting_key in self._skirt_settings or setting_key in self._prime_settings or setting_key in self._tower_settings or setting_key == "print_sequence": self._updateDisallowedAreas() rebuild_me = True @@ -383,6 +383,10 @@ class BuildVolume(SceneNode): def _getBedAdhesionSize(self, container_stack): skirt_size = 0.0 + # If we are printing one at a time, we need to add the bed adhesion size to the disallowed areas of the objects + if container_stack.getProperty("print_sequence", "value") == "one_at_a_time": + return 0.1 # Return a very small value, so we do draw disallowed area's near the edges. + adhesion_type = container_stack.getProperty("adhesion_type", "value") if adhesion_type == "skirt": skirt_distance = container_stack.getProperty("skirt_gap", "value") diff --git a/cura/ConvexHullDecorator.py b/cura/ConvexHullDecorator.py index 5185579633..770ed42a9f 100644 --- a/cura/ConvexHullDecorator.py +++ b/cura/ConvexHullDecorator.py @@ -56,6 +56,7 @@ class ConvexHullDecorator(SceneNodeDecorator): if self._global_stack and self._node: if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"): hull = hull.getMinkowskiHull(Polygon(numpy.array(self._global_stack.getProperty("machine_head_polygon", "value"), numpy.float32))) + hull = self._add2DAdhesionMargin(hull) return hull ## Get the convex hull of the node with the full head size @@ -229,17 +230,16 @@ class ConvexHullDecorator(SceneNodeDecorator): machine_head_coords = numpy.array( self._global_stack.getProperty("machine_head_with_fans_polygon", "value"), numpy.float32) - head_y_size = abs(machine_head_coords).min() # safe margin to take off in all directions if adhesion_type == "raft": - extra_margin = max(0, self._global_stack.getProperty("raft_margin", "value") - head_y_size) + extra_margin = max(0, self._global_stack.getProperty("raft_margin", "value")) elif adhesion_type == "brim": - extra_margin = max(0, self._global_stack.getProperty("brim_width", "value") - head_y_size) + extra_margin = max(0, self._global_stack.getProperty("brim_line_count", "value") * self._global_stack.getProperty("skirt_brim_line_width", "value")) elif adhesion_type == "skirt": extra_margin = max( 0, self._global_stack.getProperty("skirt_gap", "value") + - self._global_stack.getProperty("skirt_line_count", "value") * self._global_stack.getProperty("skirt_brim_line_width", "value") - - head_y_size) + self._global_stack.getProperty("skirt_line_count", "value") * self._global_stack.getProperty("skirt_brim_line_width", "value")) + # adjust head_and_fans with extra margin if extra_margin > 0: # In Cura 2.2+, there is a function to create this circle-like polygon. @@ -288,4 +288,4 @@ class ConvexHullDecorator(SceneNodeDecorator): _affected_settings = [ "adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap", "raft_margin", "print_sequence", - "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance"] + "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance", "brim_line_count"] diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 80bd4eb9a9..2091eff5ed 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -23,6 +23,7 @@ from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.SetTransformOperation import SetTransformOperation +from UM.Operations.TranslateOperation import TranslateOperation from cura.SetParentOperation import SetParentOperation from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType @@ -36,7 +37,6 @@ from . import BuildVolume from . import CameraAnimation from . import PrintInformation from . import CuraActions -from . import MultiMaterialDecorator from . import ZOffsetDecorator from . import CuraSplashScreen from . import CameraImageProvider @@ -180,6 +180,8 @@ class CuraApplication(QtApplication): ContainerRegistry.getInstance().addContainer(empty_material_container) empty_quality_container = copy.deepcopy(empty_container) empty_quality_container._id = "empty_quality" + empty_quality_container.setName("Not supported") + empty_quality_container.addMetaDataEntry("quality_type", "normal") empty_quality_container.addMetaDataEntry("type", "quality") ContainerRegistry.getInstance().addContainer(empty_quality_container) empty_quality_changes_container = copy.deepcopy(empty_container) @@ -700,8 +702,7 @@ class CuraApplication(QtApplication): op = GroupedOperation() for node in nodes: node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator) - op.addOperation(SetTransformOperation(node, Vector(0,0,0))) - + op.addOperation(SetTransformOperation(node, Vector(0, node.getWorldPosition().y - node.getBoundingBox().bottom, 0))) op.push() ## Reset all transformations on nodes with mesh data. @@ -724,7 +725,8 @@ class CuraApplication(QtApplication): for node in nodes: # Ensure that the object is above the build platform node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator) - op.addOperation(SetTransformOperation(node, Vector(0,0,0), Quaternion(), Vector(1, 1, 1))) + + op.addOperation(SetTransformOperation(node, Vector(0, node.getMeshData().getCenterPosition().y, 0), Quaternion(), Vector(1, 1, 1))) op.push() @@ -792,8 +794,6 @@ class CuraApplication(QtApplication): except Exception as e: Logger.log("d", "mergeSelected: Exception:", e) return - multi_material_decorator = MultiMaterialDecorator.MultiMaterialDecorator() - group_node.addDecorator(multi_material_decorator) # Compute the center of the objects when their origins are aligned. object_centers = [node.getMeshData().getCenterPosition().scale(node.getScale()) for node in group_node.getChildren()] diff --git a/cura/MultiMaterialDecorator.py b/cura/MultiMaterialDecorator.py deleted file mode 100644 index 5fee777309..0000000000 --- a/cura/MultiMaterialDecorator.py +++ /dev/null @@ -1,11 +0,0 @@ -from UM.Scene.SceneNodeDecorator import SceneNodeDecorator - -class MultiMaterialDecorator(SceneNodeDecorator): - def __init__(self): - super().__init__() - - def isMultiMaterial(self): - return True - - def __deepcopy__(self, memo): - return MultiMaterialDecorator() \ No newline at end of file diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py index 2a42765d3f..c9afb4ccbd 100644 --- a/cura/PlatformPhysics.py +++ b/cura/PlatformPhysics.py @@ -102,11 +102,11 @@ class PlatformPhysics: # Get the overlap distance for both convex hulls. If this returns None, there is no intersection. head_hull = node.callDecoration("getConvexHullHead") if head_hull: - overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHullHead")) + overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHull")) if not overlap: other_head_hull = other_node.callDecoration("getConvexHullHead") if other_head_hull: - overlap = node.callDecoration("getConvexHullHead").intersectsPolygon(other_head_hull) + overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_head_hull) else: own_convex_hull = node.callDecoration("getConvexHull") other_convex_hull = other_node.callDecoration("getConvexHull") diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index e220a5f2f1..40e036eba5 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -423,8 +423,8 @@ class ContainerManager(QObject): # stack and clear the user settings. # # \return \type{bool} True if the operation was successfully, False if not. - @pyqtSlot(result = bool) - def createQualityChanges(self): + @pyqtSlot(str, result = bool) + def createQualityChanges(self, base_name): global_stack = UM.Application.getInstance().getGlobalContainerStack() if not global_stack: return False @@ -435,8 +435,9 @@ class ContainerManager(QObject): return False self._machine_manager.blurSettings.emit() - - unique_name = self._container_registry.uniqueName(active_quality_name) + if base_name is None or base_name == "": + base_name = active_quality_name + unique_name = self._container_registry.uniqueName(base_name) # Go through the active stacks and create quality_changes containers from the user containers. for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): @@ -540,8 +541,8 @@ class ContainerManager(QObject): # \param quality_name The name of the quality to duplicate. # # \return A string containing the name of the duplicated containers, or an empty string if it failed. - @pyqtSlot(str, result = str) - def duplicateQualityOrQualityChanges(self, quality_name): + @pyqtSlot(str, str, result = str) + def duplicateQualityOrQualityChanges(self, quality_name, base_name): global_stack = UM.Application.getInstance().getGlobalContainerStack() if not global_stack or not quality_name: return "" @@ -551,7 +552,10 @@ class ContainerManager(QObject): UM.Logger.log("d", "Unable to duplicate the quality %s, because it doesn't exist.", quality_name) return "" - new_name = self._container_registry.uniqueName(quality_name) + if base_name is None: + base_name = quality_name + + new_name = self._container_registry.uniqueName(base_name) container_type = containers[0].getMetaDataEntry("type") if container_type == "quality": diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 91cfd5e811..bf82a95f1a 100644 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -7,7 +7,7 @@ from PyQt5.QtWidgets import QMessageBox from UM.Application import Application from UM.Preferences import Preferences from UM.Logger import Logger - +from UM.Message import Message from UM.Settings.SettingRelation import RelationType import UM.Settings @@ -547,6 +547,7 @@ class MachineManager(QObject): preferred_material = None if old_material: preferred_material_name = old_material.getName() + self.setActiveMaterial(self._updateMaterialContainer(self._global_container_stack.getBottom(), containers[0], preferred_material_name).id) else: Logger.log("w", "While trying to set the active variant, no variant was found to replace.") @@ -614,9 +615,16 @@ class MachineManager(QObject): stack_quality_changes = self._empty_quality_changes_container old_quality = stack.findContainer(type = "quality") - old_quality.nameChanged.disconnect(self._onQualityNameChanged) + if old_quality: + old_quality.nameChanged.disconnect(self._onQualityNameChanged) + else: + Logger.log("w", "Could not find old quality while changing active quality.") + old_changes = stack.findContainer(type = "quality_changes") - old_changes.nameChanged.disconnect(self._onQualityNameChanged) + if old_changes: + old_changes.nameChanged.disconnect(self._onQualityNameChanged) + else: + Logger.log("w", "Could not find old quality_changes while changing active quality.") stack.replaceContainer(stack.getContainerIndex(old_quality), stack_quality) stack.replaceContainer(stack.getContainerIndex(old_changes), stack_quality_changes) @@ -799,15 +807,15 @@ class MachineManager(QObject): if containers: return containers[0] - if "name" in search_criteria or "id" in search_criteria: + containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) + if "variant" in search_criteria or "id" in search_criteria: # If a material by this name can not be found, try a wider set of search criteria - search_criteria.pop("name", None) + search_criteria.pop("variant", None) search_criteria.pop("id", None) - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) if containers: return containers[0] - + Logger.log("w", "Unable to find a material container with provided criteria, returning an empty one instead.") return self._empty_material_container def _updateQualityContainer(self, definition, variant_container, material_container = None, preferred_quality_name = None): @@ -848,10 +856,9 @@ class MachineManager(QObject): containers = container_registry.findInstanceContainers(**search_criteria) if containers: return containers[0] - # We still weren't able to find a quality for this specific material. # Try to find qualities for a generic version of the material. - material_search_criteria = { "type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic" } + material_search_criteria = { "type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"} if definition.getMetaDataEntry("has_machine_quality"): if material_container: material_search_criteria["definition"] = material_container.getDefinition().id @@ -865,7 +872,6 @@ class MachineManager(QObject): material_search_criteria["variant"] = variant_container.id else: material_search_criteria["definition"] = "fdmprinter" - material_containers = container_registry.findInstanceContainers(**material_search_criteria) if material_containers: search_criteria["material"] = material_containers[0].getId() @@ -883,6 +889,9 @@ class MachineManager(QObject): if containers: return containers[0] + # Notify user that we were unable to find a matching quality + message = Message(catalog.i18nc("@info:status", "Unable to find a quality profile for this combination, using an empty one instead.")) + message.show() return self._empty_quality_container ## Finds a quality-changes container to use if any other container diff --git a/plugins/ChangeLogPlugin/ChangeLog.txt b/plugins/ChangeLogPlugin/ChangeLog.txt index ad09470ccd..8041bbb171 100644 --- a/plugins/ChangeLogPlugin/ChangeLog.txt +++ b/plugins/ChangeLogPlugin/ChangeLog.txt @@ -1,7 +1,7 @@ [2.3.0] *Speed improvements -The first thing you will notice is the speed. STL loading is now 10 to 20 times faster, layer time is significantly faster and slicing speed is slightly improved. +The first thing you will notice is the speed. STL loading is now 10 to 20 times faster, layerview is significantly faster and slicing speed is slightly improved. *Multi Extrusion Support Machines with multiple extruders are now supported. If you’ve got the Ultimaker Original with the dual extrusion upgrade kit, we’ve got you covered. @@ -59,9 +59,6 @@ Set the travel speed of the initial layer(s) to reduce risk of extruder pulling *Support Bottoms This new feature duplicates the Support Roofs feature in the places where the support rests on the model. -*Auto Temperature -Automatically change the temperature of a layer based on the average flow of material in the layer. - *Bug fixes & minor changes Deleting grouped objects works as intended again. Duplicating groups works as intended again. diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index c7cedc92ca..4d40da899d 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -206,7 +206,14 @@ class StartSliceJob(Job): # Use resolvement value if available, or take the value resolved_value = stack.getProperty(key, "resolve") if resolved_value is not None: - settings[key] = resolved_value + # There is a resolvement value. Check if we need to use it. + user_container = stack.findContainer({"type": "user"}) + quality_changes_container = stack.findContainer({"type": "quality_changes"}) + if user_container.hasProperty(key,"value") or quality_changes_container.hasProperty(key,"value"): + # Normal case + settings[key] = stack.getProperty(key, "value") + else: + settings[key] = resolved_value else: # Normal case settings[key] = stack.getProperty(key, "value") diff --git a/plugins/CuraProfileReader/CuraProfileReader.py b/plugins/CuraProfileReader/CuraProfileReader.py index 8007a8e696..772b11890b 100644 --- a/plugins/CuraProfileReader/CuraProfileReader.py +++ b/plugins/CuraProfileReader/CuraProfileReader.py @@ -26,7 +26,11 @@ class CuraProfileReader(ProfileReader): # not be read or didn't contain a valid profile, \code None \endcode is # returned. def read(self, file_name): - archive = zipfile.ZipFile(file_name, "r") + try: + archive = zipfile.ZipFile(file_name, "r") + except Exception: + # zipfile doesn't give proper exceptions, so we can only catch broad ones + return [] results = [] for profile_id in archive.namelist(): # Create an empty profile. diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py index c2a932b68c..c15b079097 100644 --- a/plugins/GCodeWriter/GCodeWriter.py +++ b/plugins/GCodeWriter/GCodeWriter.py @@ -89,17 +89,17 @@ class GCodeWriter(MeshWriter): prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line. prefix_length = len(prefix) - container_with_profile = stack.findContainer({"type": "quality"}) + container_with_profile = stack.findContainer({"type": "quality_changes"}) if not container_with_profile: Logger.log("e", "No valid quality profile found, not writing settings to GCode!") return "" - flat_global_container = self._createFlattenedContainerInstance(stack.getTop(),container_with_profile) + flat_global_container = self._createFlattenedContainerInstance(stack.getTop(), container_with_profile) serialized = flat_global_container.serialize() data = {"global_quality": serialized} for extruder in ExtruderManager.getInstance().getMachineExtruders(stack.getId()): - extruder_quality = extruder.findContainer({"type": "quality"}) + extruder_quality = extruder.findContainer({"type": "quality_changes"}) if not extruder_quality: Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId()) continue diff --git a/plugins/LayerView/LayerView.py b/plugins/LayerView/LayerView.py index 791ca0e153..801f4797dd 100644 --- a/plugins/LayerView/LayerView.py +++ b/plugins/LayerView/LayerView.py @@ -86,7 +86,7 @@ class LayerView(View): if not self._ghost_shader: self._ghost_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader")) - self._ghost_shader.setUniformValue("u_color", Color(0, 0, 0, 72)) + self._ghost_shader.setUniformValue("u_color", Color(0, 0, 0, 64)) for node in DepthFirstIterator(scene.getRoot()): # We do not want to render ConvexHullNode as it conflicts with the bottom layers. @@ -98,10 +98,10 @@ class LayerView(View): if node.getMeshData() and node.isVisible(): renderer.queueNode(node, shader = self._ghost_shader, - state_setup_callback = lambda gl: gl.glDepthMask(gl.GL_FALSE), - state_teardown_callback = lambda gl: gl.glDepthMask(gl.GL_TRUE) - ) + type = RenderBatch.RenderType.Transparent ) + for node in DepthFirstIterator(scene.getRoot()): + if type(node) is SceneNode: if node.getMeshData() and node.isVisible(): layer_data = node.callDecoration("getLayerData") if not layer_data: diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 3e3501a83f..3b56ac1881 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -47,7 +47,9 @@ class SolidView(View): if global_container_stack: if Preferences.getInstance().getValue("view/show_overhang"): angle = global_container_stack.getProperty("support_angle", "value") - if angle is not None and global_container_stack.getProperty("support_angle", "validationState") == ValidatorState.Valid: + # 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]: self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - angle))) else: self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) #Overhang angle of 0 causes no area at all to be marked as overhang. diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 58a948c17b..cd10348f2f 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -87,7 +87,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): super().setName(new_name) - basefile = self.getMetaDataEntry("base_file", self._id) # if basefile is none, this is a basefile. + basefile = self.getMetaDataEntry("base_file", self._id) # if basefile is self.id, this is a basefile. # Update the basefile as well, this is actually what we're trying to do # Update all containers that share GUID and basefile containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile) @@ -275,7 +275,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): # The XML material profile can have specific settings for machines. # Some machines share profiles, so they are only created once. # This function duplicates those elements so that each machine tag only has one identifier. - def _flattenMachinesXML(self, element): + def _expandMachinesXML(self, element): settings_element = element.find("./um:settings", self.__namespaces) machines = settings_element.iterfind("./um:machine", self.__namespaces) machines_to_add = [] @@ -309,7 +309,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): def _mergeXML(self, first, second): result = copy.deepcopy(first) - self._combineElement(self._flattenMachinesXML(result), self._flattenMachinesXML(second)) + self._combineElement(self._expandMachinesXML(result), self._expandMachinesXML(second)) return result def _createKey(self, element): @@ -339,7 +339,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): key = self._createKey(element) if len(element): # Check if element has children. try: - if "setting " in key: + if "setting" in element.tag and not "settings" in element.tag: # Setting can have points in it. In that case, delete all values and override them. for child in list(mapping[key]): mapping[key].remove(child) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 8b2805b3aa..29cf3adb30 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -3310,6 +3310,8 @@ "default_value": 200, "minimum_value_warning": "-1000", "maximum_value_warning": "1000", + "maximum_value": "machine_width - 0.5 * prime_tower_size", + "minimum_value": "0.5 * prime_tower_size", "settable_per_mesh": false, "settable_per_extruder": false }, @@ -3323,6 +3325,8 @@ "default_value": 200, "minimum_value_warning": "-1000", "maximum_value_warning": "1000", + "maximum_value": "machine_depth - 0.5 * prime_tower_size", + "minimum_value": "0.5 * prime_tower_size", "settable_per_mesh": false, "settable_per_extruder": false }, diff --git a/resources/materials/generic_cpe_plus.xml.fdm_material b/resources/materials/generic_cpe_plus.xml.fdm_material index b688db30fb..f234d7cb45 100644 --- a/resources/materials/generic_cpe_plus.xml.fdm_material +++ b/resources/materials/generic_cpe_plus.xml.fdm_material @@ -28,7 +28,9 @@ Generic CPE+ profile. Serves as an example file, data in this file is not correc yes - + + no + diff --git a/resources/materials/generic_tpu.xml.fdm_material b/resources/materials/generic_tpu.xml.fdm_material index 1a099af9cf..455e7b89be 100644 --- a/resources/materials/generic_tpu.xml.fdm_material +++ b/resources/materials/generic_tpu.xml.fdm_material @@ -31,7 +31,9 @@ Generic TPU 95A profile. Serves as an example file, data in this file is not cor - + + no + diff --git a/resources/materials/ultimaker_pla_silver-metallic.xml.fdm_material b/resources/materials/ultimaker_pla_silver-metallic.xml.fdm_material index 2f250e8e31..2b7fbaac46 100644 --- a/resources/materials/ultimaker_pla_silver-metallic.xml.fdm_material +++ b/resources/materials/ultimaker_pla_silver-metallic.xml.fdm_material @@ -3,7 +3,6 @@ Automatically generated PLA profile. Data in this file may not be not correct. --> - generic_pla Ultimaker @@ -38,14 +37,14 @@ Automatically generated PLA profile. Data in this file may not be not correct. - - + + - 180 + 150 diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 5ba0c82f00..34dbcf1b67 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -463,7 +463,7 @@ UM.MainWindow target: Cura.Actions.addProfile onTriggered: { - Cura.ContainerManager.createQualityChanges(); + Cura.ContainerManager.createQualityChanges(null); preferences.setPage(4); preferences.show(); diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index 4a0d87b812..56d8bd41a4 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -61,6 +61,10 @@ UM.ManagementPage return -1; } + function canCreateProfile() { + return base.currentItem && (base.currentItem.id == Cura.MachineManager.activeQualityId) && Cura.MachineManager.hasUserSettings; + } + buttons: [ Button { @@ -69,26 +73,39 @@ UM.ManagementPage enabled: base.currentItem != null ? base.currentItem.id != Cura.MachineManager.activeQualityId : false; onClicked: Cura.MachineManager.setActiveQuality(base.currentItem.id) }, + + // Create button Button { - text: base.currentItem && (base.currentItem.id == Cura.MachineManager.activeQualityId) && Cura.MachineManager.hasUserSettings ? catalog.i18nc("@label", "Create") : catalog.i18nc("@label", "Duplicate") + text: catalog.i18nc("@label", "Create") + enabled: base.canCreateProfile() + visible: base.canCreateProfile() iconName: "list-add"; onClicked: { - var selectedContainer; - if (base.currentItem.id == Cura.MachineManager.activeQualityId && Cura.MachineManager.hasUserSettings) { - selectedContainer = Cura.ContainerManager.createQualityChanges(); - } else { - selectedContainer = Cura.ContainerManager.duplicateQualityOrQualityChanges(base.currentItem.name); - } - base.selectContainer(selectedContainer); - - renameDialog.removeWhenRejected = true; - renameDialog.open(); - renameDialog.selectText(); + newNameDialog.object = base.currentItem != null ? base.currentItem.name : ""; + newNameDialog.open(); + newNameDialog.selectText(); } }, + + // Duplicate button + Button + { + text: catalog.i18nc("@label", "Duplicate") + enabled: ! base.canCreateProfile() + visible: ! base.canCreateProfile() + iconName: "list-add"; + + onClicked: + { + newDuplicateNameDialog.object = base.currentItem.name; + newDuplicateNameDialog.open(); + newDuplicateNameDialog.selectText(); + } + }, + Button { text: catalog.i18nc("@action:button", "Remove"); @@ -103,7 +120,6 @@ UM.ManagementPage enabled: base.currentItem != null ? !base.currentItem.readOnly : false; onClicked: { - renameDialog.removeWhenRejected = false; renameDialog.open(); renameDialog.selectText(); } @@ -128,7 +144,6 @@ UM.ManagementPage signal showProfileNameDialog() onShowProfileNameDialog: { - renameDialog.removeWhenRejected = true; renameDialog.open(); renameDialog.selectText(); } @@ -145,7 +160,7 @@ UM.ManagementPage Label { id: profileName - text: base.currentItem ? base.currentItem.name : "" + text: base.currentItem ? base.currentItem.name: "" font: UM.Theme.getFont("large") width: parent.width elide: Text.ElideRight @@ -249,24 +264,44 @@ UM.ManagementPage objectList.currentIndex = -1 //Reset selection. } } + UM.RenameDialog { id: renameDialog; object: base.currentItem != null ? base.currentItem.name : "" - property bool removeWhenRejected: false onAccepted: { Cura.ContainerManager.renameQualityChanges(base.currentItem.name, newName) objectList.currentIndex = -1 //Reset selection. } - onRejected: + } + + // Dialog to request a name when creating a new profile + UM.RenameDialog + { + id: newNameDialog; + object: ""; + onAccepted: { - if(removeWhenRejected) - { - Cura.ContainerManager.removeQualityChanges(base.currentItem.name) - } + var selectedContainer = Cura.ContainerManager.createQualityChanges(newName); + base.selectContainer(selectedContainer); + objectList.currentIndex = -1 //Reset selection. } } + + // Dialog to request a name when duplicating a new profile + UM.RenameDialog + { + id: newDuplicateNameDialog; + object: ""; + onAccepted: + { + var selectedContainer = Cura.ContainerManager.duplicateQualityOrQualityChanges(base.currentItem.name, newName); + base.selectContainer(selectedContainer); + objectList.currentIndex = -1 //Reset selection. + } + } + MessageDialog { id: messageDialog diff --git a/resources/qml/SidebarHeader.qml b/resources/qml/SidebarHeader.qml index 3350ebeb7b..c9c0e722eb 100644 --- a/resources/qml/SidebarHeader.qml +++ b/resources/qml/SidebarHeader.qml @@ -277,7 +277,7 @@ Column height: UM.Theme.getSize("setting_control").height tooltip: Cura.MachineManager.activeQualityName style: UM.Theme.styles.sidebar_header_button - + property var valueWarning: Cura.MachineManager.activeQualityId == "empty_quality" menu: ProfileMenu { } UM.SimpleButton diff --git a/resources/themes/cura/styles.qml b/resources/themes/cura/styles.qml index 8d813bc2b5..3cfb4514ee 100644 --- a/resources/themes/cura/styles.qml +++ b/resources/themes/cura/styles.qml @@ -11,7 +11,22 @@ QtObject { property Component sidebar_header_button: Component { ButtonStyle { background: Rectangle { - color: control.enabled ? Theme.getColor("setting_control") : Theme.getColor("setting_control_disabled") + color: + { + if(control.enabled) + { + if(control.valueWarning) + { + return Theme.getColor("setting_validation_warning"); + } else + { + return Theme.getColor("setting_control"); + } + } else { + return Theme.getColor("setting_control_disabled"); + } + } + border.width: Theme.getSize("default_lining").width border.color: !control.enabled ? Theme.getColor("setting_control_disabled_border") : control.hovered ? Theme.getColor("setting_control_border_highlight") : Theme.getColor("setting_control_border")