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 {