diff --git a/cura/CameraAnimation.py b/cura/CameraAnimation.py index 1d91613edf..37f230a30d 100644 --- a/cura/CameraAnimation.py +++ b/cura/CameraAnimation.py @@ -12,8 +12,8 @@ class CameraAnimation(QVariantAnimation): def __init__(self, parent = None): super().__init__(parent) self._camera_tool = None - self.setDuration(500) - self.setEasingCurve(QEasingCurve.InOutQuad) + self.setDuration(300) + self.setEasingCurve(QEasingCurve.OutQuad) def setCameraTool(self, camera_tool): self._camera_tool = camera_tool diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 27451c745c..1680e7c6a6 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -393,6 +393,7 @@ class CuraApplication(QtApplication): showDiscardOrKeepProfileChanges = pyqtSignal() def discardOrKeepProfileChanges(self): + has_user_interaction = False choice = Preferences.getInstance().getValue("cura/choice_on_profile_override") if choice == "always_discard": # don't show dialog and DISCARD the profile @@ -403,8 +404,10 @@ class CuraApplication(QtApplication): else: # ALWAYS ask whether to keep or discard the profile self.showDiscardOrKeepProfileChanges.emit() + has_user_interaction = True + return has_user_interaction - #sidebarSimpleDiscardOrKeepProfileChanges = pyqtSignal() + onDiscardOrKeepProfileChangesClosed = pyqtSignal() # Used to notify other managers that the dialog was closed @pyqtSlot(str) def discardOrKeepProfileChangesClosed(self, option): @@ -412,9 +415,25 @@ class CuraApplication(QtApplication): global_stack = self.getGlobalContainerStack() for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): extruder.getTop().clear() - global_stack.getTop().clear() + # if the user decided to keep settings then the user settings should be re-calculated and validated for errors + # before slicing. To ensure that slicer uses right settings values + elif option == "keep": + global_stack = self.getGlobalContainerStack() + for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): + user_extruder_container = extruder.getTop() + if user_extruder_container: + user_extruder_container.update() + + user_global_container = global_stack.getTop() + if user_global_container: + user_global_container.update() + + # notify listeners that quality has changed (after user selected discard or keep) + self.onDiscardOrKeepProfileChangesClosed.emit() + self.getMachineManager().activeQualityChanged.emit() + @pyqtSlot(int) def messageBoxClosed(self, button): if self._message_box_callback: diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 563965915a..fc5c415f87 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -47,6 +47,10 @@ class MachineManager(QObject): self._active_container_stack = None # type: CuraContainerStack self._global_container_stack = None # type: GlobalStack + # Used to store the new containers until after confirming the dialog + self._new_variant_container = None + self._new_material_container = None + self._error_check_timer = QTimer() self._error_check_timer.setInterval(250) self._error_check_timer.setSingleShot(True) @@ -58,6 +62,7 @@ class MachineManager(QObject): self._instance_container_timer.timeout.connect(self.__onInstanceContainersChanged) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) + ## When the global container is changed, active material probably needs to be updated. self.globalContainerChanged.connect(self.activeMaterialChanged) self.globalContainerChanged.connect(self.activeVariantChanged) @@ -84,6 +89,9 @@ class MachineManager(QObject): ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeStackChanged) self.activeStackChanged.connect(self.activeStackValueChanged) + # when a user closed dialog check if any delayed material or variant changes need to be applied + Application.getInstance().onDiscardOrKeepProfileChangesClosed.connect(self._executeDelayedActiveContainerStackChanges) + Preferences.getInstance().addPreference("cura/active_machine", "") self._global_event_keys = set() @@ -109,7 +117,7 @@ class MachineManager(QObject): "The selected material is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Incompatible Material")) - globalContainerChanged = pyqtSignal() # Emitted whenever the global stack is changed (ie: when changing between printers, changing a global profile, but not when changing a value) + globalContainerChanged = pyqtSignal() # Emitted whenever the global stack is changed (ie: when changing between printers, changing a global profile, but not when changing a value) activeMaterialChanged = pyqtSignal() activeVariantChanged = pyqtSignal() activeQualityChanged = pyqtSignal() @@ -333,6 +341,7 @@ class MachineManager(QObject): self.activeQualityChanged.emit() self.activeVariantChanged.emit() self.activeMaterialChanged.emit() + self._updateStacksHaveErrors() # Prevents unwanted re-slices after changing machine self._error_check_timer.start() def _onInstanceContainersChanged(self, container): @@ -349,6 +358,8 @@ class MachineManager(QObject): @pyqtSlot(str) def setActiveMachine(self, stack_id: str) -> None: self.blurSettings.emit() # Ensure no-one has focus. + self._cancelDelayedActiveContainerStackChanges() + containers = ContainerRegistry.getInstance().findContainerStacks(id = stack_id) if containers: Application.getInstance().setGlobalContainerStack(containers[0]) @@ -747,7 +758,7 @@ class MachineManager(QObject): self.blurSettings.emit() old_material.nameChanged.disconnect(self._onMaterialNameChanged) - self._active_container_stack.material = material_container + self._new_material_container = material_container # self._active_container_stack will be updated with a delay Logger.log("d", "Active material changed") material_container.nameChanged.connect(self._onMaterialNameChanged) @@ -801,13 +812,13 @@ class MachineManager(QObject): old_material = self._active_container_stack.material if old_variant: self.blurSettings.emit() - self._active_container_stack.variant = containers[0] + self._new_variant_container = containers[0] # self._active_container_stack will be updated with a delay Logger.log("d", "Active variant changed to {active_variant_id}".format(active_variant_id = containers[0].getId())) preferred_material_name = None if old_material: preferred_material_name = old_material.getName() - - self.setActiveMaterial(self._updateMaterialContainer(self._global_container_stack.getBottom(), self._global_container_stack, containers[0], preferred_material_name).id) + preferred_material_id = self._updateMaterialContainer(self._global_container_stack.getBottom(), self._global_container_stack, containers[0], preferred_material_name).id + self.setActiveMaterial(preferred_material_id) else: Logger.log("w", "While trying to set the active variant, no variant was found to replace.") @@ -854,19 +865,44 @@ class MachineManager(QObject): self._replaceQualityOrQualityChangesInStack(stack, stack_quality, postpone_emit=True) self._replaceQualityOrQualityChangesInStack(stack, stack_quality_changes, postpone_emit=True) - # Send emits that are postponed in replaceContainer. - # Here the stacks are finished replacing and every value can be resolved based on the current state. - for setting_info in new_quality_settings_list: - setting_info["stack"].sendPostponedEmits() - # Connect to onQualityNameChanged for stack in name_changed_connect_stacks: stack.nameChanged.connect(self._onQualityNameChanged) - if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: - self._askUserToKeepOrClearCurrentSettings() + has_user_interaction = False - self.activeQualityChanged.emit() + if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: + # Show the keep/discard user settings dialog + has_user_interaction = Application.getInstance().discardOrKeepProfileChanges() + else: + # If the user doesn't have any of adjusted settings then slicing will be triggered by emit() + # Send emits that are postponed in replaceContainer. + # Here the stacks are finished replacing and every value can be resolved based on the current state. + for setting_info in new_quality_settings_list: + setting_info["stack"].sendPostponedEmits() + + if not has_user_interaction: + self._executeDelayedActiveContainerStackChanges() + self.activeQualityChanged.emit() + + ## Used to update material and variant in the active container stack with a delay. + # This delay prevents the stack from triggering a lot of signals (eventually resulting in slicing) + # before the user decided to keep or discard any of their changes using the dialog. + # The Application.onDiscardOrKeepProfileChangesClosed signal triggers this method. + def _executeDelayedActiveContainerStackChanges(self): + if self._new_material_container is not None: + self._active_container_stack.material = self._new_material_container + self._new_material_container = None + + if self._new_variant_container is not None: + self._active_container_stack.variant = self._new_variant_container + self._new_variant_container = None + + ## Cancel set changes for material and variant in the active container stack. + # Used for ignoring any changes when switching between printers (setActiveMachine) + def _cancelDelayedActiveContainerStackChanges(self): + self._new_material_container = None + self._new_variant_container = None ## Determine the quality and quality changes settings for the current machine for a quality name. # @@ -985,9 +1021,6 @@ class MachineManager(QObject): stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged) self._onQualityNameChanged() - def _askUserToKeepOrClearCurrentSettings(self): - Application.getInstance().discardOrKeepProfileChanges() - @pyqtProperty(str, notify = activeVariantChanged) def activeVariantName(self) -> str: if self._active_container_stack: diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py index c9fac23d91..a352564bc2 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py @@ -240,3 +240,4 @@ class ProcessSlicedLayersJob(Job): else: if self._progress_message: self._progress_message.hide() + diff --git a/plugins/PluginBrowser/PluginBrowser.qml b/plugins/PluginBrowser/PluginBrowser.qml index 71e88c652b..13000d23ad 100644 --- a/plugins/PluginBrowser/PluginBrowser.qml +++ b/plugins/PluginBrowser/PluginBrowser.qml @@ -114,7 +114,7 @@ UM.Dialog anchors.rightMargin: UM.Theme.getSize("default_margin").width Label { - text: "" + model.name + " - " + model.author + text: "" + model.name + "" + ((model.author !== "") ? (" - " + model.author) : "") width: contentWidth height: contentHeight + UM.Theme.getSize("default_margin").height verticalAlignment: Text.AlignVCenter diff --git a/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py b/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py index cfa793996b..e482cbd4e3 100644 --- a/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py +++ b/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py @@ -220,7 +220,9 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte self.setPrinters(json_data) def materialHotendChangedMessage(self, callback): - pass # Do nothing. + # When there is just one printer, the activate configuration option is enabled + if (self._cluster_size == 1): + super().materialHotendChangedMessage(callback = callback) def _startCameraStream(self): ## Request new image diff --git a/plugins/UM3NetworkPrinting/UM3InfoComponents.qml b/plugins/UM3NetworkPrinting/UM3InfoComponents.qml index 2c3902dcff..8d0d8a0754 100644 --- a/plugins/UM3NetworkPrinting/UM3InfoComponents.qml +++ b/plugins/UM3NetworkPrinting/UM3InfoComponents.qml @@ -115,8 +115,16 @@ Item { tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura") text: catalog.i18nc("@action:button", "Activate Configuration") - visible: printerConnected + visible: printerConnected && !isClusterPrinter() onClicked: manager.loadConfigurationFromPrinter() + + function isClusterPrinter() { + var clusterSize = Cura.MachineManager.printerOutputDevices[0].clusterSize + // This is a non cluster printer or the cluster it is just one printer + if (clusterSize == undefined || clusterSize == 1) + return false + return true + } } } diff --git a/resources/definitions/creality_cr10.def.json b/resources/definitions/creality_cr10.def.json index ced6f32b7b..bacae6e2e5 100644 --- a/resources/definitions/creality_cr10.def.json +++ b/resources/definitions/creality_cr10.def.json @@ -59,9 +59,6 @@ "skirt_gap": { "default_value": 5 }, - "machine_start_gcode": { - "default_value": "G21 ;metric values\nG90 ;absolute Positioning\nG28 ; home all axes\nG1 Z5 F3000 ; lift\nG1 X20 Y2 F1500 ; avoid binder clips\nG1 Z0.2 F3000 ; get ready to prime\nG92 E0 ; reset extrusion distance\nG1 X120 E10 F600 ; prime nozzle\nG1 X150 F5000 ; quick wipe" - }, "machine_end_gcode": { "default_value": "G91\nG1 F1800 E-3\nG1 F3000 Z10\nG90\nG28 X0 Y0 ; home x and y axis\nM106 S0 ; turn off cooling fan\nM104 S0 ; turn off extruder\nM140 S0 ; turn off bed\nM84 ; disable motors" }, diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index e953d18865..22bbe93b3f 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -682,6 +682,7 @@ "value": "line_width", "default_value": 0.4, "type": "float", + "limit_to_extruder": "wall_0_extruder_nr if wall_x_extruder_nr == wall_0_extruder_nr else -1", "settable_per_mesh": true, "children": { @@ -880,29 +881,45 @@ "type": "category", "children": { - "wall_0_extruder_nr": + "wall_extruder_nr": { - "label": "Outer Wall Extruder", - "description": "The extruder train used for printing the outer wall. This is used in multi-extrusion.", + "label": "Wall Extruder", + "description": "The extruder train used for printing the walls. This is used in multi-extrusion.", "type": "optional_extruder", "default_value": "-1", "settable_per_mesh": false, "settable_per_extruder": false, "settable_per_meshgroup": true, "settable_globally": true, - "enabled": "machine_extruder_count > 1" - }, - "wall_x_extruder_nr": - { - "label": "Inner Walls Extruder", - "description": "The extruder train used for printing the inner walls. This is used in multi-extrusion.", - "type": "optional_extruder", - "default_value": "-1", - "settable_per_mesh": false, - "settable_per_extruder": false, - "settable_per_meshgroup": true, - "settable_globally": true, - "enabled": "machine_extruder_count > 1" + "enabled": "machine_extruder_count > 1", + "children": { + "wall_0_extruder_nr": + { + "label": "Outer Wall Extruder", + "description": "The extruder train used for printing the outer wall. This is used in multi-extrusion.", + "type": "optional_extruder", + "value": "wall_extruder_nr", + "default_value": "-1", + "settable_per_mesh": false, + "settable_per_extruder": false, + "settable_per_meshgroup": true, + "settable_globally": true, + "enabled": "machine_extruder_count > 1" + }, + "wall_x_extruder_nr": + { + "label": "Inner Wall Extruder", + "description": "The extruder train used for printing the inner walls. This is used in multi-extrusion.", + "type": "optional_extruder", + "value": "wall_extruder_nr", + "default_value": "-1", + "settable_per_mesh": false, + "settable_per_extruder": false, + "settable_per_meshgroup": true, + "settable_globally": true, + "enabled": "machine_extruder_count > 1" + } + } }, "wall_thickness": { @@ -1872,6 +1889,7 @@ "default_value": 10, "minimum_value": "0", "maximum_value": "10", + "enabled": false, "settable_per_mesh": false, "settable_per_extruder": true }, @@ -1884,6 +1902,7 @@ "default_value": 100, "minimum_value": "0", "maximum_value": "100", + "enabled": false, "settable_per_mesh": false, "settable_per_extruder": true }, @@ -3954,6 +3973,16 @@ "limit_to_extruder": "support_infill_extruder_nr", "enabled": "support_enable and support_use_towers", "settable_per_mesh": true + }, + "remove_empty_first_layers": + { + "label": "Remove Empty First Layers", + "description": "Remove empty layers beneath the first printed layer if they are present.", + "type": "bool", + "default_value": true, + "enabled": "not support_enable", + "settable_per_mesh": false, + "settable_per_extruder": false } } }, diff --git a/resources/definitions/ultimaker3.def.json b/resources/definitions/ultimaker3.def.json index 21f80e18fd..05e39e365c 100644 --- a/resources/definitions/ultimaker3.def.json +++ b/resources/definitions/ultimaker3.def.json @@ -150,7 +150,7 @@ "top_bottom_thickness": { "value": "1" }, "travel_avoid_distance": { "value": "3" }, "wall_0_inset": { "value": "0" }, - "wall_line_width_x": { "value": "round(line_width * 0.3 / 0.35, 2)" }, + "wall_line_width_x": { "value": "round(wall_line_width * 0.3 / 0.35, 2)" }, "wall_thickness": { "value": "1" } } } diff --git a/resources/qml/Menus/MaterialMenu.qml b/resources/qml/Menus/MaterialMenu.qml index 1688bc228a..359f4f41d0 100644 --- a/resources/qml/Menus/MaterialMenu.qml +++ b/resources/qml/Menus/MaterialMenu.qml @@ -14,6 +14,14 @@ Menu property int extruderIndex: 0 property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 + property bool isClusterPrinter: + { + var clusterSize = Cura.MachineManager.printerOutputDevices[0].clusterSize + // This is a non cluster printer or the cluster it is just one printer + if (clusterSize == undefined || clusterSize == 1) + return false + return true + } UM.SettingPropertyProvider { @@ -29,14 +37,14 @@ Menu id: automaticMaterial text: { - if(printerConnected && Cura.MachineManager.printerOutputDevices[0].materialNames.length > extruderIndex) + if(printerConnected && Cura.MachineManager.printerOutputDevices[0].materialNames.length > extruderIndex && !isClusterPrinter) { var materialName = Cura.MachineManager.printerOutputDevices[0].materialNames[extruderIndex]; return catalog.i18nc("@title:menuitem %1 is the automatically selected material", "Automatic: %1").arg(materialName); } return ""; } - visible: printerConnected && Cura.MachineManager.printerOutputDevices[0].materialNames.length > extruderIndex + visible: printerConnected && Cura.MachineManager.printerOutputDevices[0].materialNames.length > extruderIndex && !isClusterPrinter onTriggered: { var materialId = Cura.MachineManager.printerOutputDevices[0].materialIds[extruderIndex]; diff --git a/resources/qml/Menus/NozzleMenu.qml b/resources/qml/Menus/NozzleMenu.qml index b51b3b1907..1c75a346f3 100644 --- a/resources/qml/Menus/NozzleMenu.qml +++ b/resources/qml/Menus/NozzleMenu.qml @@ -14,20 +14,28 @@ Menu property int extruderIndex: 0 property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 + property bool isClusterPrinter: + { + var clusterSize = Cura.MachineManager.printerOutputDevices[0].clusterSize + // This is a non cluster printer or the cluster it is just one printer + if (clusterSize == undefined || clusterSize == 1) + return false + return true + } MenuItem { id: automaticNozzle text: { - if(printerConnected && Cura.MachineManager.printerOutputDevices[0].hotendIds.length > extruderIndex) + if(printerConnected && Cura.MachineManager.printerOutputDevices[0].hotendIds.length > extruderIndex && !isClusterPrinter) { var nozzleName = Cura.MachineManager.printerOutputDevices[0].hotendIds[extruderIndex]; return catalog.i18nc("@title:menuitem %1 is the nozzle currently loaded in the printer", "Automatic: %1").arg(nozzleName); } return ""; } - visible: printerConnected && Cura.MachineManager.printerOutputDevices[0].hotendIds.length > extruderIndex + visible: printerConnected && Cura.MachineManager.printerOutputDevices[0].hotendIds.length > extruderIndex && !isClusterPrinter onTriggered: { var activeExtruderIndex = ExtruderManager.activeExtruderIndex; diff --git a/resources/qml/SaveButton.qml b/resources/qml/SaveButton.qml index 7423fc2368..acc97ebf11 100644 --- a/resources/qml/SaveButton.qml +++ b/resources/qml/SaveButton.qml @@ -91,8 +91,8 @@ Item { id: saveRow width: base.width height: saveToButton.height - anchors.top: progressBar.bottom - anchors.topMargin: UM.Theme.getSize("sidebar_margin").height + anchors.bottom: parent.bottom + anchors.bottomMargin: UM.Theme.getSize("sidebar_margin").height anchors.left: parent.left Row {