Browse Source

Merge branch 'CURA-7609_Pick_any_printer_when_opening_a_project_file_single_dropdown' of github.com:Ultimaker/Cura

Jaime van Kessel 4 years ago
parent
commit
ebdab94b98

+ 17 - 13
plugins/3MFReader/ThreeMFWorkspaceReader.py

@@ -133,12 +133,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
         # In Cura 2.5 and 2.6, the empty profiles used to have those long names
         self._old_empty_profile_id_dict = {"empty_%s" % k: "empty" for k in ["material", "variant"]}
 
-        self._is_same_machine_type = False
         self._old_new_materials = {} # type: Dict[str, str]
         self._machine_info = None
 
     def _clearState(self):
-        self._is_same_machine_type = False
         self._id_mapping = {}
         self._old_new_materials = {}
         self._machine_info = None
@@ -229,6 +227,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
         # Read definition containers
         #
         machine_definition_id = None
+        updatable_machines = []
         machine_definition_container_count = 0
         extruder_definition_container_count = 0
         definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
@@ -245,6 +244,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
             definition_container_type = definition_container.get("type")
             if definition_container_type == "machine":
                 machine_definition_id = container_id
+                machine_definition_containers = self._container_registry.findDefinitionContainers(id = machine_definition_id)
+                if machine_definition_containers:
+                    updatable_machines = [machine for machine in self._container_registry.findContainerStacks(type = "machine") if machine.definition == machine_definition_containers[0]]
                 machine_type = definition_container["name"]
                 variant_type_name = definition_container.get("variants_name", variant_type_name)
 
@@ -386,8 +388,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
             machine_definition_id = id_list[7]
 
         stacks = self._container_registry.findContainerStacks(name = machine_name, type = "machine")
-        self._is_same_machine_type = True
         existing_global_stack = None
+        global_stack = None
 
         if stacks:
             global_stack = stacks[0]
@@ -400,7 +402,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
                 if global_stack.getContainer(index).getId() != container_id:
                     machine_conflict = True
                     break
-            self._is_same_machine_type = global_stack.definition.getId() == machine_definition_id
+
+        if updatable_machines and not containers_found_dict["machine"]:
+            containers_found_dict["machine"] = True
 
         # Get quality type
         parser = ConfigParser(interpolation = None)
@@ -485,7 +489,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
             if intent_id not in ("empty", "empty_intent"):
                 extruder_info.intent_info = instance_container_info_dict[intent_id]
 
-            if not machine_conflict and containers_found_dict["machine"]:
+            if not machine_conflict and containers_found_dict["machine"] and global_stack:
                 if int(position) >= len(global_stack.extruderList):
                     continue
 
@@ -555,9 +559,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
         self._machine_info.custom_quality_name = quality_name
         self._machine_info.intent_category = intent_category
 
-        if machine_conflict and not self._is_same_machine_type:
-            machine_conflict = False
-
         is_printer_group = False
         if machine_conflict:
             group_name = existing_global_stack.getMetaDataEntry("group_name")
@@ -578,6 +579,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
         self._dialog.setNumSettingsOverriddenByQualityChanges(num_settings_overridden_by_quality_changes)
         self._dialog.setNumUserSettings(num_user_settings)
         self._dialog.setActiveMode(active_mode)
+        self._dialog.setUpdatableMachines(updatable_machines)
         self._dialog.setMachineName(machine_name)
         self._dialog.setMaterialLabels(material_labels)
         self._dialog.setMachineType(machine_type)
@@ -658,8 +660,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
 
         application.expandedCategoriesChanged.emit()  # Notify the GUI of the change
 
-        # If a machine with the same name is of a different type, always create a new one.
-        if not self._is_same_machine_type or self._resolve_strategies["machine"] != "override":
+        # If there are no machines of the same type, create a new machine.
+        if self._resolve_strategies["machine"] != "override" or self._dialog.updatableMachinesModel.count <= 1:
             # We need to create a new machine
             machine_name = self._container_registry.uniqueName(self._machine_info.name)
 
@@ -669,10 +671,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
 
                 self._container_registry.addContainer(global_stack)
         else:
-            # Find the machine
-            global_stacks = self._container_registry.findContainerStacks(name = self._machine_info.name, type = "machine")
+            # Find the machine which will be overridden
+            global_stacks = self._container_registry.findContainerStacks(name = self._dialog.getMachineToOverride(), type = "machine")
             if not global_stacks:
-                message = Message(i18n_catalog.i18nc("@info:error Don't translate the XML tag <filename>!", "Project file <filename>{0}</filename> is made using profiles that are unknown to this version of Ultimaker Cura.", file_name))
+                message = Message(i18n_catalog.i18nc("@info:error Don't translate the XML tag <filename>!", 
+                                                     "Project file <filename>{0}</filename> is made using profiles that"
+                                                     " are unknown to this version of Ultimaker Cura.", file_name))
                 message.show()
                 self.setWorkspaceName("")
                 return [], {}

+ 43 - 0
plugins/3MFReader/UpdatableMachinesModel.py

@@ -0,0 +1,43 @@
+# Copyright (c) 2020 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from typing import Dict, List
+
+from PyQt5.QtCore import Qt
+
+from UM.Qt.ListModel import ListModel
+from cura.Settings.GlobalStack import GlobalStack
+
+create_new_list_item = {
+    "id":   "new",
+    "name": "Create new",
+    "displayName": "Create new",
+    "type": "default_option"  # to make sure we are not mixing the "Create new" option with a printer with id "new"
+}  # type: Dict[str, str]
+
+
+class UpdatableMachinesModel(ListModel):
+    """Model that holds cura packages.
+
+    By setting the filter property the instances held by this model can be changed.
+    """
+
+    def __init__(self, parent = None) -> None:
+        super().__init__(parent)
+
+        self.addRoleName(Qt.UserRole + 1, "id")
+        self.addRoleName(Qt.UserRole + 2, "name")
+        self.addRoleName(Qt.UserRole + 3, "displayName")
+        self.addRoleName(Qt.UserRole + 4, "type")  # Either "default_option" or "machine"
+
+    def update(self, machines: List[GlobalStack]) -> None:
+        items = [create_new_list_item]  # type: List[Dict[str, str]]
+
+        for machine in sorted(machines, key = lambda printer: printer.name):
+            items.append({
+                "id":   machine.id,
+                "name": machine.name,
+                "displayName": "Update " + machine.name,
+                "type": "machine"
+            })
+        self.setItems(items)

+ 73 - 47
plugins/3MFReader/WorkspaceDialog.py

@@ -1,5 +1,6 @@
-# Copyright (c) 2016 Ultimaker B.V.
+# Copyright (c) 2020 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
+from typing import List, Optional, Dict, cast
 
 from PyQt5.QtCore import pyqtSignal, QObject, pyqtProperty, QCoreApplication
 from UM.FlameProfiler import pyqtSlot
@@ -7,10 +8,15 @@ from UM.PluginRegistry import PluginRegistry
 from UM.Application import Application
 from UM.i18n import i18nCatalog
 from UM.Settings.ContainerRegistry import ContainerRegistry
+from cura.Settings.GlobalStack import GlobalStack
+from .UpdatableMachinesModel import UpdatableMachinesModel
 
 import os
 import threading
 import time
+
+from cura.CuraApplication import CuraApplication
+
 i18n_catalog = i18nCatalog("cura")
 
 
@@ -29,6 +35,7 @@ class WorkspaceDialog(QObject):
                         "quality_changes": self._default_strategy,
                         "definition_changes": self._default_strategy,
                         "material": self._default_strategy}
+        self._override_machine = None
         self._visible = False
         self.showDialogSignal.connect(self.__show)
 
@@ -51,6 +58,7 @@ class WorkspaceDialog(QObject):
         self._extruders = []
         self._objects_on_plate = False
         self._is_printer_group = False
+        self._updatable_machines_model = UpdatableMachinesModel(self)
 
     machineConflictChanged = pyqtSignal()
     qualityChangesConflictChanged = pyqtSignal()
@@ -63,6 +71,7 @@ class WorkspaceDialog(QObject):
     qualityTypeChanged = pyqtSignal()
     intentNameChanged = pyqtSignal()
     machineNameChanged = pyqtSignal()
+    updatableMachinesChanged = pyqtSignal()
     materialLabelsChanged = pyqtSignal()
     objectsOnPlateChanged = pyqtSignal()
     numUserSettingsChanged = pyqtSignal()
@@ -81,33 +90,33 @@ class WorkspaceDialog(QObject):
             self.isPrinterGroupChanged.emit()
 
     @pyqtProperty(str, notify=variantTypeChanged)
-    def variantType(self):
+    def variantType(self) -> str:
         return self._variant_type
 
-    def setVariantType(self, variant_type):
+    def setVariantType(self, variant_type: str) -> None:
         if self._variant_type != variant_type:
             self._variant_type = variant_type
             self.variantTypeChanged.emit()
 
     @pyqtProperty(str, notify=machineTypeChanged)
-    def machineType(self):
+    def machineType(self) -> str:
         return self._machine_type
 
-    def setMachineType(self, machine_type):
+    def setMachineType(self, machine_type: str) -> None:
         self._machine_type = machine_type
         self.machineTypeChanged.emit()
 
-    def setNumUserSettings(self, num_user_settings):
+    def setNumUserSettings(self, num_user_settings: int) -> None:
         if self._num_user_settings != num_user_settings:
             self._num_user_settings = num_user_settings
             self.numVisibleSettingsChanged.emit()
 
     @pyqtProperty(int, notify=numUserSettingsChanged)
-    def numUserSettings(self):
+    def numUserSettings(self) -> int:
         return self._num_user_settings
 
     @pyqtProperty(bool, notify=objectsOnPlateChanged)
-    def hasObjectsOnPlate(self):
+    def hasObjectsOnPlate(self) -> bool:
         return self._objects_on_plate
 
     def setHasObjectsOnPlate(self, objects_on_plate):
@@ -116,10 +125,10 @@ class WorkspaceDialog(QObject):
             self.objectsOnPlateChanged.emit()
 
     @pyqtProperty("QVariantList", notify = materialLabelsChanged)
-    def materialLabels(self):
+    def materialLabels(self) -> List[str]:
         return self._material_labels
 
-    def setMaterialLabels(self, material_labels):
+    def setMaterialLabels(self, material_labels: List[str]) -> None:
         if self._material_labels != material_labels:
             self._material_labels = material_labels
             self.materialLabelsChanged.emit()
@@ -134,36 +143,44 @@ class WorkspaceDialog(QObject):
             self.extrudersChanged.emit()
 
     @pyqtProperty(str, notify = machineNameChanged)
-    def machineName(self):
+    def machineName(self) -> str:
         return self._machine_name
 
-    def setMachineName(self, machine_name):
+    def setMachineName(self, machine_name: str) -> None:
         if self._machine_name != machine_name:
             self._machine_name = machine_name
             self.machineNameChanged.emit()
 
+    @pyqtProperty(QObject, notify = updatableMachinesChanged)
+    def updatableMachinesModel(self) -> UpdatableMachinesModel:
+        return cast(UpdatableMachinesModel, self._updatable_machines_model)
+
+    def setUpdatableMachines(self, updatable_machines: List[GlobalStack]) -> None:
+        self._updatable_machines_model.update(updatable_machines)
+        self.updatableMachinesChanged.emit()
+
     @pyqtProperty(str, notify=qualityTypeChanged)
-    def qualityType(self):
+    def qualityType(self) -> str:
         return self._quality_type
 
-    def setQualityType(self, quality_type):
+    def setQualityType(self, quality_type: str) -> None:
         if self._quality_type != quality_type:
             self._quality_type = quality_type
             self.qualityTypeChanged.emit()
 
     @pyqtProperty(int, notify=numSettingsOverridenByQualityChangesChanged)
-    def numSettingsOverridenByQualityChanges(self):
+    def numSettingsOverridenByQualityChanges(self) -> int:
         return self._num_settings_overridden_by_quality_changes
 
-    def setNumSettingsOverriddenByQualityChanges(self, num_settings_overridden_by_quality_changes):
+    def setNumSettingsOverriddenByQualityChanges(self, num_settings_overridden_by_quality_changes: int) -> None:
         self._num_settings_overridden_by_quality_changes = num_settings_overridden_by_quality_changes
         self.numSettingsOverridenByQualityChangesChanged.emit()
 
     @pyqtProperty(str, notify=qualityNameChanged)
-    def qualityName(self):
+    def qualityName(self) -> str:
         return self._quality_name
 
-    def setQualityName(self, quality_name):
+    def setQualityName(self, quality_name: str) -> None:
         if self._quality_name != quality_name:
             self._quality_name = quality_name
             self.qualityNameChanged.emit()
@@ -178,80 +195,87 @@ class WorkspaceDialog(QObject):
             self.intentNameChanged.emit()
 
     @pyqtProperty(str, notify=activeModeChanged)
-    def activeMode(self):
+    def activeMode(self) -> str:
         return self._active_mode
 
-    def setActiveMode(self, active_mode):
+    def setActiveMode(self, active_mode: int) -> None:
         if active_mode == 0:
             self._active_mode = i18n_catalog.i18nc("@title:tab", "Recommended")
         else:
             self._active_mode = i18n_catalog.i18nc("@title:tab", "Custom")
         self.activeModeChanged.emit()
 
-    @pyqtProperty(int, notify = hasVisibleSettingsFieldChanged)
-    def hasVisibleSettingsField(self):
+    @pyqtProperty(bool, notify = hasVisibleSettingsFieldChanged)
+    def hasVisibleSettingsField(self) -> bool:
         return self._has_visible_settings_field
 
-    def setHasVisibleSettingsField(self, has_visible_settings_field):
+    def setHasVisibleSettingsField(self, has_visible_settings_field: bool) -> None:
         self._has_visible_settings_field = has_visible_settings_field
         self.hasVisibleSettingsFieldChanged.emit()
 
     @pyqtProperty(int, constant = True)
-    def totalNumberOfSettings(self):
+    def totalNumberOfSettings(self) -> int:
         general_definition_containers = ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")
         if not general_definition_containers:
             return 0
         return len(general_definition_containers[0].getAllKeys())
 
     @pyqtProperty(int, notify = numVisibleSettingsChanged)
-    def numVisibleSettings(self):
+    def numVisibleSettings(self) -> int:
         return self._num_visible_settings
 
-    def setNumVisibleSettings(self, num_visible_settings):
+    def setNumVisibleSettings(self, num_visible_settings: int) -> None:
         if self._num_visible_settings != num_visible_settings:
             self._num_visible_settings = num_visible_settings
             self.numVisibleSettingsChanged.emit()
 
     @pyqtProperty(bool, notify = machineConflictChanged)
-    def machineConflict(self):
+    def machineConflict(self) -> bool:
         return self._has_machine_conflict
 
     @pyqtProperty(bool, notify=qualityChangesConflictChanged)
-    def qualityChangesConflict(self):
+    def qualityChangesConflict(self) -> bool:
         return self._has_quality_changes_conflict
 
     @pyqtProperty(bool, notify=materialConflictChanged)
-    def materialConflict(self):
+    def materialConflict(self) -> bool:
         return self._has_material_conflict
 
     @pyqtSlot(str, str)
-    def setResolveStrategy(self, key, strategy):
+    def setResolveStrategy(self, key: str, strategy: Optional[str]) -> None:
         if key in self._result:
             self._result[key] = strategy
 
+    def getMachineToOverride(self) -> str:
+        return self._override_machine
+
+    @pyqtSlot(str)
+    def setMachineToOverride(self, machine_name: str) -> None:
+        self._override_machine = machine_name
+
     @pyqtSlot()
-    def closeBackend(self):
+    def closeBackend(self) -> None:
         """Close the backend: otherwise one could end up with "Slicing..."""
 
         Application.getInstance().getBackend().close()
 
-    def setMaterialConflict(self, material_conflict):
+    def setMaterialConflict(self, material_conflict: bool) -> None:
         if self._has_material_conflict != material_conflict:
             self._has_material_conflict = material_conflict
             self.materialConflictChanged.emit()
 
-    def setMachineConflict(self, machine_conflict):
+    def setMachineConflict(self, machine_conflict: bool) -> None:
         if self._has_machine_conflict != machine_conflict:
             self._has_machine_conflict = machine_conflict
             self.machineConflictChanged.emit()
 
-    def setQualityChangesConflict(self, quality_changes_conflict):
+    def setQualityChangesConflict(self, quality_changes_conflict: bool) -> None:
         if self._has_quality_changes_conflict != quality_changes_conflict:
             self._has_quality_changes_conflict = quality_changes_conflict
             self.qualityChangesConflictChanged.emit()
 
-    def getResult(self):
-        if "machine" in self._result and not self._has_machine_conflict:
+    def getResult(self) -> Dict[str, Optional[str]]:
+        if "machine" in self._result and self.updatableMachinesModel.count <= 1:
             self._result["machine"] = None
         if "quality_changes" in self._result and not self._has_quality_changes_conflict:
             self._result["quality_changes"] = None
@@ -267,11 +291,13 @@ class WorkspaceDialog(QObject):
 
         return self._result
 
-    def _createViewFromQML(self):
-        path = os.path.join(PluginRegistry.getInstance().getPluginPath("3MFReader"), self._qml_url)
-        self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
+    def _createViewFromQML(self) -> None:
+        three_mf_reader_path = PluginRegistry.getInstance().getPluginPath("3MFReader")
+        if three_mf_reader_path:
+            path = os.path.join(three_mf_reader_path, self._qml_url)
+            self._view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
 
-    def show(self):
+    def show(self) -> None:
         # Emit signal so the right thread actually shows the view.
         if threading.current_thread() != threading.main_thread():
             self._lock.acquire()
@@ -284,7 +310,7 @@ class WorkspaceDialog(QObject):
         self.showDialogSignal.emit()
 
     @pyqtSlot()
-    def notifyClosed(self):
+    def notifyClosed(self) -> None:
         """Used to notify the dialog so the lock can be released."""
 
         self._result = {} # The result should be cleared before hide, because after it is released the main thread lock
@@ -294,7 +320,7 @@ class WorkspaceDialog(QObject):
         except:
             pass
 
-    def hide(self):
+    def hide(self) -> None:
         self._visible = False
         self._view.hide()
         try:
@@ -303,7 +329,7 @@ class WorkspaceDialog(QObject):
             pass
 
     @pyqtSlot(bool)
-    def _onVisibilityChanged(self, visible):
+    def _onVisibilityChanged(self, visible: bool) -> None:
         if not visible:
             try:
                 self._lock.release()
@@ -311,17 +337,17 @@ class WorkspaceDialog(QObject):
                 pass
 
     @pyqtSlot()
-    def onOkButtonClicked(self):
+    def onOkButtonClicked(self) -> None:
         self._view.hide()
         self.hide()
 
     @pyqtSlot()
-    def onCancelButtonClicked(self):
+    def onCancelButtonClicked(self) -> None:
         self._result = {}
         self._view.hide()
         self.hide()
 
-    def waitForClose(self):
+    def waitForClose(self) -> None:
         """Block thread until the dialog is closed."""
 
         if self._visible:
@@ -334,7 +360,7 @@ class WorkspaceDialog(QObject):
                     time.sleep(1 / 50)
                     QCoreApplication.processEvents()  # Ensure that the GUI does not freeze.
 
-    def __show(self):
+    def __show(self) -> None:
         if self._view is None:
             self._createViewFromQML()
         if self._view:

+ 42 - 23
plugins/3MFReader/WorkspaceDialog.qml

@@ -2,7 +2,7 @@
 // Cura is released under the terms of the LGPLv3 or higher.
 
 import QtQuick 2.10
-import QtQuick.Controls 1.4
+import QtQuick.Controls 2.3
 import QtQuick.Layouts 1.3
 import QtQuick.Window 2.2
 
@@ -20,6 +20,7 @@ UM.Dialog
 
     property int comboboxHeight: 15 * screenScaleFactor
     property int spacerHeight: 10 * screenScaleFactor
+    property int doubleSpacerHeight: 20 * screenScaleFactor
 
     onClosing: manager.notifyClosed()
     onVisibleChanged:
@@ -35,7 +36,7 @@ UM.Dialog
     Item
     {
         anchors.fill: parent
-        anchors.margins: 20 * screenScaleFactor
+        anchors.margins: 10 * screenScaleFactor
 
         UM.I18nCatalog
         {
@@ -79,7 +80,7 @@ UM.Dialog
             }
             Item // Spacer
             {
-                height: spacerHeight
+                height: doubleSpacerHeight
                 width: height
             }
 
@@ -101,35 +102,53 @@ UM.Dialog
                 }
                 UM.TooltipArea
                 {
-                    id: machineResolveTooltip
+                    id: machineResolveStrategyTooltip
                     width: (parent.width / 3) | 0
                     height: visible ? comboboxHeight : 0
-                    visible: manager.machineConflict
+                    visible: base.visible && machineResolveComboBox.model.count > 1
                     text: catalog.i18nc("@info:tooltip", "How should the conflict in the machine be resolved?")
                     ComboBox
                     {
-                        model: ListModel
+                        id: machineResolveComboBox
+                        model: manager.updatableMachinesModel
+                        visible: machineResolveStrategyTooltip.visible
+                        textRole: "displayName"
+                        width: parent.width
+                        onCurrentIndexChanged:
                         {
-                            Component.onCompleted:
+                            if (model.getItem(currentIndex).id == "new"
+                                && model.getItem(currentIndex).type == "default_option")
                             {
-                                append({"key": "override", "label": catalog.i18nc("@action:ComboBox option", "Update") + " " + manager.machineName});
-                                append({"key": "new", "label": catalog.i18nc("@action:ComboBox option", "Create new")});
+                                manager.setResolveStrategy("machine", "new")
                             }
-                        }
-                        Connections
-                        {
-                            target: manager
-                            onMachineNameChanged:
+                            else
                             {
-                                machineResolveComboBox.model.get(0).label = catalog.i18nc("@action:ComboBox option", "Update") + " " + manager.machineName;
+                                manager.setResolveStrategy("machine", "override")
+                                manager.setMachineToOverride(model.getItem(currentIndex).id)
                             }
                         }
-                        textRole: "label"
-                        id: machineResolveComboBox
-                        width: parent.width
-                        onActivated:
+
+                        onVisibleChanged:
                         {
-                            manager.setResolveStrategy("machine", resolveStrategiesModel.get(index).key)
+                            if (!visible) {return}
+
+                            currentIndex = 0
+                            // If the project printer exists in Cura, set it as the default dropdown menu option.
+                            // No need to check object 0, which is the "Create new" option
+                            for (var i = 1; i < model.count; i++)
+                            {
+                                if (model.getItem(i).name == manager.machineName)
+                                {
+                                    currentIndex = i
+                                    break
+                                }
+                            }
+                            // The project printer does not exist in Cura. If there is at least one printer of the same
+                            // type, select the first one, else set the index to "Create new"
+                            if (currentIndex == 0 && model.count > 1)
+                            {
+                                currentIndex = 1
+                            }
                         }
                     }
                 }
@@ -168,7 +187,7 @@ UM.Dialog
 
             Item // Spacer
             {
-                height: spacerHeight
+                height: doubleSpacerHeight
                 width: height
             }
             Row
@@ -271,7 +290,7 @@ UM.Dialog
             }
             Item // Spacer
             {
-                height: spacerHeight
+                height: doubleSpacerHeight
                 width: height
             }
             Row
@@ -333,7 +352,7 @@ UM.Dialog
 
             Item // Spacer
             {
-                height: spacerHeight
+                height: doubleSpacerHeight
                 width: height
             }