Просмотр исходного кода

Now loading user settings

CURA-11561
Erwan MATHIEU 1 год назад
Родитель
Сommit
ab0a52063d

+ 139 - 81
plugins/3MFReader/ThreeMFWorkspaceReader.py

@@ -5,6 +5,7 @@ from configparser import ConfigParser
 import zipfile
 import os
 import json
+import re
 from typing import cast, Dict, List, Optional, Tuple, Any, Set
 
 import xml.etree.ElementTree as ET
@@ -141,10 +142,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
         self._old_new_materials: Dict[str, str] = {}
         self._machine_info = None
 
+        self._load_profile = False
+
     def _clearState(self):
         self._id_mapping = {}
         self._old_new_materials = {}
         self._machine_info = None
+        self._load_profile = False
 
     def getNewId(self, old_id: str):
         """Get a unique name based on the old_id. This is different from directly calling the registry in that it caches results.
@@ -228,7 +232,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
         self._resolve_strategies = {k: None for k in resolve_strategy_keys}
         containers_found_dict = {k: False for k in resolve_strategy_keys}
 
-        # Check whether the file is a PCB
+        # Check whether the file is a PCB, which changes some import options
         is_pcb = file_name.endswith('.pcb')
 
         #
@@ -621,6 +625,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
         self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity)
         self._dialog.setMissingPackagesMetadata(missing_package_metadata)
         self._dialog.setHasVisibleSelectSameProfileChanged(is_pcb)
+        self._dialog.setAllowCreatemachine(not is_pcb)
         self._dialog.show()
 
         # Choosing the initially selected printer in MachineSelector
@@ -652,6 +657,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
         self._dialog.setIsNetworkedMachine(is_networked_machine)
         self._dialog.setIsAbstractMachine(is_abstract_machine)
         self._dialog.setMachineName(machine_name)
+        self._dialog.updateCompatibleMachine()
+        self._dialog.setSelectSameProfileChecked(self._dialog.isCompatibleMachine)
 
         # Block until the dialog is closed.
         self._dialog.waitForClose()
@@ -659,6 +666,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
         if self._dialog.getResult() == {}:
             return WorkspaceReader.PreReadResult.cancelled
 
+        self._load_profile = not is_pcb or self._dialog.selectSameProfileChecked
+
         self._resolve_strategies = self._dialog.getResult()
         #
         # There can be 3 resolve strategies coming from the dialog:
@@ -694,16 +703,16 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
         except EnvironmentError as e:
             message = Message(i18n_catalog.i18nc("@info:error Don't translate the XML tags <filename> or <message>!",
                                                  "Project file <filename>{0}</filename> is suddenly inaccessible: <message>{1}</message>.", file_name, str(e)),
-                                                 title = i18n_catalog.i18nc("@info:title", "Can't Open Project File"),
-                                                 message_type = Message.MessageType.ERROR)
+                              title = i18n_catalog.i18nc("@info:title", "Can't Open Project File"),
+                              message_type = Message.MessageType.ERROR)
             message.show()
             self.setWorkspaceName("")
             return [], {}
         except zipfile.BadZipFile as e:
             message = Message(i18n_catalog.i18nc("@info:error Don't translate the XML tags <filename> or <message>!",
                                                  "Project file <filename>{0}</filename> is corrupt: <message>{1}</message>.", file_name, str(e)),
-                                                 title = i18n_catalog.i18nc("@info:title", "Can't Open Project File"),
-                                                 message_type = Message.MessageType.ERROR)
+                              title = i18n_catalog.i18nc("@info:title", "Can't Open Project File"),
+                              message_type = Message.MessageType.ERROR)
             message.show()
             self.setWorkspaceName("")
             return [], {}
@@ -765,9 +774,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
             # Find the machine which will be overridden
             global_stacks = self._container_registry.findContainerStacks(id = self._dialog.getMachineToOverride(), type = "machine")
             if not global_stacks:
-                message = Message(i18n_catalog.i18nc("@info:error Don't translate the XML tag <filename>!", 
+                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_type = Message.MessageType.ERROR)
+                                  message_type = Message.MessageType.ERROR)
                 message.show()
                 self.setWorkspaceName("")
                 return [], {}
@@ -781,84 +790,107 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
             for stack in extruder_stacks:
                 stack.setNextStack(global_stack, connect_signals = False)
 
-        Logger.log("d", "Workspace loading is checking definitions...")
-        # Get all the definition files & check if they exist. If not, add them.
-        definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
-        for definition_container_file in definition_container_files:
-            container_id = self._stripFileToId(definition_container_file)
-
-            definitions = self._container_registry.findDefinitionContainersMetadata(id = container_id)
-            if not definitions:
-                definition_container = DefinitionContainer(container_id)
-                try:
-                    definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"),
-                                                     file_name = definition_container_file)
-                except ContainerFormatError:
-                    # We cannot just skip the definition file because everything else later will just break if the
-                    # machine definition cannot be found.
-                    Logger.logException("e", "Failed to deserialize definition file %s in project file %s",
-                                        definition_container_file, file_name)
-                    definition_container = self._container_registry.findDefinitionContainers(id = "fdmprinter")[0] #Fall back to defaults.
-                self._container_registry.addContainer(definition_container)
-            Job.yieldThread()
-            QCoreApplication.processEvents()  # Ensure that the GUI does not freeze.
-
-        Logger.log("d", "Workspace loading is checking materials...")
-        # Get all the material files and check if they exist. If not, add them.
-        xml_material_profile = self._getXmlProfileClass()
-        if self._material_container_suffix is None:
-            self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).suffixes[0]
-        if xml_material_profile:
-            material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
-            for material_container_file in material_container_files:
-                to_deserialize_material = False
-                container_id = self._stripFileToId(material_container_file)
-                need_new_name = False
-                materials = self._container_registry.findInstanceContainers(id = container_id)
+        user_settings = {}
 
-                if not materials:
-                    # No material found, deserialize this material later and add it
-                    to_deserialize_material = True
-                else:
-                    material_container = materials[0]
-                    old_material_root_id = material_container.getMetaDataEntry("base_file")
-                    if old_material_root_id is not None and not self._container_registry.isReadOnly(old_material_root_id):  # Only create new materials if they are not read only.
-                        to_deserialize_material = True
+        if self._load_profile:
+            Logger.log("d", "Workspace loading is checking definitions...")
+            # Get all the definition files & check if they exist. If not, add them.
+            definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
+            for definition_container_file in definition_container_files:
+                container_id = self._stripFileToId(definition_container_file)
 
-                        if self._resolve_strategies["material"] == "override":
-                            # Remove the old materials and then deserialize the one from the project
-                            root_material_id = material_container.getMetaDataEntry("base_file")
-                            application.getContainerRegistry().removeContainer(root_material_id)
-                        elif self._resolve_strategies["material"] == "new":
-                            # Note that we *must* deserialize it with a new ID, as multiple containers will be
-                            # auto created & added.
-                            container_id = self.getNewId(container_id)
-                            self._old_new_materials[old_material_root_id] = container_id
-                            need_new_name = True
-
-                if to_deserialize_material:
-                    material_container = xml_material_profile(container_id)
+                definitions = self._container_registry.findDefinitionContainersMetadata(id = container_id)
+                if not definitions:
+                    definition_container = DefinitionContainer(container_id)
                     try:
-                        material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
-                                                       file_name = container_id + "." + self._material_container_suffix)
+                        definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"),
+                                                         file_name = definition_container_file)
                     except ContainerFormatError:
-                        Logger.logException("e", "Failed to deserialize material file %s in project file %s",
-                                            material_container_file, file_name)
-                        continue
-                    if need_new_name:
-                        new_name = ContainerRegistry.getInstance().uniqueName(material_container.getName())
-                        material_container.setName(new_name)
-                    material_container.setDirty(True)
-                    self._container_registry.addContainer(material_container)
+                        # We cannot just skip the definition file because everything else later will just break if the
+                        # machine definition cannot be found.
+                        Logger.logException("e", "Failed to deserialize definition file %s in project file %s",
+                                            definition_container_file, file_name)
+                        definition_container = self._container_registry.findDefinitionContainers(id = "fdmprinter")[0] #Fall back to defaults.
+                    self._container_registry.addContainer(definition_container)
                 Job.yieldThread()
                 QCoreApplication.processEvents()  # Ensure that the GUI does not freeze.
 
+            Logger.log("d", "Workspace loading is checking materials...")
+            # Get all the material files and check if they exist. If not, add them.
+            xml_material_profile = self._getXmlProfileClass()
+            if self._material_container_suffix is None:
+                self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).suffixes[0]
+            if xml_material_profile:
+                material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
+                for material_container_file in material_container_files:
+                    to_deserialize_material = False
+                    container_id = self._stripFileToId(material_container_file)
+                    need_new_name = False
+                    materials = self._container_registry.findInstanceContainers(id = container_id)
+
+                    if not materials:
+                        # No material found, deserialize this material later and add it
+                        to_deserialize_material = True
+                    else:
+                        material_container = materials[0]
+                        old_material_root_id = material_container.getMetaDataEntry("base_file")
+                        if old_material_root_id is not None and not self._container_registry.isReadOnly(old_material_root_id):  # Only create new materials if they are not read only.
+                            to_deserialize_material = True
+
+                            if self._resolve_strategies["material"] == "override":
+                                # Remove the old materials and then deserialize the one from the project
+                                root_material_id = material_container.getMetaDataEntry("base_file")
+                                application.getContainerRegistry().removeContainer(root_material_id)
+                            elif self._resolve_strategies["material"] == "new":
+                                # Note that we *must* deserialize it with a new ID, as multiple containers will be
+                                # auto created & added.
+                                container_id = self.getNewId(container_id)
+                                self._old_new_materials[old_material_root_id] = container_id
+                                need_new_name = True
+
+                    if to_deserialize_material:
+                        material_container = xml_material_profile(container_id)
+                        try:
+                            material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
+                                                           file_name = container_id + "." + self._material_container_suffix)
+                        except ContainerFormatError:
+                            Logger.logException("e", "Failed to deserialize material file %s in project file %s",
+                                                material_container_file, file_name)
+                            continue
+                        if need_new_name:
+                            new_name = ContainerRegistry.getInstance().uniqueName(material_container.getName())
+                            material_container.setName(new_name)
+                        material_container.setDirty(True)
+                        self._container_registry.addContainer(material_container)
+                    Job.yieldThread()
+                    QCoreApplication.processEvents()  # Ensure that the GUI does not freeze.
+        else:
+            Logger.log("d", "Workspace loading user settings...")
+            try:
+                user_settings = json.loads(archive.open("Cura/user-settings.json").read().decode("utf-8"))
+            except KeyError as e:
+                # If there is no user settings file, it's not a PCB, so notify user of failure.
+                Logger.log("w", "File %s is not a valid PCB.", file_name)
+                message = Message(
+                    i18n_catalog.i18nc("@info:error Don't translate the XML tags <filename> or <message>!",
+                                       "Project file <filename>{0}</filename> is corrupt: <message>{1}</message>.",
+                                       file_name, str(e)),
+                    title=i18n_catalog.i18nc("@info:title", "Can't Open Project File"),
+                    message_type=Message.MessageType.ERROR)
+                message.show()
+                self.setWorkspaceName("")
+                return [], {}
+
+
         if global_stack:
-            # Handle quality changes if any
-            self._processQualityChanges(global_stack)
+            if self._load_profile:
+                # Handle quality changes if any
+                self._processQualityChanges(global_stack)
 
-            # Prepare the machine
-            self._applyChangesToMachine(global_stack, extruder_stack_dict)
+                # Prepare the machine
+                self._applyChangesToMachine(global_stack, extruder_stack_dict)
+            else:
+                self._applyUserSettings(global_stack, extruder_stack_dict, user_settings)
 
             Logger.log("d", "Workspace loading is notifying rest of the code of changes...")
             # Actually change the active machine.
@@ -1181,21 +1213,47 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
             material_node = machine_node.variants[extruder_stack.variant.getName()].materials[root_material_id]
             extruder_stack.material = material_node.container
 
-    def _applyChangesToMachine(self, global_stack, extruder_stack_dict):
-        # Clear all first
+    def _clearMachineSettings(self, global_stack, extruder_stack_dict):
         self._clearStack(global_stack)
         for extruder_stack in extruder_stack_dict.values():
             self._clearStack(extruder_stack)
 
+        self._quality_changes_to_apply = None
+        self._quality_type_to_apply = None
+        self._intent_category_to_apply = None
+        self._user_settings_to_apply = None
+
+    def _applyUserSettings(self, global_stack, extruder_stack_dict, user_settings):
+        # Clear all first
+        self._clearMachineSettings(global_stack, extruder_stack_dict)
+
+        for stack_name, settings in user_settings.items():
+            if stack_name == 'global':
+                ThreeMFWorkspaceReader._applyUserSettingsOnStack(global_stack, settings)
+            else:
+                extruder_match = re.fullmatch('extruder_([0-9]+)', stack_name)
+                if extruder_match is not None:
+                    extruder_nr = extruder_match.group(1)
+                    if extruder_nr in extruder_stack_dict:
+                        ThreeMFWorkspaceReader._applyUserSettingsOnStack(extruder_stack_dict[extruder_nr], settings)
+
+    @staticmethod
+    def _applyUserSettingsOnStack(stack, user_settings):
+        user_settings_container = stack.userChanges
+
+        for setting_to_import, setting_value in user_settings.items():
+            user_settings_container.setProperty(setting_to_import, 'value', setting_value)
+
+    def _applyChangesToMachine(self, global_stack, extruder_stack_dict):
+        # Clear all first
+        self._clearMachineSettings(global_stack, extruder_stack_dict)
+
         self._applyDefinitionChanges(global_stack, extruder_stack_dict)
         self._applyUserChanges(global_stack, extruder_stack_dict)
         self._applyVariants(global_stack, extruder_stack_dict)
         self._applyMaterials(global_stack, extruder_stack_dict)
 
         # prepare the quality to select
-        self._quality_changes_to_apply = None
-        self._quality_type_to_apply = None
-        self._intent_category_to_apply = None
         if self._machine_info.quality_changes_info is not None:
             self._quality_changes_to_apply = self._machine_info.quality_changes_info.name
         else:

+ 25 - 4
plugins/3MFReader/WorkspaceDialog.py

@@ -73,6 +73,8 @@ class WorkspaceDialog(QObject):
         self._is_networked_machine = False
         self._is_compatible_machine = False
         self._has_visible_select_same_profile = False
+        self._select_same_profile_checked = True
+        self._allow_create_machine = True
 
     machineConflictChanged = pyqtSignal()
     qualityChangesConflictChanged = pyqtSignal()
@@ -98,6 +100,7 @@ class WorkspaceDialog(QObject):
     missingPackagesChanged = pyqtSignal()
     isCompatibleMachineChanged = pyqtSignal()
     hasVisibleSelectSameProfileChanged = pyqtSignal()
+    selectSameProfileCheckedChanged = pyqtSignal()
 
     @pyqtProperty(bool, notify = isPrinterGroupChanged)
     def isPrinterGroup(self) -> bool:
@@ -295,17 +298,19 @@ class WorkspaceDialog(QObject):
 
     @pyqtSlot(str)
     def setMachineToOverride(self, machine_name: str) -> None:
+        self._override_machine = machine_name
+        self.updateCompatibleMachine()
+
+    def updateCompatibleMachine(self):
         registry = ContainerRegistry.getInstance()
-        containers_expected = registry.findDefinitionContainers(name = self._machine_type)
-        containers_selected = registry.findContainerStacks(id = machine_name)
+        containers_expected = registry.findDefinitionContainers(name=self._machine_type)
+        containers_selected = registry.findContainerStacks(id=self._override_machine)
         if len(containers_expected) == 1 and len(containers_selected) == 1:
             new_compatible_machine = (containers_expected[0] == containers_selected[0].definition)
             if new_compatible_machine != self._is_compatible_machine:
                 self._is_compatible_machine = new_compatible_machine
                 self.isCompatibleMachineChanged.emit()
 
-        self._override_machine = machine_name
-
     @pyqtProperty(bool, notify = isCompatibleMachineChanged)
     def isCompatibleMachine(self) -> bool:
         return self._is_compatible_machine
@@ -319,6 +324,22 @@ class WorkspaceDialog(QObject):
     def hasVisibleSelectSameProfile(self):
         return self._has_visible_select_same_profile
 
+    def setSelectSameProfileChecked(self, select_same_profile_checked):
+        if select_same_profile_checked != self._select_same_profile_checked:
+            self._select_same_profile_checked = select_same_profile_checked
+            self.selectSameProfileCheckedChanged.emit()
+
+    @pyqtProperty(bool, notify = selectSameProfileCheckedChanged, fset = setSelectSameProfileChecked)
+    def selectSameProfileChecked(self):
+        return self._select_same_profile_checked
+
+    def setAllowCreatemachine(self, allow_create_machine):
+        self._allow_create_machine = allow_create_machine
+
+    @pyqtProperty(bool, constant = True)
+    def allowCreateMachine(self):
+        return self._allow_create_machine
+
     @pyqtSlot()
     def closeBackend(self) -> None:
         """Close the backend: otherwise one could end up with "Slicing..."""

+ 12 - 3
plugins/3MFReader/WorkspaceDialog.qml

@@ -120,13 +120,17 @@ UM.Dialog
 
                         minDropDownWidth: machineSelector.width
 
-                        buttons: [
+                        Component
+                        {
+                            id: componentNewPrinter
+
                             Cura.SecondaryButton
                             {
                                 id: createNewPrinter
                                 text: catalog.i18nc("@button", "Create new")
                                 fixedWidthMode: true
                                 width: parent.width - leftPadding * 1.5
+                                visible: manager.allowCreateMachine
                                 onClicked:
                                 {
                                     toggleContent()
@@ -136,7 +140,9 @@ UM.Dialog
                                     manager.setIsNetworkedMachine(false)
                                 }
                             }
-                        ]
+                        }
+
+                        buttons: manager.allowCreateMachine ? [componentNewPrinter.createObject()] : []
 
                         onSelectPrinter: function(machine)
                         {
@@ -191,9 +197,12 @@ UM.Dialog
                         {
                             text: catalog.i18nc("@action:checkbox", "Select the same profile")
                             enabled: manager.isCompatibleMachine
-                            onEnabledChanged: checked = enabled
+                            onEnabledChanged: manager.selectSameProfileChecked = enabled
                             tooltip: enabled ? "" : catalog.i18nc("@tooltip", "You can use the same profile only if you have the same printer as the project was published with")
                             visible: manager.hasVisibleSelectSameProfile
+
+                            checked: manager.selectSameProfileChecked
+                            onCheckedChanged: manager.selectSameProfileChecked = checked
                         }
                     }
 

+ 1 - 1
plugins/3MFWriter/SettingsExportModel.py

@@ -105,7 +105,7 @@ class SettingsExportModel(QObject):
 
     @staticmethod
     def _exportSettings(settings_stack):
-        user_settings_container = settings_stack.getTop()
+        user_settings_container = settings_stack.userChanges
         user_keys = user_settings_container.getAllKeys()
 
         settings_export = []